# Copyright 2014 Huawei Technologies Co. Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Database model""" import copy import datetime import logging import netaddr import re import simplejson as json from sqlalchemy import BigInteger from sqlalchemy import Boolean from sqlalchemy import Column from sqlalchemy import ColumnDefault from sqlalchemy import DateTime from sqlalchemy import Enum from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy import Float from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy.orm import relationship, backref from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Text from sqlalchemy.types import TypeDecorator from sqlalchemy import UniqueConstraint from compass.db import callback as metadata_callback from compass.db import exception from compass.db import validator as metadata_validator from compass.utils import util BASE = declarative_base() class JSONEncoded(TypeDecorator): """Represents an immutable structure as a json-encoded string.""" impl = Text def process_bind_param(self, value, dialect): if value is not None: value = json.dumps(value) return value def process_result_value(self, value, dialect): if value is not None: value = json.loads(value) return value class TimestampMixin(object): created_at = Column(DateTime, default=lambda: datetime.datetime.now()) updated_at = Column(DateTime, default=lambda: datetime.datetime.now(), onupdate=lambda: datetime.datetime.now()) class HelperMixin(object): def initialize(self): self.update() def update(self): pass @staticmethod def type_compatible(value, column_type): if value is None: return True if not hasattr(column_type, 'python_type'): return True column_python_type = column_type.python_type if isinstance(value, column_python_type): return True if issubclass(column_python_type, basestring): return isinstance(value, basestring) if column_python_type in [int, long]: return type(value) in [int, long] if column_python_type in [float]: return type(value) in [float] if column_python_type in [bool]: return type(value) in [bool] return False def validate(self): columns = self.__mapper__.columns for key, column in columns.items(): value = getattr(self, key) if not self.type_compatible(value, column.type): raise exception.InvalidParameter( 'column %s value %r type is unexpected: %s' % ( key, value, column.type ) ) def to_dict(self): keys = self.__mapper__.columns.keys() dict_info = {} for key in keys: if key.startswith('_'): continue value = getattr(self, key) if value is not None: if isinstance(value, datetime.datetime): value = util.format_datetime(value) dict_info[key] = value return dict_info class MetadataMixin(HelperMixin): name = Column(String(80)) display_name = Column(String(80)) path = Column(String(256)) description = Column(Text) is_required = Column(Boolean, default=False) required_in_whole_config = Column(Boolean, default=False) mapping_to = Column(String(80), default='') _validator = Column('validator', Text) js_validator = Column(Text) default_value = Column(JSONEncoded) _default_callback = Column('default_callback', Text) default_callback_params = Column( 'default_callback_params', JSONEncoded, default={} ) options = Column(JSONEncoded) _options_callback = Column('options_callback', Text) options_callback_params = Column( 'options_callback_params', JSONEncoded, default={} ) _autofill_callback = Column('autofill_callback', Text) autofill_callback_params = Column( 'autofill_callback_params', JSONEncoded, default={} ) required_in_options = Column(Boolean, default=False) def initialize(self): if not self.display_name: if self.name: self.display_name = self.name super(MetadataMixin, self).initialize() def validate(self): super(MetadataMixin, self).validate() if not self.name: raise exception.InvalidParamter( 'name is not set in os metadata %s' % self.id ) @property def validator(self): if not self._validator: return None func = eval( self._validator, metadata_validator.VALIDATOR_GLOBALS, metadata_validator.VALIDATOR_LOCALS ) if not callable(func): raise Exception( 'validator %s is not callable' % self._validator ) return func @validator.setter def validator(self, value): if not value: self._validator = None elif isinstance(value, basestring): self._validator = value elif callable(value): self._validator = value.func_name else: raise Exception( 'validator %s is not callable' % value ) @property def default_callback(self): if not self._default_callback: return None func = eval( self._default_callback, metadata_callback.CALLBACK_GLOBALS, metadata_callback.CALLBACK_LOCALS ) if not callable(func): raise Exception( 'default callback %s is not callable' % self._default_callback ) return func @default_callback.setter def default_callback(self, value): if not value: self._default_callback = None elif isinstance(value, basestring): self._default_callback = value elif callable(value): self._default_callback = value.func_name else: raise Exception( 'default callback %s is not callable' % value ) @property def options_callback(self): if not self._options_callback: return None func = eval( self._options_callback, metadata_callback.CALLBACK_GLOBALS, metadata_callback.CALLBACK_LOCALS ) if not callable(func): raise Exception( 'options callback %s is not callable' % self._options_callback ) return func @options_callback.setter def options_callback(self, value): if not value: self._options_callback = None elif isinstance(value, basestring): self._options_callback = value elif callable(value): self._options_callback = value.func_name else: raise Exception( 'options callback %s is not callable' % value ) @property def autofill_callback(self): if not self._autofill_callback: return None func = eval( self._autofill_callback, metadata_callback.CALLBACK_GLOBALS, metadata_callback.CALLBACK_LOCALS ) if not callable(func): raise Exception( 'autofill callback %s is not callable' % ( self._autofill_callback ) ) return func @autofill_callback.setter def autofill_callback(self, value): if not value: self._autofill_callback = None elif isinstance(value, basestring): self._autofill_callback = value elif callable(value): self._autofill_callback = value.func_name else: raise Exception( 'autofill callback %s is not callable' % value ) def to_dict(self): self_dict_info = {} if self.field: self_dict_info.update(self.field.to_dict()) else: self_dict_info['field_type_data'] = 'dict' self_dict_info['field_type'] = dict self_dict_info.update(super(MetadataMixin, self).to_dict()) validator = self.validator if validator: self_dict_info['validator'] = validator default_callback = self.default_callback if default_callback: self_dict_info['default_callback'] = default_callback options_callback = self.options_callback if options_callback: self_dict_info['options_callback'] = options_callback autofill_callback = self.autofill_callback if autofill_callback: self_dict_info['autofill_callback'] = autofill_callback js_validator = self.js_validator if js_validator: self_dict_info['js_validator'] = js_validator dict_info = { '_self': self_dict_info } for child in self.children: dict_info.update(child.to_dict()) return { self.name: dict_info } return dict_info class FieldMixin(HelperMixin): id = Column(Integer, primary_key=True) field = Column(String(80), unique=True) field_type_data = Column( 'field_type', Enum( 'basestring', 'int', 'float', 'list', 'bool', 'dict', 'object' ), ColumnDefault('basestring') ) display_type = Column( Enum( 'checkbox', 'radio', 'select', 'multiselect', 'combobox', 'text', 'multitext', 'password' ), ColumnDefault('text') ) _validator = Column('validator', Text) js_validator = Column(Text) description = Column(Text) @property def field_type(self): if not self.field_type_data: return None field_type = eval(self.field_type_data) if not type(field_type) == type: raise Exception( '%s is not type' % self.field_type_data ) return field_type @field_type.setter def field_type(self, value): if not value: self.field_type_data = None elif isinstance(value, basestring): self.field_type_data = value elif type(value) == type: self.field_type_data = value.__name__ else: raise Exception( '%s is not type' % value ) @property def validator(self): if not self._validator: return None func = eval( self._validator, metadata_validator.VALIDATOR_GLOBALS, metadata_validator.VALIDATOR_LOCALS ) if not callable(func): raise Exception( '%s is not callable' % self._validator ) return func @validator.setter def validator(self, value): if not value: self._validator = None elif isinstance(value, basestring): self._validator = value elif callable(value): self._validator = value.func_name else: raise Exception( '%s is not callable' % value ) def to_dict(self): dict_info = super(FieldMixin, self).to_dict() dict_info['field_type'] = self.field_type validator = self.validator if validator: dict_info['validator'] = self.validator js_validator = self.js_validator if js_validator: dict_info['js_validator'] = self.js_validator return dict_info class InstallerMixin(HelperMixin): name = Column(String(80)) alias = Column(String(80), unique=True) settings = Column(JSONEncoded, default={}) def validate(self): super(InstallerMixin, self).validate() if not self.name: raise exception.InvalidParameter( 'name is not set in installer %s' % self.name ) class StateMixin(TimestampMixin, HelperMixin): state = Column( Enum( 'UNINITIALIZED', 'INITIALIZED', 'INSTALLING', 'SUCCESSFUL', 'ERROR' ), ColumnDefault('UNINITIALIZED') ) percentage = Column(Float, default=0.0) message = Column(Text, default='') severity = Column( Enum('INFO', 'WARNING', 'ERROR'), ColumnDefault('INFO') ) def update(self): if self.state in ['UNINITIALIZED', 'INITIALIZED']: self.percentage = 0.0 self.severity = 'INFO' self.message = '' if self.state == 'INSTALLING': if self.severity == 'ERROR': self.state = 'ERROR' elif self.percentage >= 1.0: self.state = 'SUCCESSFUL' self.percentage = 1.0 if self.state == 'SUCCESSFUL': self.percentage = 1.0 super(StateMixin, self).update() class LogHistoryMixin(TimestampMixin, HelperMixin): position = Column(Integer, default=0) partial_line = Column(Text, default='') percentage = Column(Float, default=0.0) message = Column(Text, default='') severity = Column( Enum('ERROR', 'WARNING', 'INFO'), ColumnDefault('INFO') ) line_matcher_name = Column( String(80), default='start' ) def validate(self): if not self.filename: raise exception.InvalidParameter( 'filename is not set in %s' % self.id ) class HostNetwork(BASE, TimestampMixin, HelperMixin): """Host network table.""" __tablename__ = 'host_network' id = Column(Integer, primary_key=True) host_id = Column( Integer, ForeignKey('host.id', onupdate='CASCADE', ondelete='CASCADE') ) interface = Column( String(80)) subnet_id = Column( Integer, ForeignKey('subnet.id', onupdate='CASCADE', ondelete='CASCADE') ) ip_int = Column(BigInteger, unique=True) is_mgmt = Column(Boolean, default=False) is_promiscuous = Column(Boolean, default=False) __table_args__ = ( UniqueConstraint('host_id', 'interface', name='constraint'), ) def __init__(self, host_id, interface, **kwargs): self.host_id = host_id self.interface = interface super(HostNetwork, self).__init__(**kwargs) @property def ip(self): return str(netaddr.IPAddress(self.ip_int)) @ip.setter def ip(self, value): self.ip_int = int(netaddr.IPAddress(value)) @property def netmask(self): return str(netaddr.IPNetwork(self.subnet.subnet).netmask) def update(self): self.host.config_validated = False def validate(self): super(HostNetwork, self).validate() if not self.subnet: raise exception.InvalidParameter( 'subnet is not set in %s interface %s' % ( self.host_id, self.interface ) ) if not self.ip_int: raise exception.InvalidParameter( 'ip is not set in %s interface %s' % ( self.host_id, self.interface ) ) ip = netaddr.IPAddress(self.ip_int) subnet = netaddr.IPNetwork(self.subnet.subnet) if ip not in subnet: raise exception.InvalidParameter( 'ip %s is not in subnet %s' % ( str(ip), str(subnet) ) ) def to_dict(self): dict_info = super(HostNetwork, self).to_dict() dict_info['ip'] = self.ip dict_info['interface'] = self.interface dict_info['netmask'] = self.netmask dict_info['subnet'] = self.subnet.subnet return dict_info class ClusterHostLogHistory(BASE, LogHistoryMixin): """clusterhost installing log history for each file. """ __tablename__ = 'clusterhost_log_history' clusterhost_id = Column( 'id', Integer, ForeignKey('clusterhost.id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True ) filename = Column(String(80), primary_key=True) cluster_id = Column( Integer, ForeignKey('cluster.id') ) host_id = Column( Integer, ForeignKey('host.id') ) def __init__(self, clusterhost_id, filename, **kwargs): self.clusterhost_id = clusterhost_id self.filename = filename super(ClusterHostLogHistory, self).__init__(**kwargs) def initialize(self): self.cluster_id = self.clusterhost.cluster_id self.host_id = self.clusterhost.host_id super(ClusterHostLogHistory, self).initialize() class HostLogHistory(BASE, LogHistoryMixin): """host installing log history for each file. """ __tablename__ = 'host_log_history' id = Column( Integer, ForeignKey('host.id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True) filename = Column(String(80), primary_key=True) def __init__(self, id, filename, **kwargs): self.id = id self.filename = filename super(HostLogHistory, self).__init__(**kwargs) class ClusterHostState(BASE, StateMixin): """ClusterHost state table.""" __tablename__ = 'clusterhost_state' id = Column( Integer, ForeignKey( 'clusterhost.id', onupdate='CASCADE', ondelete='CASCADE' ), primary_key=True ) def update(self): super(ClusterHostState, self).update() host_state = self.clusterhost.host.state if self.state == 'INITIALIZED': if host_state.state in ['UNINITIALIZED']: host_state.state = 'INITIALIZED' host_state.update() elif self.state == 'INSTALLING': if host_state.state in ['UNINITIALIZED', 'INITIALIZED']: host_state.state = 'INSTALLING' host_state.update() elif self.state == 'SUCCESSFUL': if host_state.state != 'SUCCESSFUL': host_state.state = 'SUCCESSFUL' host_state.update() class ClusterHost(BASE, TimestampMixin, HelperMixin): """ClusterHost table.""" __tablename__ = 'clusterhost' clusterhost_id = Column('id', Integer, primary_key=True) cluster_id = Column( Integer, ForeignKey('cluster.id', onupdate='CASCADE', ondelete='CASCADE') ) host_id = Column( Integer, ForeignKey('host.id', onupdate='CASCADE', ondelete='CASCADE') ) _roles = Column('roles', JSONEncoded, default=[]) config_step = Column(String(80), default='') package_config = Column(JSONEncoded, default={}) config_validated = Column(Boolean, default=False) deployed_package_config = Column(JSONEncoded, default={}) log_history = relationship( ClusterHostLogHistory, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('clusterhost') ) __table_args__ = ( UniqueConstraint('cluster_id', 'host_id', name='constraint'), ) state = relationship( ClusterHostState, uselist=False, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('clusterhost') ) def __init__(self, cluster_id, host_id, **kwargs): self.cluster_id = cluster_id self.host_id = host_id self.state = ClusterHostState() super(ClusterHost, self).__init__(**kwargs) def update(self): if self.host.reinstall_os: if self.state in ['SUCCESSFUL', 'ERROR']: if self.config_validated: self.state.state = 'INITIALIZED' else: self.state.state = 'UNINITIALIZED' self.state.update() @property def name(self): return '%s.%s' % (self.host.name, self.cluster.name) @property def patched_package_config(self): return self.package_config @patched_package_config.setter def patched_package_config(self, value): package_config = copy.deepcopy(self.package_config) self.package_config = util.merge_dict(package_config, value) logging.debug( 'patch clusterhost %s package_config: %s', self.clusterhost_id, value ) self.config_validated = False @property def put_package_config(self): return self.package_config @put_package_config.setter def put_package_config(self, value): package_config = copy.deepcopy(self.package_config) package_config.update(value) self.package_config = package_config logging.debug( 'put clusterhost %s package_config: %s', self.clusterhost_id, value ) self.config_validated = False @property def patched_os_config(self): return self.host.os_config @patched_os_config.setter def patched_os_config(self, value): host = self.host host.patched_os_config = value @property def put_os_config(self): return self.host.os_config @put_os_config.setter def put_os_config(self, value): host = self.host host.put_os_config = value @property def deployed_os_config(self): return self.host.deployed_os_config @deployed_os_config.setter def deployed_os_config(self, value): host = self.host host.deployed_os_config = value @hybrid_property def distributed_system_name(self): return self.cluster.distributed_system_name @distributed_system_name.expression def distributed_system_name(cls): return cls.cluster.distributed_system_name @hybrid_property def os_name(self): return self.host.os_name @os_name.expression def os_name(cls): return cls.host.os_name @hybrid_property def clustername(self): return self.cluster.name @clustername.expression def clustername(cls): return cls.cluster.name @hybrid_property def hostname(self): return self.host.name @hostname.expression def hostname(cls): return cls.host.name @property def distributed_system_installed(self): return self.state.state == 'SUCCESSFUL' @property def resintall_os(self): return self.host.reinstall_os @property def reinstall_distributed_system(self): return self.cluster.reinstall_distributed_system @property def os_installed(self): return self.host.os_installed @property def roles(self): role_names = list(self._roles) if not role_names: return [] flavor = self.cluster.flavor if not flavor: return [] roles = [] for flavor_role in flavor.ordered_flavor_roles: role = flavor_role.role if role.name in role_names: roles.append(role) return roles @roles.setter def roles(self, value): self._roles = list(value) self.config_validated = False @property def patched_roles(self): return self.roles @patched_roles.setter def patched_roles(self, value): roles = list(self._roles) roles.extend(value) self._roles = roles self.config_validated = False @hybrid_property def owner(self): return self.cluster.owner @owner.expression def owner(cls): return cls.cluster.owner def state_dict(self): cluster = self.cluster host = self.host host_state = host.state_dict() if not cluster.distributed_system: return host_state clusterhost_state = self.state.to_dict() if clusterhost_state['state'] in ['ERROR', 'SUCCESSFUL']: return clusterhost_state if ( clusterhost_state['state'] in 'INSTALLING' and clusterhost_state['percentage'] > 0 ): clusterhost_state['percentage'] = min( 1.0, ( 0.5 + clusterhost_state['percentage'] / 2 ) ) return clusterhost_state host_state['percentage'] = host_state['percentage'] / 2 if host_state['state'] == 'SUCCESSFUL': host_state['state'] = 'INSTALLING' return host_state def to_dict(self): dict_info = self.host.to_dict() dict_info.update(super(ClusterHost, self).to_dict()) state_dict = self.state_dict() dict_info.update({ 'distributed_system_name': self.distributed_system_name, 'distributed_system_installed': self.distributed_system_installed, 'reinstall_distributed_system': self.reinstall_distributed_system, 'owner': self.owner, 'clustername': self.clustername, 'name': self.name, 'state': state_dict['state'] }) roles = self.roles dict_info['roles'] = [ role.to_dict() for role in roles ] return dict_info class HostState(BASE, StateMixin): """Host state table.""" __tablename__ = 'host_state' id = Column( Integer, ForeignKey('host.id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True ) def update(self): super(HostState, self).update() host = self.host if self.state == 'INSTALLING': host.reinstall_os = False for clusterhost in self.host.clusterhosts: if clusterhost.state in [ 'SUCCESSFUL', 'ERROR' ]: clusterhost.state = 'INSTALLING' clusterhost.state.update() elif self.state == 'UNINITIALIZED': for clusterhost in self.host.clusterhosts: if clusterhost.state in [ 'INITIALIZED', 'INSTALLING', 'SUCCESSFUL', 'ERROR' ]: clusterhost.state = 'UNINITIALIZED' clusterhost.state.update() elif self.state == 'INITIALIZED': for clusterhost in self.host.clusterhosts: if clusterhost.state in [ 'INSTALLING', 'SUCCESSFUL', 'ERROR' ]: clusterhost.state = 'INITIALIZED' clusterhost.state.update() class Host(BASE, TimestampMixin, HelperMixin): """Host table.""" __tablename__ = 'host' name = Column(String(80), unique=True) os_id = Column(Integer, ForeignKey('os.id')) config_step = Column(String(80), default='') os_config = Column(JSONEncoded, default={}) config_validated = Column(Boolean, default=False) deployed_os_config = Column(JSONEncoded, default={}) os_name = Column(String(80)) creator_id = Column(Integer, ForeignKey('user.id')) owner = Column(String(80)) os_installer_id = Column( Integer, ForeignKey('os_installer.id') ) id = Column( Integer, ForeignKey('machine.id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True ) reinstall_os = Column(Boolean, default=True) host_networks = relationship( HostNetwork, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('host') ) clusterhosts = relationship( ClusterHost, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('host') ) state = relationship( HostState, uselist=False, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('host') ) log_history = relationship( HostLogHistory, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('host') ) @hybrid_property def mac(self): machine = self.machine if machine: return machine.mac else: return None @property def patched_os_config(self): return self.os_config @patched_os_config.setter def patched_os_config(self, value): os_config = copy.deepcopy(self.os_config) self.os_config = util.merge_dict(os_config, value) logging.debug('patch host os config in %s: %s', self.id, value) self.config_validated = False @property def put_os_config(self): return self.os_config @put_os_config.setter def put_os_config(self, value): os_config = copy.deepcopy(self.os_config) os_config.update(value) self.os_config = os_config logging.debug('put host os config in %s: %s', self.id, value) self.config_validated = False def __init__(self, id, **kwargs): self.id = id self.name = str(self.id) self.state = HostState() super(Host, self).__init__(**kwargs) def initialize(self): super(Host, self).initialize() def update(self): creator = self.creator if creator: self.owner = creator.email if self.reinstall_os: if self.state in ['SUCCESSFUL', 'ERROR']: if self.config_validated: self.state.state = 'INITIALIZED' else: self.state.state = 'UNINITIALIZED' self.state.update() os = self.os if os: self.os_name = os.name else: self.os_name = None super(Host, self).update() def validate(self): super(Host, self).validate() creator = self.creator if not creator: raise exception.InvalidParameter( 'creator is not set in host %s' % self.id ) os = self.os if not os: raise exception.InvalidParameter( 'os is not set in host %s' % self.id ) os_installer = self.os_installer if not os_installer: raise exception.Invalidparameter( 'os_installer is not set in host %s' % self.id ) if not os.deployable: raise exception.InvalidParameter( 'os %s is not deployable in host %s' % (os.name, self.id) ) @property def os_installed(self): return self.state.state == 'SUCCESSFUL' @property def clusters(self): return [clusterhost.cluster for clusterhost in self.clusterhosts] def state_dict(self): return self.state.to_dict() def to_dict(self): dict_info = self.machine.to_dict() dict_info.update(super(Host, self).to_dict()) state_dict = self.state_dict() ip = None for host_network in self.host_networks: if host_network.is_mgmt: ip = host_network.ip dict_info.update({ 'machine_id': self.machine.id, 'os_installed': self.os_installed, 'hostname': self.name, 'ip': ip, 'networks': [ host_network.to_dict() for host_network in self.host_networks ], 'os_installer': self.os_installer.to_dict(), 'clusters': [cluster.to_dict() for cluster in self.clusters], 'state': state_dict['state'] }) return dict_info class ClusterState(BASE, StateMixin): """Cluster state table.""" __tablename__ = 'cluster_state' id = Column( Integer, ForeignKey('cluster.id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True ) total_hosts = Column( Integer, default=0 ) installing_hosts = Column( Integer, default=0 ) completed_hosts = Column( Integer, default=0 ) failed_hosts = Column( Integer, default=0 ) def to_dict(self): dict_info = super(ClusterState, self).to_dict() dict_info['status'] = { 'total_hosts': self.total_hosts, 'installing_hosts': self.installing_hosts, 'completed_hosts': self.completed_hosts, 'failed_hosts': self.failed_hosts } return dict_info def update(self): cluster = self.cluster clusterhosts = cluster.clusterhosts self.total_hosts = len(clusterhosts) if self.state in ['UNINITIALIZED', 'INITIALIZED', 'INSTALLING']: self.installing_hosts = 0 self.failed_hosts = 0 self.completed_hosts = 0 if self.state == 'INSTALLING': cluster.reinstall_distributed_system = False if not cluster.distributed_system: for clusterhost in clusterhosts: host = clusterhost.host host_state = host.state.state if host_state == 'INSTALLING': self.installing_hosts += 1 elif host_state == 'ERROR': self.failed_hosts += 1 elif host_state == 'SUCCESSFUL': self.completed_hosts += 1 else: for clusterhost in clusterhosts: clusterhost_state = clusterhost.state.state if clusterhost_state == 'INSTALLING': self.installing_hosts += 1 elif clusterhost_state == 'ERROR': self.failed_hosts += 1 elif clusterhost_state == 'SUCCESSFUL': self.completed_hosts += 1 if self.total_hosts: if self.completed_hosts == self.total_hosts: self.percentage = 1.0 else: self.percentage = ( float(self.completed_hosts) / float(self.total_hosts) ) self.message = ( 'total %s, installing %s, completed: %s, error %s' ) % ( self.total_hosts, self.installing_hosts, self.completed_hosts, self.failed_hosts ) if self.failed_hosts: self.severity = 'ERROR' super(ClusterState, self).update() class Cluster(BASE, TimestampMixin, HelperMixin): """Cluster table.""" __tablename__ = 'cluster' id = Column(Integer, primary_key=True) name = Column(String(80), unique=True) reinstall_distributed_system = Column(Boolean, default=True) config_step = Column(String(80), default='') os_id = Column(Integer, ForeignKey('os.id')) os_name = Column(String(80)) flavor_id = Column( Integer, ForeignKey('adapter_flavor.id'), nullable=True ) flavor_name = Column(String(80), nullable=True) distributed_system_id = Column( Integer, ForeignKey('distributed_system.id'), nullable=True ) distributed_system_name = Column( String(80), nullable=True ) os_config = Column(JSONEncoded, default={}) package_config = Column(JSONEncoded, default={}) deployed_os_config = Column(JSONEncoded, default={}) deployed_package_config = Column(JSONEncoded, default={}) config_validated = Column(Boolean, default=False) adapter_id = Column(Integer, ForeignKey('adapter.id')) adapter_name = Column(String(80), nullable=True) creator_id = Column(Integer, ForeignKey('user.id')) owner = Column(String(80)) clusterhosts = relationship( ClusterHost, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('cluster') ) state = relationship( ClusterState, uselist=False, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('cluster') ) def __init__(self, name, **kwargs): self.name = name self.state = ClusterState() super(Cluster, self).__init__(**kwargs) def initialize(self): super(Cluster, self).initialize() def update(self): creator = self.creator if creator: self.owner = creator.email if self.reinstall_distributed_system: if self.state in ['SUCCESSFUL', 'ERROR']: if self.config_validated: self.state.state = 'INITIALIZED' else: self.state.state = 'UNINITIALIZED' self.state.update() os = self.os if os: self.os_name = os.name else: self.os_name = None self.os_config = {} adapter = self.adapter if adapter: self.adapter_name = adapter.name distributed_system = adapter.adapter_distributed_system self.distributed_system = distributed_system if distributed_system: self.distributed_system_name = distributed_system.name else: self.distributed_system_name = None flavor = self.flavor if flavor: self.flavor_name = flavor.name else: self.flavor_name = None else: self.adapter_name = None self.distributed_system = None self.distributed_system_name = None self.flavor = None self.flavor_name = None super(Cluster, self).update() def validate(self): super(Cluster, self).validate() creator = self.creator if not creator: raise exception.InvalidParameter( 'creator is not set in cluster %s' % self.id ) os = self.os if not os: raise exception.InvalidParameter( 'os is not set in cluster %s' % self.id ) if not os.deployable: raise exception.InvalidParameter( 'os %s is not deployable' % os.name ) adapter = self.adapter if not adapter: raise exception.InvalidParameter( 'adapter is not set in cluster %s' % self.id ) if not adapter.deployable: raise exception.InvalidParameter( 'adapter %s is not deployable' % adapter.name ) supported_os_ids = [ adapter_os.os.id for adapter_os in adapter.supported_oses ] if os.id not in supported_os_ids: raise exception.InvalidParameter( 'os %s is not supported' % os.name ) distributed_system = self.distributed_system if distributed_system: if not distributed_system.deployable: raise exception.InvalidParamerter( 'distributed system %s is not deployable' % ( distributed_system.name ) ) flavor = self.flavor if not flavor: if distributed_system: raise exception.InvalidParameter( 'flavor is not set in cluster %s' % self.id ) else: flavor_adapter_id = flavor.adapter_id adapter_id = self.adapter_id if flavor_adapter_id != adapter_id: raise exception.InvalidParameter( 'flavor adapter id %s does not match adapter id %s' % ( flavor_adapter_id, adapter_id ) ) @property def patched_os_config(self): return self.os_config @patched_os_config.setter def patched_os_config(self, value): os_config = copy.deepcopy(self.os_config) self.os_config = util.merge_dict(os_config, value) logging.debug('patch cluster %s os config: %s', self.id, value) self.config_validated = False @property def put_os_config(self): return self.os_config @put_os_config.setter def put_os_config(self, value): os_config = copy.deepcopy(self.os_config) os_config.update(value) self.os_config = os_config logging.debug('put cluster %s os config: %s', self.id, value) self.config_validated = False @property def patched_package_config(self): return self.package_config @patched_package_config.setter def patched_package_config(self, value): package_config = copy.deepcopy(self.package_config) self.package_config = util.merge_dict(package_config, value) logging.debug('patch cluster %s package config: %s', self.id, value) self.config_validated = False @property def put_package_config(self): return self.package_config @put_package_config.setter def put_package_config(self, value): package_config = dict(self.package_config) package_config.update(value) self.package_config = package_config logging.debug('put cluster %s package config: %s', self.id, value) self.config_validated = False @property def distributed_system_installed(self): return self.state.state == 'SUCCESSFUL' def state_dict(self): return self.state.to_dict() def to_dict(self): dict_info = super(Cluster, self).to_dict() dict_info['distributed_system_installed'] = ( self.distributed_system_installed ) if self.flavor: dict_info['flavor'] = self.flavor.to_dict() return dict_info # User, Permission relation table class UserPermission(BASE, HelperMixin, TimestampMixin): """User permission table.""" __tablename__ = 'user_permission' id = Column(Integer, primary_key=True) user_id = Column( Integer, ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE') ) permission_id = Column( Integer, ForeignKey('permission.id', onupdate='CASCADE', ondelete='CASCADE') ) __table_args__ = ( UniqueConstraint('user_id', 'permission_id', name='constraint'), ) def __init__(self, user_id, permission_id, **kwargs): self.user_id = user_id self.permission_id = permission_id @hybrid_property def name(self): return self.permission.name def to_dict(self): dict_info = self.permission.to_dict() dict_info.update(super(UserPermission, self).to_dict()) return dict_info class Permission(BASE, HelperMixin, TimestampMixin): """Permission table.""" __tablename__ = 'permission' id = Column(Integer, primary_key=True) name = Column(String(80), unique=True) alias = Column(String(100)) description = Column(Text) user_permissions = relationship( UserPermission, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('permission') ) def __init__(self, name, **kwargs): self.name = name super(Permission, self).__init__(**kwargs) class UserToken(BASE, HelperMixin): """user token table.""" __tablename__ = 'user_token' id = Column(Integer, primary_key=True) user_id = Column( Integer, ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE') ) token = Column(String(256), unique=True) expire_timestamp = Column( DateTime, default=lambda: datetime.datetime.now() ) def __init__(self, token, **kwargs): self.token = token super(UserToken, self).__init__(**kwargs) def validate(self): super(UserToken, self).validate() if not self.user: raise exception.InvalidParameter( 'user is not set in token: %s' % self.token ) class UserLog(BASE, HelperMixin): """User log table.""" __tablename__ = 'user_log' id = Column(Integer, primary_key=True) user_id = Column( Integer, ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE') ) action = Column(Text) timestamp = Column(DateTime, default=lambda: datetime.datetime.now()) @hybrid_property def user_email(self): return self.user.email def validate(self): super(UserLog, self).validate() if not self.user: raise exception.InvalidParameter( 'user is not set in user log: %s' % self.id ) class User(BASE, HelperMixin, TimestampMixin): """User table.""" __tablename__ = 'user' id = Column(Integer, primary_key=True) email = Column(String(80), unique=True) crypted_password = Column('password', String(225)) firstname = Column(String(80)) lastname = Column(String(80)) is_admin = Column(Boolean, default=False) active = Column(Boolean, default=True) user_permissions = relationship( UserPermission, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('user') ) user_logs = relationship( UserLog, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('user') ) user_tokens = relationship( UserToken, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('user') ) clusters = relationship( Cluster, backref=backref('creator') ) hosts = relationship( Host, backref=backref('creator') ) def __init__(self, email, **kwargs): self.email = email super(User, self).__init__(**kwargs) def validate(self): super(User, self).validate() if not self.crypted_password: raise exception.InvalidParameter( 'password is not set in user : %s' % self.email ) @property def password(self): return '***********' @password.setter def password(self, password): self.crypted_password = util.encrypt(password) @hybrid_property def permissions(self): permissions = [] for user_permission in self.user_permissions: permissions.append(user_permission.permission) return permissions def to_dict(self): dict_info = super(User, self).to_dict() dict_info['permissions'] = [ permission.to_dict() for permission in self.permissions ] return dict_info def __str__(self): return '%s[email:%s,is_admin:%s,active:%s]' % ( self.__class__.__name__, self.email, self.is_admin, self.active ) class SwitchMachine(BASE, HelperMixin, TimestampMixin): """Switch Machine table.""" __tablename__ = 'switch_machine' switch_machine_id = Column( 'id', Integer, primary_key=True ) switch_id = Column( Integer, ForeignKey('switch.id', onupdate='CASCADE', ondelete='CASCADE') ) machine_id = Column( Integer, ForeignKey('machine.id', onupdate='CASCADE', ondelete='CASCADE') ) port = Column(String(80), nullable=True) vlans = Column(JSONEncoded, default=[]) __table_args__ = ( UniqueConstraint('switch_id', 'machine_id', name='constraint'), ) def __init__(self, switch_id, machine_id, **kwargs): self.switch_id = switch_id self.machine_id = machine_id super(SwitchMachine, self).__init__(**kwargs) def validate(self): super(SwitchMachine, self).validate() if not self.switch: raise exception.InvalidParameter( 'switch is not set in %s' % self.id ) if not self.machine: raise exception.Invalidparameter( 'machine is not set in %s' % self.id ) if not self.port: raise exception.InvalidParameter( 'port is not set in %s' % self.id ) @hybrid_property def mac(self): return self.machine.mac @hybrid_property def tag(self): return self.machine.tag @property def switch_ip(self): return self.switch.ip @hybrid_property def switch_ip_int(self): return self.switch.ip_int @switch_ip_int.expression def switch_ip_int(cls): return Switch.ip_int @hybrid_property def switch_vendor(self): return self.switch.vendor @switch_vendor.expression def switch_vendor(cls): return Switch.vendor @property def patched_vlans(self): return self.vlans @patched_vlans.setter def patched_vlans(self, value): if not value: return vlans = list(self.vlans) for item in value: if item not in vlans: vlans.append(item) self.vlans = vlans @property def filtered(self): filters = self.switch.filters port = self.port unmatched_allowed = True ports_pattern = re.compile(r'(\D*)(\d+)-(\d+)(\D*)') port_pattern = re.compile(r'(\D*)(\d+)(\D*)') port_match = port_pattern.match(port) if port_match: port_prefix = port_match.group(1) port_number = int(port_match.group(2)) port_suffix = port_match.group(3) else: port_prefix = '' port_number = 0 port_suffix = '' for port_filter in filters: filter_type = port_filter.get('filter_type', 'allow') denied = filter_type != 'allow' unmatched_allowed = denied if 'ports' in port_filter: if 'all' in port_filter['ports']: return denied if port in port_filter['ports']: return denied if port_match: for port_or_ports in port_filter['ports']: ports_match = ports_pattern.match(port_or_ports) if ports_match: filter_port_prefix = ports_match.group(1) filter_port_start = int(ports_match.group(2)) filter_port_end = int(ports_match.group(3)) filter_port_suffix = ports_match.group(4) if ( filter_port_prefix == port_prefix and filter_port_suffix == port_suffix and filter_port_start <= port_number and port_number <= filter_port_end ): return denied else: filter_port_prefix = port_filter.get('port_prefix', '') filter_port_suffix = port_filter.get('port_suffix', '') if ( port_match and port_prefix == filter_port_prefix and port_suffix == filter_port_suffix ): if ( 'port_start' not in port_filter or port_number >= port_filter['port_start'] ) and ( 'port_end' not in port_filter or port_number <= port_filter['port_end'] ): return denied return not unmatched_allowed def to_dict(self): dict_info = self.machine.to_dict() dict_info.update(super(SwitchMachine, self).to_dict()) dict_info['switch_ip'] = self.switch.ip return dict_info class Machine(BASE, HelperMixin, TimestampMixin): """Machine table.""" __tablename__ = 'machine' id = Column(Integer, primary_key=True) mac = Column(String(24), unique=True) ipmi_credentials = Column(JSONEncoded, default={}) tag = Column(JSONEncoded, default={}) location = Column(JSONEncoded, default={}) switch_machines = relationship( SwitchMachine, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('machine') ) host = relationship( Host, uselist=False, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('machine') ) def __init__(self, mac, **kwargs): self.mac = mac super(Machine, self).__init__(**kwargs) def validate(self): super(Machine, self).validate() try: netaddr.EUI(self.mac) except Exception: raise exception.InvalidParameter( 'mac address %s format uncorrect' % self.mac ) @property def patched_ipmi_credentials(self): return self.ipmi_credentials @patched_ipmi_credentials.setter def patched_ipmi_credentials(self, value): if not value: return ipmi_credentials = copy.deepcopy(self.ipmi_credentials) self.ipmi_credentials = util.merge_dict(ipmi_credentials, value) @property def patched_tag(self): return self.tag @patched_tag.setter def patched_tag(self, value): if not value: return tag = copy.deepcopy(self.tag) tag.update(value) self.tag = value @property def patched_location(self): return self.location @patched_location.setter def patched_location(self, value): if not value: return location = copy.deepcopy(self.location) location.update(value) self.location = location def to_dict(self): dict_info = {} dict_info['switches'] = [ { 'switch_ip': switch_machine.switch_ip, 'port': switch_machine.port, 'vlans': switch_machine.vlans } for switch_machine in self.switch_machines if not switch_machine.filtered ] if dict_info['switches']: dict_info.update(dict_info['switches'][0]) dict_info.update(super(Machine, self).to_dict()) return dict_info class Switch(BASE, HelperMixin, TimestampMixin): """Switch table.""" __tablename__ = 'switch' id = Column(Integer, primary_key=True) ip_int = Column('ip', BigInteger, unique=True) credentials = Column(JSONEncoded, default={}) vendor = Column(String(256), nullable=True) state = Column(Enum('initialized', 'unreachable', 'notsupported', 'repolling', 'error', 'under_monitoring', name='switch_state'), ColumnDefault('initialized')) _filters = Column('filters', JSONEncoded, default=[]) switch_machines = relationship( SwitchMachine, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('switch') ) @classmethod def parse_filters(cls, filters): if isinstance(filters, basestring): filters = filters.replace('\r\n', '\n').replace('\n', ';') filters = [ switch_filter for switch_filter in filters.split(';') if switch_filter ] if not isinstance(filters, list): filters = [filters] switch_filters = [] for switch_filter in filters: if not switch_filter: continue if isinstance(switch_filter, basestring): filter_dict = {} filter_items = [ item for item in switch_filter.split() if item ] if filter_items[0] in ['allow', 'deny']: filter_dict['filter_type'] = filter_items[0] filter_items = filter_items[1:] elif filter_items[0] not in [ 'ports', 'port_prefix', 'port_suffix', 'port_start', 'port_end' ]: raise exception.InvalidParameter( 'unrecognized filter type %s' % filter_items[0] ) while filter_items: if len(filter_items) >= 2: filter_dict[filter_items[0]] = filter_items[1] filter_items = filter_items[2:] else: filter_dict[filter_items[0]] = '' filter_items = filter_items[1:] switch_filter = filter_dict if not isinstance(switch_filter, dict): raise exception.InvalidParameter( 'filter %s is not dict' % switch_filter ) if 'filter_type' in switch_filter: if switch_filter['filter_type'] not in ['allow', 'deny']: raise exception.InvalidParameter( 'filter_type should be `allow` or `deny` in %s' % ( switch_filter ) ) if 'ports' in switch_filter: if isinstance(switch_filter['ports'], basestring): switch_filter['ports'] = [ port_or_ports for port_or_ports in switch_filter['ports'].split(',') if port_or_ports ] if not isinstance(switch_filter['ports'], list): raise exception.InvalidParameter( '`ports` type is not list in filter %s' % switch_filter ) for port_or_ports in switch_filter['ports']: if not isinstance(port_or_ports, basestring): raise exception.InvalidParameter( '%s type is not basestring in `ports` %s' % ( port_or_ports, switch_filter['ports'] ) ) for key in ['port_start', 'port_end']: if key in switch_filter: if isinstance(switch_filter[key], basestring): if switch_filter[key].isdigit(): switch_filter[key] = int(switch_filter[key]) if not isinstance(switch_filter[key], int): raise exception.InvalidParameter( '`%s` type is not int in filer %s' % ( key, switch_filter ) ) switch_filters.append(switch_filter) return switch_filters @classmethod def format_filters(cls, filters): filter_strs = [] for switch_filter in filters: filter_properties = [] filter_properties.append( switch_filter.get('filter_type', 'allow') ) if 'ports' in switch_filter: filter_properties.append( 'ports ' + ','.join(switch_filter['ports']) ) if 'port_prefix' in switch_filter: filter_properties.append( 'port_prefix ' + switch_filter['port_prefix'] ) if 'port_suffix' in switch_filter: filter_properties.append( 'port_suffix ' + switch_filter['port_suffix'] ) if 'port_start' in switch_filter: filter_properties.append( 'port_start ' + str(switch_filter['port_start']) ) if 'port_end' in switch_filter: filter_properties.append( 'port_end ' + str(switch_filter['port_end']) ) filter_strs.append(' '.join(filter_properties)) return ';'.join(filter_strs) def __init__(self, ip_int, **kwargs): self.ip_int = ip_int super(Switch, self).__init__(**kwargs) @property def ip(self): return str(netaddr.IPAddress(self.ip_int)) @ip.setter def ip(self, ipaddr): self.ip_int = int(netaddr.IPAddress(ipaddr)) @property def patched_credentials(self): return self.credentials @patched_credentials.setter def patched_credentials(self, value): if not value: return credentials = copy.deepcopy(self.credentials) self.credentials = util.merge_dict(credentials, value) @property def filters(self): return self._filters @filters.setter def filters(self, value): if not value: return self._filters = self.parse_filters(value) @property def put_filters(self): return self._filters @put_filters.setter def put_filters(self, value): if not value: return self._filters = self.parse_filters(value) @property def patched_filters(self): return self._filters @patched_filters.setter def patched_filters(self, value): if not value: return filters = list(self.filters) self.filters = self.parse_filters(value) + filters def to_dict(self): dict_info = super(Switch, self).to_dict() dict_info['ip'] = self.ip dict_info['filters'] = self.format_filters(self._filters) return dict_info class OSConfigMetadata(BASE, MetadataMixin): """OS config metadata.""" __tablename__ = "os_config_metadata" id = Column(Integer, primary_key=True) os_id = Column( Integer, ForeignKey( 'os.id', onupdate='CASCADE', ondelete='CASCADE' ) ) parent_id = Column( Integer, ForeignKey( 'os_config_metadata.id', onupdate='CASCADE', ondelete='CASCADE' ) ) field_id = Column( Integer, ForeignKey( 'os_config_field.id', onupdate='CASCADE', ondelete='CASCADE' ) ) children = relationship( 'OSConfigMetadata', passive_deletes=True, passive_updates=True, backref=backref('parent', remote_side=id) ) __table_args__ = ( UniqueConstraint('path', 'os_id', name='constraint'), ) def __init__(self, os_id, path, **kwargs): self.os_id = os_id self.path = path super(OSConfigMetadata, self).__init__(**kwargs) def validate(self): super(OSConfigMetadata, self).validate() if not self.os: raise exception.InvalidParameter( 'os is not set in os metadata %s' % self.id ) class OSConfigField(BASE, FieldMixin): """OS config fields.""" __tablename__ = 'os_config_field' metadatas = relationship( OSConfigMetadata, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('field')) def __init__(self, field, **kwargs): self.field = field super(OSConfigField, self).__init__(**kwargs) class AdapterOS(BASE, HelperMixin): """Adapter OS table.""" __tablename__ = 'adapter_os' adapter_os_id = Column('id', Integer, primary_key=True) os_id = Column( Integer, ForeignKey( 'os.id', onupdate='CASCADE', ondelete='CASCADE' ) ) adapter_id = Column( Integer, ForeignKey( 'adapter.id', onupdate='CASCADE', ondelete='CASCADE' ) ) def __init__(self, os_id, adapter_id, **kwargs): self.os_id = os_id self.adapter_id = adapter_id super(AdapterOS, self).__init__(**kwargs) def to_dict(self): dict_info = self.os.to_dict() dict_info.update(super(AdapterOS, self).to_dict()) return dict_info class OperatingSystem(BASE, HelperMixin): """OS table.""" __tablename__ = 'os' id = Column(Integer, primary_key=True) parent_id = Column( Integer, ForeignKey('os.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=True ) name = Column(String(80), unique=True) deployable = Column(Boolean, default=False) metadatas = relationship( OSConfigMetadata, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('os') ) clusters = relationship( Cluster, backref=backref('os') ) hosts = relationship( Host, backref=backref('os') ) children = relationship( 'OperatingSystem', passive_deletes=True, passive_updates=True, backref=backref('parent', remote_side=id) ) supported_adapters = relationship( AdapterOS, passive_deletes=True, passive_updates=True, backref=backref('os') ) def __init__(self, name): self.name = name super(OperatingSystem, self).__init__() @property def root_metadatas(self): return [ metadata for metadata in self.metadatas if metadata.parent_id is None ] def metadata_dict(self): dict_info = {} if self.parent: dict_info.update(self.parent.metadata_dict()) for metadata in self.root_metadatas: util.merge_dict(dict_info, metadata.to_dict()) return dict_info @property def os_supported_adapters(self): supported_adapters = self.supported_adapters if supported_adapters: return supported_adapters parent = self.parent if parent: return parent.os_supported_adapters else: return [] class AdapterFlavorRole(BASE, HelperMixin): """Adapter flavor roles.""" __tablename__ = 'adapter_flavor_role' flavor_id = Column( Integer, ForeignKey( 'adapter_flavor.id', onupdate='CASCADE', ondelete='CASCADE' ), primary_key=True ) role_id = Column( Integer, ForeignKey( 'adapter_role.id', onupdate='CASCADE', ondelete='CASCADE' ), primary_key=True ) def __init__(self, flavor_id, role_id): self.flavor_id = flavor_id self.role_id = role_id super(AdapterFlavorRole, self).__init__() def validate(self): super(AdapterFlavorRole, self).validate() flavor_adapter_id = self.flavor.adapter_id role_adapter_id = self.role.adapter_id if flavor_adapter_id != role_adapter_id: raise exception.InvalidParameter( 'flavor adapter %s and role adapter %s does not match' % ( flavor_adapter_id, role_adapter_id ) ) def to_dict(self): dict_info = super(AdapterFlavorRole, self).to_dict() dict_info.update( self.role.to_dict() ) return dict_info class AdapterFlavor(BASE, HelperMixin): """Adapter's flavors.""" __tablename__ = 'adapter_flavor' id = Column(Integer, primary_key=True) adapter_id = Column( Integer, ForeignKey('adapter.id', onupdate='CASCADE', ondelete='CASCADE') ) name = Column(String(80)) display_name = Column(String(80)) template = Column(String(80)) _ordered_flavor_roles = Column( 'ordered_flavor_roles', JSONEncoded, default=[] ) flavor_roles = relationship( AdapterFlavorRole, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('flavor') ) clusters = relationship( Cluster, backref=backref('flavor') ) __table_args__ = ( UniqueConstraint('name', 'adapter_id', name='constraint'), ) @property def ordered_flavor_roles(self): flavor_roles = dict([ (flavor_role.role.name, flavor_role) for flavor_role in self.flavor_roles ]) ordered_flavor_roles = [] for flavor_role in list(self._ordered_flavor_roles): if flavor_role in flavor_roles: ordered_flavor_roles.append(flavor_roles[flavor_role]) return ordered_flavor_roles @ordered_flavor_roles.setter def ordered_flavor_roles(self, value): self._ordered_flavor_roles = list(value) @property def patched_ordered_flavor_roles(self): return self.ordered_flavor_roles @patched_ordered_flavor_roles.setter def patched_ordered_flavor_roles(self, value): ordered_flavor_roles = list(self._ordered_flavor_roles) ordered_flavor_roles.extend(value) self._ordered_flavor_roles = ordered_flavor_roles def __init__(self, name, adapter_id, **kwargs): self.name = name self.adapter_id = adapter_id super(AdapterFlavor, self).__init__(**kwargs) def initialize(self): if not self.display_name: self.display_name = self.name super(AdapterFlavor, self).initialize() def validate(self): super(AdapterFlavor, self).validate() if not self.template: raise exception.InvalidParameter( 'template is not set in adapter flavor %s' % self.id ) def to_dict(self): dict_info = super(AdapterFlavor, self).to_dict() dict_info['roles'] = [ flavor_role.to_dict() for flavor_role in self.ordered_flavor_roles ] return dict_info class AdapterRole(BASE, HelperMixin): """Adapter's roles.""" __tablename__ = "adapter_role" id = Column(Integer, primary_key=True) name = Column(String(80)) display_name = Column(String(80)) description = Column(Text) optional = Column(Boolean, default=False) adapter_id = Column( Integer, ForeignKey( 'adapter.id', onupdate='CASCADE', ondelete='CASCADE' ) ) flavor_roles = relationship( AdapterFlavorRole, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('role') ) __table_args__ = ( UniqueConstraint('name', 'adapter_id', name='constraint'), ) def __init__(self, name, adapter_id, **kwargs): self.name = name self.adapter_id = adapter_id super(AdapterRole, self).__init__(**kwargs) def initialize(self): if not self.description: self.description = self.name if not self.display_name: self.display_name = self.name super(AdapterRole, self).initialize() class PackageConfigMetadata(BASE, MetadataMixin): """package config metadata.""" __tablename__ = "package_config_metadata" id = Column(Integer, primary_key=True) adapter_id = Column( Integer, ForeignKey( 'adapter.id', onupdate='CASCADE', ondelete='CASCADE' ) ) parent_id = Column( Integer, ForeignKey( 'package_config_metadata.id', onupdate='CASCADE', ondelete='CASCADE' ) ) field_id = Column( Integer, ForeignKey( 'package_config_field.id', onupdate='CASCADE', ondelete='CASCADE' ) ) children = relationship( 'PackageConfigMetadata', passive_deletes=True, passive_updates=True, backref=backref('parent', remote_side=id) ) __table_args__ = ( UniqueConstraint('path', 'adapter_id', name='constraint'), ) def __init__( self, adapter_id, path, **kwargs ): self.adapter_id = adapter_id self.path = path super(PackageConfigMetadata, self).__init__(**kwargs) def validate(self): super(PackageConfigMetadata, self).validate() if not self.adapter: raise exception.InvalidParameter( 'adapter is not set in package metadata %s' % self.id ) class PackageConfigField(BASE, FieldMixin): """Adapter cofig metadata fields.""" __tablename__ = "package_config_field" metadatas = relationship( PackageConfigMetadata, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('field')) def __init__(self, field, **kwargs): self.field = field super(PackageConfigField, self).__init__(**kwargs) class Adapter(BASE, HelperMixin): """Adapter table.""" __tablename__ = 'adapter' id = Column(Integer, primary_key=True) name = Column(String(80), unique=True) display_name = Column(String(80)) parent_id = Column( Integer, ForeignKey( 'adapter.id', onupdate='CASCADE', ondelete='CASCADE' ), nullable=True ) distributed_system_id = Column( Integer, ForeignKey( 'distributed_system.id', onupdate='CASCADE', ondelete='CASCADE' ), nullable=True ) os_installer_id = Column( Integer, ForeignKey( 'os_installer.id', onupdate='CASCADE', ondelete='CASCADE' ), nullable=True ) package_installer_id = Column( Integer, ForeignKey( 'package_installer.id', onupdate='CASCADE', ondelete='CASCADE' ), nullable=True ) deployable = Column( Boolean, default=False ) supported_oses = relationship( AdapterOS, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('adapter') ) roles = relationship( AdapterRole, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('adapter') ) flavors = relationship( AdapterFlavor, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('adapter') ) children = relationship( 'Adapter', passive_deletes=True, passive_updates=True, backref=backref('parent', remote_side=id) ) metadatas = relationship( PackageConfigMetadata, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('adapter') ) clusters = relationship( Cluster, backref=backref('adapter') ) __table_args__ = ( UniqueConstraint( 'distributed_system_id', 'os_installer_id', 'package_installer_id', name='constraint' ), ) def __init__( self, name, **kwargs ): self.name = name super(Adapter, self).__init__(**kwargs) def initialize(self): if not self.display_name: self.display_name = self.name super(Adapter, self).initialize() @property def root_metadatas(self): return [ metadata for metadata in self.metadatas if metadata.parent_id is None ] def metadata_dict(self): dict_info = {} if self.parent: dict_info.update(self.parent.metadata_dict()) for metadata in self.root_metadatas: util.merge_dict(dict_info, metadata.to_dict()) return dict_info @property def adapter_package_installer(self): if self.package_installer: return self.package_installer elif self.parent: return self.parent.adapter_package_installer else: return None @property def adapter_os_installer(self): if self.os_installer: return self.os_installer elif self.parent: return self.parent.adapter_os_installer else: return None @property def adapter_distributed_system(self): distributed_system = self.distributed_system if distributed_system: return distributed_system parent = self.parent if parent: return parent.adapter_distributed_system else: return None @property def adapter_supported_oses(self): supported_oses = self.supported_oses if supported_oses: return supported_oses parent = self.parent if parent: return parent.adapter_supported_oses else: return [] @property def adapter_roles(self): roles = self.roles if roles: return roles parent = self.parent if parent: return parent.adapter_roles else: return [] @property def adapter_flavors(self): flavors = self.flavors if flavors: return flavors parent = self.parent if parent: return parent.adapter_flavors else: return [] def to_dict(self): dict_info = super(Adapter, self).to_dict() dict_info.update({ 'roles': [ role.to_dict() for role in self.adapter_roles ], 'supported_oses': [ adapter_os.to_dict() for adapter_os in self.adapter_supported_oses ], 'flavors': [ flavor.to_dict() for flavor in self.adapter_flavors ] }) distributed_system = self.adapter_distributed_system if distributed_system: dict_info['distributed_system_id'] = distributed_system.id dict_info['distributed_system_name'] = distributed_system.name os_installer = self.adapter_os_installer if os_installer: dict_info['os_installer'] = os_installer.to_dict() package_installer = self.adapter_package_installer if package_installer: dict_info['package_installer'] = package_installer.to_dict() return dict_info class DistributedSystem(BASE, HelperMixin): """distributed system table.""" __tablename__ = 'distributed_system' id = Column(Integer, primary_key=True) parent_id = Column( Integer, ForeignKey( 'distributed_system.id', onupdate='CASCADE', ondelete='CASCADE' ), nullable=True ) name = Column(String(80), unique=True) deployable = Column(Boolean, default=False) adapters = relationship( Adapter, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('distributed_system') ) clusters = relationship( Cluster, backref=backref('distributed_system') ) children = relationship( 'DistributedSystem', passive_deletes=True, passive_updates=True, backref=backref('parent', remote_side=id) ) def __init__(self, name): self.name = name super(DistributedSystem, self).__init__() class OSInstaller(BASE, InstallerMixin): """OS installer table.""" __tablename__ = 'os_installer' id = Column(Integer, primary_key=True) adpaters = relationship( Adapter, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('os_installer') ) hosts = relationship( Host, backref=backref('os_installer') ) def __init__(self, alias, **kwargs): self.alias = alias super(OSInstaller, self).__init__(**kwargs) class PackageInstaller(BASE, InstallerMixin): """package installer table.""" __tablename__ = 'package_installer' id = Column(Integer, primary_key=True) adapters = relationship( Adapter, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('package_installer') ) def __init__(self, alias, **kwargs): self.alias = alias super(PackageInstaller, self).__init__(**kwargs) class Subnet(BASE, TimestampMixin, HelperMixin): """network table.""" __tablename__ = 'subnet' id = Column(Integer, primary_key=True) name = Column(String(80), unique=True) subnet = Column(String(80), unique=True) host_networks = relationship( HostNetwork, passive_deletes=True, passive_updates=True, cascade='all, delete-orphan', backref=backref('subnet') ) def __init__(self, subnet, **kwargs): self.subnet = subnet super(Subnet, self).__init__(**kwargs) def initialize(self): if not self.name: self.name = self.subnet super(Subnet, self).initialize()