Can background and run plc.
[sfa.git] / archive / changes / Slices.py
1 from types import StringTypes
2 import time
3 import re
4
5 from PLC.Faults import *
6 from PLC.Parameter import Parameter, Mixed
7 from PLC.Filter import Filter
8 from PLC.Debug import profile
9 from PLC.Table import Row, Table
10 from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
11 from PLC.Nodes import Node
12 from PLC.Persons import Person, Persons
13 from PLC.SliceAttributes import SliceAttribute
14
15 class Slice(Row):
16     """
17     Representation of a row in the slices table. To use, optionally
18     instantiate with a dict of values. Update as you would a
19     dict. Commit to the database with sync().To use, instantiate
20     with a dict of values.
21     """
22
23     table_name = 'slices'
24     primary_key = 'slice_id'
25     join_tables = ['slice_node', 'slice_person', 'slice_attribute', 'peer_slice', 'node_slice_whitelist']
26     fields = {
27         'slice_id': Parameter(int, "Slice identifier"),
28         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
29         'name': Parameter(str, "Slice name", max = 32),
30         'instantiation': Parameter(str, "Slice instantiation state"),
31         'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
32         'description': Parameter(str, "Slice description", max = 2048, nullok = True),
33         'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice"),
34         'creator_person_id': Parameter(int, "Identifier of the account that created this slice"),
35         'created': Parameter(int, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
36         'expires': Parameter(int, "Date and time when slice expires, in seconds since UNIX epoch"),
37         'uuid': Parameter(str, "Universal Unique Identifier"),
38         'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
39         'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
40         'slice_attribute_ids': Parameter([int], "List of slice attributes", ro = True),
41         'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
42         'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
43         }
44     related_fields = {
45         'persons': [Mixed(Parameter(int, "Person identifier"),
46                           Parameter(str, "Email address"))],
47         'nodes': [Mixed(Parameter(int, "Node identifier"),
48                         Parameter(str, "Fully qualified hostname"))]
49         }
50     # for Cache
51     class_key = 'name'
52     foreign_fields = ['instantiation', 'url', 'description', 'max_nodes', 'expires', 'uuid']
53     foreign_xrefs = [
54         {'field': 'node_ids' ,         'class': 'Node',   'table': 'slice_node' },
55         {'field': 'person_ids',        'class': 'Person', 'table': 'slice_person'},
56         {'field': 'creator_person_id', 'class': 'Person', 'table': 'unused-on-direct-refs'},
57         {'field': 'site_id',           'class': 'Site',   'table': 'unused-on-direct-refs'},
58     ]
59     # forget about this one, it is read-only anyway
60     # handling it causes Cache to re-sync all over again 
61     # 'created'
62
63     def validate_name(self, name):
64         # N.B.: Responsibility of the caller to ensure that login_base
65         # portion of the slice name corresponds to a valid site, if
66         # desired.
67
68         # 1. Lowercase.
69         # 2. Begins with login_base (letters or numbers).
70         # 3. Then single underscore after login_base.
71         # 4. Then letters, numbers, or underscores.
72         good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
73         if not name or \
74            not re.match(good_name, name):
75             raise PLCInvalidArgument, "Invalid slice name"
76
77         conflicts = Slices(self.api, [name])
78         for slice in conflicts:
79             if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
80                 raise PLCInvalidArgument, "Slice name already in use, %s"%name
81
82         return name
83
84     def validate_instantiation(self, instantiation):
85         instantiations = [row['instantiation'] for row in SliceInstantiations(self.api)]
86         if instantiation not in instantiations:
87             raise PLCInvalidArgument, "No such instantiation state"
88
89         return instantiation
90
91     validate_created = Row.validate_timestamp
92
93     def validate_expires(self, expires):
94         # N.B.: Responsibility of the caller to ensure that expires is
95         # not too far into the future.
96         check_future = not ('is_deleted' in self and self['is_deleted'])
97         return Row.validate_timestamp(self, expires, check_future = check_future)
98
99     add_person = Row.add_object(Person, 'slice_person')
100     remove_person = Row.remove_object(Person, 'slice_person')
101
102     add_node = Row.add_object(Node, 'slice_node')
103     remove_node = Row.remove_object(Node, 'slice_node')
104
105     add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
106     delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
107
108     def associate_persons(self, auth, field, value):
109         """
110         Adds persons found in value list to this slice (using AddPersonToSlice).
111         Deletes persons not found in value list from this slice (using DeletePersonFromSlice).
112         """
113         
114         assert 'person_ids' in self
115         assert 'slice_id' in self
116         assert isinstance(value, list)
117
118         (person_ids, emails) = self.separate_types(value)[0:2]
119
120         # Translate emails into person_ids      
121         if emails:
122             persons = Persons(self.api, emails, ['person_id']).dict('person_id')
123             person_ids += persons.keys()
124         
125         # Add new ids, remove stale ids
126         if self['person_ids'] != person_ids:
127             from PLC.Methods.AddPersonToSlice import AddPersonToSlice
128             from PLC.Methods.DeletePersonFromSlice import DeletePersonFromSlice
129             new_persons = set(person_ids).difference(self['person_ids'])
130             stale_persons = set(self['person_ids']).difference(person_ids)
131
132             for new_person in new_persons:
133                 AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, new_person, self['slice_id'])
134             for stale_person in stale_persons:
135                 DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, stale_person, self['slice_id'])
136
137     def associate_nodes(self, auth, field, value):
138         """
139         Adds nodes found in value list to this slice (using AddSliceToNodes).
140         Deletes nodes not found in value list from this slice (using DeleteSliceFromNodes).
141         """
142
143         from PLC.Nodes import Nodes
144
145         assert 'node_ids' in self
146         assert 'slice_id' in self
147         assert isinstance(value, list)
148         
149         (node_ids, hostnames) = self.separate_types(value)[0:2]
150         
151         # Translate hostnames into node_ids
152         if hostnames:
153             nodes = Nodes(self.api, hostnames, ['node_id']).dict('node_id')
154             node_ids += nodes.keys()
155         
156         # Add new ids, remove stale ids
157         if self['node_ids'] != node_ids:
158             from PLC.Methods.AddSliceToNodes import AddSliceToNodes
159             from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
160             new_nodes = set(node_ids).difference(self['node_ids'])
161             stale_nodes = set(self['node_ids']).difference(node_ids)
162             
163             if new_nodes:
164                 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, self['slice_id'], list(new_nodes))
165             if stale_nodes:
166                 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, self['slice_id'], list(stale_nodes))                        
167     def associate_slice_attributes(self, auth, fields, value):
168         """
169         Deletes slice_attribute_ids not found in value list (using DeleteSliceAttribute). 
170         Adds slice_attributes if slice_fields w/o slice_id is found (using AddSliceAttribute).
171         Updates slice_attribute if slice_fields w/ slice_id is found (using UpdateSlceiAttribute).  
172         """
173         
174         assert 'slice_attribute_ids' in self
175         assert isinstance(value, list)
176
177         (attribute_ids, blank, attributes) = self.separate_types(value)
178         
179         # There is no way to add attributes by id. They are
180         # associated with a slice when they are created.
181         # So we are only looking to delete here 
182         if self['slice_attribute_ids'] != attribute_ids:
183             from PLC.Methods.DeleteSliceAttribute import DeleteSliceAttribute
184             stale_attributes = set(self['slice_attribute_ids']).difference(attribute_ids)
185         
186             for stale_attribute in stale_attributes:
187                 DeleteSliceAttribute.__call__(DeleteSliceAttribute(self.api), auth, stale_attribute['slice_attribute_id'])              
188         
189         # If dictionary exists, we are either adding new
190         # attributes or updating existing ones.
191         if attributes:
192             from PLC.Methods.AddSliceAttribute import AddSliceAttribute
193             from PLC.Methods.UpdateSliceAttribute import UpdateSliceAttribute
194         
195             added_attributes = filter(lambda x: 'slice_attribute_id' not in x, attributes)
196             updated_attributes = filter(lambda x: 'slice_attribute_id' in x, attributes)
197
198             for added_attribute in added_attributes:
199                 if 'attribute_type' in added_attribute:
200                     type = added_attribute['attribute_type']
201                 elif 'attribute_type_id' in added_attribute:
202                     type = added_attribute['attribute_type_id']
203                 else:
204                     raise PLCInvalidArgument, "Must specify attribute_type or attribute_type_id"
205
206                 if 'value' in added_attribute:
207                     value = added_attribute['value']
208                 else:
209                     raise PLCInvalidArgument, "Must specify a value"
210                 
211                 if 'node_id' in added_attribute:
212                     node_id = added_attribute['node_id']
213                 else:
214                     node_id = None
215
216                 if 'nodegroup_id' in added_attribute:
217                     nodegroup_id = added_attribute['nodegroup_id']
218                 else:
219                     nodegroup_id = None 
220  
221                 AddSliceAttribute.__call__(AddSliceAttribute(self.api), auth, self['slice_id'], type, value, node_id, nodegroup_id)
222             for updated_attribute in updated_attributes:
223                 attribute_id = updated_attribute.pop('slice_attribute_id')
224                 if attribute_id not in self['slice_attribute_ids']:
225                     raise PLCInvalidArgument, "Attribute doesnt belong to this slice" 
226                 else:
227                     UpdateSliceAttribute.__call__(UpdateSliceAttribute(self.api), auth, attribute_id, updated_attribute)                 
228         
229     def sync(self, commit = True):
230         """
231         Add or update a slice.
232         """
233
234         # Before a new slice is added, delete expired slices
235         if 'slice_id' not in self:
236             expired = Slices(self.api, expires = -int(time.time()))
237             for slice in expired:
238                 slice.delete(commit)
239
240         Row.sync(self, commit)
241
242     def delete(self, commit = True):
243         """
244         Delete existing slice.
245         """
246
247         assert 'slice_id' in self
248
249         # Clean up miscellaneous join tables
250         for table in self.join_tables:
251             self.api.db.do("DELETE FROM %s WHERE slice_id = %d" % \
252                            (table, self['slice_id']))
253
254         # Mark as deleted
255         self['is_deleted'] = True
256         self.sync(commit)
257
258
259 class Slices(Table):
260     """
261     Representation of row(s) from the slices table in the
262     database.
263     """
264
265     def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
266         Table.__init__(self, api, Slice, columns)
267
268         sql = "SELECT %s FROM view_slices WHERE is_deleted IS False" % \
269               ", ".join(self.columns)
270
271         if expires is not None:
272             if expires >= 0:
273                 sql += " AND expires > %d" % expires
274             else:
275                 expires = -expires
276                 sql += " AND expires < %d" % expires
277
278         if slice_filter is not None:
279             if isinstance(slice_filter, (list, tuple, set)):
280                 # Separate the list into integers and strings
281                 ints = filter(lambda x: isinstance(x, (int, long)), slice_filter)
282                 strs = filter(lambda x: isinstance(x, StringTypes), slice_filter)
283                 slice_filter = Filter(Slice.fields, {'slice_id': ints, 'name': strs})
284                 sql += " AND (%s) %s" % slice_filter.sql(api, "OR")
285             elif isinstance(slice_filter, dict):
286                 slice_filter = Filter(Slice.fields, slice_filter)
287                 sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
288             elif isinstance (slice_filter, StringTypes):
289                 slice_filter = Filter(Slice.fields, {'name':[slice_filter]})
290                 sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
291             elif isinstance (slice_filter, int):
292                 slice_filter = Filter(Slice.fields, {'slice_id':[slice_filter]})
293                 sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
294             else:
295                 raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
296
297         self.selectall(sql)