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: set_roles get attached as 'roles' to the tagtype instance,
61 # also get_roles and set_roles get attached to the created methods
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
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 write access on the corresponding tag
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
75 # the entry point accepts a single class or a list of classes
76 def define_accessors (module, objclasses, *args, **kwds):
77 if not isinstance(objclasses,list):
78 objclasses=[objclasses]
79 for objclass in objclasses:
80 define_accessors_ (module, objclass, *args, **kwds)
82 # this is for one class
83 def define_accessors_ (module, objclass, methodsuffix, tagname,
84 category, description,
85 get_roles=all_roles, set_roles=admin_roles,
86 expose_in_api = False):
88 if objclass not in taggable_classes:
90 raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
92 raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
94 # side-effect on, say, Node.tags, if required
96 getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
98 classname=objclass.__name__
99 get_name = "Get" + classname + methodsuffix
100 set_name = "Set" + classname + methodsuffix
101 locator_name = "Locate" + classname + methodsuffix
103 # accessor method objects under PLC.Method.Method
104 get_class = type (get_name, (Method,),
105 {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
106 (classname,tagname)})
107 set_class = type (set_name, (Method,),
108 {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
109 (classname,tagname)})
112 get_accepts = [ Auth () ]
113 primary_key=objclass.primary_key
114 secondary_key = taggable_classes[objclass]['secondary_key']
115 get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
116 # for set, idem set of arguments + one additional arg, the new value
117 set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
120 get_returns = Mixed (Parameter (str), Parameter(NoneType))
121 set_returns = Parameter(NoneType)
124 setattr(get_class,'roles',get_roles)
125 setattr(get_class,'accepts',get_accepts)
126 setattr(get_class,'returns', get_returns)
127 # that was useful for legacy method only, but we now need type_checking
128 # setattr(get_class,'skip_type_check',True)
130 setattr(set_class,'roles',set_roles)
131 setattr(set_class,'accepts',set_accepts)
132 setattr(set_class,'returns', set_returns)
133 # that was useful for legacy method only, but we now need type_checking
134 # setattr(set_class,'skip_type_check',True)
136 table_class = taggable_classes[objclass]['table_class']
137 joins_class = taggable_classes[objclass]['joins_class']
138 join_class = taggable_classes[objclass]['join_class']
140 # locate the tag and create it if needed
141 # this method is attached to the Accessor class
142 def tag_locator (self, enforce=False):
143 return self.locate_or_create_tag (tagname=tagname,
145 description=description,
149 # attach it to the Accessor class
150 Accessor.register_tag_locator(locator_name,tag_locator)
152 # body of the get method
153 def get_call (self, auth, id_or_name):
154 # locate the tag, see above
155 tag_locator = Accessor.retrieve_tag_locator(locator_name)
156 tag_type = tag_locator(AccessorSingleton(self.api))
157 tag_type_id=tag_type['tag_type_id']
159 filter = {'tag_type_id':tag_type_id}
160 if isinstance (id_or_name,int):
161 filter[primary_key]=id_or_name
163 filter[secondary_key]=id_or_name
164 joins = joins_class (self.api,filter,['value'])
166 # xxx - we return None even if id_or_name is not valid
169 return joins[0]['value']
172 setattr (get_class,"call",get_call)
174 # body of the set method
175 def set_call (self, auth, id_or_name, value):
177 if isinstance (id_or_name, int):
178 filter={primary_key:id_or_name}
180 filter={secondary_key:id_or_name}
181 # we need the full monty b/c of the permission system
182 # objs = table_class(self.api, filter,[primary_key,secondary_key])
183 objs = table_class(self.api, filter)
185 raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
186 # the object being tagged
188 primary_id = obj[primary_key]
190 # locate the tag, see above
191 tag_locator = Accessor.retrieve_tag_locator(locator_name)
192 tag_type = tag_locator(AccessorSingleton(self.api))
193 tag_type_id = tag_type['tag_type_id']
195 # check authorization
196 if not hasattr(objclass,'caller_may_write_tag'):
197 raise PLCAuthenticationFailure, "class %s misses method caller_may_write_tag"%objclass.__name__
198 obj.caller_may_write_tag (self.api,self.caller,tag_type)
200 # locate the join object (e.g. NodeTag or similar)
201 filter = {'tag_type_id':tag_type_id}
202 if isinstance (id_or_name,int):
203 filter[primary_key]=id_or_name
205 filter[secondary_key]=id_or_name
206 joins = joins_class (self.api,filter)
207 # setting to something non void
208 if value is not None:
210 join = join_class (self.api)
211 join['tag_type_id']=tag_type_id
212 join[primary_key]=primary_id
216 joins[0]['value']=value
218 # providing an empty value means clean up
224 self.event_objects= { objclass.__name__ : [primary_id] }
225 self.message=objclass.__name__
226 if secondary_key in objs[0]:
227 self.message += " %s "%objs[0][secondary_key]
229 self.message += " %d "%objs[0][primary_key]
230 self.message += "updated"
234 setattr (set_class,"call",set_call)
237 setattr(module,get_name,get_class)
238 setattr(module,set_name,set_class)
239 # add in <module>.methods
241 methods=getattr(module,'methods')
244 methods += [get_name,set_name]
245 setattr(module,'methods',methods)