2 # Thierry Parmentelat - INRIA
4 # pylint: disable=c0103, c0111
6 from PLC.Faults import *
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
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
24 # need to import so the core classes get decorated with caller_may_write_tag
25 from PLC.AuthorizeHelpers import AuthorizeHelpers
27 # known classes : { class -> details }
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'},
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']
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
58 # The expose_in_api flag tells whether this tag may be handled
59 # through the Add/Get/Update methods as a native field
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
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
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
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
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)
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,
89 if objclass not in taggable_classes:
91 raise PLCInvalidArgument("PLC.Accessors.Factory: unknown class %s"%objclass.__name__)
93 raise PLCInvalidArgument("PLC.Accessors.Factory: unknown class ??")
95 # side-effect on, say, Node.tags, if required
97 getattr(objclass, 'tags')[tagname] = Parameter(str, "accessor")
99 classname = objclass.__name__
100 get_name = "Get" + classname + methodsuffix
101 set_name = "Set" + classname + methodsuffix
102 locator_name = "Locate" + classname + methodsuffix
104 # accessor method objects under PLC.Method.Method
105 get_class = type(get_name, (Method,),
107 "Accessor 'get' method designed for %s objects using tag %s"%\
108 (classname, tagname)})
109 set_class = type(set_name, (Method,),
111 "Accessor 'set' method designed for %s objects using tag %s"%\
112 (classname, tagname)})
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")]
123 get_returns = Mixed(Parameter(str), Parameter(type(None)))
124 set_returns = Parameter(type(None))
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)
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)
139 table_class = taggable_classes[objclass]['table_class']
140 joins_class = taggable_classes[objclass]['joins_class']
141 join_class = taggable_classes[objclass]['join_class']
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(
149 description=description,
153 # attach it to the Accessor class
154 Accessor.register_tag_locator(locator_name, tag_locator)
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']
163 filter = {'tag_type_id': tag_type_id}
164 if isinstance(id_or_name, int):
165 filter[primary_key] = id_or_name
167 filter[secondary_key] = id_or_name
168 joins = joins_class(self.api, filter, ['value'])
170 # xxx - we return None even if id_or_name is not valid
173 return joins[0]['value']
176 setattr(get_class, "call", get_call)
178 # body of the set method
179 def set_call(self, auth, id_or_name, value):
181 if isinstance(id_or_name, int):
182 filter = {primary_key:id_or_name}
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)
189 raise PLCInvalidArgument("Cannot set tag on %s %r"%(objclass.__name__, id_or_name))
190 # the object being tagged
192 primary_id = obj[primary_key]
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']
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)
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
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:
215 join = join_class(self.api)
216 join['tag_type_id'] = tag_type_id
217 join[primary_key] = primary_id
218 join['value'] = value
221 joins[0]['value'] = value
223 # providing an empty value means clean up
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]
234 self.message += " %d "%objs[0][primary_key]
235 self.message += "updated"
239 setattr(set_class, "call", set_call)
242 setattr(module, get_name, get_class)
243 setattr(module, set_name, set_class)
244 # add in <module>.methods
246 methods = getattr(module, 'methods')
249 methods += [get_name, set_name]
250 setattr(module, 'methods', methods)