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

View File

@ -30,7 +30,3 @@ depends_on = ${repr(depends_on)}
def upgrade():
${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
from sqlalchemy import dialects
from nova.db.api.models import MediumText
from nova.db import types
from nova.objects import keypair
# revision identifiers, used by Alembic.
@ -164,7 +164,7 @@ def upgrade():
sa.Column('updated_at', sa.DateTime),
sa.Column('id', sa.Integer, primary_key=True, 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(
'instance_uuid', name='uniq_request_specs0instance_uuid'),
sa.Index('request_spec_instance_uuid_idx', 'instance_uuid'),
@ -196,8 +196,8 @@ def upgrade():
'locked_by',
sa.Enum('owner', 'admin', name='build_requests0locked_by')),
sa.Column('instance_uuid', sa.String(length=36)),
sa.Column('instance', MediumText()),
sa.Column('block_device_mappings', MediumText()),
sa.Column('instance', types.MediumText()),
sa.Column('block_device_mappings', types.MediumText()),
sa.Column('tags', sa.Text()),
sa.UniqueConstraint(
'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
import sqlalchemy as sa
import sqlalchemy.dialects.mysql
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext import declarative
from sqlalchemy import orm
from sqlalchemy import schema
from nova.db import types
LOG = logging.getLogger(__name__)
def MediumText():
return sa.Text().with_variant(
sqlalchemy.dialects.mysql.MEDIUMTEXT(), 'mysql')
class _NovaAPIBase(models.ModelBase, models.TimestampMixin):
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."""
__tablename__ = 'aggregate_hosts'
__table_args__ = (schema.UniqueConstraint(
@ -48,7 +45,7 @@ class AggregateHost(API_BASE):
sa.Integer, sa.ForeignKey('aggregates.id'), nullable=False)
class AggregateMetadata(API_BASE):
class AggregateMetadata(BASE):
"""Represents a metadata key/value pair for an aggregate."""
__tablename__ = 'aggregate_metadata'
__table_args__ = (
@ -64,7 +61,7 @@ class AggregateMetadata(API_BASE):
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."""
__tablename__ = 'aggregates'
__table_args__ = (
@ -102,7 +99,7 @@ class Aggregate(API_BASE):
return self.metadetails['availability_zone']
class CellMapping(API_BASE):
class CellMapping(BASE):
"""Contains information on communicating with a cell"""
__tablename__ = 'cell_mappings'
__table_args__ = (
@ -123,7 +120,7 @@ class CellMapping(API_BASE):
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"""
__tablename__ = 'instance_mappings'
__table_args__ = (
@ -154,7 +151,7 @@ class InstanceMapping(API_BASE):
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"""
__tablename__ = "host_mappings"
__table_args__ = (
@ -168,7 +165,7 @@ class HostMapping(API_BASE):
host = sa.Column(sa.String(255), nullable=False)
class RequestSpec(API_BASE):
class RequestSpec(BASE):
"""Represents the information passed to the scheduler."""
__tablename__ = 'request_specs'
@ -180,10 +177,10 @@ class RequestSpec(API_BASE):
id = sa.Column(sa.Integer, primary_key=True)
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"""
__tablename__ = 'flavors'
__table_args__ = (
@ -205,7 +202,7 @@ class Flavors(API_BASE):
description = sa.Column(sa.Text)
class FlavorExtraSpecs(API_BASE):
class FlavorExtraSpecs(BASE):
"""Represents additional specs as key/value pairs for a flavor"""
__tablename__ = 'flavor_extra_specs'
__table_args__ = (
@ -226,7 +223,7 @@ class FlavorExtraSpecs(API_BASE):
primaryjoin='FlavorExtraSpecs.flavor_id == Flavors.id')
class FlavorProjects(API_BASE):
class FlavorProjects(BASE):
"""Represents projects associated with flavors"""
__tablename__ = 'flavor_projects'
__table_args__ = (schema.UniqueConstraint('flavor_id', 'project_id',
@ -242,7 +239,7 @@ class FlavorProjects(API_BASE):
primaryjoin='FlavorProjects.flavor_id == Flavors.id')
class BuildRequest(API_BASE):
class BuildRequest(BASE):
"""Represents the information passed to the scheduler."""
__tablename__ = 'build_requests'
@ -257,8 +254,8 @@ class BuildRequest(API_BASE):
# 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(MediumText())
block_device_mappings = sa.Column(MediumText())
instance = sa.Column(types.MediumText())
block_device_mappings = sa.Column(types.MediumText())
tags = sa.Column(sa.Text())
# TODO(alaski): Drop these from the db in Ocata
# 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']
class KeyPair(API_BASE):
class KeyPair(BASE):
"""Represents a public key pair for ssh / WinRM."""
__tablename__ = 'key_pairs'
__table_args__ = (
@ -288,7 +285,7 @@ class KeyPair(API_BASE):
# 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."""
__tablename__ = 'resource_classes'
__table_args__ = (
@ -300,7 +297,7 @@ class ResourceClass(API_BASE):
# 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."""
__tablename__ = "resource_providers"
@ -332,7 +329,7 @@ class ResourceProvider(API_BASE):
# 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."""
__tablename__ = "inventories"
@ -369,7 +366,7 @@ class Inventory(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split
class Allocation(API_BASE):
class Allocation(BASE):
"""A use of inventory."""
__tablename__ = "allocations"
@ -396,7 +393,7 @@ class Allocation(API_BASE):
# 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."""
__tablename__ = 'resource_provider_aggregates'
@ -411,7 +408,7 @@ class ResourceProviderAggregate(API_BASE):
# TODO(stephenfin): Remove this as it's now unused post-placement split
class PlacementAggregate(API_BASE):
class PlacementAggregate(BASE):
"""A grouping of resource providers."""
__tablename__ = 'placement_aggregates'
__table_args__ = (
@ -422,7 +419,7 @@ class PlacementAggregate(API_BASE):
uuid = sa.Column(sa.String(36), index=True)
class InstanceGroupMember(API_BASE):
class InstanceGroupMember(BASE):
"""Represents the members for an instance group."""
__tablename__ = 'instance_group_member'
__table_args__ = (
@ -434,7 +431,7 @@ class InstanceGroupMember(API_BASE):
sa.Integer, sa.ForeignKey('instance_groups.id'), nullable=False)
class InstanceGroupPolicy(API_BASE):
class InstanceGroupPolicy(BASE):
"""Represents the policy type for an instance group."""
__tablename__ = 'instance_group_policy'
__table_args__ = (
@ -448,7 +445,7 @@ class InstanceGroupPolicy(API_BASE):
rules = sa.Column(sa.Text)
class InstanceGroup(API_BASE):
class InstanceGroup(BASE):
"""Represents an instance group.
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]
class Quota(API_BASE):
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
@ -511,7 +508,7 @@ class Quota(API_BASE):
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."""
__tablename__ = 'project_user_quotas'
@ -535,7 +532,7 @@ class ProjectUserQuota(API_BASE):
hard_limit = sa.Column(sa.Integer)
class QuotaClass(API_BASE):
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
@ -555,7 +552,7 @@ class QuotaClass(API_BASE):
hard_limit = sa.Column(sa.Integer)
class QuotaUsage(API_BASE):
class QuotaUsage(BASE):
"""Represents the current usage for a given resource."""
__tablename__ = 'quota_usages'
@ -578,7 +575,7 @@ class QuotaUsage(API_BASE):
until_refresh = sa.Column(sa.Integer)
class Reservation(API_BASE):
class Reservation(BASE):
"""Represents a resource reservation for quotas."""
__tablename__ = 'reservations'
@ -604,7 +601,7 @@ class Reservation(API_BASE):
primaryjoin='Reservation.usage_id == QuotaUsage.id')
class Trait(API_BASE):
class Trait(BASE):
"""Represents a trait."""
__tablename__ = "traits"
@ -618,7 +615,7 @@ class Trait(API_BASE):
# 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"""
__tablename__ = "resource_provider_traits"
@ -638,7 +635,7 @@ class ResourceProviderTrait(API_BASE):
# TODO(stephenfin): Remove this as it's unused
class Project(API_BASE):
class Project(BASE):
"""The project is the Keystone project."""
__tablename__ = 'projects'
@ -655,7 +652,7 @@ class Project(API_BASE):
# TODO(stephenfin): Remove this as it's unused
class User(API_BASE):
class User(BASE):
"""The user is the Keystone user."""
__tablename__ = 'users'
@ -669,7 +666,7 @@ class User(API_BASE):
# TODO(stephenfin): Remove this as it's unused
class Consumer(API_BASE):
class Consumer(BASE):
"""Represents a resource consumer."""
__tablename__ = 'consumers'

View File

@ -25,14 +25,6 @@ from nova.objects import keypair
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():
return sa.String(length=43).with_variant(
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_size', sa.Integer),
sa.Column('no_device', sa.Boolean),
sa.Column('connection_info', MediumText()),
sa.Column('connection_info', types.MediumText()),
sa.Column(
'instance_uuid', sa.String(length=36),
sa.ForeignKey(
@ -391,9 +383,9 @@ def upgrade(migrate_engine):
sa.Column('vcpus_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('hypervisor_type', MediumText(), nullable=False),
sa.Column('hypervisor_type', types.MediumText(), 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('free_ram_mb', 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')),
sa.Column('code', sa.Integer, nullable=False),
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('deleted', sa.Integer),
sa.Index('instance_faults_host_idx', 'host'),
@ -612,7 +604,7 @@ def upgrade(migrate_engine):
sa.Column('updated_at', sa.DateTime),
sa.Column('deleted_at', sa.DateTime),
sa.Column('id', sa.Integer, primary_key=True, nullable=False),
sa.Column('network_info', MediumText()),
sa.Column('network_info', types.MediumText()),
sa.Column(
'instance_uuid', sa.String(length=36),
sa.ForeignKey(
@ -797,14 +789,14 @@ def upgrade(migrate_engine):
sa.Column('ramdisk_id', sa.String(length=255)),
sa.Column('launch_index', sa.Integer),
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('vm_state', sa.String(length=255)),
sa.Column('memory_mb', sa.Integer),
sa.Column('vcpus', sa.Integer),
sa.Column('hostname', 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('launched_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('locked', sa.Boolean),
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('vm_mode', sa.String(length=255)),
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('user_id', 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(
'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 pool
from nova.db.main import models
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
@ -29,7 +31,20 @@ if config.attributes.get('configure_logger', True):
# for 'autogenerate' support
# from myapp import mymodel
# 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():
@ -45,6 +60,7 @@ def run_migrations_offline():
context.configure(
url=url,
target_metadata=target_metadata,
include_name=include_name,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
@ -80,7 +96,9 @@ def run_migrations_online():
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
connection=connection,
target_metadata=target_metadata,
include_name=include_name,
)
with context.begin_transaction():

View File

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

View File

@ -33,14 +33,6 @@ branch_labels = 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():
return sa.String(length=43).with_variant(
dialects.postgresql.INET(), 'postgresql',
@ -204,14 +196,14 @@ def upgrade():
sa.Column('ramdisk_id', sa.String(length=255)),
sa.Column('launch_index', sa.Integer),
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('vm_state', sa.String(length=255)),
sa.Column('memory_mb', sa.Integer),
sa.Column('vcpus', sa.Integer),
sa.Column('hostname', 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('launched_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('locked', sa.Boolean),
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('vm_mode', sa.String(length=255)),
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_size', sa.Integer),
sa.Column('no_device', sa.Boolean),
sa.Column('connection_info', MediumText()),
sa.Column('connection_info', types.MediumText()),
sa.Column(
'instance_uuid', sa.String(length=36),
sa.ForeignKey(
@ -486,9 +478,9 @@ def upgrade():
sa.Column('vcpus_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('hypervisor_type', MediumText(), nullable=False),
sa.Column('hypervisor_type', types.MediumText(), 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('free_ram_mb', sa.Integer),
sa.Column('free_disk_gb', sa.Integer),
@ -686,7 +678,7 @@ def upgrade():
'instances.uuid', name='fk_instance_faults_instance_uuid')),
sa.Column('code', sa.Integer, nullable=False),
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('deleted', sa.Integer),
sa.Index('instance_faults_host_idx', 'host'),
@ -716,7 +708,7 @@ def upgrade():
sa.Column('updated_at', sa.DateTime),
sa.Column('deleted_at', sa.DateTime),
sa.Column('id', sa.Integer, primary_key=True, nullable=False),
sa.Column('network_info', MediumText()),
sa.Column('network_info', types.MediumText()),
sa.Column(
'instance_uuid', sa.String(length=36),
sa.ForeignKey(
@ -1004,7 +996,7 @@ def upgrade():
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('user_id', 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(
'type', sa.Enum('ssh', 'x509', name='keypair_types'),

View File

@ -31,17 +31,13 @@ from sqlalchemy import schema
from nova.db import types
CONF = cfg.CONF
# we don't configure 'cls' since we have models that don't use the
# TimestampMixin
BASE = declarative.declarative_base()
def MediumText():
return sa.Text().with_variant(
sqlalchemy.dialects.mysql.MEDIUMTEXT(), 'mysql')
class NovaBase(models.TimestampMixin,
models.ModelBase):
metadata = None
class NovaBase(models.TimestampMixin, models.ModelBase):
def __copy__(self):
"""Implement a safe copy.copy().
@ -133,7 +129,7 @@ class ComputeNode(BASE, NovaBase, models.SoftDeleteMixin):
vcpus_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)
hypervisor_type = sa.Column(MediumText(), nullable=False)
hypervisor_type = sa.Column(types.MediumText(), nullable=False)
hypervisor_version = sa.Column(sa.Integer, nullable=False)
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
# above, since it is copied from <cpu> tag of getCapabilities()
# (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)
host_ip = sa.Column(types.IPAddress())
supported_instances = sa.Column(sa.Text)
@ -265,7 +261,7 @@ class Instance(BASE, NovaBase, models.SoftDeleteMixin):
launch_index = sa.Column(sa.Integer)
key_name = sa.Column(sa.String(255))
key_data = sa.Column(MediumText())
key_data = sa.Column(types.MediumText())
power_state = sa.Column(sa.Integer)
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
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))
@ -305,7 +301,7 @@ class Instance(BASE, NovaBase, models.SoftDeleteMixin):
# To remember on which host an instance booted.
# 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
# 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)
# 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'),
nullable=False)
@ -644,7 +640,7 @@ class BlockDeviceMapping(BASE, NovaBase, models.SoftDeleteMixin):
# for no device to suppress devices.
no_device = sa.Column(sa.Boolean)
connection_info = sa.Column(MediumText())
connection_info = sa.Column(types.MediumText())
tag = sa.Column(sa.String(255))
@ -767,7 +763,7 @@ class KeyPair(BASE, NovaBase, models.SoftDeleteMixin):
user_id = 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'),
nullable=False, server_default='ssh')
@ -1323,7 +1319,7 @@ class InstanceFault(BASE, NovaBase, models.SoftDeleteMixin):
sa.ForeignKey('instances.uuid'))
code = sa.Column(sa.Integer(), nullable=False)
message = sa.Column(sa.String(255))
details = sa.Column(MediumText())
details = sa.Column(types.MediumText())
host = sa.Column(sa.String(255))

View File

@ -17,12 +17,23 @@
import netaddr
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 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):
"""An SQLAlchemy type representing an IP-address."""
@ -30,9 +41,10 @@ class IPAddress(types.TypeDecorator):
def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql':
return dialect.type_descriptor(postgresql.INET())
else:
return dialect.type_descriptor(types.String(39))
return dialect.type_descriptor(
sqlalchemy.dialects.postgresql.INET())
return dialect.type_descriptor(types.String(39))
def process_bind_param(self, value, dialect):
"""Process/Formats the value before insert it into the db."""
@ -40,7 +52,7 @@ class IPAddress(types.TypeDecorator):
return value
# NOTE(maurosr): The purpose here is to convert ipv6 to the shortened
# form, not validate it.
elif netutils.is_valid_ipv6(value):
if netutils.is_valid_ipv6(value):
return utils.get_shortened_ipv6(value)
return value
@ -52,9 +64,10 @@ class CIDR(types.TypeDecorator):
def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql':
return dialect.type_descriptor(postgresql.INET())
else:
return dialect.type_descriptor(types.String(43))
return dialect.type_descriptor(
sqlalchemy.dialects.postgresql.INET())
return dialect.type_descriptor(types.String(43))
def process_bind_param(self, value, dialect):
"""Process/Formats the value before insert it into the db."""

View File

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

View File

@ -14,7 +14,7 @@
# under the License.
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
@ -78,9 +78,10 @@ class TestSoftDeletesDeprecated(test.NoDBTestCase):
# Soft deletes are deprecated. Whitelist the tables that currently
# allow soft deletes. No new tables should be added to this whitelist.
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():
columns = [column.name for column in table.columns]
if 'deleted' in columns or 'deleted_at' in columns:
tables.append(table_name)
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
# a model to an object (as opposed to a fake dict), just
# ignore the datetimes in the comparison.
if (isinstance(db, api_models.API_BASE) and
isinstance(value, datetime.datetime)):
if (
isinstance(db, api_models.BASE) and
isinstance(value, datetime.datetime)
):
continue
test.assertEqual(db[field], obj[field])