cleanup accessors
[plcapi.git] / PLC / Accessors / Factory.py
1 #
2 # Thierry Parmentelat - INRIA
3 #
4 from types import NoneType
5
6 from PLC.Faults import *
7
8 from PLC.Auth import Auth
9 from PLC.Parameter import Parameter, Mixed
10 from PLC.Method import Method
11 from PLC.Accessor import Accessor, AccessorSingleton
12
13 from PLC.Nodes import Nodes, Node
14 from PLC.NodeTags import NodeTags, NodeTag
15 from PLC.Interfaces import Interfaces, Interface
16 from PLC.InterfaceTags import InterfaceTags, InterfaceTag
17 from PLC.Slices import Slices, Slice
18 from PLC.SliceTags import SliceTags, SliceTag
19 from PLC.Sites import Sites, Site
20 from PLC.SiteTags import SiteTags, SiteTag
21 from PLC.Persons import Persons, Person
22 from PLC.PersonTags import PersonTags, PersonTag
23
24 # need to import so the core classes get decorated with caller_may_write_tag
25 from PLC.AuthorizeHelpers import AuthorizeHelpers
26
27 # known classes : { class -> details }
28 taggable_classes = { Node : {'table_class' : Nodes,
29                              'joins_class' : NodeTags, 'join_class' : NodeTag,
30                              'secondary_key': 'hostname'},
31                      Interface : {'table_class' : Interfaces,
32                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
33                                   'secondary_key' : 'ip'},
34                      Slice: {'table_class' : Slices,
35                              'joins_class': SliceTags, 'join_class': SliceTag,
36                              'secondary_key':'name'},
37                      Site: {'table_class' : Sites,
38                              'joins_class': SiteTags, 'join_class': SiteTag,
39                              'secondary_key':'login_base'},
40                      Person: {'table_class' : Persons,
41                              'joins_class': PersonTags, 'join_class': PersonTag,
42                              'secondary_key':'email'},
43                      }
44
45 # xxx probably defined someplace else
46 admin_roles = ['admin']
47 person_roles = [ 'admin', 'pi', 'tech', 'user' ]
48 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
49 tech_roles = [ 'admin', 'pi', 'tech' ]
50
51 #
52 # generates 2 method classes:
53 # Get<classname><methodsuffix> (auth, id_or_name) -> value or None
54 # Set<classname><methodsuffix> (auth, id_or_name, value) -> value
55 # value is always a string, no cast nor typecheck for now
56 #
57 # The expose_in_api flag tells whether this tag may be handled
58 #   through the Add/Get/Update methods as a native field
59 #
60 # note: set_roles get attached as 'roles' to the tagtype instance,
61 # also get_roles and set_roles get attached to the created methods
62 #
63 # in addition a convenience method like e.g. LocateNodeArch is defined
64 # in the Accessor class; its purpose is to retrieve the tag, or to create it if needed
65
66 # Legacy NOTE:
67 # prior to plcapi-5.0-19, this used to accept an additional argument
68 # named min_role_id; this was redundant and confusing, it has been
69 # removed, we now use set_roles to restrict write access on the corresponding tag
70
71 # the convention here is that methodsuffix should be mixed case, e.g. MyStuff
72 # while tagname is expected to be lowercase
73 # you then end up with e.g. GetPersonMyStuff
74
75 # the entry point accepts a single class or a list of classes
76 def define_accessors (module, objclasses, *args, **kwds):
77     if not isinstance(objclasses,list):
78         objclasses=[objclasses]
79     for objclass in objclasses:
80         define_accessors_ (module, objclass, *args, **kwds)
81
82 # this is for one class
83 def define_accessors_ (module, objclass, methodsuffix, tagname,
84                        category, description,
85                        get_roles=all_roles, set_roles=admin_roles, 
86                        expose_in_api = False):
87
88     if objclass not in taggable_classes:
89         try:
90             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
91         except:
92             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
93
94     # side-effect on, say, Node.tags, if required
95     if expose_in_api:
96         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
97
98     classname=objclass.__name__
99     get_name = "Get" + classname + methodsuffix
100     set_name = "Set" + classname + methodsuffix
101     locator_name = "Locate" + classname + methodsuffix
102
103     # accessor method objects under PLC.Method.Method
104     get_class = type (get_name, (Method,),
105                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
106                            (classname,tagname)})
107     set_class = type (set_name, (Method,),
108                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
109                            (classname,tagname)})
110
111     # accepts
112     get_accepts = [ Auth () ]
113     primary_key=objclass.primary_key
114     secondary_key = taggable_classes[objclass]['secondary_key']
115     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
116     # for set, idem set of arguments + one additional arg, the new value
117     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
118
119     # returns
120     get_returns = Mixed (Parameter (str), Parameter(NoneType))
121     set_returns = Parameter(NoneType)
122
123     # store in classes
124     setattr(get_class,'roles',get_roles)
125     setattr(get_class,'accepts',get_accepts)
126     setattr(get_class,'returns', get_returns)
127 # that was useful for legacy method only, but we now need type_checking
128 #    setattr(get_class,'skip_type_check',True)
129
130     setattr(set_class,'roles',set_roles)
131     setattr(set_class,'accepts',set_accepts)
132     setattr(set_class,'returns', set_returns)
133 # that was useful for legacy method only, but we now need type_checking
134 #    setattr(set_class,'skip_type_check',True)
135
136     table_class = taggable_classes[objclass]['table_class']
137     joins_class = taggable_classes[objclass]['joins_class']
138     join_class = taggable_classes[objclass]['join_class']
139
140     # locate the tag and create it if needed
141     # this method is attached to the Accessor class
142     def tag_locator (self, enforce=False):
143         return self.locate_or_create_tag (tagname=tagname,
144                                           category=category,
145                                           description=description,
146                                           roles=set_roles,
147                                           enforce=enforce)
148
149     # attach it to the Accessor class
150     Accessor.register_tag_locator(locator_name,tag_locator)
151
152     # body of the get method
153     def get_call (self, auth, id_or_name):
154         # locate the tag, see above
155         tag_locator = Accessor.retrieve_tag_locator(locator_name)
156         tag_type = tag_locator(AccessorSingleton(self.api))
157         tag_type_id=tag_type['tag_type_id']
158
159         filter = {'tag_type_id':tag_type_id}
160         if isinstance (id_or_name,int):
161             filter[primary_key]=id_or_name
162         else:
163             filter[secondary_key]=id_or_name
164         joins = joins_class (self.api,filter,['value'])
165         if not joins:
166             # xxx - we return None even if id_or_name is not valid
167             return None
168         else:
169             return joins[0]['value']
170
171     # attach it
172     setattr (get_class,"call",get_call)
173
174     # body of the set method
175     def set_call (self, auth, id_or_name, value):
176         # locate the object
177         if isinstance (id_or_name, int):
178             filter={primary_key:id_or_name}
179         else:
180             filter={secondary_key:id_or_name}
181 # we need the full monty b/c of the permission system
182 #        objs = table_class(self.api, filter,[primary_key,secondary_key])
183         objs = table_class(self.api, filter)
184         if not objs:
185             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
186         # the object being tagged
187         obj=objs[0]
188         primary_id = obj[primary_key]
189
190         # locate the tag, see above
191         tag_locator = Accessor.retrieve_tag_locator(locator_name)
192         tag_type = tag_locator(AccessorSingleton(self.api))
193         tag_type_id = tag_type['tag_type_id']
194
195         # check authorization
196         if not hasattr(objclass,'caller_may_write_tag'):
197             raise PLCAuthenticationFailure, "class %s misses method caller_may_write_tag"%objclass.__name__
198         obj.caller_may_write_tag (self.api,self.caller,tag_type)
199
200         # locate the join object (e.g. NodeTag or similar)
201         filter = {'tag_type_id':tag_type_id}
202         if isinstance (id_or_name,int):
203             filter[primary_key]=id_or_name
204         else:
205             filter[secondary_key]=id_or_name
206         joins = joins_class (self.api,filter)
207         # setting to something non void
208         if value is not None:
209             if not joins:
210                 join = join_class (self.api)
211                 join['tag_type_id']=tag_type_id
212                 join[primary_key]=primary_id
213                 join['value']=value
214                 join.sync()
215             else:
216                 joins[0]['value']=value
217                 joins[0].sync()
218         # providing an empty value means clean up
219         else:
220             if joins:
221                 join=joins[0]
222                 join.delete()
223         # log it
224         self.event_objects= { objclass.__name__ : [primary_id] }
225         self.message=objclass.__name__
226         if secondary_key in objs[0]:
227             self.message += " %s "%objs[0][secondary_key]
228         else:
229             self.message += " %d "%objs[0][primary_key]
230         self.message += "updated"
231         return value
232
233     # attach it
234     setattr (set_class,"call",set_call)
235
236     # define in module
237     setattr(module,get_name,get_class)
238     setattr(module,set_name,set_class)
239     # add in <module>.methods
240     try:
241         methods=getattr(module,'methods')
242     except:
243         methods=[]
244     methods += [get_name,set_name]
245     setattr(module,'methods',methods)