default for get_roles in the accessors factory is all_roles
[plcapi.git] / PLC / Accessors / Factory.py
index f4dbca8..ebe6720 100644 (file)
+#
 # Thierry Parmentelat - INRIA
-# $Id$
-
+#
 from types import NoneType
 
-from PLC.Method import Method
-from PLC.Auth import Auth
-from PLC.Parameter import Parameter, Mixed
-
 from PLC.Faults import *
 
-from PLC.Nodes import Node
-from PLC.Interfaces import Interface
-from PLC.Slices import Slice
-from PLC.Ilinks import Ilink
-
-from PLC.TagTypes import TagTypes, TagType
-
-# known classes : { class -> secondary_key }
-taggable_classes = { Node : 'hostname', 
-                     Interface : None, 
-                     Slice: 'login_base', 
-                     Ilink : None}
+from PLC.Auth import Auth
+from PLC.Parameter import Parameter, Mixed
+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.InterfaceTags import InterfaceTags, InterfaceTag
+from PLC.Slices import Slices, Slice
+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
+
+# this is another story..
+#from PLC.Ilinks import Ilink
+
+# known classes : { class -> details }
+taggable_classes = { Node : {'table_class' : Nodes,
+                             'joins_class' : NodeTags, 'join_class' : NodeTag,
+                             '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'},
+#                     Ilink : xxx
+                     }
 
 # xxx probably defined someplace else
+admin_roles = ['admin']
 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
+tech_roles = [ 'admin', 'pi', 'tech' ]
 
+#
 # generates 2 method classes:
-# Get<classname><methodsuffix> (auth, id_or_name) -> tagvalue or None
-# Set<classname><methodsuffix> (auth, id_or_name, tagvalue) -> None
-# tagvalue is always a string, no cast nor typecheck for now
+# Get<classname><methodsuffix> (auth, id_or_name) -> value or None
+# Set<classname><methodsuffix> (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, 
+# note: roles get attached to the tagtype instance,
 # while get_roles and set_roles get attached to the created methods
+# this might need a cleanup
+#
+# 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 access on the corresponding tag
+
+def define_accessors (module, objclass, methodsuffix, tagname,
+                      category, description,
+                      get_roles=all_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
-    secondary_key = taggable_classes[objclass]
-    if not secondary_key:
-        get_accepts += [ objclass.fields[primary_key] ]
-    else:
-        get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
-    # for set, idem + one additional arg
+    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") ]
 
     # returns
@@ -82,40 +120,101 @@ def get_set_factory (objclass, methodsuffix,
     setattr(set_class,'returns', set_returns)
     setattr(set_class,'skip_typecheck',True)
 
+    table_class = taggable_classes[objclass]['table_class']
+    joins_class = taggable_classes[objclass]['joins_class']
+    join_class = taggable_classes[objclass]['join_class']
+
+    # locate the tag and create it if needed
+    # this method is attached to the Accessor class
+    def locate_or_create_tag (self):
+        return self.locate_or_create_tag (tagname=tagname,
+                                          category=category,
+                                          description=description,
+                                          roles=set_roles)
+
+    # attach it to the Accessor class
+    setattr(Accessor,locator_name,locate_or_create_tag)
+
     # body of the get method
     def get_call (self, auth, id_or_name):
-        print 'Automagical Accessor get method',classname,get_name,tagname,primary_key,secondary_key
-        print 'Warning: PLC/Accessors/Factory is an ongoing work'
-        tag_type_id = locate_or_create_tag_type_id (self.api, tagname, 
-                                                    category, description, tag_min_role_id)
-        return 'foobar'
+        # locate the tag, see above
+        locator = getattr(Accessor,locator_name)
+        tag_type_id = locator(AccessorSingleton(self.api))
+
+        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'])
+        if not joins:
+            # xxx - we return None even if id_or_name is not valid
+            return None
+        else:
+            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):
-        print 'Automagical Accessor set method',classname,get_name,tagname,primary_key,secondary_key
-        print 'Warning: PLC/Accessors/Factory is an ongoing work'
-        return None
+    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,secondary_key])
+        if not objs:
+            raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
+        primary_id = objs[0][primary_key]
+
+        # locate the tag, see above
+        locator = getattr(Accessor,locator_name)
+        tag_type_id = locator(AccessorSingleton(self.api))
+
+        # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
+        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)
+        # 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:
+            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 )
-
-### might need to use a cache
-def locate_or_create_tag_type_id (api, tagname, category, description, min_role_id):
-    # search tag
-    tag_types = TagTypes (api, {'tagname':tagname})
-    # not found: create it
-    if tag_types:
-        print 'FOUND preexisting'
-        tag_type_id = tag_types[0]['tag_type_id']
-    else:
-        print 'not FOUND : creating'
-        tag_type_fields = {'tagname':tagname, 
-                           'category' :  category,
-                           'description' : description,
-                           'min_role_id': min_role_id}
-        tag_type = TagType (api, tag_type_fields)
-        tag_type.sync()
-        tag_type_id = tag_type['tag_type_id']
-
-    return tag_type_id
+    # define in module
+    setattr(module,get_name,get_class)
+    setattr(module,set_name,set_class)
+    # add in <module>.methods
+    try:
+        methods=getattr(module,'methods')
+    except:
+        methods=[]
+    methods += [get_name,set_name]
+    setattr(module,'methods',methods)