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):
113 assert 'slice_id' in self
114 nodes = Nodes(self.api, node_filter)
116 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
117 'node_id': node['node_id']})
120 def remove_node(self, node_filter):
121 assert 'slice_id' in self
122 nodes = Nodes(self.api, node_filter)
124 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
125 'node_id': node['node_id']})
128 #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
129 #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
131 def sync(self, commit = True, validate=True):
133 Add or update a slice.
135 # sync the nova record and the plc record
136 AlchemyObj.sync(self, commit=commit, validate=validate)
137 # create the nova record
138 nova_fields = ['enabled', 'description']
139 nova_can_update = lambda (field, value): field in nova_fields
140 nova_slice = dict(filter(nova_can_update, self.items()))
141 nova_slice['tenant_name'] = self['name']
142 if 'slice_id' not in self:
144 # Before a new slice is added, delete expired slices
145 #expired = Slices(self.api, expires = -int(time.time()))
146 #for slice in expired:
147 # slice.delete(commit)
148 self.object = self.api.client_shell.keystone.tenants.create(**nova_slice)
149 self['tenant_id'] = self.object.id
150 self['created'] = now
151 self['expires'] = now + timedelta(days=14)
152 AlchemyObj.insert(self, dict(self))
153 slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
154 self['slice_id'] = slice.slice_id
156 self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice)
157 AlchemyObj.update(self, {'slice_id': self['slice_id']}, dict(self))
159 def delete(self, commit = True):
161 Delete existing slice.
163 assert 'slice_id' in self
164 assert 'tenant_id' in self
166 # delete the nova object
167 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
168 self.api.client_shell.keystone.tenants.delete(tenant)
170 # delete relationships
171 for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}):
172 slice_person.delete()
173 for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}):
175 for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}):
179 AlchemyObj.delete(self, filter={'slice_id': self['slice_id']})
183 Representation of row(s) from the slices table in the
187 def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
189 # the view that we're selecting upon: start with view_slices
191 slices = Slice().select()
192 elif isinstance (slice_filter, StringTypes):
193 slices = Slice().select(filter={'name': slice_filter})
194 elif isinstance(slice_filter, dict):
195 slices = Slice().select(filter=slice_filter)
196 elif isinstance(slice_filter, (list, tuple, set)):
197 slices = Slice().select()
198 slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter]
200 raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
203 slice = Slice(api, object=slice)
204 if not columns or 'person_ids' in columns:
205 slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']})
206 slice['person_ids'] = [rec.person_id for rec in slice_persons]
208 if not columns or 'node_ids' in columns:
209 slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']})
210 slice['node_ids'] = [rec.node_id for rec in slice_nodes]
212 if not columns or 'slice_tag_ids' in columns:
213 slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']})
214 slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags]
218 def refresh(self, api):
220 Import tenants from keystone.
223 slices = Slice().select()
224 slice_names = [slice.name for slice in slices]
226 # get current tenants
227 tenants = api.client_shell.keystone.tenants.list()
229 # add tenants that dont already exist
230 for tenant in tenants:
231 # site tenants should not contain '_'
232 if '_' in tenant.name and tenant.name not in slice_names:
233 description = tenant.description
234 if not description: description = tenant.name
235 slice = Slice(api, {'name': tenant.name,
236 'tenant_id': tenant.id,
237 'enabled': tenant.enabled,
238 'description': description
243 # slice may have a login base prefix that doesn't exist yet.