prune everything about aspects that is python2 and too tedious to port
[plcapi.git] / PLC / Accessors / Factory.py
index f2ced3f..fec0d63 100644 (file)
@@ -1,13 +1,14 @@
+#
 # Thierry Parmentelat - INRIA
 # Thierry Parmentelat - INRIA
-# $Id$
-
+#
 from types import NoneType
 
 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.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.Nodes import Nodes, Node
 from PLC.NodeTags import NodeTags, NodeTag
@@ -15,69 +16,103 @@ 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.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
+# need to import so the core classes get decorated with caller_may_write_tag
+from PLC.AuthorizeHelpers import AuthorizeHelpers
 
 
-from PLC.TagTypes import TagTypes, TagType
-
-# 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,
                              'joins_class' : NodeTags, 'join_class' : NodeTag,
-                             'value_key': 'tagvalue', 'secondary_key': 'hostname'},
-                     Interface : {'table_class' : Interfaces, 
+                             'secondary_key': 'hostname'},
+                     Interface : {'table_class' : Interfaces,
                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
-                                  'value_key' : 'value' }, 
-                     Slice: {'table_class' : Slices, 
+                                  'secondary_key' : 'ip'},
+                     Slice: {'table_class' : Slices,
                              'joins_class': SliceTags, 'join_class': SliceTag,
                              'joins_class': SliceTags, 'join_class': SliceTag,
-                             'value_key' : 'value', 'secondary_key':'login_base'},
-#                     Ilink : xxx
+                             '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
                      }
 
 # 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' ]
 
 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
 tech_roles = [ 'admin', 'pi', 'tech' ]
 
+#
 # generates 2 method classes:
 # 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
 #
 #
-# note: tag_min_role_id gets attached to the tagtype instance, 
-# while get_roles and set_roles get attached to the created methods
+# The expose_in_api flag tells whether this tag may be handled
+#   through the Add/Get/Update methods as a native field
+#
+# 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 define_accessors (module, 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 ??"
     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
     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)})
     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
     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") ]
 
     # for set, idem set of arguments + one additional arg, the new value
     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
 
@@ -89,67 +124,80 @@ def define_accessors (module, objclass, methodsuffix,
     setattr(get_class,'roles',get_roles)
     setattr(get_class,'accepts',get_accepts)
     setattr(get_class,'returns', get_returns)
     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,'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']
     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):
 
     # 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
         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:
         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 None
         else:
-            return joins[0][value_key]
+            return joins[0]['value']
 
     # attach it
     setattr (get_class,"call",get_call)
 
 
     # 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}
         # 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)
         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()
+        # 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']
 
         tag_type_id = tag_type['tag_type_id']
 
-        # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
+        # 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
         filter = {'tag_type_id':tag_type_id}
         if isinstance (id_or_name,int):
             filter[primary_key]=id_or_name
@@ -157,21 +205,30 @@ def define_accessors (module, objclass, methodsuffix,
             filter[secondary_key]=id_or_name
         joins = joins_class (self.api,filter)
         # setting to something non void
             filter[secondary_key]=id_or_name
         joins = joins_class (self.api,filter)
         # setting to something non void
-        if tagvalue is not None:
+        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
             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['value']=value
                 join.sync()
             else:
                 join.sync()
             else:
-                joins[0][value_key]=tagvalue
+                joins[0]['value']=value
                 joins[0].sync()
         # providing an empty value means clean up
         else:
             if joins:
                 join=joins[0]
                 join.delete()
                 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)
 
     # attach it
     setattr (set_class,"call",set_call)
@@ -186,4 +243,3 @@ def define_accessors (module, objclass, methodsuffix,
         methods=[]
     methods += [get_name,set_name]
     setattr(module,'methods',methods)
         methods=[]
     methods += [get_name,set_name]
     setattr(module,'methods',methods)
-