4 from PLC.Faults import *
5 from PLC.Parameter import Parameter, Mixed
6 from PLC.Filter import Filter
7 from PLC.Debug import profile
8 from PLC.Table import Row, Table
9 from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
10 from PLC.Nodes import Node
11 from PLC.Persons import Person, Persons
12 from PLC.SliceTags import SliceTag
13 from PLC.Timestamp import Timestamp
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.
24 primary_key = 'slice_id'
25 join_tables = ['slice_node', 'slice_person', 'slice_tag', 'peer_slice', 'node_slice_whitelist', 'leases', ]
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 = 64),
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 'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
38 'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
39 'slice_tag_ids': Parameter([int], "List of slice attributes", ro = True),
40 'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
41 'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
44 'persons': [Mixed(Parameter(int, "Person identifier"),
45 Parameter(str, "Email address"))],
46 'nodes': [Mixed(Parameter(int, "Node identifier"),
47 Parameter(str, "Fully qualified hostname"))]
50 view_tags_name="view_slice_tags"
53 def validate_name(self, name):
54 # N.B.: Responsibility of the caller to ensure that login_base
55 # portion of the slice name corresponds to a valid site, if
59 # 2. Begins with login_base (letters or numbers).
60 # 3. Then single underscore after login_base.
61 # 4. Then letters, numbers, or underscores.
62 good_name = r'^[a-z0-9\.]+_[a-zA-Z0-9_\.]+$'
64 not re.match(good_name, name):
65 raise PLCInvalidArgument("Invalid slice name")
67 conflicts = Slices(self.api, [name])
68 for slice in conflicts:
69 if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
70 raise PLCInvalidArgument("Slice name already in use, %s"%name)
74 def validate_instantiation(self, instantiation):
75 instantiations = [row['instantiation'] for row in SliceInstantiations(self.api)]
76 if instantiation not in instantiations:
77 raise PLCInvalidArgument("No such instantiation state")
81 validate_created = Row.validate_timestamp
83 def validate_expires(self, expires):
84 # N.B.: Responsibility of the caller to ensure that expires is
85 # not too far into the future.
86 check_future = not ('is_deleted' in self and self['is_deleted'])
87 return Timestamp.sql_validate( expires, check_future = check_future)
89 add_person = Row.add_object(Person, 'slice_person')
90 remove_person = Row.remove_object(Person, 'slice_person')
92 add_node = Row.add_object(Node, 'slice_node')
93 remove_node = Row.remove_object(Node, 'slice_node')
95 add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
96 delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
98 def associate_persons(self, auth, field, value):
100 Adds persons found in value list to this slice (using AddPersonToSlice).
101 Deletes persons not found in value list from this slice (using DeletePersonFromSlice).
104 assert 'person_ids' in self
105 assert 'slice_id' in self
106 assert isinstance(value, list)
108 (person_ids, emails) = self.separate_types(value)[0:2]
110 # Translate emails into person_ids
112 persons = Persons(self.api, emails, ['person_id']).dict('person_id')
113 person_ids += list(persons.keys())
115 # Add new ids, remove stale ids
116 if self['person_ids'] != person_ids:
117 from PLC.Methods.AddPersonToSlice import AddPersonToSlice
118 from PLC.Methods.DeletePersonFromSlice import DeletePersonFromSlice
119 new_persons = set(person_ids).difference(self['person_ids'])
120 stale_persons = set(self['person_ids']).difference(person_ids)
122 for new_person in new_persons:
123 AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, new_person, self['slice_id'])
124 for stale_person in stale_persons:
125 DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, stale_person, self['slice_id'])
127 def associate_nodes(self, auth, field, value):
129 Adds nodes found in value list to this slice (using AddSliceToNodes).
130 Deletes nodes not found in value list from this slice (using DeleteSliceFromNodes).
133 from PLC.Nodes import Nodes
135 assert 'node_ids' in self
136 assert 'slice_id' in self
137 assert isinstance(value, list)
139 (node_ids, hostnames) = self.separate_types(value)[0:2]
141 # Translate hostnames into node_ids
143 nodes = Nodes(self.api, hostnames, ['node_id']).dict('node_id')
144 node_ids += list(nodes.keys())
146 # Add new ids, remove stale ids
147 if self['node_ids'] != node_ids:
148 from PLC.Methods.AddSliceToNodes import AddSliceToNodes
149 from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
150 new_nodes = set(node_ids).difference(self['node_ids'])
151 stale_nodes = set(self['node_ids']).difference(node_ids)
154 AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, self['slice_id'], list(new_nodes))
156 DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, self['slice_id'], list(stale_nodes))
157 def associate_slice_tags(self, auth, fields, value):
159 Deletes slice_tag_ids not found in value list (using DeleteSliceTag).
160 Adds slice_tags if slice_fields w/o slice_id is found (using AddSliceTag).
161 Updates slice_tag if slice_fields w/ slice_id is found (using UpdateSlceiAttribute).
164 assert 'slice_tag_ids' in self
165 assert isinstance(value, list)
167 (attribute_ids, blank, attributes) = self.separate_types(value)
169 # There is no way to add attributes by id. They are
170 # associated with a slice when they are created.
171 # So we are only looking to delete here
172 if self['slice_tag_ids'] != attribute_ids:
173 from PLC.Methods.DeleteSliceTag import DeleteSliceTag
174 stale_attributes = set(self['slice_tag_ids']).difference(attribute_ids)
176 for stale_attribute in stale_attributes:
177 DeleteSliceTag.__call__(DeleteSliceTag(self.api), auth, stale_attribute['slice_tag_id'])
179 # If dictionary exists, we are either adding new
180 # attributes or updating existing ones.
182 from PLC.Methods.AddSliceTag import AddSliceTag
183 from PLC.Methods.UpdateSliceTag import UpdateSliceTag
185 added_attributes = [x for x in attributes if 'slice_tag_id' not in x]
186 updated_attributes = [x for x in attributes if 'slice_tag_id' in x]
188 for added_attribute in added_attributes:
189 if 'tag_type' in added_attribute:
190 type = added_attribute['tag_type']
191 elif 'tag_type_id' in added_attribute:
192 type = added_attribute['tag_type_id']
194 raise PLCInvalidArgument("Must specify tag_type or tag_type_id")
196 if 'value' in added_attribute:
197 value = added_attribute['value']
199 raise PLCInvalidArgument("Must specify a value")
201 if 'node_id' in added_attribute:
202 node_id = added_attribute['node_id']
206 if 'nodegroup_id' in added_attribute:
207 nodegroup_id = added_attribute['nodegroup_id']
211 AddSliceTag.__call__(AddSliceTag(self.api), auth, self['slice_id'], type, value, node_id, nodegroup_id)
212 for updated_attribute in updated_attributes:
213 attribute_id = updated_attribute.pop('slice_tag_id')
214 if attribute_id not in self['slice_tag_ids']:
215 raise PLCInvalidArgument("Attribute doesnt belong to this slice")
217 UpdateSliceTag.__call__(UpdateSliceTag(self.api), auth, attribute_id, updated_attribute)
219 def sync(self, commit = True):
221 Add or update a slice.
224 # Before a new slice is added, delete expired slices
225 if 'slice_id' not in self:
226 expired = Slices(self.api, expires = -int(time.time()))
227 for slice in expired:
230 Row.sync(self, commit)
232 def delete(self, commit = True):
234 Delete existing slice.
237 assert 'slice_id' in self
239 # Clean up miscellaneous join tables
240 for table in self.join_tables:
241 self.api.db.do("DELETE FROM %s WHERE slice_id = %d" % \
242 (table, self['slice_id']))
245 self['is_deleted'] = True
251 Representation of row(s) from the slices table in the
255 def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
256 Table.__init__(self, api, Slice, columns)
258 # the view that we're selecting upon: start with view_slices
260 # as many left joins as requested tags
261 for tagname in self.tag_columns:
262 view= "%s left join %s using (%s)"%(view,Slice.tagvalue_view_name(tagname),
265 sql = "SELECT %s FROM %s WHERE is_deleted IS False" % \
266 (", ".join(list(self.columns.keys())+list(self.tag_columns.keys())),view)
268 if expires is not None:
270 sql += " AND expires > %d" % expires
273 sql += " AND expires < %d" % expires
275 if slice_filter is not None:
276 if isinstance(slice_filter, (list, tuple, set)):
277 # Separate the list into integers and strings
278 ints = [x for x in slice_filter if isinstance(x, int)]
279 strs = [x for x in slice_filter if isinstance(x, str)]
280 slice_filter = Filter(Slice.fields, {'slice_id': ints, 'name': strs})
281 sql += " AND (%s) %s" % slice_filter.sql(api, "OR")
282 elif isinstance(slice_filter, dict):
283 allowed_fields=dict(list(Slice.fields.items())+list(Slice.tags.items()))
284 slice_filter = Filter(allowed_fields, slice_filter)
285 sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
286 elif isinstance (slice_filter, str):
287 slice_filter = Filter(Slice.fields, {'name':slice_filter})
288 sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
289 elif isinstance (slice_filter, int):
290 slice_filter = Filter(Slice.fields, {'slice_id':slice_filter})
291 sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
293 raise PLCInvalidArgument("Wrong slice filter %r"%slice_filter)