Merge commit 'local_master/master'
[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: tag_min_role_id gets 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 def define_accessors (module, objclass, methodsuffix, tagname,
69                       category, description,
70                       get_roles=['admin'], set_roles=['admin'],
71                       tag_min_role_id=10, expose_in_api = False):
72
73     if objclass not in taggable_classes:
74         try:
75             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
76         except:
77             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
78
79     # side-effect on, say, Node.tags, if required
80     if expose_in_api:
81         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
82
83     classname=objclass.__name__
84     get_name = "Get" + classname + methodsuffix
85     set_name = "Set" + classname + methodsuffix
86     locator_name = "Locate" + classname + methodsuffix
87
88     # accessor method objects under PLC.Method.Method
89     get_class = type (get_name, (Method,),
90                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
91                            (classname,tagname)})
92     set_class = type (set_name, (Method,),
93                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
94                            (classname,tagname)})
95
96     # accepts
97     get_accepts = [ Auth () ]
98     primary_key=objclass.primary_key
99     secondary_key = taggable_classes[objclass]['secondary_key']
100     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
101     # for set, idem set of arguments + one additional arg, the new value
102     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
103
104     # returns
105     get_returns = Mixed (Parameter (str), Parameter(NoneType))
106     set_returns = Parameter(NoneType)
107
108     # store in classes
109     setattr(get_class,'roles',get_roles)
110     setattr(get_class,'accepts',get_accepts)
111     setattr(get_class,'returns', get_returns)
112     setattr(get_class,'skip_typecheck',True)
113
114     setattr(set_class,'roles',set_roles)
115     setattr(set_class,'accepts',set_accepts)
116     setattr(set_class,'returns', set_returns)
117     setattr(set_class,'skip_typecheck',True)
118
119     table_class = taggable_classes[objclass]['table_class']
120     joins_class = taggable_classes[objclass]['joins_class']
121     join_class = taggable_classes[objclass]['join_class']
122
123     # locate the tag and create it if needed
124     # this method is attached to the Accessor class
125     def locate_or_create_tag (self):
126         return self.locate_or_create_tag (tagname=tagname,
127                                           category=category,
128                                           description=description,
129                                           min_role_id=tag_min_role_id)
130
131     # attach it to the Accessor class
132     setattr(Accessor,locator_name,locate_or_create_tag)
133
134     # body of the get method
135     def get_call (self, auth, id_or_name):
136         # locate the tag, see above
137         locator = getattr(Accessor,locator_name)
138         tag_type_id = locator(AccessorSingleton(self.api))
139
140         filter = {'tag_type_id':tag_type_id}
141         if isinstance (id_or_name,int):
142             filter[primary_key]=id_or_name
143         else:
144             filter[secondary_key]=id_or_name
145         joins = joins_class (self.api,filter,['value'])
146         if not joins:
147             # xxx - we return None even if id_or_name is not valid
148             return None
149         else:
150             return joins[0]['value']
151
152     # attach it
153     setattr (get_class,"call",get_call)
154
155     # body of the set method
156     def set_call (self, auth, id_or_name, value):
157         # locate the object
158         if isinstance (id_or_name, int):
159             filter={primary_key:id_or_name}
160         else:
161             filter={secondary_key:id_or_name}
162         objs = table_class(self.api, filter,[primary_key,secondary_key])
163         if not objs:
164             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
165         primary_id = objs[0][primary_key]
166
167         # locate the tag, see above
168         locator = getattr(Accessor,locator_name)
169         tag_type_id = locator(AccessorSingleton(self.api))
170
171         # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
172         filter = {'tag_type_id':tag_type_id}
173         if isinstance (id_or_name,int):
174             filter[primary_key]=id_or_name
175         else:
176             filter[secondary_key]=id_or_name
177         joins = joins_class (self.api,filter)
178         # setting to something non void
179         if value is not None:
180             if not joins:
181                 join = join_class (self.api)
182                 join['tag_type_id']=tag_type_id
183                 join[primary_key]=primary_id
184                 join['value']=value
185                 join.sync()
186             else:
187                 joins[0]['value']=value
188                 joins[0].sync()
189         # providing an empty value means clean up
190         else:
191             if joins:
192                 join=joins[0]
193                 join.delete()
194         # log it
195         self.event_objects= { objclass.__name__ : [primary_id] }
196         self.message=objclass.__name__
197         if secondary_key in objs[0]:
198             self.message += " %s "%objs[0][secondary_key]
199         else:
200             self.message += " %d "%objs[0][primary_key]
201         self.message += "updated"
202         return value
203
204     # attach it
205     setattr (set_class,"call",set_call)
206
207     # define in module
208     setattr(module,get_name,get_class)
209     setattr(module,set_name,set_class)
210     # add in <module>.methods
211     try:
212         methods=getattr(module,'methods')
213     except:
214         methods=[]
215     methods += [get_name,set_name]
216     setattr(module,'methods',methods)