1 from types import StringTypes
4 from datetime import datetime, timedelta
5 from collections import defaultdict
7 from PLC.Faults import *
8 from PLC.Parameter import Parameter, Mixed
9 from PLC.Debug import profile
10 from PLC.Roles import Role, Roles
11 from PLC.Nodes import Node
12 from PLC.Persons import Person, Persons
13 from PLC.SlicePersons import SlicePerson, SlicePersons
14 from PLC.SliceNodes import SliceNode, SliceNodes
15 from PLC.SliceTags import SliceTag, SliceTags
16 from PLC.Timestamp import Timestamp
17 from PLC.Storage.AlchemyObject import AlchemyObj
19 class Slice(AlchemyObj):
21 Representation of a row in the slices table. To use, optionally
22 instantiate with a dict of values. Update as you would a
23 dict. Commit to the database with sync().To use, instantiate
24 with a dict of values.
30 'slice_id': Parameter(int, "Slice identifier", primary_key=True),
31 'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
32 'tenant_id': Parameter(int, "Keystone tenant identifier"),
33 'name': Parameter(str, "Slice name", max = 32),
34 'instantiation': Parameter(str, "Slice instantiation state", nullok=True),
35 'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
36 'description': Parameter(str, "Slice description", max = 2048, nullok = True),
37 'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice", default=100),
38 'creator_person_id': Parameter(str, "Identifier of the account that created this slice"),
39 'created': Parameter(datetime, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
40 'expires': Parameter(datetime, "Date and time when slice expires, in seconds since UNIX epoch"),
41 'node_ids': Parameter([str], "List of nodes in this slice", joined = True),
42 'person_ids': Parameter([str], "List of accounts that can use this slice", joined = True),
43 'slice_tag_ids': Parameter([int], "List of slice attributes", joined = True),
44 'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
45 'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
49 def validate_name(self, name):
50 # N.B.: Responsibility of the caller to ensure that login_base
51 # portion of the slice name corresponds to a valid site, if
55 # 2. Begins with login_base (letters or numbers).
56 # 3. Then single underscore after login_base.
57 # 4. Then letters, numbers, or underscores.
58 good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
60 not re.match(good_name, name):
61 raise PLCInvalidArgument, "Invalid slice name"
63 conflicts = Slices(self.api, [name])
64 for slice in conflicts:
65 if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
66 raise PLCInvalidArgument, "Slice name already in use, %s"%name
70 def validate_expires(self, expires):
71 # N.B.: Responsibility of the caller to ensure that expires is
72 # not too far into the future.
73 check_future = not ('is_deleted' in self and self['is_deleted'])
74 return Timestamp.sql_validate( expires, check_future = check_future)
76 def add_person(self, person_filter, role_name=None):
77 assert 'slice_id' in self
78 assert 'tenant_id' in self
81 roles = Roles(self.api, role_name)
83 raise PLCInvalidArgument, "No such role %s" % role_name
85 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
86 persons = Persons(self.api, person_filter)
87 for person in persons:
88 keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
89 tenant.add_user(keystone_user, role.object)
90 slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
91 'person_id': person['person_id']})
94 def remove_person(self, person_filter, role=None):
95 assert 'slice_id' in self
96 assert 'tenant_id' in self
99 roles = Roles(self.api, role_name)
101 raise PLCInvalidArgument, "No such role %s" % role_name
103 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
104 persons = Persons(self.api, person_filter)
105 for person in persons:
106 keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
107 tenant.remove_user(keystone_user, role.object)
108 slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
109 'person_id': person['person_id']})
110 slice_person.delete()
113 def add_node(self, node_filter, commit=True):
114 from PLC.Nodes import Nodes
115 assert 'slice_id' in self
116 nodes = Nodes(self.api, node_filter)
118 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
119 'node_id': node['node_id']})
122 def spawn_instance(self, nodes, caller):
124 default_image = self.api.config.nova_defualt_image
125 defualt_flavor = self.api.config.nova_default_flavor
127 # use the caller's nova keypair
128 keypairs = self.api.client_shell.nova.keypairs.list()
130 raise PLCInvalidArgument("caller has no nova key")
131 key_name = keypairs[0].name
133 # get all public keys
134 from PLC.Persons import Persons
135 from PLC.Keys import Keys
136 persons = Persons(self.api, self['person_ids'])
138 for person in persons:
139 key_ids.extend(person['key_ids'])
140 keys = Keys(self.api, key_ids)
141 pubkeys = [k['key'] for k in keys]
142 authorized_keys = "\n".join(pubkeys)
143 files = {'/root/.ssh/authorized_keys': authorized_keys}
145 # sort slice tags by node (sliver)
146 slice_tags = SliceTags(self.api, {'slice_id': self['slice_id']})
147 sliver_tags = defaultdict(dict)
148 for slice_tag in slice_tags:
149 node_id = slice_tag['node_id']
150 sliver_tags[node_id][slice_tag['tagname']] = slice_tag
153 if sliver_tags[node['node_id']].get('image'):
154 image = sliver_tags[node['node_id']].get('image')
156 image = default_image
158 def get_flavor(node):
159 if sliver_tags[node['node_id']].get('flavor'):
160 flavor = sliver_tags[node['node_id']].get('flavor')
162 flavor = default_image
165 if self['slice_id'] not in node['slice_ids']:
166 image = get_image(node)
167 flavor = get_flavor(node)
168 flavor_id = api.client_shell.nova.flavors.find(name=flavor)
169 image_id = api.client_shell.glance.get_image(name=image)
170 api.client_shell.nova.servers.create(flavor=flavor_id,
176 def remove_node(self, node_filter, commit=True):
177 from PLC.Nodes import Nodes
178 assert 'slice_id' in self
179 nodes = Nodes(self.api, node_filter)
181 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
182 'node_id': node['node_id']})
185 #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
186 #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
188 def sync(self, commit = True, validate=True):
190 Add or update a slice.
192 # sync the nova record and the plc record
193 AlchemyObj.sync(self, commit=commit, validate=validate)
194 # create the nova record
195 nova_fields = ['enabled', 'description']
196 nova_can_update = lambda (field, value): field in nova_fields
197 nova_slice = dict(filter(nova_can_update, self.items()))
198 nova_slice['tenant_name'] = self['name']
199 if 'slice_id' not in self:
201 # Before a new slice is added, delete expired slices
202 #expired = Slices(self.api, expires = -int(time.time()))
203 #for slice in expired:
204 # slice.delete(commit)
205 self.object = self.api.client_shell.keystone.tenants.create(**nova_slice)
206 self['tenant_id'] = self.object.id
207 self['created'] = now
208 self['expires'] = now + timedelta(days=14)
209 AlchemyObj.insert(self, dict(self))
210 slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
211 self['slice_id'] = slice.slice_id
213 self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice)
214 AlchemyObj.updatedb(self, {'slice_id': self['slice_id']}, dict(self))
216 def delete(self, commit = True):
218 Delete existing slice.
220 assert 'slice_id' in self
221 assert 'tenant_id' in self
223 # delete the nova object
224 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
225 self.api.client_shell.keystone.tenants.delete(tenant)
227 # delete relationships
228 for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}):
229 slice_person.delete()
230 for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}):
232 for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}):
236 AlchemyObj.delete(self, filter={'slice_id': self['slice_id']})
240 Representation of row(s) from the slices table in the
244 def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
246 # the view that we're selecting upon: start with view_slices
248 slices = Slice().select()
249 elif isinstance (slice_filter, StringTypes):
250 slices = Slice().select(filter={'name': slice_filter})
251 elif isinstance(slice_filter, dict):
252 slices = Slice().select(filter=slice_filter)
253 elif isinstance(slice_filter, (list, tuple, set)):
254 slices = Slice().select()
255 slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter]
257 raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
260 slice = Slice(api, object=slice)
261 if not columns or 'person_ids' in columns:
262 slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']})
263 slice['person_ids'] = [rec.person_id for rec in slice_persons]
265 if not columns or 'node_ids' in columns:
266 slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']})
267 slice['node_ids'] = [rec.node_id for rec in slice_nodes]
269 if not columns or 'slice_tag_ids' in columns:
270 slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']})
271 slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags]
275 def refresh(self, api):
277 Import tenants from keystone.
280 slices = Slice().select()
281 slice_names = [slice.name for slice in slices]
283 # get current tenants
284 tenants = api.client_shell.keystone.tenants.list()
286 # add tenants that dont already exist
287 for tenant in tenants:
288 # site tenants should not contain '_'
289 if '_' in tenant.name and tenant.name not in slice_names:
290 description = tenant.description
291 if not description: description = tenant.name
292 slice = Slice(api, {'name': tenant.name,
293 'tenant_id': tenant.id,
294 'enabled': tenant.enabled,
295 'description': description,
300 # slice may have a login base prefix that doesn't exist yet.