bugfix
[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 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 def define_accessors (module, objclass, methodsuffix, tagname,
73                       category, description,
74                       get_roles=all_roles, set_roles=admin_roles, 
75                       expose_in_api = False):
76
77     if objclass not in taggable_classes:
78         try:
79             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
80         except:
81             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
82
83     # side-effect on, say, Node.tags, if required
84     if expose_in_api:
85         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
86
87     classname=objclass.__name__
88     get_name = "Get" + classname + methodsuffix
89     set_name = "Set" + classname + methodsuffix
90     locator_name = "Locate" + classname + methodsuffix
91
92     # accessor method objects under PLC.Method.Method
93     get_class = type (get_name, (Method,),
94                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
95                            (classname,tagname)})
96     set_class = type (set_name, (Method,),
97                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
98                            (classname,tagname)})
99
100     # accepts
101     get_accepts = [ Auth () ]
102     primary_key=objclass.primary_key
103     secondary_key = taggable_classes[objclass]['secondary_key']
104     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
105     # for set, idem set of arguments + one additional arg, the new value
106     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
107
108     # returns
109     get_returns = Mixed (Parameter (str), Parameter(NoneType))
110     set_returns = Parameter(NoneType)
111
112     # store in classes
113     setattr(get_class,'roles',get_roles)
114     setattr(get_class,'accepts',get_accepts)
115     setattr(get_class,'returns', get_returns)
116     setattr(get_class,'skip_typecheck',True)
117
118     setattr(set_class,'roles',set_roles)
119     setattr(set_class,'accepts',set_accepts)
120     setattr(set_class,'returns', set_returns)
121     setattr(set_class,'skip_typecheck',True)
122
123     table_class = taggable_classes[objclass]['table_class']
124     joins_class = taggable_classes[objclass]['joins_class']
125     join_class = taggable_classes[objclass]['join_class']
126
127     # locate the tag and create it if needed
128     # this method is attached to the Accessor class
129     def locate_or_create_tag (self):
130         return self.locate_or_create_tag (tagname=tagname,
131                                           category=category,
132                                           description=description,
133                                           roles=set_roles)
134
135     # attach it to the Accessor class
136     setattr(Accessor,locator_name,locate_or_create_tag)
137
138     # body of the get method
139     def get_call (self, auth, id_or_name):
140         # locate the tag, see above
141         locator = getattr(Accessor,locator_name)
142         tag_type_id = locator(AccessorSingleton(self.api))
143
144         filter = {'tag_type_id':tag_type_id}
145         if isinstance (id_or_name,int):
146             filter[primary_key]=id_or_name
147         else:
148             filter[secondary_key]=id_or_name
149         joins = joins_class (self.api,filter,['value'])
150         if not joins:
151             # xxx - we return None even if id_or_name is not valid
152             return None
153         else:
154             return joins[0]['value']
155
156     # attach it
157     setattr (get_class,"call",get_call)
158
159     # body of the set method
160     def set_call (self, auth, id_or_name, value):
161         # locate the object
162         if isinstance (id_or_name, int):
163             filter={primary_key:id_or_name}
164         else:
165             filter={secondary_key:id_or_name}
166         objs = table_class(self.api, filter,[primary_key,secondary_key])
167         if not objs:
168             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
169         primary_id = objs[0][primary_key]
170
171         # locate the tag, see above
172         locator = getattr(Accessor,locator_name)
173         tag_type_id = locator(AccessorSingleton(self.api))
174
175         # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
176         filter = {'tag_type_id':tag_type_id}
177         if isinstance (id_or_name,int):
178             filter[primary_key]=id_or_name
179         else:
180             filter[secondary_key]=id_or_name
181         joins = joins_class (self.api,filter)
182         # setting to something non void
183         if value is not None:
184             if not joins:
185                 join = join_class (self.api)
186                 join['tag_type_id']=tag_type_id
187                 join[primary_key]=primary_id
188                 join['value']=value
189                 join.sync()
190             else:
191                 joins[0]['value']=value
192                 joins[0].sync()
193         # providing an empty value means clean up
194         else:
195             if joins:
196                 join=joins[0]
197                 join.delete()
198         # log it
199         self.event_objects= { objclass.__name__ : [primary_id] }
200         self.message=objclass.__name__
201         if secondary_key in objs[0]:
202             self.message += " %s "%objs[0][secondary_key]
203         else:
204             self.message += " %d "%objs[0][primary_key]
205         self.message += "updated"
206         return value
207
208     # attach it
209     setattr (set_class,"call",set_call)
210
211     # define in module
212     setattr(module,get_name,get_class)
213     setattr(module,set_name,set_class)
214     # add in <module>.methods
215     try:
216         methods=getattr(module,'methods')
217     except:
218         methods=[]
219     methods += [get_name,set_name]
220     setattr(module,'methods',methods)