from types import StringTypes import time import re from datetime import datetime, timedelta from PLC.Faults import * from PLC.Parameter import Parameter, Mixed from PLC.Debug import profile from PLC.Roles import Role, Roles from PLC.Nodes import Node from PLC.Persons import Person, Persons from PLC.SlicePersons import SlicePerson, SlicePersons from PLC.SliceNodes import SliceNode, SliceNodes from PLC.SliceTags import SliceTag, SliceTags from PLC.Timestamp import Timestamp from PLC.Storage.AlchemyObject import AlchemyObj class Slice(AlchemyObj): """ Representation of a row in the slices table. To use, optionally instantiate with a dict of values. Update as you would a dict. Commit to the database with sync().To use, instantiate with a dict of values. """ tablename = 'slices' fields = { 'slice_id': Parameter(int, "Slice identifier", primary_key=True), 'site_id': Parameter(int, "Identifier of the site to which this slice belongs"), 'tenant_id': Parameter(int, "Keystone tenant identifier"), 'name': Parameter(str, "Slice name", max = 32), 'instantiation': Parameter(str, "Slice instantiation state", nullok=True), 'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True), 'description': Parameter(str, "Slice description", max = 2048, nullok = True), 'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice", default=100), 'creator_person_id': Parameter(str, "Identifier of the account that created this slice"), 'created': Parameter(datetime, "Date and time when slice was created, in seconds since UNIX epoch", ro = True), 'expires': Parameter(datetime, "Date and time when slice expires, in seconds since UNIX epoch"), 'node_ids': Parameter([str], "List of nodes in this slice", joined = True), 'person_ids': Parameter([str], "List of accounts that can use this slice", joined = True), 'slice_tag_ids': Parameter([int], "List of slice attributes", joined = True), 'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True), 'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True), } tags = {} def validate_name(self, name): # N.B.: Responsibility of the caller to ensure that login_base # portion of the slice name corresponds to a valid site, if # desired. # 1. Lowercase. # 2. Begins with login_base (letters or numbers). # 3. Then single underscore after login_base. # 4. Then letters, numbers, or underscores. good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$' if not name or \ not re.match(good_name, name): raise PLCInvalidArgument, "Invalid slice name" conflicts = Slices(self.api, [name]) for slice in conflicts: if 'slice_id' not in self or self['slice_id'] != slice['slice_id']: raise PLCInvalidArgument, "Slice name already in use, %s"%name return name def validate_expires(self, expires): # N.B.: Responsibility of the caller to ensure that expires is # not too far into the future. check_future = not ('is_deleted' in self and self['is_deleted']) return Timestamp.sql_validate( expires, check_future = check_future) def add_person(self, person_filter, role_name=None): assert 'slice_id' in self assert 'tenant_id' in self if not role_name: role_name = 'user' roles = Roles(self.api, role_name) if not roles: raise PLCInvalidArgument, "No such role %s" % role_name role = roles[0] tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id']) persons = Persons(self.api, person_filter) for person in persons: keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id']) tenant.add_user(keystone_user, role.object) slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'], 'person_id': person['person_id']}) slice_person.sync() def remove_person(self, person_filter, role=None): assert 'slice_id' in self assert 'tenant_id' in self if not role_name: role_name = 'user' roles = Roles(self.api, role_name) if not roles: raise PLCInvalidArgument, "No such role %s" % role_name role = roles[0] tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id']) persons = Persons(self.api, person_filter) for person in persons: keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id']) tenant.remove_user(keystone_user, role.object) slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'], 'person_id': person['person_id']}) slice_person.delete() def add_node(self, node_filter, commit=True): from PLC.Nodes import Nodes assert 'slice_id' in self nodes = Nodes(self.api, node_filter) for node in nodes: slice_node = SliceNode(self.api, {'slice_id': self['slice_id'], 'node_id': node['node_id']}) slice_node.sync() def remove_node(self, node_filter, commit=True): from PLC.Nodes import Nodes assert 'slice_id' in self nodes = Nodes(self.api, node_filter) for node in nodes: slice_node = SliceNode(self.api, {'slice_id': self['slice_id'], 'node_id': node['node_id']}) slice_node.delete() #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist') #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist') def sync(self, commit = True, validate=True): """ Add or update a slice. """ # sync the nova record and the plc record AlchemyObj.sync(self, commit=commit, validate=validate) # create the nova record nova_fields = ['enabled', 'description'] nova_can_update = lambda (field, value): field in nova_fields nova_slice = dict(filter(nova_can_update, self.items())) nova_slice['tenant_name'] = self['name'] if 'slice_id' not in self: now = datetime.now() # Before a new slice is added, delete expired slices #expired = Slices(self.api, expires = -int(time.time())) #for slice in expired: # slice.delete(commit) self.object = self.api.client_shell.keystone.tenants.create(**nova_slice) self['tenant_id'] = self.object.id self['created'] = now self['expires'] = now + timedelta(days=14) AlchemyObj.insert(self, dict(self)) slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0] self['slice_id'] = slice.slice_id else: self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice) AlchemyObj.updatedb(self, {'slice_id': self['slice_id']}, dict(self)) def delete(self, commit = True): """ Delete existing slice. """ assert 'slice_id' in self assert 'tenant_id' in self # delete the nova object tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id']) self.api.client_shell.keystone.tenants.delete(tenant) # delete relationships for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}): slice_person.delete() for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}): slice_node.delete() for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}): slice_tag.delete() # delete slice AlchemyObj.delete(self, filter={'slice_id': self['slice_id']}) class Slices(list): """ Representation of row(s) from the slices table in the database. """ def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())): # the view that we're selecting upon: start with view_slices if not slice_filter: slices = Slice().select() elif isinstance (slice_filter, StringTypes): slices = Slice().select(filter={'name': slice_filter}) elif isinstance(slice_filter, dict): slices = Slice().select(filter=slice_filter) elif isinstance(slice_filter, (list, tuple, set)): slices = Slice().select() slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter] else: raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter for slice in slices: slice = Slice(api, object=slice) if not columns or 'person_ids' in columns: slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']}) slice['person_ids'] = [rec.person_id for rec in slice_persons] if not columns or 'node_ids' in columns: slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']}) slice['node_ids'] = [rec.node_id for rec in slice_nodes] if not columns or 'slice_tag_ids' in columns: slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']}) slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags] self.append(slice) def refresh(self, api): """ Import tenants from keystone. """ # get current slices slices = Slice().select() slice_names = [slice.name for slice in slices] # get current tenants tenants = api.client_shell.keystone.tenants.list() # add tenants that dont already exist for tenant in tenants: # site tenants should not contain '_' if '_' in tenant.name and tenant.name not in slice_names: description = tenant.description if not description: description = tenant.name slice = Slice(api, {'name': tenant.name, 'tenant_id': tenant.id, 'enabled': tenant.enabled, 'description': description 'is_public': True}) try: slice.sync() except: # slice may have a login base prefix that doesn't exist yet. pass