locate_or_create_tag returns tag_type, and not tag_type_id anymore
[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 from PLC.AuthorizeHelpers import AuthorizeHelpers
25
26 # known classes : { class -> details }
27 taggable_classes = { Node : {'table_class' : Nodes,
28                              'joins_class' : NodeTags, 'join_class' : NodeTag,
29                              'secondary_key': 'hostname'},
30                      Interface : {'table_class' : Interfaces,
31                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
32                                   'secondary_key' : 'ip'},
33                      Slice: {'table_class' : Slices,
34                              'joins_class': SliceTags, 'join_class': SliceTag,
35                              'secondary_key':'name'},
36                      Site: {'table_class' : Sites,
37                              'joins_class': SiteTags, 'join_class': SiteTag,
38                              'secondary_key':'login_base'},
39                      Person: {'table_class' : Persons,
40                              'joins_class': PersonTags, 'join_class': PersonTag,
41                              'secondary_key':'email'},
42                      }
43
44 # xxx probably defined someplace else
45 admin_roles = ['admin']
46 person_roles = [ 'admin', 'pi', 'tech', 'user' ]
47 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
48 tech_roles = [ 'admin', 'pi', 'tech' ]
49
50 #
51 # generates 2 method classes:
52 # Get<classname><methodsuffix> (auth, id_or_name) -> value or None
53 # Set<classname><methodsuffix> (auth, id_or_name, value) -> value
54 # value is always a string, no cast nor typecheck for now
55 #
56 # The expose_in_api flag tells whether this tag may be handled
57 #   through the Add/Get/Update methods as a native field
58 #
59 # note: roles get attached to the tagtype instance,
60 # while get_roles and set_roles get attached to the created methods
61 # this might need a cleanup
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 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 def define_accessors (module, objclass, methodsuffix, tagname,
75                       category, description,
76                       get_roles=all_roles, set_roles=admin_roles, 
77                       expose_in_api = False):
78
79     if objclass not in taggable_classes:
80         try:
81             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
82         except:
83             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
84
85     # side-effect on, say, Node.tags, if required
86     if expose_in_api:
87         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
88
89     classname=objclass.__name__
90     get_name = "Get" + classname + methodsuffix
91     set_name = "Set" + classname + methodsuffix
92     locator_name = "Locate" + classname + methodsuffix
93
94     # accessor method objects under PLC.Method.Method
95     get_class = type (get_name, (Method,),
96                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
97                            (classname,tagname)})
98     set_class = type (set_name, (Method,),
99                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
100                            (classname,tagname)})
101
102     # accepts
103     get_accepts = [ Auth () ]
104     primary_key=objclass.primary_key
105     secondary_key = taggable_classes[objclass]['secondary_key']
106     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
107     # for set, idem set of arguments + one additional arg, the new value
108     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
109
110     # returns
111     get_returns = Mixed (Parameter (str), Parameter(NoneType))
112     set_returns = Parameter(NoneType)
113
114     # store in classes
115     setattr(get_class,'roles',get_roles)
116     setattr(get_class,'accepts',get_accepts)
117     setattr(get_class,'returns', get_returns)
118 # that was useful for legacy method only, but we now need type_checking
119 #    setattr(get_class,'skip_type_check',True)
120
121     setattr(set_class,'roles',set_roles)
122     setattr(set_class,'accepts',set_accepts)
123     setattr(set_class,'returns', set_returns)
124 # that was useful for legacy method only, but we now need type_checking
125 #    setattr(set_class,'skip_type_check',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 = locator(AccessorSingleton(self.api))
147         tag_type_id=tag_type['tag_type_id']
148
149         filter = {'tag_type_id':tag_type_id}
150         if isinstance (id_or_name,int):
151             filter[primary_key]=id_or_name
152         else:
153             filter[secondary_key]=id_or_name
154         joins = joins_class (self.api,filter,['value'])
155         if not joins:
156             # xxx - we return None even if id_or_name is not valid
157             return None
158         else:
159             return joins[0]['value']
160
161     # attach it
162     setattr (get_class,"call",get_call)
163
164     # body of the set method
165     def set_call (self, auth, id_or_name, value):
166         # locate the object
167         if isinstance (id_or_name, int):
168             filter={primary_key:id_or_name}
169         else:
170             filter={secondary_key:id_or_name}
171 # we need the full monty b/c of the permission system
172 #        objs = table_class(self.api, filter,[primary_key,secondary_key])
173         objs = table_class(self.api, filter)
174         if not objs:
175             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
176         # the object being tagged
177         obj=objs[0]
178         primary_id = obj[primary_key]
179
180         # locate the tag, see above
181         locator = getattr(Accessor,locator_name)
182         tag_type = locator(AccessorSingleton(self.api))
183         tag_type_id = tag_type['tag_type_id']
184
185         # check authorization
186         if hasattr(objclass,'caller_may_write_tag'):
187             obj.caller_may_write_tag (self.api,self.caller,tag_type)
188
189         # locate the join object (e.g. NodeTag or similar)
190         filter = {'tag_type_id':tag_type_id}
191         if isinstance (id_or_name,int):
192             filter[primary_key]=id_or_name
193         else:
194             filter[secondary_key]=id_or_name
195         joins = joins_class (self.api,filter)
196         # setting to something non void
197         if value is not None:
198             if not joins:
199                 join = join_class (self.api)
200                 join['tag_type_id']=tag_type_id
201                 join[primary_key]=primary_id
202                 join['value']=value
203                 join.sync()
204             else:
205                 joins[0]['value']=value
206                 joins[0].sync()
207         # providing an empty value means clean up
208         else:
209             if joins:
210                 join=joins[0]
211                 join.delete()
212         # log it
213         self.event_objects= { objclass.__name__ : [primary_id] }
214         self.message=objclass.__name__
215         if secondary_key in objs[0]:
216             self.message += " %s "%objs[0][secondary_key]
217         else:
218             self.message += " %d "%objs[0][primary_key]
219         self.message += "updated"
220         return value
221
222     # attach it
223     setattr (set_class,"call",set_call)
224
225     # define in module
226     setattr(module,get_name,get_class)
227     setattr(module,set_name,set_class)
228     # add in <module>.methods
229     try:
230         methods=getattr(module,'methods')
231     except:
232         methods=[]
233     methods += [get_name,set_name]
234     setattr(module,'methods',methods)