define tags exposed to the API in the Accessors area
[plcapi.git] / PLC / Accessors / Factory.py
1 # Thierry Parmentelat - INRIA
2 # $Id$
3
4 from types import NoneType
5
6 from PLC.Method import Method
7 from PLC.Auth import Auth
8 from PLC.Parameter import Parameter, Mixed
9
10 from PLC.Faults import *
11
12 from PLC.Nodes import Nodes, Node
13 from PLC.NodeTags import NodeTags, NodeTag
14 from PLC.Interfaces import Interfaces, Interface
15 from PLC.InterfaceTags import InterfaceTags, InterfaceTag
16 from PLC.Slices import Slices, Slice
17 from PLC.SliceTags import SliceTags, SliceTag
18
19 # this is another story..
20 #from PLC.Ilinks import Ilink
21
22 from PLC.TagTypes import TagTypes, TagType
23
24 # known classes : { class -> secondary_key }
25 taggable_classes = { Node : {'table_class' : Nodes, 
26                              'joins_class' : NodeTags, 'join_class' : NodeTag,
27                              'secondary_key': 'hostname'},
28                      Interface : {'table_class' : Interfaces, 
29                                   'joins_class': InterfaceTags, 'join_class': InterfaceTag,
30                                   },
31                      Slice: {'table_class' : Slices, 
32                              'joins_class': SliceTags, 'join_class': SliceTag,
33                              'secondary_key':'login_base'},
34 #                     Ilink : xxx
35                      }
36
37 # xxx probably defined someplace else
38 all_roles = [ 'admin', 'pi', 'tech', 'user', 'node' ]
39 tech_roles = [ 'admin', 'pi', 'tech' ]
40
41 #
42 # generates 2 method classes:
43 # Get<classname><methodsuffix> (auth, id_or_name) -> value or None
44 # Set<classname><methodsuffix> (auth, id_or_name, value) -> None
45 # value is always a string, no cast nor typecheck for now
46 #
47 # The expose_in_api flag tells whether this tag may be handled 
48 #   through the Add/Get/Update methods as a native field
49 #
50 # note: tag_min_role_id gets attached to the tagtype instance, 
51 # while get_roles and set_roles get attached to the created methods
52 # this might need a cleanup
53
54
55 def define_accessors (module, objclass, methodsuffix, tagname, 
56                       category, description, 
57                       get_roles=['admin'], set_roles=['admin'], 
58                       tag_min_role_id=10, expose_in_api = False):
59     
60     if objclass not in taggable_classes:
61         try:
62             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class %s"%objclass.__name__
63         except:
64             raise PLCInvalidArgument,"PLC.Accessors.Factory: unknown class ??"
65
66     # side-effect on, say, Node.tags, if required
67     if expose_in_api:
68         getattr(objclass,'tags')[tagname]=Parameter(str,"accessor")
69
70     classname=objclass.__name__
71     get_name = "Get" + classname + methodsuffix
72     set_name = "Set" + classname + methodsuffix
73
74     # create method objects under PLC.Method.Method
75     get_class = type (get_name, (Method,),
76                       {"__doc__":"Accessor 'get' method designed for %s objects using tag %s"%\
77                            (classname,tagname)})
78     set_class = type (set_name, (Method,),
79                       {"__doc__":"Accessor 'set' method designed for %s objects using tag %s"%\
80                            (classname,tagname)})
81     # accepts 
82     get_accepts = [ Auth () ]
83     primary_key=objclass.primary_key
84     try:
85         secondary_key = taggable_classes[objclass]['secondary_key']
86         get_accepts += [ Mixed (objclass.fields[primary_key], objclass.fields[secondary_key]) ]
87     except:
88         secondary_key = None
89         get_accepts += [ objclass.fields[primary_key] ]
90     # for set, idem set of arguments + one additional arg, the new value
91     set_accepts = get_accepts + [ Parameter (str,"New tag value") ]
92
93     # returns
94     get_returns = Mixed (Parameter (str), Parameter(NoneType))
95     set_returns = Parameter(NoneType)
96
97     # store in classes
98     setattr(get_class,'roles',get_roles)
99     setattr(get_class,'accepts',get_accepts)
100     setattr(get_class,'returns', get_returns)
101     setattr(get_class,'skip_typecheck',True)
102
103     setattr(set_class,'roles',set_roles)
104     setattr(set_class,'accepts',set_accepts)
105     setattr(set_class,'returns', set_returns)
106     setattr(set_class,'skip_typecheck',True)
107     
108     table_class = taggable_classes[objclass]['table_class']
109     joins_class = taggable_classes[objclass]['joins_class']
110     join_class = taggable_classes[objclass]['join_class']
111
112     # body of the get method
113     def get_call (self, auth, id_or_name):
114         # search the tagtype - xxx - might need a cache
115         tag_types = TagTypes (self.api, {'tagname': tagname})
116         if not tag_types:
117             return None
118         tag_type_id = tag_types[0]['tag_type_id']
119         filter = {'tag_type_id':tag_type_id}
120         if isinstance (id_or_name,int):
121             filter[primary_key]=id_or_name
122         else:
123             filter[secondary_key]=id_or_name
124         joins = joins_class (self.api,filter,['value'])
125         if not joins:
126             # xxx - we return None even if id_or_name is not valid 
127             return None
128         else:
129             return joins[0]['value']
130
131     # attach it
132     setattr (get_class,"call",get_call)
133
134     # body of the set method 
135     def set_call (self, auth, id_or_name, value):
136         # locate the object
137         if isinstance (id_or_name, int):
138             filter={primary_key:id_or_name}
139         else:
140             filter={secondary_key:id_or_name}
141         objs = table_class(self.api, filter,[primary_key])
142         if not objs:
143             raise PLCInvalidArgument, "Cannot set tag on %s %r"%(objclass.__name__,id_or_name)
144         primary_id = objs[0][primary_key]
145                            
146         # search tag type & create if needed
147         tag_types = TagTypes (self.api, {'tagname':tagname})
148         if tag_types:
149             tag_type = tag_types[0]
150         else:
151             # not found: create it
152             tag_type_fields = {'tagname':tagname, 
153                                'category' :  category,
154                                'description' : description,
155                                'min_role_id': tag_min_role_id}
156             tag_type = TagType (self.api, tag_type_fields)
157             tag_type.sync()
158         tag_type_id = tag_type['tag_type_id']
159
160         # locate the join object (e.g. NodeTag, SliceTag or InterfaceTag)
161         filter = {'tag_type_id':tag_type_id}
162         if isinstance (id_or_name,int):
163             filter[primary_key]=id_or_name
164         else:
165             filter[secondary_key]=id_or_name
166         joins = joins_class (self.api,filter)
167         # setting to something non void
168         if value is not None:
169             if not joins:
170                 join = join_class (self.api)
171                 join['tag_type_id']=tag_type_id
172                 join[primary_key]=primary_id
173                 join['value']=value
174                 join.sync()
175             else:
176                 joins[0]['value']=value
177                 joins[0].sync()
178         # providing an empty value means clean up
179         else:
180             if joins:
181                 join=joins[0]
182                 join.delete()
183
184     # attach it
185     setattr (set_class,"call",set_call)
186
187     # define in module
188     setattr(module,get_name,get_class)
189     setattr(module,set_name,set_class)
190     # add in <module>.methods
191     try:
192         methods=getattr(module,'methods')
193     except:
194         methods=[]
195     methods += [get_name,set_name]
196     setattr(module,'methods',methods)
197