X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=PLC%2FAccessors%2FFactory.py;h=fec0d63ec513b9090ab7b9f3faf79236a297c709;hb=33b3bbd76195f96288024d5acea660a75138e7e8;hp=99c782e3ce593aad5c382365069696f4b817b802;hpb=196d474841ff8cbfed8ddff43f01ffb7933a15d8;p=plcapi.git diff --git a/PLC/Accessors/Factory.py b/PLC/Accessors/Factory.py index 99c782e..fec0d63 100644 --- a/PLC/Accessors/Factory.py +++ b/PLC/Accessors/Factory.py @@ -1,82 +1,118 @@ +# # Thierry Parmentelat - INRIA -# $Id$ - +# from types import NoneType -from PLC.Method import Method +from PLC.Faults import * + from PLC.Auth import Auth from PLC.Parameter import Parameter, Mixed - -from PLC.Faults import * +from PLC.Method import Method +from PLC.Accessor import Accessor, AccessorSingleton from PLC.Nodes import Nodes, Node from PLC.NodeTags import NodeTags, NodeTag from PLC.Interfaces import Interfaces, Interface -from PLC.InterfaceSettings import InterfaceSettings, InterfaceSetting +from PLC.InterfaceTags import InterfaceTags, InterfaceTag from PLC.Slices import Slices, Slice -from PLC.SliceAttributes import SliceAttributes, SliceAttribute - -# this is another story.. -#from PLC.Ilinks import Ilink +from PLC.SliceTags import SliceTags, SliceTag +from PLC.Sites import Sites, Site +from PLC.SiteTags import SiteTags, SiteTag +from PLC.Persons import Persons, Person +from PLC.PersonTags import PersonTags, PersonTag -from PLC.TagTypes import TagTypes, TagType +# need to import so the core classes get decorated with caller_may_write_tag +from PLC.AuthorizeHelpers import AuthorizeHelpers -# known classes : { class -> secondary_key } -taggable_classes = { Node : {'table_class' : Nodes, +# known classes : { class -> details } +taggable_classes = { Node : {'table_class' : Nodes, 'joins_class' : NodeTags, 'join_class' : NodeTag, - 'value_key': 'tagvalue', 'secondary_key': 'hostname'}, - Interface : {'table_class' : Interfaces, - 'joins_class': InterfaceSettings, 'join_class': InterfaceSetting, - 'value_key' : 'value' }, - Slice: {'table_class' : Slices, - 'joins_class': SliceAttributes, 'join_class': SliceAttribute, - 'value_key' : 'value', 'secondary_key':'login_base'}, -# Ilink : xxx + 'secondary_key': 'hostname'}, + Interface : {'table_class' : Interfaces, + 'joins_class': InterfaceTags, 'join_class': InterfaceTag, + 'secondary_key' : 'ip'}, + Slice: {'table_class' : Slices, + 'joins_class': SliceTags, 'join_class': SliceTag, + 'secondary_key':'name'}, + Site: {'table_class' : Sites, + 'joins_class': SiteTags, 'join_class': SiteTag, + 'secondary_key':'login_base'}, + Person: {'table_class' : Persons, + 'joins_class': PersonTags, 'join_class': PersonTag, + 'secondary_key':'email'}, } # xxx probably defined someplace else +admin_roles = ['admin'] +person_roles = [ 'admin', 'pi', 'tech', 'user' ] all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ] +tech_roles = [ 'admin', 'pi', 'tech' ] +# # generates 2 method classes: -# Get (auth, id_or_name) -> tagvalue or None -# Set (auth, id_or_name, tagvalue) -> None -# tagvalue is always a string, no cast nor typecheck for now +# Get (auth, id_or_name) -> value or None +# Set (auth, id_or_name, value) -> value +# value is always a string, no cast nor typecheck for now +# +# The expose_in_api flag tells whether this tag may be handled +# through the Add/Get/Update methods as a native field # -# note: tag_min_role_id gets attached to the tagtype instance, -# while get_roles and set_roles get attached to the created methods +# note: set_roles get attached as 'roles' to the tagtype instance, +# also get_roles and set_roles get attached to the created methods +# +# in addition a convenience method like e.g. LocateNodeArch is defined +# in the Accessor class; its purpose is to retrieve the tag, or to create it if needed # -# returns a tuple (get_method, set_method) -# See Accessors* for examples +# Legacy NOTE: +# prior to plcapi-5.0-19, this used to accept an additional argument +# named min_role_id; this was redundant and confusing, it has been +# removed, we now use set_roles to restrict write access on the corresponding tag + +# the convention here is that methodsuffix should be mixed case, e.g. MyStuff +# while tagname is expected to be lowercase +# you then end up with e.g. GetPersonMyStuff + +# the entry point accepts a single class or a list of classes +def define_accessors (module, objclasses, *args, **kwds): + if not isinstance(objclasses,list): + objclasses=[objclasses] + for objclass in objclasses: + define_accessors_ (module, objclass, *args, **kwds) + +# this is for one class +def define_accessors_ (module, objclass, methodsuffix, tagname, + category, description, + get_roles=all_roles, set_roles=admin_roles, + expose_in_api = False): -def get_set_factory (objclass, methodsuffix, - tagname, category, description, tag_min_role_id=10, - get_roles=['admin'], set_roles=['admin']): - if objclass not in taggable_classes: try: raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__ except: raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??" + + # side-effect on, say, Node.tags, if required + if expose_in_api: + getattr(objclass,'tags')[tagname]=Parameter(str,"accessor") + classname=objclass.__name__ get_name = "Get" + classname + methodsuffix set_name = "Set" + classname + methodsuffix + locator_name = "Locate" + classname + methodsuffix - # create method objects under PLC.Method.Method + # accessor method objects under PLC.Method.Method get_class = type (get_name, (Method,), {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\ (classname,tagname)}) set_class = type (set_name, (Method,), {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\ (classname,tagname)}) - # accepts + + # accepts get_accepts = [ Auth () ] primary_key=objclass.primary_key - try: - secondary_key = taggable_classes[objclass]['secondary_key'] - get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ] - except: - secondary_key = None - get_accepts += [ objclass.fields[primary_key] ] + secondary_key = taggable_classes[objclass]['secondary_key'] + get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ] # for set, idem set of arguments + one additional arg, the new value set_accepts = get_accepts + [ Parameter (str,"New tag value") ] @@ -88,84 +124,122 @@ def get_set_factory (objclass, methodsuffix, setattr(get_class,'roles',get_roles) setattr(get_class,'accepts',get_accepts) setattr(get_class,'returns', get_returns) - setattr(get_class,'skip_typecheck',True) +# that was useful for legacy method only, but we now need type_checking +# setattr(get_class,'skip_type_check',True) setattr(set_class,'roles',set_roles) setattr(set_class,'accepts',set_accepts) setattr(set_class,'returns', set_returns) - setattr(set_class,'skip_typecheck',True) - +# that was useful for legacy method only, but we now need type_checking +# setattr(set_class,'skip_type_check',True) + table_class = taggable_classes[objclass]['table_class'] joins_class = taggable_classes[objclass]['joins_class'] join_class = taggable_classes[objclass]['join_class'] - value_key = taggable_classes[objclass]['value_key'] + + # locate the tag and create it if needed + # this method is attached to the Accessor class + def tag_locator (self, enforce=False): + return self.locate_or_create_tag (tagname=tagname, + category=category, + description=description, + roles=set_roles, + enforce=enforce) + + # attach it to the Accessor class + Accessor.register_tag_locator(locator_name,tag_locator) # body of the get method def get_call (self, auth, id_or_name): - # search the tagtype - xxx - might need a cache - tag_types = TagTypes (self.api, {'tagname': tagname}) - if not tag_types: - return None - tag_type_id = tag_types[0]['tag_type_id'] + # locate the tag, see above + tag_locator = Accessor.retrieve_tag_locator(locator_name) + tag_type = tag_locator(AccessorSingleton(self.api)) + tag_type_id=tag_type['tag_type_id'] + filter = {'tag_type_id':tag_type_id} if isinstance (id_or_name,int): filter[primary_key]=id_or_name else: filter[secondary_key]=id_or_name - joins = joins_class (self.api,filter,[value_key]) + joins = joins_class (self.api,filter,['value']) if not joins: - # xxx - we return None even if id_or_name is not valid + # xxx - we return None even if id_or_name is not valid return None else: - return joins[0][value_key] + return joins[0]['value'] # attach it setattr (get_class,"call",get_call) - # body of the set method - def set_call (self, auth, id_or_name, tagvalue): + # body of the set method + def set_call (self, auth, id_or_name, value): # locate the object if isinstance (id_or_name, int): filter={primary_key:id_or_name} else: filter={secondary_key:id_or_name} - objs = table_class(self.api, filter,[primary_key]) +# we need the full monty b/c of the permission system +# objs = table_class(self.api, filter,[primary_key,secondary_key]) + objs = table_class(self.api, filter) if not objs: raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name) - primary_id = objs[0][primary_key] - - # search tag type & create if needed - tag_types = TagTypes (self.api, {'tagname':tagname}) - if tag_types: - tag_type = tag_types[0] - else: - # not found: create it - tag_type_fields = {'tagname':tagname, - 'category' : category, - 'description' : description, - 'min_role_id': tag_min_role_id} - tag_type = TagType (self.api, tag_type_fields) - tag_type.sync() - # proceed + # the object being tagged + obj=objs[0] + primary_id = obj[primary_key] + + # locate the tag, see above + tag_locator = Accessor.retrieve_tag_locator(locator_name) + tag_type = tag_locator(AccessorSingleton(self.api)) tag_type_id = tag_type['tag_type_id'] + + # check authorization + if not hasattr(objclass,'caller_may_write_tag'): + raise PLCAuthenticationFailure, "class %s misses method caller_may_write_tag"%objclass.__name__ + obj.caller_may_write_tag (self.api,self.caller,tag_type) + + # locate the join object (e.g. NodeTag or similar) filter = {'tag_type_id':tag_type_id} if isinstance (id_or_name,int): filter[primary_key]=id_or_name else: filter[secondary_key]=id_or_name joins = joins_class (self.api,filter) - if not joins: - join = join_class (self.api) - join['tag_type_id']=tag_type_id - join[primary_key]=primary_id - join[value_key]=tagvalue - join.sync() + # setting to something non void + if value is not None: + if not joins: + join = join_class (self.api) + join['tag_type_id']=tag_type_id + join[primary_key]=primary_id + join['value']=value + join.sync() + else: + joins[0]['value']=value + joins[0].sync() + # providing an empty value means clean up else: - joins[0][value_key]=tagvalue - joins[0].sync() + if joins: + join=joins[0] + join.delete() + # log it + self.event_objects= { objclass.__name__ : [primary_id] } + self.message=objclass.__name__ + if secondary_key in objs[0]: + self.message += " %s "%objs[0][secondary_key] + else: + self.message += " %d "%objs[0][primary_key] + self.message += "updated" + return value # attach it setattr (set_class,"call",set_call) - return ( get_class, set_class ) - + # define in module + setattr(module,get_name,get_class) + setattr(module,set_name,set_class) + # add in .methods + try: + methods=getattr(module,'methods') + except: + methods=[] + methods += [get_name,set_name] + setattr(module,'methods',methods)