class NeutronConfig(NetworkingConfig): __tablename__ = 'neutron_config' __mapper_args__ = { 'polymorphic_identity': 'neutron_config', } id = Column(Integer, ForeignKey('networking_configs.id'), primary_key=True) vlan_range = Column(MutableList.as_mutable(JSON), default=[]) gre_id_range = Column(MutableList.as_mutable(JSON), default=[]) base_mac = Column(psql.MACADDR, nullable=False) internal_cidr = Column(psql.CIDR) internal_gateway = Column(psql.INET) baremetal_gateway = Column(psql.INET) baremetal_range = Column(MutableList.as_mutable(JSON), default=[]) # Neutron L3 names for default internal / floating networks # which were previously knows as net04 and net04_ext. internal_name = Column(String(50), nullable=False) floating_name = Column(String(50), nullable=False) segmentation_type = Column( Enum(*consts.NEUTRON_SEGMENT_TYPES, name='segmentation_type'), nullable=False, default=consts.NEUTRON_SEGMENT_TYPES.vlan ) net_l23_provider = Column( Enum(*consts.NEUTRON_L23_PROVIDERS, name='net_l23_provider'), nullable=False, default=consts.NEUTRON_L23_PROVIDERS.ovs )
class NetworkingConfig(Base): __tablename__ = 'networking_configs' id = Column(Integer, primary_key=True) discriminator = Column(String(50)) cluster_id = Column(Integer, ForeignKey('clusters.id', ondelete="CASCADE")) dns_nameservers = Column(MutableList.as_mutable(JSON), default=["8.8.4.4", "8.8.8.8"]) floating_ranges = Column(MutableList.as_mutable(JSON), default=[]) configuration_template = Column(MutableDict.as_mutable(JSON), nullable=True) __mapper_args__ = {'polymorphic_on': discriminator}
class Plugin(Base): __tablename__ = 'plugins' __table_args__ = (UniqueConstraint('name', 'version', name='_name_version_unique'), ) id = Column(Integer, primary_key=True) name = Column(String(100), nullable=False) title = Column(String(100), nullable=False) version = Column(String(32), nullable=False) description = Column(String(400)) releases = Column(MutableList.as_mutable(JSON), default=[]) fuel_version = Column(MutableList.as_mutable(JSON), default=[]) groups = Column(MutableList.as_mutable(JSON), server_default='[]', nullable=False) authors = Column(MutableList.as_mutable(JSON), server_default='[]', nullable=False) licenses = Column(MutableList.as_mutable(JSON), server_default='[]', nullable=False) homepage = Column(Text, nullable=True) package_version = Column(String(32), nullable=False) is_hotpluggable = Column(Boolean, default=False) attributes_metadata = Column(MutableDict.as_mutable(JSON), server_default='{}', nullable=False) volumes_metadata = Column(MutableDict.as_mutable(JSON), server_default='{}', nullable=False) roles_metadata = Column(MutableDict.as_mutable(JSON), server_default='{}', nullable=False) network_roles_metadata = Column(MutableList.as_mutable(JSON), server_default='[]', nullable=False) components_metadata = Column(MutableList.as_mutable(JSON), server_default='[]') deployment_tasks = Column(MutableList.as_mutable(JSON), server_default='[]', nullable=False) # TODO(apopovych): To support old plugins versions we need separate # tasks which runs directly during deployment(stored in `deployment_tasks` # attribute) and which executes before/after of deployment process # (also called pre/post deployment tasks and stored in `tasks` # attribute). In future `deployment_tasks` and `tasks` should have # one format and this attribute will be removed. tasks = Column(MutableList.as_mutable(JSON), server_default='[]', nullable=False) clusters = relationship("Cluster", secondary=ClusterPlugins.__table__, backref="plugins") links = relationship("PluginLink", backref="plugin", cascade="delete")
class NodeAttributes(Base): __tablename__ = 'node_attributes' id = Column(Integer, primary_key=True) node_id = Column(Integer, ForeignKey('nodes.id', ondelete='CASCADE')) interfaces = Column(MutableDict.as_mutable(JSON), default={}) vms_conf = Column(MutableList.as_mutable(JSON), default=[], server_default='[]')
class DeploymentSequence(Base): __tablename__ = 'deployment_sequences' __table_args__ = (sa.UniqueConstraint('release_id', 'name', name='_name_uc'), ) id = sa.Column(sa.Integer, primary_key=True) release_id = sa.Column(sa.Integer, sa.ForeignKey('releases.id'), nullable=False) name = sa.Column(sa.String(255), nullable=False) # contains graphs to execute graphs = sa.Column(MutableList.as_mutable(JSON), nullable=False)
class Cluster(Base): __tablename__ = 'clusters' id = Column(Integer, primary_key=True) #集群模式 mode = Column( Enum(*consts.CLUSTER_MODES, name='cluster_mode'), nullable=False, default=consts.CLUSTER_MODES.ha_compact ) status = Column( Enum(*consts.CLUSTER_STATUSES, name='cluster_status'), nullable=False, default=consts.CLUSTER_STATUSES.new ) net_provider = Column( Enum(*consts.CLUSTER_NET_PROVIDERS, name='net_provider'), nullable=False, default=consts.CLUSTER_NET_PROVIDERS.neutron ) network_config = relationship("NetworkingConfig", backref=backref("cluster"), cascade="all,delete", uselist=False) ui_settings = Column( MutableDict.as_mutable(JSON), nullable=False, server_default=jsonutils.dumps({ "view_mode": "standard", "filter": {}, "sort": [{"roles": "asc"}], "filter_by_labels": {}, "sort_by_labels": [], "search": "" }), ) name = Column(UnicodeText, unique=True, nullable=False) release_id = Column(Integer, ForeignKey('releases.id'), nullable=False) nodes = relationship( "Node", backref="cluster", cascade="delete", order_by='Node.id') tasks = relationship("Task", backref="cluster") plugin_links = relationship( "ClusterPluginLink", backref="cluster", cascade="delete") attributes = relationship("Attributes", uselist=False, backref="cluster", cascade="delete") changes_list = relationship("ClusterChanges", backref="cluster", cascade="delete") # We must keep all notifications even if cluster is removed. # It is because we want user to be able to see # the notification history so that is why we don't use # cascade="delete" in this relationship # During cluster deletion sqlalchemy engine will set null # into cluster foreign key column of notification entity notifications = relationship("Notification", backref="cluster") node_groups = relationship( "NodeGroup", backref="cluster", cascade="delete" ) replaced_deployment_info = Column( MutableDict.as_mutable(JSON), default={} ) replaced_provisioning_info = Column( MutableDict.as_mutable(JSON), default={}) is_customized = Column(Boolean, default=False) fuel_version = Column(Text, nullable=False) components = Column( MutableList.as_mutable(JSON), default=[], server_default='[]', nullable=False) extensions = Column(psql.ARRAY(String(consts.EXTENSION_NAME_MAX_SIZE)), default=[], nullable=False, server_default='{}') volumes_metadata = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}') roles_metadata = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}') tags_metadata = Column(MutableDict.as_mutable(JSON), server_default='{}', nullable=False) @property def changes(self): return [ {"name": i.name, "node_id": i.node_id} for i in self.changes_list ] @changes.setter def changes(self, value): self.changes_list = value @property def is_ha_mode(self): return self.mode in ('ha_full', 'ha_compact') @property def full_name(self): return '%s (id=%s, mode=%s)' % (self.name, self.id, self.mode) @property def is_locked(self): allowed_status = ( consts.CLUSTER_STATUSES.error, consts.CLUSTER_STATUSES.new, consts.CLUSTER_STATUSES.operational, consts.CLUSTER_STATUSES.stopped, consts.CLUSTER_STATUSES.partially_deployed ) return self.status not in allowed_status @property def network_groups(self): net_list = [] for ng in self.node_groups: net_list.extend(ng.networks) return net_list
class DeploymentGraphTask(Base): __tablename__ = 'deployment_graph_tasks' __table_args__ = ( sa.UniqueConstraint( 'deployment_graph_id', 'task_name', name='_task_name_deployment_graph_id_uc'), ) id = sa.Column( sa.Integer, primary_key=True) deployment_graph_id = sa.Column( sa.Integer, sa.ForeignKey('deployment_graphs.id', ondelete='CASCADE'), nullable=False) deployment_graph = sa.orm.relationship( 'DeploymentGraph', backref=sa.orm.backref("tasks", cascade="all, delete-orphan")) # not task_id because it could be perceived as fk # and not id because it is not unique inside table task_name = sa.Column( sa.String(255), index=True, nullable=False) version = sa.Column( sa.String(255), nullable=False, server_default='1.0.0', default='1.0.0') # this field may contain string or dict condition = sa.Column( JSON(), nullable=True) type = sa.Column( sa.Enum( *consts.ORCHESTRATOR_TASK_TYPES, name='deployment_graph_tasks_type'), nullable=False) groups = sa.Column( sa.dialects.postgresql.ARRAY(sa.String(255)), default=[], server_default='{}', nullable=False) tasks = sa.Column( sa.dialects.postgresql.ARRAY(sa.String(255)), default=[], server_default='{}', nullable=False) roles = sa.Column( # node roles sa.dialects.postgresql.ARRAY(sa.String(255)), default=[], server_default='{}', nullable=False) # list of Nailgun events on which this task should be re-executed reexecute_on = sa.Column( sa.dialects.postgresql.ARRAY(sa.String(255)), default=[], server_default='{}', nullable=False) refresh_on = sa.Column( # new in 8.0 sa.dialects.postgresql.ARRAY(sa.String(255)), default=[], server_default='{}', nullable=False) required_for = sa.Column( sa.dialects.postgresql.ARRAY(sa.String(255)), default=[], server_default='{}', nullable=False) requires = sa.Column( sa.dialects.postgresql.ARRAY(sa.String(255)), default=[], server_default='{}', nullable=False) # cross-depended-by with hypen is deprecated notation cross_depended_by = sa.Column( MutableList.as_mutable(JSON), default=[], server_default='[]') # cross-depends with hypen is deprecated notation cross_depends = sa.Column( MutableList.as_mutable(JSON), default=[], server_default='[]') parameters = sa.Column( MutableDict.as_mutable(JSON), default={}, server_default='{}') # custom field for all fields that does not fit into the schema _custom = sa.Column( MutableDict.as_mutable(JSON), default={}, server_default='{}')
class Node(Base): __tablename__ = 'nodes' __table_args__ = (UniqueConstraint('cluster_id', 'hostname', name='_hostname_cluster_uc'), ) id = Column(Integer, primary_key=True) uuid = Column(String(36), nullable=False, default=lambda: str(uuid.uuid4()), unique=True) cluster_id = Column(Integer, ForeignKey('clusters.id', ondelete='CASCADE')) group_id = Column(Integer, ForeignKey('nodegroups.id', ondelete='SET NULL'), nullable=True) name = Column(Unicode(100)) status = Column(Enum(*consts.NODE_STATUSES, name='node_status'), nullable=False, default=consts.NODE_STATUSES.discover) meta = Column(MutableDict.as_mutable(JSON), default={}) mac = Column(psql.MACADDR, nullable=False, unique=True) ip = Column(psql.INET) hostname = Column(String(255), nullable=False, default="", server_default="") manufacturer = Column(Unicode(50)) platform_name = Column(String(150)) kernel_params = Column(Text) progress = Column(Integer, default=0) os_platform = Column(String(150)) pending_addition = Column(Boolean, default=False) pending_deletion = Column(Boolean, default=False) changes = relationship("ClusterChanges", backref="node") error_type = Column(String(100)) error_msg = Column(Text) timestamp = Column(DateTime, nullable=False) online = Column(Boolean, default=True) labels = Column(MutableDict.as_mutable(JSON), nullable=False, server_default='{}') tags = relationship('NodeTag', cascade='delete, delete-orphan') roles = Column(psql.ARRAY(String(consts.ROLE_NAME_MAX_SIZE)), default=[], nullable=False, server_default='{}') pending_roles = Column(psql.ARRAY(String(consts.ROLE_NAME_MAX_SIZE)), default=[], nullable=False, server_default='{}') primary_roles = Column(psql.ARRAY(String(consts.ROLE_NAME_MAX_SIZE)), default=[], nullable=False, server_default='{}') nic_interfaces = relationship("NodeNICInterface", backref="node", cascade="all, delete-orphan", order_by="NodeNICInterface.name") bond_interfaces = relationship("NodeBondInterface", backref="node", cascade="all, delete-orphan", order_by="NodeBondInterface.name") # hash function from raw node agent request data - for caching purposes agent_checksum = Column(String(40), nullable=True) ip_addrs = relationship("IPAddr", viewonly=True) replaced_deployment_info = Column(MutableList.as_mutable(JSON), default=[]) replaced_provisioning_info = Column(MutableDict.as_mutable(JSON), default={}) network_template = Column(MutableDict.as_mutable(JSON), default=None, server_default=None, nullable=True) extensions = Column(psql.ARRAY(String(consts.EXTENSION_NAME_MAX_SIZE)), default=[], nullable=False, server_default='{}') vms_conf = Column(MutableList.as_mutable(JSON), default=[], server_default='[]', nullable=False) attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) @property def interfaces(self): return self.nic_interfaces + self.bond_interfaces @property def uid(self): return str(self.id) @property def offline(self): return not self.online @property def network_data(self): # TODO(enchantner): move to object from nailgun.extensions.network_manager.manager import NetworkManager return NetworkManager.get_node_networks(self) @property def needs_reprovision(self): return self.status == 'error' and self.error_type == 'provision' and \ not self.pending_deletion @property def needs_redeploy(self): return (self.status in [ consts.NODE_STATUSES.error, consts.NODE_STATUSES.provisioned, consts.NODE_STATUSES.stopped ] or len(self.pending_roles)) and not self.pending_deletion @property def needs_redeletion(self): return self.status == 'error' and self.error_type == 'deletion' @property def human_readable_name(self): return self.name or self.mac @property def full_name(self): return u'%s (id=%s, mac=%s)' % (self.name, self.id, self.mac) @property def tag_names(self): return (t.tag.tag for t in self.tags) @property def all_roles(self): """Returns all roles, self.roles and self.pending_roles.""" return set(self.pending_roles + self.roles) def _check_interface_has_required_params(self, iface): return bool(iface.get('name') and iface.get('mac')) def _clean_iface(self, iface): # cleaning up unnecessary fields - set to None if bad for param in ["max_speed", "current_speed"]: val = iface.get(param) if not (isinstance(val, int) and val >= 0): val = None iface[param] = val return iface def update_meta(self, data): # helper for basic checking meta before updation result = [] if "interfaces" in data: for iface in data["interfaces"]: if not self._check_interface_has_required_params(iface): logger.warning("Invalid interface data: {0}. " "Interfaces are not updated.".format(iface)) data["interfaces"] = self.meta.get("interfaces") self.meta = data return result.append(self._clean_iface(iface)) data["interfaces"] = result self.meta = data def create_meta(self, data): # helper for basic checking meta before creation result = [] if "interfaces" in data: for iface in data["interfaces"]: if not self._check_interface_has_required_params(iface): logger.warning("Invalid interface data: {0}. " "Skipping interface.".format(iface)) continue result.append(self._clean_iface(iface)) data["interfaces"] = result self.meta = data
class NodeNICInterface(Base): __tablename__ = 'node_nic_interfaces' id = Column(Integer, primary_key=True) node_id = Column(Integer, ForeignKey('nodes.id', ondelete="CASCADE"), nullable=False) name = Column(String(128), nullable=False) mac = Column(psql.MACADDR, nullable=False) max_speed = Column(Integer) current_speed = Column(Integer) assigned_networks_list = relationship( "NetworkGroup", secondary=NetworkNICAssignment.__table__, order_by="NetworkGroup.id") ip_addr = Column(psql.INET) netmask = Column(psql.INET) state = Column(String(25)) interface_properties = Column(MutableDict.as_mutable(JSON), default={}, nullable=False, server_default='{}') parent_id = Column(Integer, ForeignKey('node_bond_interfaces.id')) driver = Column(Text) bus_info = Column(Text) pxe = Column(Boolean, default=False, nullable=False) offloading_modes = Column(MutableList.as_mutable(JSON), default=[], nullable=False, server_default='[]') @property def type(self): return consts.NETWORK_INTERFACE_TYPES.ether @property def assigned_networks(self): return [{ "id": n.id, "name": n.name } for n in self.assigned_networks_list] @assigned_networks.setter def assigned_networks(self, value): self.assigned_networks_list = value # TODO(fzhadaev): move to object @classmethod def offloading_modes_as_flat_dict(cls, modes): """Represents multilevel structure of offloading modes as flat dict This is done to ease merging :param modes: list of offloading modes :return: flat dictionary {mode['name']: mode['state']} """ result = dict() if modes is None: return result for mode in modes: result[mode["name"]] = mode["state"] if mode["sub"]: result.update(cls.offloading_modes_as_flat_dict(mode["sub"])) return result
class Task(Base): __tablename__ = 'tasks' __table_args__ = (Index('cluster_name_idx', 'cluster_id', 'name'), ) id = Column(Integer, primary_key=True) cluster_id = Column(Integer, ForeignKey('clusters.id', ondelete='CASCADE')) uuid = Column(String(36), nullable=False, default=lambda: str(uuid.uuid4())) name = Column(Enum(*consts.TASK_NAMES, name='task_name'), nullable=False, default='super') message = Column(Text) status = Column(Enum(*consts.TASK_STATUSES, name='task_status'), nullable=False, default='running') progress = Column(Integer, default=0) cache = deferred(Column(MutableDict.as_mutable(JSON), default={})) # By design 'result' value accept dict and list types # depends on task type. Don't do this field MutableDict. result = Column(JSON, default={}) parent_id = Column(Integer, ForeignKey('tasks.id', ondelete='CASCADE')) subtasks = relationship("Task", backref=backref('parent', remote_side=[id]), cascade="all,delete", order_by='Task.id') notifications = relationship("Notification", backref=backref('task', remote_side=[id])) # Task weight is used to calculate supertask progress # sum([t.progress * t.weight for t in supertask.subtasks]) / # sum([t.weight for t in supertask.subtasks]) weight = Column(Float, default=1.0) deleted_at = Column(DateTime) dry_run = Column(Boolean(), nullable=False, default=False, server_default='false') graph_type = Column(String(255)) deployment_info = deferred( Column(MutableDict.as_mutable(JSON), nullable=True)) cluster_settings = deferred( Column(MutableDict.as_mutable(JSON), nullable=True)) network_settings = deferred( Column(MutableDict.as_mutable(JSON), nullable=True)) tasks_snapshot = deferred( Column(MutableList.as_mutable(JSON), nullable=True)) deployment_history = relationship("DeploymentHistory", backref="task", cascade="all,delete") time_start = Column(TIMESTAMP(), nullable=True) time_end = Column(TIMESTAMP(), nullable=True) def __repr__(self): return "<Task '{0}' {1} ({2}) {3}>".format(self.name, self.uuid, self.cluster_id, self.status) def create_subtask(self, name, **kwargs): if not name: raise ValueError("Subtask name not specified") task = Task(name=name, cluster=self.cluster, **kwargs) self.subtasks.append(task) db().flush() return task def is_completed(self): return self.status == consts.TASK_STATUSES.error or \ self.status == consts.TASK_STATUSES.ready
class Release(Base): __tablename__ = 'releases' __table_args__ = (UniqueConstraint('name', 'version'), ) id = Column(Integer, primary_key=True) name = Column(Unicode(100), nullable=False) version = Column(String(30), nullable=False) description = Column(Unicode) operating_system = Column(String(50), nullable=False) state = Column(Enum(*consts.RELEASE_STATES, name='release_state'), nullable=False, default=consts.RELEASE_STATES.unavailable) networks_metadata = Column(MutableDict.as_mutable(JSON), default={}) attributes_metadata = Column(MutableDict.as_mutable(JSON), default={}) volumes_metadata = Column(MutableDict.as_mutable(JSON), default={}) modes_metadata = Column(MutableDict.as_mutable(JSON), default={}) roles_metadata = Column(MutableDict.as_mutable(JSON), default={}) tags_metadata = Column(MutableDict.as_mutable(JSON), default={}) network_roles_metadata = Column(MutableList.as_mutable(JSON), default=[], server_default='[]') vmware_attributes_metadata = Column(MutableDict.as_mutable(JSON), default={}) components_metadata = Column(MutableList.as_mutable(JSON), default=[], server_default='[]') required_component_types = Column(MutableList.as_mutable(JSON), default=[], server_default='[]', nullable=False) modes = Column(MutableList.as_mutable(JSON), default=[]) clusters = relationship("Cluster", primaryjoin="Release.id==Cluster.release_id", backref="release", cascade="all,delete") extensions = Column(psql.ARRAY(String(consts.EXTENSION_NAME_MAX_SIZE)), default=[], nullable=False, server_default='{}') node_attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) nic_attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) bond_attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) # TODO(enchantner): get rid of properties @property def openstack_version(self): return self.version.split('-')[0] @property def environment_version(self): """Returns environment version based on release version. A release version consists of 'OSt' and 'MOS' versions: '2014.1.1-5.0.2' so we need to extract 'MOS' version and returns it as result. :returns: an environment version """ # unfortunately, Fuel 5.0 didn't have an env version in release_version # so we need to handle that special case if self.version == '2014.1': version = '5.0' else: try: version = self.version.split('-')[1] except IndexError: version = '' return version @property def os_weight(self): try: weight = consts.RELEASE_OS[::-1].index(self.operating_system) except ValueError: weight = -1 return weight def __cmp__(self, other): """Allows to compare two releases :other: an instance of nailgun.db.sqlalchemy.models.release.Release """ if self.environment_version < other.environment_version: return -1 if self.environment_version > other.environment_version: return 1 if self.openstack_version < other.openstack_version: return -1 if self.openstack_version > other.openstack_version: return 1 if self.os_weight == other.os_weight == -1: if self.operating_system > other.operating_system: return -1 if self.operating_system < other.operating_system: return 1 else: if self.os_weight < other.os_weight: return -1 if self.os_weight > other.os_weight: return 1 return 0