class NodeBondInterface(Base): __tablename__ = 'node_bond_interfaces' id = Column(Integer, primary_key=True) node_id = Column(Integer, ForeignKey('nodes.id', ondelete="CASCADE"), nullable=False) name = Column(String(32), nullable=False) mac = Column(psql.MACADDR) assigned_networks_list = relationship( "NetworkGroup", secondary=NetworkBondAssignment.__table__, order_by="NetworkGroup.id") state = Column(String(25)) interface_properties = Column(MutableDict.as_mutable(JSON), default={}, nullable=False, server_default='{}') mode = Column(Enum(*consts.BOND_MODES, name='bond_mode'), nullable=False, default=consts.BOND_MODES.active_backup) bond_properties = Column(MutableDict.as_mutable(JSON), default={}, nullable=False, server_default='{}') slaves = relationship("NodeNICInterface", backref="bond") attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) @property def max_speed(self): return None @property def current_speed(self): return None @property def type(self): return consts.NETWORK_INTERFACE_TYPES.bond @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
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='[]') # 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. # Will be deprecated since plugins v5 # (ikutukov) tasks yaml will stay here till fuel EOL to support upgrades # with old environments and old plugins. 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 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', ondelete='SET NULL')) driver = Column(Text) bus_info = Column(Text) pxe = Column(Boolean, default=False, nullable=False) attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) meta = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) @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
class ClusterPlugin(Base): __tablename__ = 'cluster_plugins' id = Column(Integer, primary_key=True) plugin_id = Column(Integer, ForeignKey('plugins.id', ondelete='CASCADE'), nullable=False) cluster_id = Column(Integer, ForeignKey('clusters.id', ondelete='CASCADE'), nullable=False) enabled = Column(Boolean, nullable=False, default=False, server_default='false') # Initially, 'attributes' is a copy of 'Plugin.attributes_metadata'. # We need this column in order to store in there the modified (by user) # version of attributes, because we don't want to store them in cluster # attributes with no chance to remove. attributes = Column(MutableDict.as_mutable(JSON), nullable=False, server_default='{}') cluster = relationship("Cluster", backref=backref("cluster_plugins", cascade="delete")) plugin = relationship("Plugin", backref=backref("cluster_plugins", cascade="delete"))
class NodeBondInterfaceClusterPlugin(Base): """Operates with Bond data from plugins Example (fetch plugin data for specific bond): db().query( NodeBondInterfaceClusterPlugin ).join( models.ClusterPlugin, ).filter( NodeBondInterfaceClusterPlugin.bond_id == bond_id ).filter( models.ClusterPlugin.enabled.is_(True)).all() """ __tablename__ = 'node_bond_interface_cluster_plugins' id = Column(Integer, primary_key=True) cluster_plugin_id = Column(Integer, ForeignKey('cluster_plugins.id', ondelete='CASCADE'), nullable=False) bond_id = Column(Integer, ForeignKey('node_bond_interfaces.id', ondelete='CASCADE'), nullable=False) node_id = Column(Integer, ForeignKey('nodes.id', ondelete='CASCADE'), nullable=False) attributes = Column(MutableDict.as_mutable(JSON), nullable=False, server_default='{}') node = relationship("Node", backref=backref("node_bond_interface_cluster_plugins", cascade="delete"))
class MasterNodeSettings(Base): __tablename__ = 'master_node_settings' id = Column(Integer, primary_key=True) master_node_uid = Column(String(36), nullable=False) settings = Column(MutableDict.as_mutable(JSON), nullable=False, default={}, server_default='{}')
class DeploymentGraph(Base): __tablename__ = 'deployment_graphs' id = sa.Column(sa.Integer, primary_key=True) name = sa.Column( # not planned to used in business logic and # added to make work with custom graphs convenient sa.String(255), nullable=True) # contains YAQL expression to select nodes for applying the graph node_filter = sa.Column(sa.String(4096), nullable=True) # contains attributes which will be applied to appropriate object # if execution of graph completes successfully. on_success = sa.Column(MutableDict.as_mutable(JSON), nullable=True) # contains attributes which will be applied to appropriate object # if execution of graph fails. on_error = sa.Column(MutableDict.as_mutable(JSON), nullable=True) # contains attributes which will be applied to appropriate object # if execution of graph interrupts. on_stop = sa.Column(MutableDict.as_mutable(JSON), nullable=True)
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 DeploymentHistory(Base): __tablename__ = 'deployment_history' __table_args__ = ( sa.Index('deployment_history_task_id_and_status', 'task_id', 'status'), sa.Index('deployment_history_task_name_status_idx', 'deployment_graph_task_name', 'status'), sa.UniqueConstraint( 'task_id', 'node_id', 'deployment_graph_task_name', name='_task_id_node_id_deployment_graph_task_name_uc'), ) id = sa.Column(sa.Integer, primary_key=True) task_id = sa.Column(sa.Integer, sa.ForeignKey('tasks.id', ondelete='CASCADE'), nullable=False) deployment_graph_task_name = sa.Column(sa.String, nullable=False) # String, because history need to be saved tasks for master and None nodes node_id = sa.Column(sa.String) time_start = sa.Column(sa.DateTime, nullable=True) time_end = sa.Column(sa.DateTime, nullable=True) status = sa.Column(sa.Enum(*consts.HISTORY_TASK_STATUSES, name='history_task_statuses'), nullable=False, default=consts.HISTORY_TASK_STATUSES.pending) custom = sa.Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) summary = deferred( sa.Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=True))
class OpenStackWorkloadStats(Base): __tablename__ = 'oswl_stats' __table_args__ = (UniqueConstraint('cluster_id', 'created_date', 'resource_type'), ) id = Column(Integer, primary_key=True) cluster_id = Column(Integer, nullable=False, index=True) created_date = Column(Date, nullable=False, index=True) updated_time = Column(Time, nullable=False) resource_type = Column(Enum(*consts.OSWL_RESOURCE_TYPES, name='oswl_resource_type'), nullable=False, index=True) resource_data = Column(MutableDict.as_mutable(JSON), nullable=True) resource_checksum = Column(Text, nullable=False) is_sent = Column(Boolean, nullable=False, default=False, index=True) version_info = Column(MutableDict.as_mutable(postgresql.JSON), nullable=True, default={}, server_default='{}')
class ActionLog(Base): __tablename__ = 'action_logs' id = Column(Integer, primary_key=True) actor_id = Column(String(64), nullable=True) action_group = Column(String(64), nullable=False) action_name = Column(String(64), nullable=False) action_type = Column(Enum(*consts.ACTION_TYPES, name='action_type'), nullable=False) start_timestamp = Column(DateTime, nullable=False) end_timestamp = Column(DateTime, nullable=True) is_sent = Column(Boolean, default=False) additional_info = Column(MutableDict.as_mutable(JSON), default={}, nullable=False) cluster_id = Column(Integer, nullable=True) task_uuid = Column(String(36), nullable=True)
class NodeClusterPlugin(Base): __tablename__ = 'node_cluster_plugins' id = Column(Integer, primary_key=True) cluster_plugin_id = Column(Integer, ForeignKey('cluster_plugins.id', ondelete='CASCADE'), nullable=False) node_id = Column(Integer, ForeignKey('nodes.id', ondelete='CASCADE'), nullable=False) attributes = Column(MutableDict.as_mutable(JSON), nullable=False, server_default='{}') node = relationship("Node", backref=backref("node_cluster_plugins", cascade="delete"))
class NetworkGroup(Base): __tablename__ = 'network_groups' id = Column(Integer, primary_key=True) name = Column(String(50), nullable=False) # can be nullable only for fuelweb admin net release = Column(Integer, ForeignKey('releases.id', ondelete='CASCADE')) # can be nullable only for fuelweb admin net group_id = Column(Integer, ForeignKey('nodegroups.id', ondelete='CASCADE'), nullable=True) vlan_start = Column(Integer) cidr = Column(psql.CIDR) gateway = Column(psql.INET) ip_ranges = relationship("IPAddrRange", backref="network_group", cascade="all, delete, delete-orphan") nodes = relationship("Node", secondary=IPAddr.__table__, backref="networks", passive_deletes=True) meta = Column(MutableDict.as_mutable(JSON), default={})
class OpenstackConfig(Base): __tablename__ = 'openstack_configs' id = Column(Integer, primary_key=True) is_active = Column(Boolean, nullable=False, default=True) config_type = Column(Enum(*consts.OPENSTACK_CONFIG_TYPES, name='openstack_config_types'), nullable=False) # asaprykin: In case there will be global configuration # nullable should be set to 'True' cluster_id = Column(Integer, ForeignKey('clusters.id', ondelete='CASCADE'), nullable=False) node_id = Column(Integer, ForeignKey('nodes.id', ondelete='SET NULL'), nullable=True) node_role = Column(String(consts.ROLE_NAME_MAX_SIZE), nullable=True) created_at = Column(DateTime, nullable=False, default=datetime.now) configuration = Column(MutableDict.as_mutable(JSON), nullable=False, default={}, server_default='{}')
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 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
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 Attributes(Base): __tablename__ = 'attributes' id = Column(Integer, primary_key=True) cluster_id = Column(Integer, ForeignKey('clusters.id', ondelete='CASCADE')) editable = Column(MutableDict.as_mutable(JSON)) generated = Column(MutableDict.as_mutable(JSON))
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 CapacityLog(Base): __tablename__ = 'capacity_log' id = Column(Integer, primary_key=True) report = Column(MutableDict.as_mutable(JSON)) datetime = Column(DateTime, default=lambda: datetime.now())
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', ondelete='SET NULL')) 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='[]') attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) meta = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) @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 NodeBondInterface(Base): __tablename__ = 'node_bond_interfaces' id = Column(Integer, primary_key=True) node_id = Column(Integer, ForeignKey('nodes.id', ondelete="CASCADE"), nullable=False) name = Column(String(32), nullable=False) mac = Column(psql.MACADDR) assigned_networks_list = relationship( "NetworkGroup", secondary=NetworkBondAssignment.__table__, order_by="NetworkGroup.id") state = Column(String(25)) interface_properties = Column(MutableDict.as_mutable(JSON), default={}, nullable=False, server_default='{}') mode = Column(Enum(*consts.BOND_MODES, name='bond_mode'), nullable=False, default=consts.BOND_MODES.active_backup) bond_properties = Column(MutableDict.as_mutable(JSON), default={}, nullable=False, server_default='{}') slaves = relationship("NodeNICInterface", backref="bond") attributes = Column(MutableDict.as_mutable(JSON), default={}, server_default='{}', nullable=False) @property def max_speed(self): return None @property def current_speed(self): return None @property def type(self): return consts.NETWORK_INTERFACE_TYPES.bond @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 @property def offloading_modes(self): tmp = None intersection_dict = {} for interface in self.slaves: modes = interface.offloading_modes if tmp is None: tmp = modes intersection_dict = \ interface.offloading_modes_as_flat_dict(tmp) continue intersection_dict = self._intersect_offloading_dicts( intersection_dict, interface.offloading_modes_as_flat_dict(modes)) return self._apply_intersection(tmp, intersection_dict) @offloading_modes.setter def offloading_modes(self, new_modes): new_modes_dict = \ NodeNICInterface.offloading_modes_as_flat_dict(new_modes) for interface in self.slaves: self._update_modes(interface.offloading_modes, new_modes_dict) interface.offloading_modes.changed() def _update_modes(self, modes, update_dict): for mode in modes: if mode['name'] in update_dict: mode['state'] = update_dict[mode['name']] if mode['sub']: self._update_modes(mode['sub'], update_dict) def _intersect_offloading_dicts(self, dict1, dict2): result = dict() for mode in dict1: if mode in dict2: result[mode] = dict1[mode] and dict2[mode] return result def _apply_intersection(self, modes, intersection_dict): result = list() if modes is None: return result for mode in copy.deepcopy(modes): if mode["name"] not in intersection_dict: continue mode["state"] = intersection_dict[mode["name"]] if mode["sub"]: mode["sub"] = \ self._apply_intersection(mode["sub"], intersection_dict) result.append(mode) 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") 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) 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)) deployment_history = relationship("DeploymentHistory", backref="task", cascade="all,delete") 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