from types import StringTypes import time import re from sqlalchemy import Column, Integer, String, DateTime from sqlalchemy import Table, Column, MetaData, join, ForeignKey from sqlalchemy.orm import relationship, backref from sqlalchemy.orm import column_property from sqlalchemy.orm import object_mapper from sqlalchemy.orm import validates from PLC.Storage.AlchemyObject import AlchemyObj from PLC.Faults import * from PLC.Parameter import Parameter, Mixed from PLC.Filter import Filter from PLC.Debug import profile from PLC.Nodes import Node from PLC.Persons import Person, Persons from PLC.Timestamp import Timestamp 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 = { 'id': Parameter(int, "Slice identifier", primary_key=True), 'site_id': Parameter(int, "Identifier of the site to which this slice belongs"), 'name': Parameter(str, "Slice name", max = 32), '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"), 'creator_person_id': Parameter(int, "Identifier of the account that created this slice"), 'created': Parameter(int, "Date and time when slice was created, in seconds since UNIX epoch", ro = True), 'expires': Parameter(int, "Date and time when slice expires, in seconds since UNIX epoch"), 'node_ids': Parameter([int], "List of nodes in this slice", ro = True), 'person_ids': Parameter([int], "List of accounts that can use this slice", ro = 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 'id' not in self or self['id'] != 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) #add_person = Row.add_object(Person, 'slice_person') #remove_person = Row.remove_object(Person, 'slice_person') #add_node = Row.add_object(Node, 'slice_node') #remove_node = Row.remove_object(Node, 'slice_node') #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. """ AlchemyObj.sync(self, commit, validate) if 'id' not in self: # Before a new slice is added, delete expired slices expired = Slices(self.api, expires = -int(time.time())) for slice in expired: slice.delete(commit) Slice().insert(dict(self)) def delete(self, commit = True): """ Delete existing slice. """ assert 'slice_id' in self # Clean up miscellaneous join tables for table in self.join_tables: self.api.db.do("DELETE FROM %s WHERE slice_id = %d" % \ (table, self['slice_id'])) # Mark as deleted self['is_deleted'] = True self.sync(commit) 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.id in slice_filter] else: raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter for slice in slices: self.append(slice)