539dbbb032e94eb590f4e29e5efab3ea13347dc7
[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: roles get attached to the tagtype instance,
61 # while get_roles and set_roles get attached to the created methods
62 # this might need a cleanup
63 #
64 # in addition a convenience method like e.g. LocateNodeArch is defined
65 # in the Accessor class; its purpose is to retrieve the tag, or to create it if needed
66
67 # Legacy NOTE:
68 # prior to plcapi-5.0-19, this used to accept an additional argument
69 # named min_role_id; this was redundant and confusing, it has been
70 # removed, we now use set_roles to restrict access on the corresponding tag
71
72 # the convention here is that methodsuffix should be mixed case, e.g. MyStuff
73 # while tagname is expected to be lowercase
74 # you then end up with e.g. GetPersonMyStuff
75 def define_accessors (module, objclass, methodsuffix, tagname,
76                       category, description,
77                       get_roles=all_roles, set_roles=admin_roles, 
78                       expose_in_api = False):
79
80     if objclass not in taggable_classes:
81         try:
82             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
83         except:
84             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
85
86     # side-effect on, say, Node.tags, if required
87     if expose_in_api:
88         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
89
90     classname=objclass.__name__
91     get_name = "Get" + classname + methodsuffix
92     set_name = "Set" + classname + methodsuffix
93     locator_name = "Locate" + classname + methodsuffix
94
95     # accessor method objects under PLC.Method.Method
96     get_class = type (get_name, (Method,),
97                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
98                            (classname,tagname)})
99     set_class = type (set_name, (Method,),
100                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
101                            (classname,tagname)})
102
103     # accepts
104     get_accepts = [ Auth () ]
105     primary_key=objclass.primary_key
106     secondary_key = taggable_classes[objclass]['secondary_key']
107     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
108     # for set, idem set of arguments + one additional arg, the new value
109     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
110
111     # returns
112     get_returns = Mixed (Parameter (str), Parameter(NoneType))
113     set_returns = Parameter(NoneType)
114
115     # store in classes
116     setattr(get_class,'roles',get_roles)
117     setattr(get_class,'accepts',get_accepts)
118     setattr(get_class,'returns', get_returns)
119 # that was useful for legacy method only, but we now need type_checking
120 #    setattr(get_class,'skip_type_check',True)
121
122     setattr(set_class,'roles',set_roles)
123     setattr(set_class,'accepts',set_accepts)
124     setattr(set_class,'returns', set_returns)
125 # that was useful for legacy method only, but we now need type_checking
126 #    setattr(set_class,'skip_type_check',True)
127
128     table_class = taggable_classes[objclass]['table_class']
129     joins_class = taggable_classes[objclass]['joins_class']
130     join_class = taggable_classes[objclass]['join_class']
131
132     # locate the tag and create it if needed
133     # this method is attached to the Accessor class
134     def locate_or_create_tag (self):
135         return self.locate_or_create_tag (tagname=tagname,
136                                           category=category,
137                                           description=description,
138                                           roles=set_roles)
139
140     # attach it to the Accessor class
141     setattr(Accessor,locator_name,locate_or_create_tag)
142
143     # body of the get method
144     def get_call (self, auth, id_or_name):
145         # locate the tag, see above
146         locator = getattr(Accessor,locator_name)
147         tag_type = locator(AccessorSingleton(self.api))
148         tag_type_id=tag_type['tag_type_id']
149
150         filter = {'tag_type_id':tag_type_id}
151         if isinstance (id_or_name,int):
152             filter[primary_key]=id_or_name
153         else:
154             filter[secondary_key]=id_or_name
155         joins = joins_class (self.api,filter,['value'])
156         if not joins:
157             # xxx - we return None even if id_or_name is not valid
158             return None
159         else:
160             return joins[0]['value']
161
162     # attach it
163     setattr (get_class,"call",get_call)
164
165     # body of the set method
166     def set_call (self, auth, id_or_name, value):
167         # locate the object
168         if isinstance (id_or_name, int):
169             filter={primary_key:id_or_name}
170         else:
171             filter={secondary_key:id_or_name}
172 # we need the full monty b/c of the permission system
173 #        objs = table_class(self.api, filter,[primary_key,secondary_key])
174         objs = table_class(self.api, filter)
175         if not objs:
176             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
177         # the object being tagged
178         obj=objs[0]
179         primary_id = obj[primary_key]
180
181         # locate the tag, see above
182         locator = getattr(Accessor,locator_name)
183         tag_type = locator(AccessorSingleton(self.api))
184         tag_type_id = tag_type['tag_type_id']
185
186         # check authorization
187         if not hasattr(objclass,'caller_may_write_tag'):
188             raise PLCAuthenticationFailure, "class %s misses method caller_may_write_tag"%objclass.__name__
189         obj.caller_may_write_tag (self.api,self.caller,tag_type)
190
191         # locate the join object (e.g. NodeTag or similar)
192         filter = {'tag_type_id':tag_type_id}
193         if isinstance (id_or_name,int):
194             filter[primary_key]=id_or_name
195         else:
196             filter[secondary_key]=id_or_name
197         joins = joins_class (self.api,filter)
198         # setting to something non void
199         if value is not None:
200             if not joins:
201                 join = join_class (self.api)
202                 join['tag_type_id']=tag_type_id
203                 join[primary_key]=primary_id
204                 join['value']=value
205                 join.sync()
206             else:
207                 joins[0]['value']=value
208                 joins[0].sync()
209         # providing an empty value means clean up
210         else:
211             if joins:
212                 join=joins[0]
213                 join.delete()
214         # log it
215         self.event_objects= { objclass.__name__ : [primary_id] }
216         self.message=objclass.__name__
217         if secondary_key in objs[0]:
218             self.message += " %s "%objs[0][secondary_key]
219         else:
220             self.message += " %d "%objs[0][primary_key]
221         self.message += "updated"
222         return value
223
224     # attach it
225     setattr (set_class,"call",set_call)
226
227     # define in module
228     setattr(module,get_name,get_class)
229     setattr(module,set_name,set_class)
230     # add in <module>.methods
231     try:
232         methods=getattr(module,'methods')
233     except:
234         methods=[]
235     methods += [get_name,set_name]
236     setattr(module,'methods',methods)