
A new dictionary field `network_data` is added to the node object. This new field can be populated by the operator with node static network configuration. Ironic API now performs formal JSON document validation of node `network_data` field content against user-supplied JSON schema at driver validation step. As of this commit, the new `network_data` field is not actually used by ironic, otherwise it should be perfectly functional. In the following commits, network static configuration will be taken from this field and handed over to ironic ramdisk bootstrapping utilities. Change-Id: I868b3b56a17f59e5aa1494b2e0ebc9c4e34ef173 Story: 2006691 Task: 36991
412 lines
15 KiB
Python
412 lines
15 KiB
Python
# -*- encoding: utf-8 -*-
|
|
#
|
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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.
|
|
|
|
"""
|
|
SQLAlchemy models for baremetal data.
|
|
"""
|
|
|
|
from os import path
|
|
from urllib import parse as urlparse
|
|
|
|
from oslo_db import options as db_options
|
|
from oslo_db.sqlalchemy import models
|
|
from oslo_db.sqlalchemy import types as db_types
|
|
from sqlalchemy import Boolean, Column, DateTime, false, Index
|
|
from sqlalchemy import ForeignKey, Integer
|
|
from sqlalchemy import schema, String, Text
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy import orm
|
|
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _
|
|
from ironic.conf import CONF
|
|
|
|
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + path.join('$state_path',
|
|
'ironic.sqlite')
|
|
|
|
|
|
db_options.set_defaults(CONF, connection=_DEFAULT_SQL_CONNECTION)
|
|
|
|
|
|
def table_args():
|
|
engine_name = urlparse.urlparse(CONF.database.connection).scheme
|
|
if engine_name == 'mysql':
|
|
return {'mysql_engine': CONF.database.mysql_engine,
|
|
'mysql_charset': "utf8"}
|
|
return None
|
|
|
|
|
|
class IronicBase(models.TimestampMixin,
|
|
models.ModelBase):
|
|
|
|
metadata = None
|
|
|
|
version = Column(String(15), nullable=True)
|
|
|
|
def as_dict(self):
|
|
d = {}
|
|
for c in self.__table__.columns:
|
|
d[c.name] = self[c.name]
|
|
return d
|
|
|
|
|
|
Base = declarative_base(cls=IronicBase)
|
|
|
|
|
|
class Chassis(Base):
|
|
"""Represents a hardware chassis."""
|
|
|
|
__tablename__ = 'chassis'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('uuid', name='uniq_chassis0uuid'),
|
|
table_args()
|
|
)
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36))
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
description = Column(String(255), nullable=True)
|
|
|
|
|
|
class Conductor(Base):
|
|
"""Represents a conductor service entry."""
|
|
|
|
__tablename__ = 'conductors'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('hostname', name='uniq_conductors0hostname'),
|
|
table_args()
|
|
)
|
|
id = Column(Integer, primary_key=True)
|
|
hostname = Column(String(255), nullable=False)
|
|
drivers = Column(db_types.JsonEncodedList)
|
|
online = Column(Boolean, default=True)
|
|
conductor_group = Column(String(255), nullable=False, default='',
|
|
server_default='')
|
|
|
|
|
|
class ConductorHardwareInterfaces(Base):
|
|
"""Internal table used to track what is loaded on each conductor."""
|
|
|
|
__tablename__ = 'conductor_hardware_interfaces'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint(
|
|
'conductor_id',
|
|
'hardware_type',
|
|
'interface_type',
|
|
'interface_name',
|
|
name='uniq_conductorhardwareinterfaces0'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
conductor_id = Column(Integer, ForeignKey('conductors.id'), nullable=False)
|
|
hardware_type = Column(String(255), nullable=False)
|
|
interface_type = Column(String(16), nullable=False)
|
|
interface_name = Column(String(255), nullable=False)
|
|
default = Column(Boolean, default=False, nullable=False)
|
|
|
|
|
|
class Node(Base):
|
|
"""Represents a bare metal node."""
|
|
|
|
__tablename__ = 'nodes'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('uuid', name='uniq_nodes0uuid'),
|
|
schema.UniqueConstraint('instance_uuid',
|
|
name='uniq_nodes0instance_uuid'),
|
|
schema.UniqueConstraint('name', name='uniq_nodes0name'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36))
|
|
# NOTE(tenbrae): we store instance_uuid directly on the node so that we can
|
|
# filter on it more efficiently, even though it is
|
|
# user-settable, and would otherwise be in node.properties.
|
|
instance_uuid = Column(String(36), nullable=True)
|
|
name = Column(String(255), nullable=True)
|
|
chassis_id = Column(Integer, ForeignKey('chassis.id'), nullable=True)
|
|
power_state = Column(String(15), nullable=True)
|
|
target_power_state = Column(String(15), nullable=True)
|
|
provision_state = Column(String(15), nullable=True)
|
|
target_provision_state = Column(String(15), nullable=True)
|
|
provision_updated_at = Column(DateTime, nullable=True)
|
|
last_error = Column(Text, nullable=True)
|
|
instance_info = Column(db_types.JsonEncodedDict(mysql_as_long=True))
|
|
properties = Column(db_types.JsonEncodedDict)
|
|
driver = Column(String(255))
|
|
driver_info = Column(db_types.JsonEncodedDict)
|
|
driver_internal_info = Column(db_types.JsonEncodedDict)
|
|
clean_step = Column(db_types.JsonEncodedDict)
|
|
deploy_step = Column(db_types.JsonEncodedDict)
|
|
resource_class = Column(String(80), nullable=True)
|
|
|
|
raid_config = Column(db_types.JsonEncodedDict)
|
|
target_raid_config = Column(db_types.JsonEncodedDict)
|
|
|
|
# NOTE(tenbrae): this is the host name of the conductor which has
|
|
# acquired a TaskManager lock on the node.
|
|
# We should use an INT FK (conductors.id) in the future.
|
|
reservation = Column(String(255), nullable=True)
|
|
|
|
# NOTE(tenbrae): this is the id of the last conductor which prepared local
|
|
# state for the node (eg, a PXE config file).
|
|
# When affinity and the hash ring's mapping do not match,
|
|
# this indicates that a conductor should rebuild local state.
|
|
conductor_affinity = Column(Integer,
|
|
ForeignKey('conductors.id',
|
|
name='nodes_conductor_affinity_fk'),
|
|
nullable=True)
|
|
conductor_group = Column(String(255), nullable=False, default='',
|
|
server_default='')
|
|
|
|
maintenance = Column(Boolean, default=False)
|
|
maintenance_reason = Column(Text, nullable=True)
|
|
fault = Column(String(255), nullable=True)
|
|
console_enabled = Column(Boolean, default=False)
|
|
inspection_finished_at = Column(DateTime, nullable=True)
|
|
inspection_started_at = Column(DateTime, nullable=True)
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
automated_clean = Column(Boolean, nullable=True)
|
|
protected = Column(Boolean, nullable=False, default=False,
|
|
server_default=false())
|
|
protected_reason = Column(Text, nullable=True)
|
|
owner = Column(String(255), nullable=True)
|
|
lessee = Column(String(255), nullable=True)
|
|
allocation_id = Column(Integer, ForeignKey('allocations.id'),
|
|
nullable=True)
|
|
description = Column(Text, nullable=True)
|
|
|
|
bios_interface = Column(String(255), nullable=True)
|
|
boot_interface = Column(String(255), nullable=True)
|
|
console_interface = Column(String(255), nullable=True)
|
|
deploy_interface = Column(String(255), nullable=True)
|
|
inspect_interface = Column(String(255), nullable=True)
|
|
management_interface = Column(String(255), nullable=True)
|
|
network_interface = Column(String(255), nullable=True)
|
|
raid_interface = Column(String(255), nullable=True)
|
|
rescue_interface = Column(String(255), nullable=True)
|
|
retired = Column(Boolean, nullable=True, default=False,
|
|
server_default=false())
|
|
retired_reason = Column(Text, nullable=True)
|
|
network_data = Column(db_types.JsonEncodedDict)
|
|
storage_interface = Column(String(255), nullable=True)
|
|
power_interface = Column(String(255), nullable=True)
|
|
vendor_interface = Column(String(255), nullable=True)
|
|
|
|
|
|
class Port(Base):
|
|
"""Represents a network port of a bare metal node."""
|
|
|
|
__tablename__ = 'ports'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('address', name='uniq_ports0address'),
|
|
schema.UniqueConstraint('uuid', name='uniq_ports0uuid'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36))
|
|
address = Column(String(18))
|
|
node_id = Column(Integer, ForeignKey('nodes.id'), nullable=True)
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
local_link_connection = Column(db_types.JsonEncodedDict)
|
|
portgroup_id = Column(Integer, ForeignKey('portgroups.id'), nullable=True)
|
|
pxe_enabled = Column(Boolean, default=True)
|
|
internal_info = Column(db_types.JsonEncodedDict)
|
|
physical_network = Column(String(64), nullable=True)
|
|
is_smartnic = Column(Boolean, nullable=True, default=False)
|
|
|
|
|
|
class Portgroup(Base):
|
|
"""Represents a group of network ports of a bare metal node."""
|
|
|
|
__tablename__ = 'portgroups'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('uuid', name='uniq_portgroups0uuid'),
|
|
schema.UniqueConstraint('address', name='uniq_portgroups0address'),
|
|
schema.UniqueConstraint('name', name='uniq_portgroups0name'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36))
|
|
name = Column(String(255), nullable=True)
|
|
node_id = Column(Integer, ForeignKey('nodes.id'), nullable=True)
|
|
address = Column(String(18))
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
internal_info = Column(db_types.JsonEncodedDict)
|
|
standalone_ports_supported = Column(Boolean, default=True)
|
|
mode = Column(String(255))
|
|
properties = Column(db_types.JsonEncodedDict)
|
|
|
|
|
|
class NodeTag(Base):
|
|
"""Represents a tag of a bare metal node."""
|
|
|
|
__tablename__ = 'node_tags'
|
|
__table_args__ = (
|
|
Index('node_tags_idx', 'tag'),
|
|
table_args())
|
|
node_id = Column(Integer, ForeignKey('nodes.id'),
|
|
primary_key=True, nullable=False)
|
|
tag = Column(String(255), primary_key=True, nullable=False)
|
|
|
|
node = orm.relationship(
|
|
"Node",
|
|
backref='tags',
|
|
primaryjoin='and_(NodeTag.node_id == Node.id)',
|
|
foreign_keys=node_id
|
|
)
|
|
|
|
|
|
class VolumeConnector(Base):
|
|
"""Represents a volume connector of a bare metal node."""
|
|
|
|
__tablename__ = 'volume_connectors'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('uuid', name='uniq_volumeconnectors0uuid'),
|
|
schema.UniqueConstraint(
|
|
'type',
|
|
'connector_id',
|
|
name='uniq_volumeconnectors0type0connector_id'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36))
|
|
node_id = Column(Integer, ForeignKey('nodes.id'), nullable=True)
|
|
type = Column(String(32))
|
|
connector_id = Column(String(255))
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
|
|
|
|
class VolumeTarget(Base):
|
|
"""Represents a volume target of a bare metal node."""
|
|
|
|
__tablename__ = 'volume_targets'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('uuid', name='uniq_volumetargets0uuid'),
|
|
schema.UniqueConstraint('node_id',
|
|
'boot_index',
|
|
name='uniq_volumetargets0node_id0boot_index'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36))
|
|
node_id = Column(Integer, ForeignKey('nodes.id'), nullable=True)
|
|
volume_type = Column(String(64))
|
|
properties = Column(db_types.JsonEncodedDict)
|
|
boot_index = Column(Integer)
|
|
volume_id = Column(String(36))
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
|
|
|
|
class NodeTrait(Base):
|
|
"""Represents a trait of a bare metal node."""
|
|
|
|
__tablename__ = 'node_traits'
|
|
__table_args__ = (
|
|
Index('node_traits_idx', 'trait'),
|
|
table_args())
|
|
node_id = Column(Integer, ForeignKey('nodes.id'),
|
|
primary_key=True, nullable=False)
|
|
trait = Column(String(255), primary_key=True, nullable=False)
|
|
node = orm.relationship(
|
|
"Node",
|
|
backref='traits',
|
|
primaryjoin='and_(NodeTrait.node_id == Node.id)',
|
|
foreign_keys=node_id
|
|
)
|
|
|
|
|
|
class BIOSSetting(Base):
|
|
"""Represents a bios setting of a bare metal node."""
|
|
|
|
__tablename__ = 'bios_settings'
|
|
__table_args__ = (table_args())
|
|
node_id = Column(Integer, ForeignKey('nodes.id'),
|
|
primary_key=True, nullable=False)
|
|
name = Column(String(255), primary_key=True, nullable=False)
|
|
value = Column(Text, nullable=True)
|
|
|
|
|
|
class Allocation(Base):
|
|
"""Represents an allocation of a node for deployment."""
|
|
|
|
__tablename__ = 'allocations'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', name='uniq_allocations0name'),
|
|
schema.UniqueConstraint('uuid', name='uniq_allocations0uuid'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36), nullable=False)
|
|
name = Column(String(255), nullable=True)
|
|
node_id = Column(Integer, ForeignKey('nodes.id'), nullable=True)
|
|
state = Column(String(15), nullable=False)
|
|
owner = Column(String(255), nullable=True)
|
|
last_error = Column(Text, nullable=True)
|
|
resource_class = Column(String(80), nullable=True)
|
|
traits = Column(db_types.JsonEncodedList)
|
|
candidate_nodes = Column(db_types.JsonEncodedList)
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
# The last conductor to handle this allocation (internal field).
|
|
conductor_affinity = Column(Integer, ForeignKey('conductors.id'),
|
|
nullable=True)
|
|
|
|
|
|
class DeployTemplate(Base):
|
|
"""Represents a deployment template."""
|
|
|
|
__tablename__ = 'deploy_templates'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('uuid', name='uniq_deploytemplates0uuid'),
|
|
schema.UniqueConstraint('name', name='uniq_deploytemplates0name'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
uuid = Column(String(36))
|
|
name = Column(String(255), nullable=False)
|
|
extra = Column(db_types.JsonEncodedDict)
|
|
|
|
|
|
class DeployTemplateStep(Base):
|
|
"""Represents a deployment step in a deployment template."""
|
|
|
|
__tablename__ = 'deploy_template_steps'
|
|
__table_args__ = (
|
|
Index('deploy_template_id', 'deploy_template_id'),
|
|
Index('deploy_template_steps_interface_idx', 'interface'),
|
|
Index('deploy_template_steps_step_idx', 'step'),
|
|
table_args())
|
|
id = Column(Integer, primary_key=True)
|
|
deploy_template_id = Column(Integer, ForeignKey('deploy_templates.id'),
|
|
nullable=False)
|
|
interface = Column(String(255), nullable=False)
|
|
step = Column(String(255), nullable=False)
|
|
args = Column(db_types.JsonEncodedDict, nullable=False)
|
|
priority = Column(Integer, nullable=False)
|
|
deploy_template = orm.relationship(
|
|
"DeployTemplate",
|
|
backref='steps',
|
|
primaryjoin=(
|
|
'and_(DeployTemplateStep.deploy_template_id == '
|
|
'DeployTemplate.id)'),
|
|
foreign_keys=deploy_template_id
|
|
)
|
|
|
|
|
|
def get_class(model_name):
|
|
"""Returns the model class with the specified name.
|
|
|
|
:param model_name: the name of the class
|
|
:returns: the class with the specified name
|
|
:raises: Exception if there is no class associated with the name
|
|
"""
|
|
for model in Base.__subclasses__():
|
|
if model.__name__ == model_name:
|
|
return model
|
|
|
|
raise exception.IronicException(
|
|
_("Cannot find model with name: %s") % model_name)
|