db: Enable auto-generation of migrations

Let's take advantage of the features that alembic provides [1]. We use
this opportunity to clean up how we do our base model creation and
remove the downgrade section from the migration script template, since
we don't support downgrades.

[1] https://alembic.sqlalchemy.org/en/latest/autogenerate.html

Change-Id: I18846a5c7557db45bb63b97c7e8be5c4367e4547
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2021-07-08 16:11:19 +01:00
parent 905c9723e9
commit 1ba2c1c55d
13 changed files with 126 additions and 123 deletions

View File

@ -15,7 +15,7 @@ from migrate import UniqueConstraint
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import dialects from sqlalchemy import dialects
from nova.db.api.models import MediumText from nova.db import types
from nova.objects import keypair from nova.objects import keypair
@ -145,7 +145,7 @@ def upgrade(migrate_engine):
sa.Column('updated_at', sa.DateTime), sa.Column('updated_at', sa.DateTime),
sa.Column('id', sa.Integer, primary_key=True, nullable=False), sa.Column('id', sa.Integer, primary_key=True, nullable=False),
sa.Column('instance_uuid', sa.String(36), nullable=False), sa.Column('instance_uuid', sa.String(36), nullable=False),
sa.Column('spec', MediumText(), nullable=False), sa.Column('spec', types.MediumText(), nullable=False),
UniqueConstraint( UniqueConstraint(
'instance_uuid', name='uniq_request_specs0instance_uuid'), 'instance_uuid', name='uniq_request_specs0instance_uuid'),
sa.Index('request_spec_instance_uuid_idx', 'instance_uuid'), sa.Index('request_spec_instance_uuid_idx', 'instance_uuid'),
@ -176,8 +176,8 @@ def upgrade(migrate_engine):
'locked_by', 'locked_by',
sa.Enum('owner', 'admin', name='build_requests0locked_by')), sa.Enum('owner', 'admin', name='build_requests0locked_by')),
sa.Column('instance_uuid', sa.String(length=36)), sa.Column('instance_uuid', sa.String(length=36)),
sa.Column('instance', MediumText()), sa.Column('instance', types.MediumText()),
sa.Column('block_device_mappings', MediumText()), sa.Column('block_device_mappings', types.MediumText()),
sa.Column('tags', sa.Text()), sa.Column('tags', sa.Text()),
UniqueConstraint( UniqueConstraint(
'instance_uuid', name='uniq_build_requests0instance_uuid'), 'instance_uuid', name='uniq_build_requests0instance_uuid'),

View File

@ -30,7 +30,3 @@ depends_on = ${repr(depends_on)}
def upgrade(): def upgrade():
${upgrades if upgrades else "pass"} ${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -21,7 +21,7 @@ from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import dialects from sqlalchemy import dialects
from nova.db.api.models import MediumText from nova.db import types
from nova.objects import keypair from nova.objects import keypair
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
@ -164,7 +164,7 @@ def upgrade():
sa.Column('updated_at', sa.DateTime), sa.Column('updated_at', sa.DateTime),
sa.Column('id', sa.Integer, primary_key=True, nullable=False), sa.Column('id', sa.Integer, primary_key=True, nullable=False),
sa.Column('instance_uuid', sa.String(36), nullable=False), sa.Column('instance_uuid', sa.String(36), nullable=False),
sa.Column('spec', MediumText(), nullable=False), sa.Column('spec', types.MediumText(), nullable=False),
sa.UniqueConstraint( sa.UniqueConstraint(
'instance_uuid', name='uniq_request_specs0instance_uuid'), 'instance_uuid', name='uniq_request_specs0instance_uuid'),
sa.Index('request_spec_instance_uuid_idx', 'instance_uuid'), sa.Index('request_spec_instance_uuid_idx', 'instance_uuid'),
@ -196,8 +196,8 @@ def upgrade():
'locked_by', 'locked_by',
sa.Enum('owner', 'admin', name='build_requests0locked_by')), sa.Enum('owner', 'admin', name='build_requests0locked_by')),
sa.Column('instance_uuid', sa.String(length=36)), sa.Column('instance_uuid', sa.String(length=36)),
sa.Column('instance', MediumText()), sa.Column('instance', types.MediumText()),
sa.Column('block_device_mappings', MediumText()), sa.Column('block_device_mappings', types.MediumText()),
sa.Column('tags', sa.Text()), sa.Column('tags', sa.Text()),
sa.UniqueConstraint( sa.UniqueConstraint(
'instance_uuid', name='uniq_build_requests0instance_uuid'), 'instance_uuid', name='uniq_build_requests0instance_uuid'),

View File

