1 from types import StringTypes
4 from datetime import datetime, timedelta
6 from PLC.Faults import *
7 from PLC.Parameter import Parameter, Mixed
8 from PLC.Debug import profile
9 from PLC.Roles import Role, Roles
10 from PLC.Nodes import Node
11 from PLC.Persons import Person, Persons
12 from PLC.SlicePersons import SlicePerson, SlicePersons
13 from PLC.SliceNodes import SliceNode, SliceNodes
14 from PLC.SliceTags import SliceTag, SliceTags
15 from PLC.Timestamp import Timestamp
16 from PLC.Storage.AlchemyObject import AlchemyObj
18 class Slice(AlchemyObj):
20 Representation of a row in the slices table. To use, optionally
21 instantiate with a dict of values. Update as you would a
22 dict. Commit to the database with sync().To use, instantiate
23 with a dict of values.
29 'slice_id': Parameter(int, "Slice identifier", primary_key=True),
30 'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
31 'tenant_id': Parameter(int, "Keystone tenant identifier"),
32 'name': Parameter(str, "Slice name", max = 32),
33 'instantiation': Parameter(str, "Slice instantiation state", nullok=True),
34 'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
35 'description': Parameter(str, "Slice description", max = 2048, nullok = True),
36 'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice", default=100),
37 'creator_person_id': Parameter(str, "Identifier of the account that created this slice"),
38 'created': Parameter(datetime, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
39 'expires': Parameter(datetime, "Date and time when slice expires, in seconds since UNIX epoch"),
40 'node_ids': Parameter([str], "List of nodes in this slice", joined = True),
41 'person_ids': Parameter([str], "List of accounts that can use this slice", joined = True),
42 'slice_tag_ids': Parameter([int], "List of slice attributes", joined = True),
43 'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
44 'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
48 def validate_name(self, name):
49 # N.B.: Responsibility of the caller to ensure that login_base
50 # portion of the slice name corresponds to a valid site, if
54 # 2. Begins with login_base (letters or numbers).
55 # 3. Then single underscore after login_base.
56 # 4. Then letters, numbers, or underscores.
57 good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
59 not re.match(good_name, name):
60 raise PLCInvalidArgument, "Invalid slice name"
62 conflicts = Slices(self.api, [name])
63 for slice in conflicts:
64 if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
65 raise PLCInvalidArgument, "Slice name already in use, %s"%name
69 def validate_expires(self, expires):
70 # N.B.: Responsibility of the caller to ensure that expires is
71 # not too far into the future.
72 check_future = not ('is_deleted' in self and self['is_deleted'])
73 return Timestamp.sql_validate( expires, check_future = check_future)
75 def add_person(self, person_filter, role_name=None):
76 assert 'slice_id' in self
77 assert 'tenant_id' in self
80 roles = Roles(self.api, role_name)
82 raise PLCInvalidArgument, "No such role %s" % role_name
84 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
85 persons = Persons(self.api, person_filter)
86 for person in persons:
87 keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
88 tenant.add_user(keystone_user, role.object)
89 slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
90 'person_id': person['person_id']})
93 def remove_person(self, person_filter, role=None):
94 assert 'slice_id' in self
95 assert 'tenant_id' in self
98 roles = Roles(self.api, role_name)
100 raise PLCInvalidArgument, "No such role %s" % role_name
102 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
103 persons = Persons(self.api, person_filter)
104 for person in persons:
105 keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
106 tenant.remove_user(keystone_user, role.object)
107 slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
108 'person_id': person['person_id']})
109 slice_person.delete()
112 def add_node(self, node_filter, commit=True):
113 from PLC.Nodes import Nodes
114 assert 'slice_id' in self
115 nodes = Nodes(self.api, node_filter)
117 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
118 'node_id': node['node_id']})
121 def remove_node(self, node_filter, commit=True):
122 from PLC.Nodes import Nodes
123 assert 'slice_id' in self
124 nodes = Nodes(self.api, node_filter)
126 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
127 'node_id': node['node_id']})
130 #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
131 #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
133 def sync(self, commit = True, validate=True):
135 Add or update a slice.
137 # sync the nova record and the plc record
138 AlchemyObj.sync(self, commit=commit, validate=validate)
139 # create the nova record
140 nova_fields = ['enabled', 'description']
141 nova_can_update = lambda (field, value): field in nova_fields
142 nova_slice = dict(filter(nova_can_update, self.items()))
143 nova_slice['tenant_name'] = self['name']
144 if 'slice_id' not in self:
146 # Before a new slice is added, delete expired slices
147 #expired = Slices(self.api, expires = -int(time.time()))
148 #for slice in expired:
149 # slice.delete(commit)
150 self.object = self.api.client_shell.keystone.tenants.create(**nova_slice)
151 self['tenant_id'] = self.object.id
152 self['created'] = now
153 self['expires'] = now + timedelta(days=14)
154 AlchemyObj.insert(self, dict(self))
155 slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
156 self['slice_id'] = slice.slice_id
158 self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice)
159 AlchemyObj.updatedb(self, {'slice_id': self['slice_id']}, dict(self))
161 def delete(self, commit = True):
163 Delete existing slice.
165 assert 'slice_id' in self
166 assert 'tenant_id' in self
168 # delete the nova object
169 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
170 self.api.client_shell.keystone.tenants.delete(tenant)
172 # delete relationships
173 for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}):
174 slice_person.delete()
175 for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}):
177 for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}):
181 AlchemyObj.delete(self, filter={'slice_id': self['slice_id']})
185 Representation of row(s) from the slices table in the
189 def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
191 # the view that we're selecting upon: start with view_slices
193 slices = Slice().select()
194 elif isinstance (slice_filter, StringTypes):
195 slices = Slice().select(filter={'name': slice_filter})
196 elif isinstance(slice_filter, dict):
197 slices = Slice().select(filter=slice_filter)
198 elif isinstance(slice_filter, (list, tuple, set)):
199 slices = Slice().select()
200 slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter]
202 raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
205 slice = Slice(api, object=slice)
206 if not columns or 'person_ids' in columns:
207 slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']})
208 slice['person_ids'] = [rec.person_id for rec in slice_persons]
210 if not columns or 'node_ids' in columns:
211 slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']})
212 slice['node_ids'] = [rec.node_id for rec in slice_nodes]
214 if not columns or 'slice_tag_ids' in columns:
215 slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']})
216 slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags]
220 def refresh(self, api):
222 Import tenants from keystone.
225 slices = Slice().select()
226 slice_names = [slice.name for slice in slices]
228 # get current tenants
229 tenants = api.client_shell.keystone.tenants.list()
231 # add tenants that dont already exist
232 for tenant in tenants:
233 # site tenants should not contain '_'
234 if '_' in tenant.name and tenant.name not in slice_names:
235 description = tenant.description
236 if not description: description = tenant.name
237 slice = Slice(api, {'name': tenant.name,
238 'tenant_id': tenant.id,
239 'enabled': tenant.enabled,
240 'description': description
245 # slice may have a login base prefix that doesn't exist yet.