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