@ -15,26 +15,23 @@ from oslo_db.sqlalchemy import models
from oslo_log import log as logging from oslo_log import log as logging
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.dialects.mysql import sqlalchemy.dialects.mysql
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext import declarative
from sqlalchemy import orm from sqlalchemy import orm
from sqlalchemy import schema from sqlalchemy import schema
from nova.db import types
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def MediumText():
return sa.Text().with_variant(
sqlalchemy.dialects.mysql.MEDIUMTEXT(), 'mysql')
class _NovaAPIBase(models.ModelBase, models.TimestampMixin): class _NovaAPIBase(models.ModelBase, models.TimestampMixin):
pass pass
API_BASE = declarative_base(cls=_NovaAPIBase) BASE = declarative.declarative_base(cls=_NovaAPIBase)
class AggregateHost(API_BASE): class AggregateHost(BASE):
"""Represents a host that is member of an aggregate.""" """Represents a host that is member of an aggregate."""
__tablename__ = 'aggregate_hosts' __tablename__ = 'aggregate_hosts'
__table_args__ = (schema.UniqueConstraint( __table_args__ = (schema.UniqueConstraint(
@ -48,7 +45,7 @@ class AggregateHost(API_BASE):
sa.Integer, sa.ForeignKey('aggregates.id'), nullable=False) sa.Integer, sa.ForeignKey('aggregates.id'), nullable=False)
class AggregateMetadata(API_BASE): class AggregateMetadata(BASE):
"""Represents a metadata key/value pair for an aggregate.""" """Represents a metadata key/value pair for an aggregate."""
__tablename__ = 'aggregate_metadata' __tablename__ = 'aggregate_metadata'
__table_args__ = ( __table_args__ = (
@ -64,7 +61,7 @@ class AggregateMetadata(API_BASE):
sa.Integer, sa.ForeignKey('aggregates.id'), nullable=False) sa.Integer, sa.ForeignKey('aggregates.id'), nullable=False)
class Aggregate(API_BASE): class Aggregate(BASE):
"""Represents a cluster of hosts that exists in this zone.""" """Represents a cluster of hosts that exists in this zone."""
__tablename__ = 'aggregates' __tablename__ = 'aggregates'
__table_args__ = ( __table_args__ = (
@ -102,7 +99,7 @@ class Aggregate(API_BASE):
return self.metadetails['availability_zone'] return self.metadetails['availability_zone']
class CellMapping(API_BASE): class CellMapping(BASE):
"""Contains information on communicating with a cell""" """Contains information on communicating with a cell"""
__tablename__ = 'cell_mappings' __tablename__ = 'cell_mappings'
__table_args__ = ( __table_args__ = (
@ -123,7 +120,7 @@ class CellMapping(API_BASE):
primaryjoin='CellMapping.id == HostMapping.cell_id') primaryjoin='CellMapping.id == HostMapping.cell_id')
class InstanceMapping(API_BASE): class InstanceMapping(BASE):
"""Contains the mapping of an instance to which cell it is in""" """Contains the mapping of an instance to which cell it is in"""
__tablename__ = 'instance_mappings' __tablename__ = 'instance_mappings'
__table_args__ = ( __table_args__ = (
@ -154,7 +151,7 @@ class InstanceMapping(API_BASE):
primaryjoin='InstanceMapping.cell_id == CellMapping.id') primaryjoin='InstanceMapping.cell_id == CellMapping.id')
class HostMapping(API_BASE): class HostMapping(BASE):
"""Contains mapping of a compute host to which cell it is in""" """Contains mapping of a compute host to which cell it is in"""
__tablename__ = "host_mappings" __tablename__ = "host_mappings"
__table_args__ = ( __table_args__ = (
@ -168,7 +165,7 @@ class HostMapping(API_BASE):
host = sa.Column(sa.String(255), nullable=False) host = sa.Column(sa.String(255), nullable=False)
class RequestSpec(API_BASE): class RequestSpec(BASE):
"""Represents the information passed to the scheduler.""" """Represents the information passed to the scheduler."""
__tablename__ = 'request_specs' __tablename__ = 'request_specs'
@ -180,10 +177,10 @@ class RequestSpec(API_BASE):
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
instance_uuid = sa.Column(sa.String(36), nullable=False) instance_uuid = sa.Column(sa.String(36), nullable=False)
spec = sa.Column(MediumText(), nullable=False) spec = sa.Column(types.MediumText(), nullable=False)
class Flavors(API_BASE): class Flavors(BASE):
"""Represents possible flavors for instances""" """Represents possible flavors for instances"""
__tablename__ = 'flavors' __tablename__ = 'flavors'
__table_args__ = ( __table_args__ = (
@ -205,7 +202,7 @@ class Flavors(API_BASE):
description = sa.Column(sa.Text) description = sa.Column(sa.Text)
class FlavorExtraSpecs(API_BASE): class FlavorExtraSpecs(BASE):
"""Represents additional specs as key/value pairs for a flavor""" """Represents additional specs as key/value pairs for a flavor"""
__tablename__ = 'flavor_extra_specs' __tablename__ = 'flavor_extra_specs'
__table_args__ = ( __table_args__ = (
@ -226,7 +223,7 @@ class FlavorExtraSpecs(API_BASE):
primaryjoin='FlavorExtraSpecs.flavor_id == Flavors.id') primaryjoin='FlavorExtraSpecs.flavor_id == Flavors.id')
class FlavorProjects(API_BASE): class FlavorProjects(BASE):
"""Represents projects associated with flavors""" """Represents projects associated with flavors"""
__tablename__ = 'flavor_projects' __tablename__ = 'flavor_projects'
__table_args__ = (schema.UniqueConstraint('flavor_id', 'project_id', __table_args__ = (schema.UniqueConstraint('flavor_id', 'project_id',
@ -242,7 +239,7 @@ class FlavorProjects(API_BASE):
primaryjoin='FlavorProjects.flavor_id == Flavors.id') primaryjoin='FlavorProjects.flavor_id == Flavors.id')
class BuildRequest(API_BASE): class BuildRequest(BASE):
"""Represents the information passed to the scheduler.""" """Represents the information passed to the scheduler."""
__tablename__ = 'build_requests' __tablename__ = 'build_requests'
@ -257,8 +254,8 @@ class BuildRequest(API_BASE):
# TODO(mriedem): instance_uuid should be nullable=False # TODO(mriedem): instance_uuid should be nullable=False
instance_uuid = sa.Column(sa.String(36)) instance_uuid = sa.Column(sa.String(36))
project_id = sa.Column(sa.String(255), nullable=False) project_id = sa.Column(sa.String(255), nullable=False)
instance = sa.Column(MediumText()) instance = sa.Column(types.MediumText())
block_device_mappings = sa.Column(MediumText()) block_device_mappings = sa.Column(types.MediumText())
tags = sa.Column(sa.Text()) tags = sa.Column(sa.Text())
# TODO(alaski): Drop these from the db in Ocata # TODO(alaski): Drop these from the db in Ocata
# columns_to_drop = ['request_spec_id', 'user_id', 'display_name', # columns_to_drop = ['request_spec_id', 'user_id', 'display_name',
@ -269,7 +266,7 @@ class BuildRequest(API_BASE):
# 'ramdisk_id', 'root_device_name', 'user_data'] # 'ramdisk_id', 'root_device_name', 'user_data']
class KeyPair(API_BASE): class KeyPair(BASE):
"""Represents a public key pair for ssh / WinRM.""" """Represents a public key pair for ssh / WinRM."""
__tablename__ = 'key_pairs' __tablename__ = 'key_pairs'
__table_args__ = ( __table_args__ = (
@ -288,7 +285,7 @@ class KeyPair(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split # TODO(stephenfin): Remove this as it's now unused post-placement split
class ResourceClass(API_BASE): class ResourceClass(BASE):
"""Represents the type of resource for an inventory or allocation.""" """Represents the type of resource for an inventory or allocation."""
__tablename__ = 'resource_classes' __tablename__ = 'resource_classes'
__table_args__ = ( __table_args__ = (
@ -300,7 +297,7 @@ class ResourceClass(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split # TODO(stephenfin): Remove this as it's now unused post-placement split
class ResourceProvider(API_BASE): class ResourceProvider(BASE):
"""Represents a mapping to a providers of resources.""" """Represents a mapping to a providers of resources."""
__tablename__ = "resource_providers" __tablename__ = "resource_providers"
@ -332,7 +329,7 @@ class ResourceProvider(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split # TODO(stephenfin): Remove this as it's now unused post-placement split
class Inventory(API_BASE): class Inventory(BASE):
"""Represents a quantity of available resource.""" """Represents a quantity of available resource."""
__tablename__ = "inventories" __tablename__ = "inventories"
@ -369,7 +366,7 @@ class Inventory(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split # TODO(stephenfin): Remove this as it's now unused post-placement split
class Allocation(API_BASE): class Allocation(BASE):
"""A use of inventory.""" """A use of inventory."""
__tablename__ = "allocations" __tablename__ = "allocations"
@ -396,7 +393,7 @@ class Allocation(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split # TODO(stephenfin): Remove this as it's now unused post-placement split
class ResourceProviderAggregate(API_BASE): class ResourceProviderAggregate(BASE):
"""Associate a resource provider with an aggregate.""" """Associate a resource provider with an aggregate."""
__tablename__ = 'resource_provider_aggregates' __tablename__ = 'resource_provider_aggregates'
@ -411,7 +408,7 @@ class ResourceProviderAggregate(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split # TODO(stephenfin): Remove this as it's now unused post-placement split
class PlacementAggregate(API_BASE): class PlacementAggregate(BASE):
"""A grouping of resource providers.""" """A grouping of resource providers."""
__tablename__ = 'placement_aggregates' __tablename__ = 'placement_aggregates'
__table_args__ = ( __table_args__ = (
@ -422,7 +419,7 @@ class PlacementAggregate(API_BASE):
uuid = sa.Column(sa.String(36), index=True) uuid = sa.Column(sa.String(36), index=True)
class InstanceGroupMember(API_BASE): class InstanceGroupMember(BASE):
"""Represents the members for an instance group.""" """Represents the members for an instance group."""
__tablename__ = 'instance_group_member' __tablename__ = 'instance_group_member'
__table_args__ = ( __table_args__ = (
@ -434,7 +431,7 @@ class InstanceGroupMember(API_BASE):
sa.Integer, sa.ForeignKey('instance_groups.id'), nullable=False) sa.Integer, sa.ForeignKey('instance_groups.id'), nullable=False)
class InstanceGroupPolicy(API_BASE): class InstanceGroupPolicy(BASE):
"""Represents the policy type for an instance group.""" """Represents the policy type for an instance group."""
__tablename__ = 'instance_group_policy' __tablename__ = 'instance_group_policy'
__table_args__ = ( __table_args__ = (
@ -448,7 +445,7 @@ class InstanceGroupPolicy(API_BASE):
rules = sa.Column(sa.Text) rules = sa.Column(sa.Text)
class InstanceGroup(API_BASE): class InstanceGroup(BASE):
"""Represents an instance group. """Represents an instance group.
A group will maintain a collection of instances and the relationship A group will maintain a collection of instances and the relationship
@ -487,7 +484,7 @@ class InstanceGroup(API_BASE):
return [m.instance_uuid for m in self._members] return [m.instance_uuid for m in self._members]
class Quota(API_BASE): class Quota(BASE):
"""Represents a single quota override for a project. """Represents a single quota override for a project.
If there is no row for a given project id and resource, then the If there is no row for a given project id and resource, then the
@ -511,7 +508,7 @@ class Quota(API_BASE):
hard_limit = sa.Column(sa.Integer) hard_limit = sa.Column(sa.Integer)
class ProjectUserQuota(API_BASE): class ProjectUserQuota(BASE):
"""Represents a single quota override for a user with in a project.""" """Represents a single quota override for a user with in a project."""
__tablename__ = 'project_user_quotas' __tablename__ = 'project_user_quotas'
@ -535,7 +532,7 @@ class ProjectUserQuota(API_BASE):
hard_limit = sa.Column(sa.Integer) hard_limit = sa.Column(sa.Integer)
class QuotaClass(API_BASE): class QuotaClass(BASE):
"""Represents a single quota override for a quota class. """Represents a single quota override for a quota class.
If there is no row for a given quota class and resource, then the If there is no row for a given quota class and resource, then the
@ -555,7 +552,7 @@ class QuotaClass(API_BASE):
hard_limit = sa.Column(sa.Integer) hard_limit = sa.Column(sa.Integer)
class QuotaUsage(API_BASE): class QuotaUsage(BASE):
"""Represents the current usage for a given resource.""" """Represents the current usage for a given resource."""
__tablename__ = 'quota_usages' __tablename__ = 'quota_usages'
@ -578,7 +575,7 @@ class QuotaUsage(API_BASE):
until_refresh = sa.Column(sa.Integer) until_refresh = sa.Column(sa.Integer)
class Reservation(API_BASE): class Reservation(BASE):
"""Represents a resource reservation for quotas.""" """Represents a resource reservation for quotas."""
__tablename__ = 'reservations' __tablename__ = 'reservations'
@ -604,7 +601,7 @@ class Reservation(API_BASE):
primaryjoin='Reservation.usage_id == QuotaUsage.id') primaryjoin='Reservation.usage_id == QuotaUsage.id')
class Trait(API_BASE): class Trait(BASE):
"""Represents a trait.""" """Represents a trait."""
__tablename__ = "traits" __tablename__ = "traits"
@ -618,7 +615,7 @@ class Trait(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split # TODO(stephenfin): Remove this as it's now unused post-placement split
class ResourceProviderTrait(API_BASE): class ResourceProviderTrait(BASE):
"""Represents the relationship between traits and resource provider""" """Represents the relationship between traits and resource provider"""
__tablename__ = "resource_provider_traits" __tablename__ = "resource_provider_traits"
@ -638,7 +635,7 @@ class ResourceProviderTrait(API_BASE):
# TODO(stephenfin): Remove this as it's unused # TODO(stephenfin): Remove this as it's unused
class Project(API_BASE): class Project(BASE):
"""The project is the Keystone project.""" """The project is the Keystone project."""
__tablename__ = 'projects' __tablename__ = 'projects'
@ -655,7 +652,7 @@ class Project(API_BASE):
# TODO(stephenfin): Remove this as it's unused # TODO(stephenfin): Remove this as it's unused
class User(API_BASE): class User(BASE):
"""The user is the Keystone user.""" """The user is the Keystone user."""
__tablename__ = 'users' __tablename__ = 'users'
@ -669,7 +666,7 @@ class User(API_BASE):
# TODO(stephenfin): Remove this as it's unused # TODO(stephenfin): Remove this as it's unused
class Consumer(API_BASE): class Consumer(BASE):
"""Represents a resource consumer.""" """Represents a resource consumer."""
__tablename__ = 'consumers' __tablename__ = 'consumers'

View File

@ -25,14 +25,6 @@ from nova.objects import keypair
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# NOTE(dprince): This wrapper allows us to easily match the Folsom MySQL
# Schema. In Folsom we created tables as latin1 and converted them to utf8
# later. This conversion causes some of the Text columns on MySQL to get
# created as mediumtext instead of just text.
def MediumText():
return sa.Text().with_variant(dialects.mysql.MEDIUMTEXT(), 'mysql')
def Inet(): def Inet():
return sa.String(length=43).with_variant( return sa.String(length=43).with_variant(
dialects.postgresql.INET(), 'postgresql', dialects.postgresql.INET(), 'postgresql',
@ -290,7 +282,7 @@ def upgrade(migrate_engine):
sa.Column('volume_id', sa.String(length=36), nullable=True), sa.Column('volume_id', sa.String(length=36), nullable=True),
sa.Column('volume_size', sa.Integer), sa.Column('volume_size', sa.Integer),
sa.Column('no_device', sa.Boolean), sa.Column('no_device', sa.Boolean),
sa.Column('connection_info', MediumText()), sa.Column('connection_info', types.MediumText()),
sa.Column( sa.Column(
'instance_uuid', sa.String(length=36), 'instance_uuid', sa.String(length=36),
sa.ForeignKey( sa.ForeignKey(
@ -391,9 +383,9 @@ def upgrade(migrate_engine):
sa.Column('vcpus_used', sa.Integer, nullable=False), sa.Column('vcpus_used', sa.Integer, nullable=False),
sa.Column('memory_mb_used', sa.Integer, nullable=False), sa.Column('memory_mb_used', sa.Integer, nullable=False),
sa.Column('local_gb_used', sa.Integer, nullable=False), sa.Column('local_gb_used', sa.Integer, nullable=False),
sa.Column('hypervisor_type', MediumText(), nullable=False), sa.Column('hypervisor_type', types.MediumText(), nullable=False),
sa.Column('hypervisor_version', sa.Integer, nullable=False), sa.Column('hypervisor_version', sa.Integer, nullable=False),
sa.Column('cpu_info', MediumText(), nullable=False), sa.Column('cpu_info', types.MediumText(), nullable=False),
sa.Column('disk_available_least', sa.Integer), sa.Column('disk_available_least', sa.Integer),
sa.Column('free_ram_mb', sa.Integer), sa.Column('free_ram_mb', sa.Integer),
sa.Column('free_disk_gb', sa.Integer), sa.Column('free_disk_gb', sa.Integer),
@ -584,7 +576,7 @@ def upgrade(migrate_engine):
'instances.uuid', name='fk_instance_faults_instance_uuid')), 'instances.uuid', name='fk_instance_faults_instance_uuid')),
sa.Column('code', sa.Integer, nullable=False), sa.Column('code', sa.Integer, nullable=False),
sa.Column('message', sa.String(length=255)), sa.Column('message', sa.String(length=255)),
sa.Column('details', MediumText()), sa.Column('details', types.MediumText()),
sa.Column('host', sa.String(length=255)), sa.Column('host', sa.String(length=255)),
sa.Column('deleted', sa.Integer), sa.Column('deleted', sa.Integer),
sa.Index('instance_faults_host_idx', 'host'), sa.Index('instance_faults_host_idx', 'host'),
@ -612,7 +604,7 @@ def upgrade(migrate_engine):
sa.Column('updated_at', sa.DateTime), sa.Column('updated_at', sa.DateTime),
sa.Column('deleted_at', sa.DateTime), sa.Column('deleted_at', sa.DateTime),
sa.Column('id', sa.Integer, primary_key=True, nullable=False), sa.Column('id', sa.Integer, primary_key=True, nullable=False),
sa.Column('network_info', MediumText()), sa.Column('network_info', types.MediumText()),
sa.Column( sa.Column(
'instance_uuid', sa.String(length=36), 'instance_uuid', sa.String(length=36),
sa.ForeignKey( sa.ForeignKey(
@ -797,14 +789,14 @@ def upgrade(migrate_engine):
sa.Column('ramdisk_id', sa.String(length=255)), sa.Column('ramdisk_id', sa.String(length=255)),
sa.Column('launch_index', sa.Integer), sa.Column('launch_index', sa.Integer),
sa.Column('key_name', sa.String(length=255)), sa.Column('key_name', sa.String(length=255)),
sa.Column('key_data', MediumText()), sa.Column('key_data', types.MediumText()),
sa.Column('power_state', sa.Integer), sa.Column('power_state', sa.Integer),
sa.Column('vm_state', sa.String(length=255)), sa.Column('vm_state', sa.String(length=255)),
sa.Column('memory_mb', sa.Integer), sa.Column('memory_mb', sa.Integer),
sa.Column('vcpus', sa.Integer), sa.Column('vcpus', sa.Integer),
sa.Column('hostname', sa.String(length=255)), sa.Column('hostname', sa.String(length=255)),
sa.Column('host', sa.String(length=255)), sa.Column('host', sa.String(length=255)),
sa.Column('user_data', MediumText()), sa.Column('user_data', types.MediumText()),
sa.Column('reservation_id', sa.String(length=255)), sa.Column('reservation_id', sa.String(length=255)),
sa.Column('launched_at', sa.DateTime), sa.Column('launched_at', sa.DateTime),
sa.Column('terminated_at', sa.DateTime), sa.Column('terminated_at', sa.DateTime),
@ -813,7 +805,7 @@ def upgrade(migrate_engine):
sa.Column('availability_zone', sa.String(length=255)), sa.Column('availability_zone', sa.String(length=255)),
sa.Column('locked', sa.Boolean), sa.Column('locked', sa.Boolean),
sa.Column('os_type', sa.String(length=255)), sa.Column('os_type', sa.String(length=255)),
sa.Column('launched_on', MediumText()), sa.Column('launched_on', types.MediumText()),
sa.Column('instance_type_id', sa.Integer), sa.Column('instance_type_id', sa.Integer),
sa.Column('vm_mode', sa.String(length=255)), sa.Column('vm_mode', sa.String(length=255)),
sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('uuid', sa.String(length=36), nullable=False),
@ -973,7 +965,7 @@ def upgrade(migrate_engine):
sa.Column('name', sa.String(length=255), nullable=False), sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.String(length=255)), sa.Column('user_id', sa.String(length=255)),
sa.Column('fingerprint', sa.String(length=255)), sa.Column('fingerprint', sa.String(length=255)),
sa.Column('public_key', MediumText()), sa.Column('public_key', types.MediumText()),
sa.Column('deleted', sa.Integer), sa.Column('deleted', sa.Integer),
sa.Column( sa.Column(
'type', sa.Enum('ssh', 'x509', name='keypair_types'), 'type', sa.Enum('ssh', 'x509', name='keypair_types'),

View File

@ -16,6 +16,8 @@ from alembic import context
from sqlalchemy import engine_from_config from sqlalchemy import engine_from_config
from sqlalchemy import pool from sqlalchemy import pool
from nova.db.main import models
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
config = context.config config = context.config
@ -29,7 +31,20 @@ if config.attributes.get('configure_logger', True):
# for 'autogenerate' support # for 'autogenerate' support
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
target_metadata = None target_metadata = models.BASE.metadata
def include_name(name, type_, parent_names):
if type_ == 'table':
return not name.startswith('shadow_')
if type_ == 'column':
return (parent_names['table_name'], name) not in {
('instances', 'internal_id'),
('instance_extra', 'vpmems'),
}
return True
def run_migrations_offline(): def run_migrations_offline():
@ -45,6 +60,7 @@ def run_migrations_offline():
context.configure( context.configure(
url=url, url=url,
target_metadata=target_metadata, target_metadata=target_metadata,
include_name=include_name,
literal_binds=True, literal_binds=True,
dialect_opts={"paramstyle": "named"}, dialect_opts={"paramstyle": "named"},
) )
@ -80,7 +96,9 @@ def run_migrations_online():
with connectable.connect() as connection: with connectable.connect() as connection:
context.configure( context.configure(
connection=connection, target_metadata=target_metadata connection=connection,
target_metadata=target_metadata,
include_name=include_name,
) )
with context.begin_transaction(): with context.begin_transaction():

View File

@ -30,7 +30,3 @@ depends_on = ${repr(depends_on)}
def upgrade(): def upgrade():
${upgrades if upgrades else "pass"} ${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -33,14 +33,6 @@ branch_labels = None
depends_on = None depends_on = None
# NOTE(dprince): This wrapper allows us to easily match the Folsom MySQL
# Schema. In Folsom we created tables as latin1 and converted them to utf8
# later. This conversion causes some of the Text columns on MySQL to get
# created as mediumtext instead of just text.
def MediumText():
return sa.Text().with_variant(dialects.mysql.MEDIUMTEXT(), 'mysql')
def Inet(): def Inet():
return sa.String(length=43).with_variant( return sa.String(length=43).with_variant(
dialects.postgresql.INET(), 'postgresql', dialects.postgresql.INET(), 'postgresql',
@ -204,14 +196,14 @@ def upgrade():
sa.Column('ramdisk_id', sa.String(length=255)), sa.Column('ramdisk_id', sa.String(length=255)),
sa.Column('launch_index', sa.Integer), sa.Column('launch_index', sa.Integer),
sa.Column('key_name', sa.String(length=255)), sa.Column('key_name', sa.String(length=255)),
sa.Column('key_data', MediumText()), sa.Column('key_data', types.MediumText()),
sa.Column('power_state', sa.Integer), sa.Column('power_state', sa.Integer),
sa.Column('vm_state', sa.String(length=255)), sa.Column('vm_state', sa.String(length=255)),
sa.Column('memory_mb', sa.Integer), sa.Column('memory_mb', sa.Integer),
sa.Column('vcpus', sa.Integer), sa.Column('vcpus', sa.Integer),
sa.Column('hostname', sa.String(length=255)), sa.Column('hostname', sa.String(length=255)),
sa.Column('host', sa.String(length=255)), sa.Column('host', sa.String(length=255)),
sa.Column('user_data', MediumText()), sa.Column('user_data', types.MediumText()),
sa.Column('reservation_id', sa.String(length=255)), sa.Column('reservation_id', sa.String(length=255)),
sa.Column('launched_at', sa.DateTime), sa.Column('launched_at', sa.DateTime),
sa.Column('terminated_at', sa.DateTime), sa.Column('terminated_at', sa.DateTime),
@ -220,7 +212,7 @@ def upgrade():
sa.Column('availability_zone', sa.String(length=255)), sa.Column('availability_zone', sa.String(length=255)),
sa.Column('locked', sa.Boolean), sa.Column('locked', sa.Boolean),
sa.Column('os_type', sa.String(length=255)), sa.Column('os_type', sa.String(length=255)),
sa.Column('launched_on', MediumText()), sa.Column('launched_on', types.MediumText()),
sa.Column('instance_type_id', sa.Integer), sa.Column('instance_type_id', sa.Integer),
sa.Column('vm_mode', sa.String(length=255)), sa.Column('vm_mode', sa.String(length=255)),
sa.Column('uuid', sa.String(length=36), nullable=False), sa.Column('uuid', sa.String(length=36), nullable=False),
@ -381,7 +373,7 @@ def upgrade():
sa.Column('volume_id', sa.String(length=36), nullable=True), sa.Column('volume_id', sa.String(length=36), nullable=True),
sa.Column('volume_size', sa.Integer), sa.Column('volume_size', sa.Integer),
sa.Column('no_device', sa.Boolean), sa.Column('no_device', sa.Boolean),
sa.Column('connection_info', MediumText()), sa.Column('connection_info', types.MediumText()),
sa.Column( sa.Column(
'instance_uuid', sa.String(length=36), 'instance_uuid', sa.String(length=36),
sa.ForeignKey( sa.ForeignKey(
@ -486,9 +478,9 @@ def upgrade():
sa.Column('vcpus_used', sa.Integer, nullable=False), sa.Column('vcpus_used', sa.Integer, nullable=False),
sa.Column('memory_mb_used', sa.Integer, nullable=False), sa.Column('memory_mb_used', sa.Integer, nullable=False),
sa.Column('local_gb_used', sa.Integer, nullable=False), sa.Column('local_gb_used', sa.Integer, nullable=False),
sa.Column('hypervisor_type', MediumText(), nullable=False), sa.Column('hypervisor_type', types.MediumText(), nullable=False),
sa.Column('hypervisor_version', sa.Integer, nullable=False), sa.Column('hypervisor_version', sa.Integer, nullable=False),
sa.Column('cpu_info', MediumText(), nullable=False), sa.Column('cpu_info', types.MediumText(), nullable=False),
sa.Column('disk_available_least', sa.Integer), sa.Column('disk_available_least', sa.Integer),
sa.Column('free_ram_mb', sa.Integer), sa.Column('free_ram_mb', sa.Integer),
sa.Column('free_disk_gb', sa.Integer), sa.Column('free_disk_gb', sa.Integer),
@ -686,7 +678,7 @@ def upgrade():
'instances.uuid', name='fk_instance_faults_instance_uuid')), 'instances.uuid', name='fk_instance_faults_instance_uuid')),
sa.Column('code', sa.Integer, nullable=False), sa.Column('code', sa.Integer, nullable=False),
sa.Column('message', sa.String(length=255)), sa.Column('message', sa.String(length=255)),
sa.Column('details', MediumText()), sa.Column('details', types.MediumText()),
sa.Column('host', sa.String(length=255)), sa.Column('host', sa.String(length=255)),
sa.Column('deleted', sa.Integer), sa.Column('deleted', sa.Integer),
sa.Index('instance_faults_host_idx', 'host'), sa.Index('instance_faults_host_idx', 'host'),
@ -716,7 +708,7 @@ def upgrade():
sa.Column('updated_at', sa.DateTime), sa.Column('updated_at', sa.DateTime),
sa.Column('deleted_at', sa.DateTime), sa.Column('deleted_at', sa.DateTime),
sa.Column('id', sa.Integer, primary_key=True, nullable=False), sa.Column('id', sa.Integer, primary_key=True, nullable=False),
sa.Column('network_info', MediumText()), sa.Column('network_info', types.MediumText()),
sa.Column( sa.Column(
'instance_uuid', sa.String(length=36), 'instance_uuid', sa.String(length=36),
sa.ForeignKey( sa.ForeignKey(
@ -1004,7 +996,7 @@ def upgrade():
sa.Column('name', sa.String(length=255), nullable=False), sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.String(length=255)), sa.Column('user_id', sa.String(length=255)),
sa.Column('fingerprint', sa.String(length=255)), sa.Column('fingerprint', sa.String(length=255)),
sa.Column('public_key', MediumText()), sa.Column('public_key', types.MediumText()),
sa.Column('deleted', sa.Integer), sa.Column('deleted', sa.Integer),
sa.Column( sa.Column(
'type', sa.Enum('ssh', 'x509', name='keypair_types'), 'type', sa.Enum('ssh', 'x509', name='keypair_types'),

View File

@ -31,17 +31,13 @@ from sqlalchemy import schema
from nova.db import types from nova.db import types
CONF = cfg.CONF CONF = cfg.CONF
# we don't configure 'cls' since we have models that don't use the
# TimestampMixin
BASE = declarative.declarative_base() BASE = declarative.declarative_base()
def MediumText(): class NovaBase(models.TimestampMixin, models.ModelBase):
return sa.Text().with_variant(
sqlalchemy.dialects.mysql.MEDIUMTEXT(), 'mysql')
class NovaBase(models.TimestampMixin,
models.ModelBase):
metadata = None
def __copy__(self): def __copy__(self):
"""Implement a safe copy.copy(). """Implement a safe copy.copy().
@ -133,7 +129,7 @@ class ComputeNode(BASE, NovaBase, models.SoftDeleteMixin):
vcpus_used = sa.Column(sa.Integer, nullable=False) vcpus_used = sa.Column(sa.Integer, nullable=False)
memory_mb_used = sa.Column(sa.Integer, nullable=False) memory_mb_used = sa.Column(sa.Integer, nullable=False)
local_gb_used = sa.Column(sa.Integer, nullable=False) local_gb_used = sa.Column(sa.Integer, nullable=False)
hypervisor_type = sa.Column(MediumText(), nullable=False) hypervisor_type = sa.Column(types.MediumText(), nullable=False)
hypervisor_version = sa.Column(sa.Integer, nullable=False) hypervisor_version = sa.Column(sa.Integer, nullable=False)
hypervisor_hostname = sa.Column(sa.String(255)) hypervisor_hostname = sa.Column(sa.String(255))
@ -155,7 +151,7 @@ class ComputeNode(BASE, NovaBase, models.SoftDeleteMixin):
# Points are "json translatable" and it must have all dictionary keys # Points are "json translatable" and it must have all dictionary keys
# above, since it is copied from <cpu> tag of getCapabilities() # above, since it is copied from <cpu> tag of getCapabilities()
# (See libvirt.virtConnection). # (See libvirt.virtConnection).
cpu_info = sa.Column(MediumText(), nullable=False) cpu_info = sa.Column(types.MediumText(), nullable=False)
disk_available_least = sa.Column(sa.Integer) disk_available_least = sa.Column(sa.Integer)
host_ip = sa.Column(types.IPAddress()) host_ip = sa.Column(types.IPAddress())
supported_instances = sa.Column(sa.Text) supported_instances = sa.Column(sa.Text)
@ -265,7 +261,7 @@ class Instance(BASE, NovaBase, models.SoftDeleteMixin):
launch_index = sa.Column(sa.Integer) launch_index = sa.Column(sa.Integer)
key_name = sa.Column(sa.String(255)) key_name = sa.Column(sa.String(255))
key_data = sa.Column(MediumText()) key_data = sa.Column(types.MediumText())
power_state = sa.Column(sa.Integer) power_state = sa.Column(sa.Integer)
vm_state = sa.Column(sa.String(255)) vm_state = sa.Column(sa.String(255))
@ -287,7 +283,7 @@ class Instance(BASE, NovaBase, models.SoftDeleteMixin):
# *not* flavorid, this is the internal primary_key # *not* flavorid, this is the internal primary_key
instance_type_id = sa.Column(sa.Integer) instance_type_id = sa.Column(sa.Integer)
user_data = sa.Column(MediumText()) user_data = sa.Column(types.MediumText())
reservation_id = sa.Column(sa.String(255)) reservation_id = sa.Column(sa.String(255))
@ -305,7 +301,7 @@ class Instance(BASE, NovaBase, models.SoftDeleteMixin):
# To remember on which host an instance booted. # To remember on which host an instance booted.
# An instance may have moved to another host by live migration. # An instance may have moved to another host by live migration.
launched_on = sa.Column(MediumText()) launched_on = sa.Column(types.MediumText())
# locked is superseded by locked_by and locked is not really # locked is superseded by locked_by and locked is not really
# necessary but still used in API code so it remains. # necessary but still used in API code so it remains.
@ -369,7 +365,7 @@ class InstanceInfoCache(BASE, NovaBase, models.SoftDeleteMixin):
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
# text column used for storing a json object of network data for api # text column used for storing a json object of network data for api
network_info = sa.Column(MediumText()) network_info = sa.Column(types.MediumText())
instance_uuid = sa.Column(sa.String(36), sa.ForeignKey('instances.uuid'), instance_uuid = sa.Column(sa.String(36), sa.ForeignKey('instances.uuid'),
nullable=False) nullable=False)
@ -644,7 +640,7 @@ class BlockDeviceMapping(BASE, NovaBase, models.SoftDeleteMixin):
# for no device to suppress devices. # for no device to suppress devices.
no_device = sa.Column(sa.Boolean) no_device = sa.Column(sa.Boolean)
connection_info = sa.Column(MediumText()) connection_info = sa.Column(types.MediumText())
tag = sa.Column(sa.String(255)) tag = sa.Column(sa.String(255))
@ -767,7 +763,7 @@ class KeyPair(BASE, NovaBase, models.SoftDeleteMixin):
user_id = sa.Column(sa.String(255)) user_id = sa.Column(sa.String(255))
fingerprint = sa.Column(sa.String(255)) fingerprint = sa.Column(sa.String(255))
public_key = sa.Column(MediumText()) public_key = sa.Column(types.MediumText())
type = sa.Column(sa.Enum('ssh', 'x509', name='keypair_types'), type = sa.Column(sa.Enum('ssh', 'x509', name='keypair_types'),
nullable=False, server_default='ssh') nullable=False, server_default='ssh')
@ -1323,7 +1319,7 @@ class InstanceFault(BASE, NovaBase, models.SoftDeleteMixin):
sa.ForeignKey('instances.uuid')) sa.ForeignKey('instances.uuid'))
code = sa.Column(sa.Integer(), nullable=False) code = sa.Column(sa.Integer(), nullable=False)
message = sa.Column(sa.String(255)) message = sa.Column(sa.String(255))
details = sa.Column(MediumText()) details = sa.Column(types.MediumText())
host = sa.Column(sa.String(255)) host = sa.Column(sa.String(255))

View File

@ -17,12 +17,23 @@
import netaddr import netaddr
from oslo_utils import netutils from oslo_utils import netutils
from sqlalchemy.dialects import postgresql import sqlalchemy as sa
import sqlalchemy.dialects.mysql
import sqlalchemy.dialects.postgresql
from sqlalchemy import types from sqlalchemy import types
from nova import utils from nova import utils
# NOTE(dprince): This wrapper allows us to easily match the Folsom MySQL
# Schema. In Folsom we created tables as latin1 and converted them to utf8
# later. This conversion causes some of the Text columns on MySQL to get
# created as mediumtext instead of just text.
def MediumText():
return sa.Text().with_variant(
sqlalchemy.dialects.mysql.MEDIUMTEXT(), 'mysql')
class IPAddress(types.TypeDecorator): class IPAddress(types.TypeDecorator):
"""An SQLAlchemy type representing an IP-address.""" """An SQLAlchemy type representing an IP-address."""
@ -30,9 +41,10 @@ class IPAddress(types.TypeDecorator):
def load_dialect_impl(self, dialect): def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql': if dialect.name == 'postgresql':
return dialect.type_descriptor(postgresql.INET()) return dialect.type_descriptor(
else: sqlalchemy.dialects.postgresql.INET())
return dialect.type_descriptor(types.String(39))
return dialect.type_descriptor(types.String(39))
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
"""Process/Formats the value before insert it into the db.""" """Process/Formats the value before insert it into the db."""
@ -40,7 +52,7 @@ class IPAddress(types.TypeDecorator):
return value return value
# NOTE(maurosr): The purpose here is to convert ipv6 to the shortened # NOTE(maurosr): The purpose here is to convert ipv6 to the shortened
# form, not validate it. # form, not validate it.
elif netutils.is_valid_ipv6(value): if netutils.is_valid_ipv6(value):
return utils.get_shortened_ipv6(value) return utils.get_shortened_ipv6(value)
return value return value
@ -52,9 +64,10 @@ class CIDR(types.TypeDecorator):
def load_dialect_impl(self, dialect): def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql': if dialect.name == 'postgresql':
return dialect.type_descriptor(postgresql.INET()) return dialect.type_descriptor(
else: sqlalchemy.dialects.postgresql.INET())
return dialect.type_descriptor(types.String(43))
return dialect.type_descriptor(types.String(43))
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
"""Process/Formats the value before insert it into the db.""" """Process/Formats the value before insert it into the db."""

View File

@ -60,7 +60,7 @@ class NovaModelsMigrationsSync(test_migrations.ModelsMigrationsSync):
return self.engine return self.engine
def get_metadata(self): def get_metadata(self):
return models.API_BASE.metadata return models.BASE.metadata
def include_object(self, object_, name, type_, reflected, compare_to): def include_object(self, object_, name, type_, reflected, compare_to):
if type_ == 'table': if type_ == 'table':

View File

@ -14,7 +14,7 @@
# under the License. # under the License.
from nova.db.api import models as api_models from nova.db.api import models as api_models
from nova.db.main import models from nova.db.main import models as main_models
from nova import test from nova import test
@ -78,9 +78,10 @@ class TestSoftDeletesDeprecated(test.NoDBTestCase):
# Soft deletes are deprecated. Whitelist the tables that currently # Soft deletes are deprecated. Whitelist the tables that currently
# allow soft deletes. No new tables should be added to this whitelist. # allow soft deletes. No new tables should be added to this whitelist.
tables = [] tables = []
for base in [models.BASE, api_models.API_BASE]: for base in [main_models.BASE, api_models.BASE]:
for table_name, table in base.metadata.tables.items(): for table_name, table in base.metadata.tables.items():
columns = [column.name for column in table.columns] columns = [column.name for column in table.columns]
if 'deleted' in columns or 'deleted_at' in columns: if 'deleted' in columns or 'deleted_at' in columns:
tables.append(table_name) tables.append(table_name)
self.assertEqual(whitelist, sorted(tables)) self.assertEqual(whitelist, sorted(tables))

View File

@ -62,8 +62,10 @@ class _TestFlavor(object):
# but the object has tz-aware datetimes. If we're comparing # but the object has tz-aware datetimes. If we're comparing
# a model to an object (as opposed to a fake dict), just # a model to an object (as opposed to a fake dict), just
# ignore the datetimes in the comparison. # ignore the datetimes in the comparison.
if (isinstance(db, api_models.API_BASE) and if (
isinstance(value, datetime.datetime)): isinstance(db, api_models.BASE) and
isinstance(value, datetime.datetime)
):
continue continue
test.assertEqual(db[field], obj[field]) test.assertEqual(db[field], obj[field])