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