494 lines
17 KiB
Python
494 lines
17 KiB
Python
# 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.
|
|
|
|
|
|
from oslo_db.sqlalchemy import models
|
|
from oslo_log import log as logging
|
|
import sqlalchemy as sa
|
|
import sqlalchemy.dialects.mysql
|
|
from sqlalchemy.ext import declarative
|
|
from sqlalchemy import orm
|
|
from sqlalchemy import schema
|
|
|
|
from nova.db import types
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
# NOTE(stephenfin): This is a list of fields that have been removed from
|
|
# various SQLAlchemy models but which still exist in the underlying tables. Our
|
|
# upgrade policy dictates that we remove fields from models at least one cycle
|
|
# before we remove the column from the underlying table. Not doing so would
|
|
# prevent us from applying the new database schema before rolling out any of
|
|
# the new code since the old code could attempt to access data in the removed
|
|
# columns. Alembic identifies this temporary mismatch between the models and
|
|
# underlying tables and attempts to resolve it. Tell it instead to ignore these
|
|
# until we're ready to remove them ourselves.
|
|
REMOVED_COLUMNS = []
|
|
|
|
# NOTE(stephenfin): A list of foreign key constraints that were removed when
|
|
# the column they were covering was removed.
|
|
REMOVED_FKEYS = []
|
|
|
|
# NOTE(stephenfin): A list of entire models that have been removed.
|
|
REMOVED_TABLES = {
|
|
# Tables that were moved the placement database in Train. The
|
|
# models were removed in Y and the tables can be dropped in Z or
|
|
# later
|
|
'allocations',
|
|
'consumers',
|
|
'inventories',
|
|
'placement_aggregates',
|
|
'projects',
|
|
'resource_classes',
|
|
'resource_provider_aggregates',
|
|
'resource_provider_traits',
|
|
'resource_providers',
|
|
'traits',
|
|
'users',
|
|
}
|
|
|
|
|
|
class _NovaAPIBase(models.ModelBase, models.TimestampMixin):
|
|
pass
|
|
|
|
|
|
BASE = declarative.declarative_base(cls=_NovaAPIBase)
|
|
|
|
|
|
class AggregateHost(BASE):
|
|
"""Represents a host that is member of an aggregate."""
|
|
__tablename__ = 'aggregate_hosts'
|
|
__table_args__ = (schema.UniqueConstraint(
|
|
"host", "aggregate_id",
|
|
name="uniq_aggregate_hosts0host0aggregate_id"
|
|
),
|
|
)
|
|
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
|
host = sa.Column(sa.String(255))
|
|
aggregate_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('aggregates.id'), nullable=False)
|
|
|
|
|
|
class AggregateMetadata(BASE):
|
|
"""Represents a metadata key/value pair for an aggregate."""
|
|
__tablename__ = 'aggregate_metadata'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint("aggregate_id", "key",
|
|
name="uniq_aggregate_metadata0aggregate_id0key"
|
|
),
|
|
sa.Index('aggregate_metadata_key_idx', 'key'),
|
|
)
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
key = sa.Column(sa.String(255), nullable=False)
|
|
value = sa.Column(sa.String(255), nullable=False)
|
|
aggregate_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('aggregates.id'), nullable=False)
|
|
|
|
|
|
class Aggregate(BASE):
|
|
"""Represents a cluster of hosts that exists in this zone."""
|
|
__tablename__ = 'aggregates'
|
|
__table_args__ = (
|
|
sa.Index('aggregate_uuid_idx', 'uuid'),
|
|
schema.UniqueConstraint("name", name="uniq_aggregate0name")
|
|
)
|
|
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
|
uuid = sa.Column(sa.String(36))
|
|
name = sa.Column(sa.String(255))
|
|
_hosts = orm.relationship(
|
|
AggregateHost,
|
|
primaryjoin='Aggregate.id == AggregateHost.aggregate_id',
|
|
cascade='delete')
|
|
_metadata = orm.relationship(
|
|
AggregateMetadata,
|
|
primaryjoin='Aggregate.id == AggregateMetadata.aggregate_id',
|
|
cascade='delete')
|
|
|
|
@property
|
|
def _extra_keys(self):
|
|
return ['hosts', 'metadetails', 'availability_zone']
|
|
|
|
@property
|
|
def hosts(self):
|
|
return [h.host for h in self._hosts]
|
|
|
|
@property
|
|
def metadetails(self):
|
|
return {m.key: m.value for m in self._metadata}
|
|
|
|
@property
|
|
def availability_zone(self):
|
|
if 'availability_zone' not in self.metadetails:
|
|
return None
|
|
return self.metadetails['availability_zone']
|
|
|
|
|
|
class CellMapping(BASE):
|
|
"""Contains information on communicating with a cell"""
|
|
__tablename__ = 'cell_mappings'
|
|
__table_args__ = (
|
|
sa.Index('uuid_idx', 'uuid'),
|
|
schema.UniqueConstraint('uuid', name='uniq_cell_mappings0uuid'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
uuid = sa.Column(sa.String(36), nullable=False)
|
|
name = sa.Column(sa.String(255))
|
|
transport_url = sa.Column(sa.Text())
|
|
database_connection = sa.Column(sa.Text())
|
|
disabled = sa.Column(sa.Boolean, default=False)
|
|
host_mapping = orm.relationship(
|
|
'HostMapping',
|
|
backref=orm.backref('cell_mapping', uselist=False),
|
|
foreign_keys=id,
|
|
primaryjoin='CellMapping.id == HostMapping.cell_id')
|
|
|
|
|
|
class InstanceMapping(BASE):
|
|
"""Contains the mapping of an instance to which cell it is in"""
|
|
__tablename__ = 'instance_mappings'
|
|
__table_args__ = (
|
|
sa.Index('project_id_idx', 'project_id'),
|
|
sa.Index('instance_uuid_idx', 'instance_uuid'),
|
|
schema.UniqueConstraint(
|
|
'instance_uuid', name='uniq_instance_mappings0instance_uuid'),
|
|
sa.Index(
|
|
'instance_mappings_user_id_project_id_idx',
|
|
'user_id',
|
|
'project_id',
|
|
),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
instance_uuid = sa.Column(sa.String(36), nullable=False)
|
|
cell_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('cell_mappings.id'), nullable=True)
|
|
project_id = sa.Column(sa.String(255), nullable=False)
|
|
# FIXME(melwitt): This should eventually be non-nullable, but we need a
|
|
# transition period first.
|
|
user_id = sa.Column(sa.String(255), nullable=True)
|
|
queued_for_delete = sa.Column(sa.Boolean)
|
|
cell_mapping = orm.relationship(
|
|
'CellMapping',
|
|
backref=orm.backref('instance_mapping', uselist=False),
|
|
foreign_keys=cell_id,
|
|
primaryjoin='InstanceMapping.cell_id == CellMapping.id')
|
|
|
|
|
|
class HostMapping(BASE):
|
|
"""Contains mapping of a compute host to which cell it is in"""
|
|
__tablename__ = "host_mappings"
|
|
__table_args__ = (
|
|
sa.Index('host_idx', 'host'),
|
|
schema.UniqueConstraint('host', name='uniq_host_mappings0host'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
cell_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('cell_mappings.id'), nullable=False)
|
|
host = sa.Column(sa.String(255), nullable=False)
|
|
|
|
|
|
class RequestSpec(BASE):
|
|
"""Represents the information passed to the scheduler."""
|
|
|
|
__tablename__ = 'request_specs'
|
|
__table_args__ = (
|
|
sa.Index('request_spec_instance_uuid_idx', 'instance_uuid'),
|
|
schema.UniqueConstraint(
|
|
'instance_uuid', name='uniq_request_specs0instance_uuid'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
instance_uuid = sa.Column(sa.String(36), nullable=False)
|
|
spec = sa.Column(types.MediumText(), nullable=False)
|
|
|
|
|
|
class Flavors(BASE):
|
|
"""Represents possible flavors for instances"""
|
|
__tablename__ = 'flavors'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint("flavorid", name="uniq_flavors0flavorid"),
|
|
schema.UniqueConstraint("name", name="uniq_flavors0name"))
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
name = sa.Column(sa.String(255), nullable=False)
|
|
memory_mb = sa.Column(sa.Integer, nullable=False)
|
|
vcpus = sa.Column(sa.Integer, nullable=False)
|
|
root_gb = sa.Column(sa.Integer)
|
|
ephemeral_gb = sa.Column(sa.Integer)
|
|
flavorid = sa.Column(sa.String(255), nullable=False)
|
|
swap = sa.Column(sa.Integer, nullable=False, default=0)
|
|
rxtx_factor = sa.Column(sa.Float, default=1)
|
|
vcpu_weight = sa.Column(sa.Integer)
|
|
disabled = sa.Column(sa.Boolean, default=False)
|
|
is_public = sa.Column(sa.Boolean, default=True)
|
|
description = sa.Column(sa.Text)
|
|
|
|
|
|
class FlavorExtraSpecs(BASE):
|
|
"""Represents additional specs as key/value pairs for a flavor"""
|
|
__tablename__ = 'flavor_extra_specs'
|
|
__table_args__ = (
|
|
sa.Index('flavor_extra_specs_flavor_id_key_idx', 'flavor_id', 'key'),
|
|
schema.UniqueConstraint('flavor_id', 'key',
|
|
name='uniq_flavor_extra_specs0flavor_id0key'),
|
|
{'mysql_collate': 'utf8_bin'},
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
key = sa.Column(sa.String(255), nullable=False)
|
|
value = sa.Column(sa.String(255))
|
|
flavor_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('flavors.id'), nullable=False)
|
|
flavor = orm.relationship(
|
|
Flavors, backref='extra_specs',
|
|
foreign_keys=flavor_id,
|
|
primaryjoin='FlavorExtraSpecs.flavor_id == Flavors.id')
|
|
|
|
|
|
class FlavorProjects(BASE):
|
|
"""Represents projects associated with flavors"""
|
|
__tablename__ = 'flavor_projects'
|
|
__table_args__ = (schema.UniqueConstraint('flavor_id', 'project_id',
|
|
name='uniq_flavor_projects0flavor_id0project_id'),)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
flavor_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('flavors.id'), nullable=False)
|
|
project_id = sa.Column(sa.String(255), nullable=False)
|
|
flavor = orm.relationship(
|
|
Flavors, backref='projects',
|
|
foreign_keys=flavor_id,
|
|
primaryjoin='FlavorProjects.flavor_id == Flavors.id')
|
|
|
|
|
|
class BuildRequest(BASE):
|
|
"""Represents the information passed to the scheduler."""
|
|
|
|
__tablename__ = 'build_requests'
|
|
__table_args__ = (
|
|
sa.Index('build_requests_instance_uuid_idx', 'instance_uuid'),
|
|
sa.Index('build_requests_project_id_idx', 'project_id'),
|
|
schema.UniqueConstraint(
|
|
'instance_uuid', name='uniq_build_requests0instance_uuid'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
# TODO(mriedem): instance_uuid should be nullable=False
|
|
instance_uuid = sa.Column(sa.String(36))
|
|
project_id = sa.Column(sa.String(255), nullable=False)
|
|
instance = sa.Column(types.MediumText())
|
|
block_device_mappings = sa.Column(types.MediumText())
|
|
tags = sa.Column(sa.Text())
|
|
|
|
|
|
class KeyPair(BASE):
|
|
"""Represents a public key pair for ssh / WinRM."""
|
|
__tablename__ = 'key_pairs'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint(
|
|
"user_id", "name", name="uniq_key_pairs0user_id0name"),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
|
|
name = sa.Column(sa.String(255), nullable=False)
|
|
user_id = sa.Column(sa.String(255), nullable=False)
|
|
fingerprint = sa.Column(sa.String(255))
|
|
public_key = sa.Column(sa.Text())
|
|
type = sa.Column(
|
|
sa.Enum('ssh', 'x509', name='keypair_types'),
|
|
nullable=False, server_default='ssh')
|
|
|
|
|
|
class InstanceGroupMember(BASE):
|
|
"""Represents the members for an instance group."""
|
|
__tablename__ = 'instance_group_member'
|
|
__table_args__ = (
|
|
sa.Index('instance_group_member_instance_idx', 'instance_uuid'),
|
|
)
|
|
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
|
|
instance_uuid = sa.Column(sa.String(255))
|
|
group_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('instance_groups.id'), nullable=False)
|
|
|
|
|
|
class InstanceGroupPolicy(BASE):
|
|
"""Represents the policy type for an instance group."""
|
|
__tablename__ = 'instance_group_policy'
|
|
__table_args__ = (
|
|
sa.Index('instance_group_policy_policy_idx', 'policy'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
|
|
policy = sa.Column(sa.String(255))
|
|
group_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('instance_groups.id'), nullable=False)
|
|
rules = sa.Column(sa.Text)
|
|
|
|
|
|
class InstanceGroup(BASE):
|
|
"""Represents an instance group.
|
|
|
|
A group will maintain a collection of instances and the relationship
|
|
between them.
|
|
"""
|
|
|
|
__tablename__ = 'instance_groups'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('uuid', name='uniq_instance_groups0uuid'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
|
user_id = sa.Column(sa.String(255))
|
|
project_id = sa.Column(sa.String(255))
|
|
uuid = sa.Column(sa.String(36), nullable=False)
|
|
name = sa.Column(sa.String(255))
|
|
_policies = orm.relationship(
|
|
InstanceGroupPolicy,
|
|
primaryjoin='InstanceGroup.id == InstanceGroupPolicy.group_id')
|
|
_members = orm.relationship(
|
|
InstanceGroupMember,
|
|
primaryjoin='InstanceGroup.id == InstanceGroupMember.group_id')
|
|
|
|
@property
|
|
def policy(self):
|
|
if len(self._policies) > 1:
|
|
msg = ("More than one policy (%(policies)s) is associated with "
|
|
"group %(group_name)s, only the first one in the list "
|
|
"would be returned.")
|
|
LOG.warning(msg, {"policies": [p.policy for p in self._policies],
|
|
"group_name": self.name})
|
|
return self._policies[0] if self._policies else None
|
|
|
|
@property
|
|
def members(self):
|
|
return [m.instance_uuid for m in self._members]
|
|
|
|
|
|
class Quota(BASE):
|
|
"""Represents a single quota override for a project.
|
|
|
|
If there is no row for a given project id and resource, then the
|
|
default for the quota class is used. If there is no row for a
|
|
given quota class and resource, then the default for the
|
|
deployment is used. If the row is present but the hard limit is
|
|
Null, then the resource is unlimited.
|
|
"""
|
|
__tablename__ = 'quotas'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint(
|
|
"project_id",
|
|
"resource",
|
|
name="uniq_quotas0project_id0resource"
|
|
),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
project_id = sa.Column(sa.String(255))
|
|
resource = sa.Column(sa.String(255), nullable=False)
|
|
hard_limit = sa.Column(sa.Integer)
|
|
|
|
|
|
class ProjectUserQuota(BASE):
|
|
"""Represents a single quota override for a user with in a project."""
|
|
|
|
__tablename__ = 'project_user_quotas'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint(
|
|
"user_id",
|
|
"project_id",
|
|
"resource",
|
|
name="uniq_project_user_quotas0user_id0project_id0resource",
|
|
),
|
|
sa.Index(
|
|
'project_user_quotas_project_id_idx', 'project_id'),
|
|
sa.Index(
|
|
'project_user_quotas_user_id_idx', 'user_id',)
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
|
|
project_id = sa.Column(sa.String(255), nullable=False)
|
|
user_id = sa.Column(sa.String(255), nullable=False)
|
|
resource = sa.Column(sa.String(255), nullable=False)
|
|
hard_limit = sa.Column(sa.Integer)
|
|
|
|
|
|
class QuotaClass(BASE):
|
|
"""Represents a single quota override for a quota class.
|
|
|
|
If there is no row for a given quota class and resource, then the
|
|
default for the deployment is used. If the row is present but the
|
|
hard limit is Null, then the resource is unlimited.
|
|
"""
|
|
|
|
__tablename__ = 'quota_classes'
|
|
__table_args__ = (
|
|
sa.Index('quota_classes_class_name_idx', 'class_name'),
|
|
)
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
|
|
class_name = sa.Column(sa.String(255))
|
|
|
|
resource = sa.Column(sa.String(255))
|
|
hard_limit = sa.Column(sa.Integer)
|
|
|
|
|
|
class QuotaUsage(BASE):
|
|
"""Represents the current usage for a given resource."""
|
|
|
|
__tablename__ = 'quota_usages'
|
|
__table_args__ = (
|
|
sa.Index('quota_usages_project_id_idx', 'project_id'),
|
|
sa.Index('quota_usages_user_id_idx', 'user_id'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True)
|
|
project_id = sa.Column(sa.String(255))
|
|
user_id = sa.Column(sa.String(255))
|
|
resource = sa.Column(sa.String(255), nullable=False)
|
|
in_use = sa.Column(sa.Integer, nullable=False)
|
|
reserved = sa.Column(sa.Integer, nullable=False)
|
|
|
|
@property
|
|
def total(self):
|
|
return self.in_use + self.reserved
|
|
|
|
until_refresh = sa.Column(sa.Integer)
|
|
|
|
|
|
class Reservation(BASE):
|
|
"""Represents a resource reservation for quotas."""
|
|
|
|
__tablename__ = 'reservations'
|
|
__table_args__ = (
|
|
sa.Index('reservations_project_id_idx', 'project_id'),
|
|
sa.Index('reservations_uuid_idx', 'uuid'),
|
|
sa.Index('reservations_expire_idx', 'expire'),
|
|
sa.Index('reservations_user_id_idx', 'user_id'),
|
|
)
|
|
|
|
id = sa.Column(sa.Integer, primary_key=True, nullable=False)
|
|
uuid = sa.Column(sa.String(36), nullable=False)
|
|
usage_id = sa.Column(
|
|
sa.Integer, sa.ForeignKey('quota_usages.id'), nullable=False)
|
|
project_id = sa.Column(sa.String(255))
|
|
user_id = sa.Column(sa.String(255))
|
|
resource = sa.Column(sa.String(255))
|
|
delta = sa.Column(sa.Integer, nullable=False)
|
|
expire = sa.Column(sa.DateTime)
|
|
usage = orm.relationship(
|
|
"QuotaUsage",
|
|
foreign_keys=usage_id,
|
|
primaryjoin='Reservation.usage_id == QuotaUsage.id')
|