accessors and db-config.d tags roughly OK
[plcapi.git] / PLC / Accessors / Factory.py
1 # Thierry Parmentelat - INRIA
2 # $Id$
3 # $URL$
4
5 from types import NoneType
6
7 from PLC.Faults import *
8
9 from PLC.Auth import Auth
10 from PLC.Parameter import Parameter, Mixed
11 from PLC.Method import Method
12 from PLC.Accessor import Accessor, AccessorSingleton
13
14 from PLC.Nodes import Nodes, Node
15 from PLC.NodeTags import NodeTags, NodeTag
16 from PLC.Interfaces import Interfaces, Interface
17 from PLC.InterfaceTags import InterfaceTags, InterfaceTag
18 from PLC.Slices import Slices, Slice
19 from PLC.SliceTags import SliceTags, SliceTag
20 from PLC.Sites import Sites, Site
21 from PLC.SiteTags import SiteTags, SiteTag
22 from PLC.Persons import Persons, Person
23 from PLC.PersonTags import PersonTags, PersonTag
24
25 # this is another story..
26 #from PLC.Ilinks import Ilink
27
28 # known classes : { class -> details }
29 taggable_classes = { 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 #                     Ilink : xxx
45                      }
46
47 # xxx probably defined someplace else
48 admin_roles = ['admin']
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: roles get attached to the tagtype instance,
62 # while get_roles and set_roles get attached to the created methods
63 # this might need a cleanup
64 #
65 # in addition a convenience method like e.g. LocateNodeArch is defined
66 # in the Accessor class; its purpose is to retrieve the tag, or to create it if needed
67
68 # Legacy NOTE:
69 # prior to plcapi-5.0-19, this used to accept an additional argument
70 # named min_role_id; this was redundant and confusing, it has been
71 # removed, we now use set_roles to restrict access on the corresponding tag
72
73 def define_accessors (module, objclass, methodsuffix, tagname,
74                       category, description,
75                       get_roles=['admin'], set_roles=['admin'], 
76                       expose_in_api = False):
77
78     if objclass not in taggable_classes:
79         try:
80             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
81         except:
82             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
83
84     # side-effect on, say, Node.tags, if required
85     if expose_in_api:
86         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
87
88     classname=objclass.__name__
89     get_name = "Get" + classname + methodsuffix
90     set_name = "Set" + classname + methodsuffix
91     locator_name = "Locate" + classname + methodsuffix
92
93     # accessor method objects under PLC.Method.Method
94     get_class = type (get_name, (Method,),
95                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
96                            (classname,tagname)})
97     set_class = type (set_name, (Method,),
98                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
99                            (classname,tagname)})
100
101     # accepts
102     get_accepts = [ Auth () ]
103     primary_key=objclass.primary_key
104     secondary_key = taggable_classes[objclass]['secondary_key']
105     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
106     # for set, idem set of arguments + one additional arg, the new value
107     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
108
109     # returns
110     get_returns = Mixed (Parameter (str), Parameter(NoneType))
111     set_returns = Parameter(NoneType)
112
113     # store in classes
114     setattr(get_class,'roles',get_roles)
115     setattr(get_class,'accepts',get_accepts)
116     setattr(get_class,'returns', get_returns)
117     setattr(get_class,'skip_typecheck',True)
118
119     setattr(set_class,'roles',set_roles)
120     setattr(set_class,'accepts',set_accepts)
121     setattr(set_class,'returns', set_returns)
122     setattr(set_class,'skip_typecheck',True)
123
124     table_class = taggable_classes[objclass]['table_class']
125     joins_class = taggable_classes[objclass]['joins_class']
126     join_class = taggable_classes[objclass]['join_class']
127
128     # locate the tag and create it if needed
129     # this method is attached to the Accessor class
130     def locate_or_create_tag (self):
131         return self.locate_or_create_tag (tagname=tagname,
132                                           category=category,
133                                           description=description,
134                                           roles=set_roles)
135
136     # attach it to the Accessor class
137     setattr(Accessor,locator_name,locate_or_create_tag)
138
139     # body of the get method
140     def get_call (self, auth, id_or_name):
141         # locate the tag, see above
142         locator = getattr(Accessor,locator_name)
143         tag_type_id = locator(AccessorSingleton(self.api))
144
145         filter = {'tag_type_id':tag_type_id}
146         if isinstance (id_or_name,int):
147             filter[primary_key]=id_or_name
148         else:
149             filter[secondary_key]=id_or_name
150         joins = joins_class (self.api,filter,['value'])
151         if not joins:
152             # xxx - we return None even if id_or_name is not valid
153             return None
154         else:
155             return joins[0]['value']
156
157     # attach it
158     setattr (get_class,"call",get_call)
159
160     # body of the set method
161     def set_call (self, auth, id_or_name, value):
162         # locate the object
163         if isinstance (id_or_name, int):
164             filter={primary_key:id_or_name}
165         else:
166             filter={secondary_key:id_or_name}
167         objs = table_class(self.api, filter,[primary_key,secondary_key])
168         if not objs:
169             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
170         primary_id = objs[0][primary_key]
171
172         # locate the tag, see above
173         locator = getattr(Accessor,locator_name)
174         tag_type_id = locator(AccessorSingleton(self.api))
175
176         # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
177         filter = {'tag_type_id':tag_type_id}
178         if isinstance (id_or_name,int):
179             filter[primary_key]=id_or_name
180         else:
181             filter[secondary_key]=id_or_name
182         joins = joins_class (self.api,filter)
183         # setting to something non void
184         if value is not None:
185             if not joins:
186                 join = join_class (self.api)
187                 join['tag_type_id']=tag_type_id
188                 join[primary_key]=primary_id
189                 join['value']=value
190                 join.sync()
191             else:
192                 joins[0]['value']=value
193                 joins[0].sync()
194         # providing an empty value means clean up
195         else:
196             if joins:
197                 join=joins[0]
198                 join.delete()
199         # log it
200         self.event_objects= { objclass.__name__ : [primary_id] }
201         self.message=objclass.__name__
202         if secondary_key in objs[0]:
203             self.message += " %s "%objs[0][secondary_key]
204         else:
205             self.message += " %d "%objs[0][primary_key]
206         self.message += "updated"
207         return value
208
209     # attach it
210     setattr (set_class,"call",set_call)
211
212     # define in module
213     setattr(module,get_name,get_class)
214     setattr(module,set_name,set_class)
215     # add in <module>.methods
216     try:
217         methods=getattr(module,'methods')
218     except:
219         methods=[]
220     methods += [get_name,set_name]
221     setattr(module,'methods',methods)