546e3d91e17e359d06aad7e21c0798fe57a97606
[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 from PLC.TagTypes import TagTypes, TagType
29
30 # known classes : { class -> secondary_key }
31 taggable_classes = { Node : {'table_class' : Nodes,
32                              'joins_class' : NodeTags, 'join_class' : NodeTag,
33                              'secondary_key': 'hostname'},
34                      Interface : {'table_class' : Interfaces,
35                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
36                                   'secondary_key' : 'ip'},
37                      Slice: {'table_class' : Slices,
38                              'joins_class': SliceTags, 'join_class': SliceTag,
39                              'secondary_key':'name'},
40                      Site: {'table_class' : Sites,
41                              'joins_class': SiteTags, 'join_class': SiteTag,
42                              'secondary_key':'login_base'},
43                      Person: {'table_class' : Persons,
44                              'joins_class': PersonTags, 'join_class': PersonTag,
45                              'secondary_key':'email'},
46 #                     Ilink : xxx
47                      }
48
49 # xxx probably defined someplace else
50 admin_roles = ['admin']
51 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
52 tech_roles = [ 'admin', 'pi', 'tech' ]
53
54 #
55 # generates 2 method classes:
56 # Get<classname><methodsuffix> (auth, id_or_name) -> value or None
57 # Set<classname><methodsuffix> (auth, id_or_name, value) -> None
58 # value is always a string, no cast nor typecheck for now
59 #
60 # The expose_in_api flag tells whether this tag may be handled
61 #   through the Add/Get/Update methods as a native field
62 #
63 # note: tag_min_role_id gets attached to the tagtype instance,
64 # while get_roles and set_roles get attached to the created methods
65 # this might need a cleanup
66 #
67 # in addition a convenience method like e.g. LocateNodeArch is defined
68 # in the Accessor class; its purpose is to retrieve the tag, or to create it if needed
69
70 def define_accessors (module, objclass, methodsuffix, tagname,
71                       category, description,
72                       get_roles=['admin'], set_roles=['admin'],
73                       tag_min_role_id=10, expose_in_api = False):
74
75     if objclass not in taggable_classes:
76         try:
77             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
78         except:
79             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
80
81     # side-effect on, say, Node.tags, if required
82     if expose_in_api:
83         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
84
85     classname=objclass.__name__
86     get_name = "Get" + classname + methodsuffix
87     set_name = "Set" + classname + methodsuffix
88     locator_name = "Locate" + classname + methodsuffix
89
90     # accessor method objects under PLC.Method.Method
91     get_class = type (get_name, (Method,),
92                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
93                            (classname,tagname)})
94     set_class = type (set_name, (Method,),
95                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
96                            (classname,tagname)})
97
98     # accepts
99     get_accepts = [ Auth () ]
100     primary_key=objclass.primary_key
101     secondary_key = taggable_classes[objclass]['secondary_key']
102     get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
103     # for set, idem set of arguments + one additional arg, the new value
104     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
105
106     # returns
107     get_returns = Mixed (Parameter (str), Parameter(NoneType))
108     set_returns = Parameter(NoneType)
109
110     # store in classes
111     setattr(get_class,'roles',get_roles)
112     setattr(get_class,'accepts',get_accepts)
113     setattr(get_class,'returns', get_returns)
114     setattr(get_class,'skip_typecheck',True)
115
116     setattr(set_class,'roles',set_roles)
117     setattr(set_class,'accepts',set_accepts)
118     setattr(set_class,'returns', set_returns)
119     setattr(set_class,'skip_typecheck',True)
120
121     table_class = taggable_classes[objclass]['table_class']
122     joins_class = taggable_classes[objclass]['joins_class']
123     join_class = taggable_classes[objclass]['join_class']
124
125     # locate the tag and create it if needed
126     # this method is attached to the Accessor class
127     def locate_or_create_tag (self):
128         return self.locate_or_create_tag (tagname=tagname,
129                                           category=category,
130                                           description=description,
131                                           min_role_id=tag_min_role_id)
132
133     # attach it to the Accessor class
134     setattr(Accessor,locator_name,locate_or_create_tag)
135
136     # body of the get method
137     def get_call (self, auth, id_or_name):
138         # locate the tag, see above
139         locator = getattr(Accessor,locator_name)
140         tag_type_id = locator(AccessorSingleton(self.api))
141
142         filter = {'tag_type_id':tag_type_id}
143         if isinstance (id_or_name,int):
144             filter[primary_key]=id_or_name
145         else:
146             filter[secondary_key]=id_or_name
147         joins = joins_class (self.api,filter,['value'])
148         if not joins:
149             # xxx - we return None even if id_or_name is not valid
150             return None
151         else:
152             return joins[0]['value']
153
154     # attach it
155     setattr (get_class,"call",get_call)
156
157     # body of the set method
158     def set_call (self, auth, id_or_name, value):
159         # locate the object
160         if isinstance (id_or_name, int):
161             filter={primary_key:id_or_name}
162         else:
163             filter={secondary_key:id_or_name}
164         objs = table_class(self.api, filter,[primary_key,secondary_key])
165         if not objs:
166             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
167         primary_id = objs[0][primary_key]
168
169         # locate the tag, see above
170         locator = getattr(Accessor,locator_name)
171         tag_type_id = locator(AccessorSingleton(self.api))
172
173         # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
174         filter = {'tag_type_id':tag_type_id}
175         if isinstance (id_or_name,int):
176             filter[primary_key]=id_or_name
177         else:
178             filter[secondary_key]=id_or_name
179         joins = joins_class (self.api,filter)
180         # setting to something non void
181         if value is not None:
182             if not joins:
183                 join = join_class (self.api)
184                 join['tag_type_id']=tag_type_id
185                 join[primary_key]=primary_id
186                 join['value']=value
187                 join.sync()
188             else:
189                 joins[0]['value']=value
190                 joins[0].sync()
191         # providing an empty value means clean up
192         else:
193             if joins:
194                 join=joins[0]
195                 join.delete()
196         # log it
197         self.event_objects= { objclass.__name__ : [primary_id] }
198         self.message=objclass.__name__
199         if secondary_key in objs[0]:
200             self.message += " %s "%objs[0][secondary_key]
201         else:
202             self.message += " %d "%objs[0][primary_key]
203         self.message += "updated"
204
205     # attach it
206     setattr (set_class,"call",set_call)
207
208     # define in module
209     setattr(module,get_name,get_class)
210     setattr(module,set_name,set_class)
211     # add in <module>.methods
212     try:
213         methods=getattr(module,'methods')
214     except:
215         methods=[]
216     methods += [get_name,set_name]
217     setattr(module,'methods',methods)