nova/nova/db/api/models.py

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')