2 # Thierry Parmentelat - INRIA
4 from types import NoneType
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 }
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'},
45 # xxx probably defined someplace else
46 admin_roles = ['admin']
47 person_roles = [ 'admin', 'pi', 'tech', 'user' ]
48 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
49 tech_roles = [ 'admin', 'pi', 'tech' ]
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
57 # The expose_in_api flag tells whether this tag may be handled
58 # through the Add/Get/Update methods as a native field
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
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 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,
87 expose_in_api = False):
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,),
106 {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
107 (classname,tagname)})
108 set_class = type (set_name, (Method,),
109 {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
110 (classname,tagname)})
113 get_accepts = [ Auth () ]
114 primary_key=objclass.primary_key
115 secondary_key = taggable_classes[objclass]['secondary_key']
116 get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
117 # for set, idem set of arguments + one additional arg, the new value
118 set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
121 get_returns = Mixed (Parameter (str), Parameter(NoneType))
122 set_returns = Parameter(NoneType)
125 setattr(get_class,'roles',get_roles)
126 setattr(get_class,'accepts',get_accepts)
127 setattr(get_class,'returns', get_returns)
128 # that was useful for legacy method only, but we now need type_checking
129 # setattr(get_class,'skip_type_check',True)
131 setattr(set_class,'roles',set_roles)
132 setattr(set_class,'accepts',set_accepts)
133 setattr(set_class,'returns', set_returns)
134 # that was useful for legacy method only, but we now need type_checking
135 # setattr(set_class,'skip_type_check',True)
137 table_class = taggable_classes[objclass]['table_class']
138 joins_class = taggable_classes[objclass]['joins_class']
139 join_class = taggable_classes[objclass]['join_class']
141 # locate the tag and create it if needed
142 # this method is attached to the Accessor class
143 def tag_locator (self, enforce=False):
144 return self.locate_or_create_tag (tagname=tagname,
146 description=description,
150 # attach it to the Accessor class
151 Accessor.register_tag_locator(locator_name,tag_locator)
153 # body of the get method
154 def get_call (self, auth, id_or_name):
155 # locate the tag, see above
156 tag_locator = Accessor.retrieve_tag_locator(locator_name)
157 tag_type = tag_locator(AccessorSingleton(self.api))
158 tag_type_id=tag_type['tag_type_id']
160 filter = {'tag_type_id':tag_type_id}
161 if isinstance (id_or_name,int):
162 filter[primary_key]=id_or_name
164 filter[secondary_key]=id_or_name
165 joins = joins_class (self.api,filter,['value'])
167 # xxx - we return None even if id_or_name is not valid
170 return joins[0]['value']
173 setattr (get_class,"call",get_call)
175 # body of the set method
176 def set_call (self, auth, id_or_name, value):
178 if isinstance (id_or_name, int):
179 filter={primary_key:id_or_name}
181 filter={secondary_key:id_or_name}
182 # we need the full monty b/c of the permission system
183 # objs = table_class(self.api, filter,[primary_key,secondary_key])
184 objs = table_class(self.api, filter)
186 raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
187 # the object being tagged
189 primary_id = obj[primary_key]
191 # locate the tag, see above
192 tag_locator = Accessor.retrieve_tag_locator(locator_name)
193 tag_type = tag_locator(AccessorSingleton(self.api))
194 tag_type_id = tag_type['tag_type_id']
196 # check authorization
197 if not hasattr(objclass,'caller_may_write_tag'):
198 raise PLCAuthenticationFailure, "class %s misses method caller_may_write_tag"%objclass.__name__
199 obj.caller_may_write_tag (self.api,self.caller,tag_type)
201 # locate the join object (e.g. NodeTag or similar)
202 filter = {'tag_type_id':tag_type_id}
203 if isinstance (id_or_name,int):
204 filter[primary_key]=id_or_name
206 filter[secondary_key]=id_or_name
207 joins = joins_class (self.api,filter)
208 # setting to something non void
209 if value is not None:
211 join = join_class (self.api)
212 join['tag_type_id']=tag_type_id
213 join[primary_key]=primary_id
217 joins[0]['value']=value
219 # providing an empty value means clean up
225 self.event_objects= { objclass.__name__ : [primary_id] }
226 self.message=objclass.__name__
227 if secondary_key in objs[0]:
228 self.message += " %s "%objs[0][secondary_key]
230 self.message += " %d "%objs[0][primary_key]
231 self.message += "updated"
235 setattr (set_class,"call",set_call)
238 setattr(module,get_name,get_class)
239 setattr(module,set_name,set_class)
240 # add in <module>.methods
242 methods=getattr(module,'methods')
245 methods += [get_name,set_name]
246 setattr(module,'methods',methods)