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.SliceInstances import SliceInstance, SliceInstances
16 from PLC.SliceTags import SliceTag, SliceTags
17 from PLC.Timestamp import Timestamp
18 from PLC.Storage.AlchemyObject import AlchemyObj
20 class Slice(AlchemyObj):
22 Representation of a row in the slices table. To use, optionally
23 instantiate with a dict of values. Update as you would a
24 dict. Commit to the database with sync().To use, instantiate
25 with a dict of values.
31 'slice_id': Parameter(int, "Slice identifier", primary_key=True),
32 'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
33 'tenant_id': Parameter(int, "Keystone tenant identifier"),
34 'name': Parameter(str, "Slice name", max = 32),
35 'instantiation': Parameter(str, "Slice instantiation state", nullok=True),
36 'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
37 'description': Parameter(str, "Slice description", max = 2048, nullok = True),
38 'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice", default=100),
39 'creator_person_id': Parameter(str, "Identifier of the account that created this slice"),
40 'created': Parameter(datetime, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
41 'expires': Parameter(datetime, "Date and time when slice expires, in seconds since UNIX epoch"),
42 'node_ids': Parameter([str], "List of nodes in this slice", joined = True),
43 'instance_ids': Parameter([str], "List of instances running under this slice", joined = True),
44 'person_ids': Parameter([str], "List of accounts that can use this slice", joined = True),
45 'slice_tag_ids': Parameter([int], "List of slice attributes", joined = True),
46 'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
47 'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
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
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_]+$'
62 not re.match(good_name, name):
63 raise PLCInvalidArgument, "Invalid slice name"
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
72 def validate_expires(self, expires):
73 # N.B.: Responsibility of the caller to ensure that expires is
74 # not too far into the future.
75 check_future = not ('is_deleted' in self and self['is_deleted'])
76 return Timestamp.sql_validate( expires, check_future = check_future)
78 def add_person(self, person_filter, role_name=None):
79 assert 'slice_id' in self
80 assert 'tenant_id' in self
83 roles = Roles(self.api, role_name)
85 raise PLCInvalidArgument, "No such role %s" % role_name
87 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
88 persons = Persons(self.api, person_filter)
89 for person in persons:
90 keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
91 tenant.add_user(keystone_user, role.object)
92 slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
93 'person_id': person['person_id']})
96 def remove_person(self, person_filter, role=None):
97 assert 'slice_id' in self
98 assert 'tenant_id' in self
101 roles = Roles(self.api, role_name)
103 raise PLCInvalidArgument, "No such role %s" % role_name
105 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
106 persons = Persons(self.api, person_filter)
107 for person in persons:
108 keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
109 tenant.remove_user(keystone_user, role.object)
110 slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
111 'person_id': person['person_id']})
112 slice_person.delete()
115 def add_node(self, node_filter, commit=True):
116 from PLC.Nodes import Nodes
117 assert 'slice_id' in self
118 nodes = Nodes(self.api, node_filter)
120 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
121 'node_id': node['node_id']})
124 def spawn_instances(self, nodes):
125 # use the caller's nova keypair
126 keypairs = self.api.client_shell.nova.keypairs.list()
128 raise PLCInvalidArgument("caller has no nova key")
129 key_name = keypairs[0].name
131 # get all public keys
132 from PLC.Persons import Persons
133 from PLC.Keys import Keys
134 persons = Persons(self.api, self['person_ids'])
136 for person in persons:
137 key_ids.extend(person['key_ids'])
138 keys = Keys(self.api, key_ids)
139 pubkeys = [k['key'] for k in keys]
140 authorized_keys = "\n".join(pubkeys)
141 files = {'/root/.ssh/authorized_keys': authorized_keys}
143 # sort slice tags by node (sliver)
144 slice_tags = SliceTags(self.api, {'slice_id': self['slice_id']})
145 sliver_tags = defaultdict(dict)
146 for slice_tag in slice_tags:
147 node_id = slice_tag['node_id']
148 sliver_tags[node_id][slice_tag['tagname']] = slice_tag
151 image = self.api.config.nova_default_image
152 slice_image = sliver_tags[None].get('image')
153 sliver_image = sliver_tags[node['node_id']].get('image')
154 if sliver_image is not None: # sliver tag
155 image = sliver_image.get('value')
156 elif slice_image is not None: # sliver tag
157 image = slice_image.get('value')
160 def get_flavor(node):
161 flavor = self.api.config.nova_default_flavor
162 slice_flavor = sliver_tags[None].get('flavor')
163 sliver_flavor = sliver_tags[node['node_id']].get('flavor')
164 if sliver_flavor is not None: # sliver tag
165 flavor = sliver_flavor.get('value')
166 elif slice_flavor is not None: # slice tag
167 flavor = slice_flavor.get('value')
171 def get_security_group(node):
172 security_group = self.api.config.nova_default_security_group
173 slice_security_group = sliver_tags[None].get('security_group')
174 sliver_security_group = sliver_tags[node['node_id']].get('security_group')
175 if sliver_security_group is not None: # sliver tag
176 security_group = slice_security_group.get('value')
177 elif slice_security_group is not None: # slice tag
178 security_group = slice_security_group.get('value')
179 return security_group
182 if self['slice_id'] not in node['slice_ids']:
183 image = get_image(node)
184 flavor = get_flavor(node)
185 security_group = get_security_group(node)
186 flavor_id = self.api.client_shell.nova.flavors.find(name=flavor)
187 images = self.api.client_shell.glance.get_images(name=image)
189 raise PLCInvalidArgument('Image bot found')
190 image_id = images[0]['id']
191 hints = {'force_hosts': node['hostname']}
192 server = self.api.client_shell.nova.servers.create(
197 security_group = security_group,
199 scheduler_hints=hints)
200 slice_instance = SliceInstance(self.api, {'slice_id': self['slice_id'],
201 'instance_id': server.id})
202 slice_instance.sync()
204 def destroy_instances(self, nodes):
205 hostnames = [n['hostname'] for n in nodes]
206 servers = self.api.client_shell.nova.servers.list()
207 for server in servers:
209 hostname = server._info['OS-EXT-SRV-ATTR:host']
210 if self['name'] == name and hostname in hostnames:
211 instance_id = server.id
212 self.api.client_shell.nova.servers.delete(server)
213 AlchemyObj.delete(SliceInstance, filter={'slice_id': self['slice_id'],
214 'instance_id': instance_id})
217 def remove_node(self, node_filter, commit=True):
218 from PLC.Nodes import Nodes
219 assert 'slice_id' in self
220 nodes = Nodes(self.api, node_filter)
222 slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
223 'node_id': node['node_id']})
226 #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
227 #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
229 def sync(self, commit = True, validate=True):
231 Add or update a slice.
233 # sync the nova record and the plc record
234 AlchemyObj.sync(self, commit=commit, validate=validate)
235 # create the nova record
236 nova_fields = ['enabled', 'description']
237 nova_can_update = lambda (field, value): field in nova_fields
238 nova_slice = dict(filter(nova_can_update, self.items()))
239 nova_slice['tenant_name'] = self['name']
240 if 'slice_id' not in self:
242 # Before a new slice is added, delete expired slices
243 #expired = Slices(self.api, expires = -int(time.time()))
244 #for slice in expired:
245 # slice.delete(commit)
246 self.object = self.api.client_shell.keystone.tenants.create(**nova_slice)
247 self['tenant_id'] = self.object.id
248 self['created'] = now
249 self['expires'] = now + timedelta(days=14)
250 AlchemyObj.insert(self, dict(self))
251 slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
252 self['slice_id'] = slice.slice_id
254 self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice)
255 AlchemyObj.updatedb(self, {'slice_id': self['slice_id']}, dict(self))
257 def delete(self, commit = True):
259 Delete existing slice.
261 assert 'slice_id' in self
262 assert 'tenant_id' in self
264 # delete the nova object
265 tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
266 self.api.client_shell.keystone.tenants.delete(tenant)
268 # delete relationships
269 for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}):
270 slice_person.delete()
271 for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}):
273 for slice_instance in SliceInstance().select(filter={'slice_id': self['slice_id']}):
274 slice_instance.delete()
275 for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}):
279 AlchemyObj.delete(self, filter={'slice_id': self['slice_id']})
283 Representation of row(s) from the slices table in the
287 def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
289 # the view that we're selecting upon: start with view_slices
291 slices = Slice().select()
292 elif isinstance (slice_filter, StringTypes):
293 slices = Slice().select(filter={'name': slice_filter})
294 elif isinstance(slice_filter, dict):
295 slices = Slice().select(filter=slice_filter)
296 elif isinstance(slice_filter, (list, tuple, set)):
297 slices = Slice().select()
298 slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter]
300 raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
303 slice = Slice(api, object=slice)
304 if not columns or 'person_ids' in columns:
305 slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']})
306 slice['person_ids'] = [rec.person_id for rec in slice_persons]
308 # we need to get the instance ids if node_ids is specified
309 if not columns or 'instance_ids' in columns or 'node_ids' in columns:
310 slice_instances = SliceInstance().select(filter={'slice_id': slice['slice_id']})
311 slice['instance_ids'] = [rec.instance_id for rec in slice_instances]
312 if not columns or 'node_ids' in columns:
313 #slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']})
314 #slice['node_ids'] = [rec.node_id for rec in slice_nodes]
315 # need to look up the manually look up each instance's host and query plc
317 instances = api.client_shell.nova.servers.list()
318 hostnames = [s._info['OS-EXT-SRV-ATTR:host'] for s in instances \
319 if s.id in slice['instance_ids']]
320 nodes = Node().select(filter={'hostname': hostnames})
321 slice['node_ids'] = [rec.node_id for rec in nodes]
323 if not columns or 'slice_tag_ids' in columns:
324 slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']})
325 slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags]
329 def refresh(self, api):
331 Import tenants from keystone.
334 slices = Slice().select()
335 slice_names = [slice.name for slice in slices]
337 # get current tenants
338 tenants = api.client_shell.keystone.tenants.list()
340 # add tenants that dont already exist
341 for tenant in tenants:
342 # site tenants should not contain '_'
343 if '_' in tenant.name and tenant.name not in slice_names:
344 description = tenant.description
345 if not description: description = tenant.name
346 slice = Slice(api, {'name': tenant.name,
347 'tenant_id': tenant.id,
348 'enabled': tenant.enabled,
349 'description': description,
354 # slice may have a login base prefix that doesn't exist yet.