more ironing in the corner of that 'types' modules
[plcapi.git] / PLC / Accessors / Factory.py
1 #
2 # Thierry Parmentelat - INRIA
3 #
4 # pylint: disable=c0103, c0111
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 = {
29     Node : {'table_class' : Nodes,
30             'joins_class' : NodeTags, 'join_class' : NodeTag,
31             'secondary_key': 'hostname'},
32     Interface : {'table_class' : Interfaces,
33                  'joins_class': InterfaceTags, 'join_class': InterfaceTag,
34                  'secondary_key' : 'ip'},
35     Slice: {'table_class' : Slices,
36             'joins_class': SliceTags, 'join_class': SliceTag,
37             'secondary_key':'name'},
38     Site: {'table_class' : Sites,
39            'joins_class': SiteTags, 'join_class': SiteTag,
40            'secondary_key':'login_base'},
41     Person: {'table_class' : Persons,
42              'joins_class': PersonTags, 'join_class': PersonTag,
43              'secondary_key':'email'},
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: set_roles get attached as 'roles' to the tagtype instance,
62 # also get_roles and set_roles get attached to the created methods
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 write 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
76 # the entry point accepts a single class or a list of classes
77 def define_accessors(module, objclasses, *args, **kwds):
78     if not isinstance(objclasses, list):
79         objclasses = [objclasses]
80     for objclass in objclasses:
81         define_accessors_(module, objclass, *args, **kwds)
82
83 # this is for one class
84 def define_accessors_(module, objclass, methodsuffix, tagname,
85                       category, description,
86                       get_roles=all_roles, set_roles=admin_roles,
87                       expose_in_api=False):
88
89     if objclass not in taggable_classes:
90         try:
91             raise PLCInvalidArgument("PLC.Accessors.Factory: unknown class %s"%objclass.__name__)
92         except:
93             raise PLCInvalidArgument("PLC.Accessors.Factory: unknown class ??")
94
95     # side-effect on, say, Node.tags, if required
96     if expose_in_api:
97         getattr(objclass, 'tags')[tagname] = Parameter(str, "accessor")
98
99     classname = objclass.__name__
100     get_name = "Get" + classname + methodsuffix
101     set_name = "Set" + classname + methodsuffix
102     locator_name = "Locate" + classname + methodsuffix
103
104     # accessor method objects under PLC.Method.Method
105     get_class = type(get_name, (Method,),
106                      {"__doc__":
107                       "Accessor 'get' method designed for %s objects using tag %s"%\
108                         (classname, tagname)})
109     set_class = type(set_name, (Method,),
110                      {"__doc__":
111                       "Accessor 'set' method designed for %s objects using tag %s"%\
112                         (classname, tagname)})
113
114     # accepts
115     get_accepts = [Auth()]
116     primary_key = objclass.primary_key
117     secondary_key = taggable_classes[objclass]['secondary_key']
118     get_accepts += [Mixed(objclass.fields[primary_key], objclass.fields[secondary_key])]
119     # for set, idem set of arguments + one additional arg, the new value
120     set_accepts = get_accepts + [Parameter(str, "New tag value")]
121
122     # returns
123     get_returns = Mixed(Parameter(str), Parameter(type(None)))
124     set_returns = Parameter(type(None))
125
126     # store in classes
127     setattr(get_class, 'roles', get_roles)
128     setattr(get_class, 'accepts', get_accepts)
129     setattr(get_class, 'returns', get_returns)
130 # that was useful for legacy method only, but we now need type_checking
131 #    setattr(get_class,'skip_type_check',True)
132
133     setattr(set_class, 'roles', set_roles)
134     setattr(set_class, 'accepts', set_accepts)
135     setattr(set_class, 'returns', set_returns)
136 # that was useful for legacy method only, but we now need type_checking
137 #    setattr(set_class,'skip_type_check',True)
138
139     table_class = taggable_classes[objclass]['table_class']
140     joins_class = taggable_classes[objclass]['joins_class']
141     join_class = taggable_classes[objclass]['join_class']
142
143     # locate the tag and create it if needed
144     # this method is attached to the Accessor class
145     def tag_locator(self, enforce=False):
146         return self.locate_or_create_tag(
147             tagname=tagname,
148             category=category,
149             description=description,
150             roles=set_roles,
151             enforce=enforce)
152
153     # attach it to the Accessor class
154     Accessor.register_tag_locator(locator_name, tag_locator)
155
156     # body of the get method
157     def get_call(self, auth, id_or_name):
158         # locate the tag, see above
159         tag_locator = Accessor.retrieve_tag_locator(locator_name)
160         tag_type = tag_locator(AccessorSingleton(self.api))
161         tag_type_id = tag_type['tag_type_id']
162
163         filter = {'tag_type_id': tag_type_id}
164         if isinstance(id_or_name, int):
165             filter[primary_key] = id_or_name
166         else:
167             filter[secondary_key] = id_or_name
168         joins = joins_class(self.api, filter, ['value'])
169         if not joins:
170             # xxx - we return None even if id_or_name is not valid
171             return None
172         else:
173             return joins[0]['value']
174
175     # attach it
176     setattr(get_class, "call", get_call)
177
178     # body of the set method
179     def set_call(self, auth, id_or_name, value):
180         # locate the object
181         if isinstance(id_or_name, int):
182             filter = {primary_key:id_or_name}
183         else:
184             filter = {secondary_key:id_or_name}
185 # we need the full monty b/c of the permission system
186 #        objs = table_class(self.api, filter,[primary_key,secondary_key])
187         objs = table_class(self.api, filter)
188         if not objs:
189             raise PLCInvalidArgument("Cannot set tag on %s %r"%(objclass.__name__, id_or_name))
190         # the object being tagged
191         obj = objs[0]
192         primary_id = obj[primary_key]
193
194         # locate the tag, see above
195         tag_locator = Accessor.retrieve_tag_locator(locator_name)
196         tag_type = tag_locator(AccessorSingleton(self.api))
197         tag_type_id = tag_type['tag_type_id']
198
199         # check authorization
200         if not hasattr(objclass, 'caller_may_write_tag'):
201             raise PLCAuthenticationFailure(
202                 "class %s misses method caller_may_write_tag"%objclass.__name__)
203         obj.caller_may_write_tag(self.api, self.caller, tag_type)
204
205         # locate the join object (e.g. NodeTag or similar)
206         filter = {'tag_type_id': tag_type_id}
207         if isinstance(id_or_name, int):
208             filter[primary_key] = id_or_name
209         else:
210             filter[secondary_key] = id_or_name
211         joins = joins_class(self.api, filter)
212         # setting to something non void
213         if value is not None:
214             if not joins:
215                 join = join_class(self.api)
216                 join['tag_type_id'] = tag_type_id
217                 join[primary_key] = primary_id
218                 join['value'] = value
219                 join.sync()
220             else:
221                 joins[0]['value'] = value
222                 joins[0].sync()
223         # providing an empty value means clean up
224         else:
225             if joins:
226                 join = joins[0]
227                 join.delete()
228         # log it
229         self.event_objects = {objclass.__name__ : [primary_id]}
230         self.message = objclass.__name__
231         if secondary_key in objs[0]:
232             self.message += " %s "%objs[0][secondary_key]
233         else:
234             self.message += " %d "%objs[0][primary_key]
235         self.message += "updated"
236         return value
237
238     # attach it
239     setattr(set_class, "call", set_call)
240
241     # define in module
242     setattr(module, get_name, get_class)
243     setattr(module, set_name, set_class)
244     # add in <module>.methods
245     try:
246         methods = getattr(module, 'methods')
247     except Exception:
248         methods = []
249     methods += [get_name, set_name]
250     setattr(module, 'methods', methods)