Revert "Implement online schema migrations"
This reverts commit 2f743b2f20a559c5d2e05aa716b9137e928eb017. At the Mitaka design summit we discussed removing this as it is not complete, not under development, and not even an active experiment for anyone. Since this being in the tree is confusing people and known to be dangerous, this removes it from the tree. Conflicts: nova/cmd/manage.py nova/db/sqlalchemy/migration.py nova/exception.py nova/tests/unit/db/test_migrations.py Change-Id: Icae28ceee3ec975c907d73b95babab58dcb30c23
This commit is contained in:
parent
63347382d5
commit
e670e4d3bb
@ -909,31 +909,6 @@ class DbCommands(object):
|
||||
"""Sync the database up to the most recent version."""
|
||||
return migration.db_sync(version)
|
||||
|
||||
@args('--dry-run', action='store_true', dest='dry_run',
|
||||
default=False, help='Print SQL statements instead of executing')
|
||||
def expand(self, dry_run):
|
||||
"""Expand database schema."""
|
||||
return migration.db_expand(dry_run)
|
||||
|
||||
@args('--dry-run', action='store_true', dest='dry_run',
|
||||
default=False, help='Print SQL statements instead of executing')
|
||||
def migrate(self, dry_run):
|
||||
"""Migrate database schema."""
|
||||
return migration.db_migrate(dry_run)
|
||||
|
||||
@args('--dry-run', action='store_true', dest='dry_run',
|
||||
default=False, help='Print SQL statements instead of executing')
|
||||
@args('--force-experimental-contract', action='store_true',
|
||||
dest='force_experimental_contract',
|
||||
help="Force experimental contract operation to run *VOLATILE*")
|
||||
def contract(self, dry_run, force_experimental_contract=False):
|
||||
"""Contract database schema."""
|
||||
if force_experimental_contract:
|
||||
return migration.db_contract(dry_run)
|
||||
print('The "contract" command is experimental and potentially '
|
||||
'dangerous. As such, it is disabled by default. Enable using '
|
||||
'"--force-experimental".')
|
||||
|
||||
def version(self):
|
||||
"""Print the current database version."""
|
||||
print(migration.db_version())
|
||||
|
@ -26,21 +26,6 @@ def db_sync(version=None, database='main'):
|
||||
return IMPL.db_sync(version=version, database=database)
|
||||
|
||||
|
||||
def db_expand(dryrun=False, database='main'):
|
||||
"""Expand database schema."""
|
||||
return IMPL.db_expand(dryrun=dryrun, database=database)
|
||||
|
||||
|
||||
def db_migrate(dryrun=False, database='main'):
|
||||
"""Migrate database schema."""
|
||||
return IMPL.db_migrate(dryrun=dryrun, database=database)
|
||||
|
||||
|
||||
def db_contract(dryrun=False, database='main'):
|
||||
"""Contract database schema."""
|
||||
return IMPL.db_contract(dryrun=dryrun, database=database)
|
||||
|
||||
|
||||
def db_version(database='main'):
|
||||
"""Display the current database version."""
|
||||
return IMPL.db_version(database=database)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -728,16 +728,9 @@ class Migration(BASE, NovaBase):
|
||||
__table_args__ = (
|
||||
Index('migrations_instance_uuid_and_status_idx', 'deleted',
|
||||
'instance_uuid', 'status'),
|
||||
# MySQL has a limit of 3072 bytes for an multi-column index. This
|
||||
# index ends up being larger than that using the utf-8 encoding.
|
||||
# Limiting the index to the prefixes will keep it under the limit.
|
||||
# FIXME(johannes): Is it MySQL or InnoDB that imposes the limit?
|
||||
Index('migrations_by_host_nodes_and_status_idx', 'deleted',
|
||||
'source_compute', 'dest_compute', 'source_node', 'dest_node',
|
||||
'status', mysql_length={'source_compute': 100,
|
||||
'dest_compute': 100,
|
||||
'source_node': 100,
|
||||
'dest_node': 100}),
|
||||
'status'),
|
||||
)
|
||||
id = Column(Integer, primary_key=True, nullable=False)
|
||||
# NOTE(tr3buchet): the ____compute variables are instance['host']
|
||||
|
@ -1950,9 +1950,5 @@ class UnsupportedImageModel(Invalid):
|
||||
msg_fmt = _("Image model '%(image)s' is not supported")
|
||||
|
||||
|
||||
class DatabaseMigrationError(NovaException):
|
||||
msg_fmt = _("Database migration failed: %(reason)s")
|
||||
|
||||
|
||||
class HostMappingNotFound(Invalid):
|
||||
msg_fmt = _("Host '%(name)s' is not mapped to any cell")
|
||||
|
@ -39,7 +39,6 @@ import glob
|
||||
import logging
|
||||
import os
|
||||
|
||||
import alembic
|
||||
from migrate import UniqueConstraint
|
||||
from migrate.versioning import repository
|
||||
import mock
|
||||
@ -871,301 +870,3 @@ class ProjectTestCase(test.NoDBTestCase):
|
||||
"which is not supported:"
|
||||
"\n\t%s" % '\n\t'.join(sorted(includes_downgrade)))
|
||||
self.assertFalse(includes_downgrade, helpful_msg)
|
||||
|
||||
|
||||
class ExpandTest(test.NoDBTestCase):
|
||||
|
||||
@mock.patch('nova.db.sqlalchemy.migration._schedule_schema_changes')
|
||||
@mock.patch('nova.db.sqlalchemy.migration._find_migrate_repo')
|
||||
def test_dryrun(self, find_repo, schedule):
|
||||
# we shouldn't lock the sqlalchemy migrate table on a dry run
|
||||
schedule.return_value = [], [], []
|
||||
sa_migration.db_expand(dryrun=True)
|
||||
self.assertEqual([], find_repo.mock_calls)
|
||||
|
||||
|
||||
class SchemaChangeSchedulerTest(test.NoDBTestCase):
|
||||
def test_add_fk_after_add_column(self):
|
||||
exist_meta = sqlalchemy.MetaData()
|
||||
sqlalchemy.Table('a', exist_meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
sqlalchemy.Table('b', exist_meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
|
||||
model_meta = sqlalchemy.MetaData()
|
||||
sqlalchemy.Table('a', model_meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
column = sqlalchemy.Column('a_id', sqlalchemy.Integer,
|
||||
sqlalchemy.ForeignKey('a.id'))
|
||||
table = sqlalchemy.Table('b', model_meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
column)
|
||||
fkc = sqlalchemy.ForeignKeyConstraint(['a_id'], ['a.id'],
|
||||
table=table)
|
||||
|
||||
addcolumn = sa_migration.AddColumn('b', column,
|
||||
desired_phase='migrate')
|
||||
addfk = sa_migration.AddForeignKey(fkc)
|
||||
|
||||
scheduler = sa_migration.Scheduler()
|
||||
scheduler.add(addfk)
|
||||
scheduler.add(addcolumn)
|
||||
|
||||
expand, migrate, contract = scheduler.schedule()
|
||||
self.assertEqual([], expand)
|
||||
self.assertEqual([addcolumn, addfk], migrate)
|
||||
self.assertEqual([], contract)
|
||||
|
||||
def test_remove_index_after_add(self):
|
||||
exist_meta = sqlalchemy.MetaData()
|
||||
oldtbl = sqlalchemy.Table('a', exist_meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('foo', sqlalchemy.Integer))
|
||||
|
||||
model_meta = sqlalchemy.MetaData()
|
||||
newtbl = sqlalchemy.Table('a', model_meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
|
||||
old_index = sqlalchemy.Index('a_id_idx', oldtbl.c.id, oldtbl.c.foo)
|
||||
new_index = sqlalchemy.Index('a_id_idx', newtbl.c.id)
|
||||
|
||||
dropidx = sa_migration.DropIndex(old_index)
|
||||
addidx = sa_migration.AddIndex(new_index, {})
|
||||
|
||||
scheduler = sa_migration.Scheduler()
|
||||
scheduler.add(addidx)
|
||||
scheduler.add(dropidx)
|
||||
|
||||
expand, migrate, contract = scheduler.schedule()
|
||||
self.assertEqual([], expand)
|
||||
self.assertEqual([dropidx, addidx], migrate)
|
||||
self.assertEqual([], contract)
|
||||
|
||||
|
||||
def _table(*args, **kwargs):
|
||||
kwargs = kwargs.copy()
|
||||
kwargs['mysql_engine'] = 'InnoDB'
|
||||
return sqlalchemy.Table(*args, **kwargs)
|
||||
|
||||
|
||||
class SchemaChangeDDLCheckers(object):
|
||||
def setUp(self):
|
||||
super(SchemaChangeDDLCheckers, self).setUp()
|
||||
|
||||
context = alembic.migration.MigrationContext.configure(self.engine)
|
||||
self.ddlop = alembic.operations.Operations(context)
|
||||
|
||||
def test_add_table(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
meta.create_all(self.engine)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
self.assertIn('id', table.c)
|
||||
|
||||
def test_drop_table(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
meta.create_all(self.engine)
|
||||
|
||||
# Will raise exception if table does not exist
|
||||
oslodbutils.get_table(self.engine, 'a')
|
||||
|
||||
op = sa_migration.DropTable(table)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
self.assertRaises(sqlalchemy.exc.NoSuchTableError,
|
||||
oslodbutils.get_table, self.engine, 'a')
|
||||
|
||||
def test_add_column(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
meta.create_all(self.engine)
|
||||
|
||||
column = sqlalchemy.Column('uuid', sqlalchemy.String(36))
|
||||
op = sa_migration.AddColumn('a', column)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
self.assertIn('id', table.c)
|
||||
self.assertIn('uuid', table.c)
|
||||
|
||||
def test_alter_column_nullable(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
column = sqlalchemy.Column('uuid', sqlalchemy.String(36))
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
column)
|
||||
meta.create_all(self.engine)
|
||||
|
||||
self.assertTrue(table.c.uuid.nullable)
|
||||
|
||||
op = sa_migration.AlterColumn('a', 'uuid',
|
||||
{'nullable': False,
|
||||
'existing_type': column.type})
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
self.assertFalse(table.c.uuid.nullable)
|
||||
|
||||
def test_alter_column_type(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
column = sqlalchemy.Column('uuid', sqlalchemy.Text)
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
column)
|
||||
meta.create_all(self.engine)
|
||||
|
||||
self.assertIsInstance(table.c.uuid.type, sqlalchemy.Text)
|
||||
|
||||
new_type = sqlalchemy.String(36)
|
||||
|
||||
op = sa_migration.AlterColumn('a', 'uuid',
|
||||
{'nullable': True,
|
||||
'type_': new_type})
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
self.assertIsInstance(table.c.uuid.type, sqlalchemy.String)
|
||||
# Text is a subclass of String, so the previous assert could pass
|
||||
# if the column type didn't change
|
||||
self.assertNotIsInstance(table.c.uuid.type, sqlalchemy.Text)
|
||||
|
||||
def test_drop_column(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
column = sqlalchemy.Column('uuid', sqlalchemy.String(36))
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
column)
|
||||
meta.create_all(self.engine)
|
||||
|
||||
op = sa_migration.DropColumn('a', column)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
self.assertIn('id', table.c)
|
||||
self.assertNotIn('uuid', table.c)
|
||||
|
||||
def test_add_index(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
meta.create_all(self.engine)
|
||||
|
||||
index = sqlalchemy.Index('a_id_idx', table.c.id)
|
||||
|
||||
op = sa_migration.AddIndex(index, {})
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
self.assertIn('a_id_idx', [i.name for i in table.indexes])
|
||||
|
||||
def test_drop_index(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
index = sqlalchemy.Index('a_id_idx', 'id')
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
index)
|
||||
meta.create_all(self.engine)
|
||||
|
||||
op = sa_migration.DropIndex(index)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
self.assertNotIn('a_id_idx', [i.name for i in table.indexes])
|
||||
|
||||
def test_add_unique_constraint(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer))
|
||||
meta.create_all(self.engine)
|
||||
|
||||
uc = sqlalchemy.UniqueConstraint(table.c.id, name='uniq_a_id')
|
||||
|
||||
op = sa_migration.AddUniqueConstraint(uc)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
# Collect all unique indexes and constraints. MySQL will
|
||||
# transparently create unique constraints as unique indexes
|
||||
# (which is different than PostgreSQL). Also, older versions
|
||||
# of SQLAlchemy will sometimes reflect these inconsistently.
|
||||
uniques = {i.name for i in table.indexes if i.unique}
|
||||
uniques.update(c.name for c in table.constraints
|
||||
if isinstance(c, sqlalchemy.UniqueConstraint))
|
||||
self.assertIn('uniq_a_id', uniques)
|
||||
|
||||
def test_drop_unique_constraint(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
uc = sqlalchemy.UniqueConstraint('id', name='uniq_a_id')
|
||||
table = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
uc)
|
||||
meta.create_all(self.engine)
|
||||
|
||||
op = sa_migration.DropUniqueConstraint(uc)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'a')
|
||||
# See comment for test_add_unique_constraint
|
||||
uniques = {i.name for i in table.indexes if i.unique}
|
||||
uniques.update(c.name for c in table.constraints
|
||||
if isinstance(c, sqlalchemy.UniqueConstraint))
|
||||
self.assertNotIn('uniq_a_id', uniques)
|
||||
|
||||
def test_add_foreign_key(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
a = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
sqlalchemy.UniqueConstraint('id'))
|
||||
b = _table('b', meta,
|
||||
sqlalchemy.Column('a_id', sqlalchemy.Integer))
|
||||
meta.create_all(self.engine)
|
||||
|
||||
fkc = sqlalchemy.ForeignKeyConstraint([b.c.a_id], [a.c.id],
|
||||
name='b_a_id_fk')
|
||||
|
||||
op = sa_migration.AddForeignKey(fkc)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'b')
|
||||
fkcs = {c.name: c for c in table.constraints
|
||||
if isinstance(c, sqlalchemy.ForeignKeyConstraint)}
|
||||
self.assertIn('b_a_id_fk', fkcs)
|
||||
|
||||
columns = [(fk.parent.name, fk.column.name)
|
||||
for fk in fkcs['b_a_id_fk'].elements]
|
||||
self.assertEqual([('a_id', 'id')], columns)
|
||||
|
||||
def test_drop_foreign_key(self):
|
||||
meta = sqlalchemy.MetaData()
|
||||
a = _table('a', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer),
|
||||
sqlalchemy.UniqueConstraint('id'))
|
||||
b = _table('b', meta,
|
||||
sqlalchemy.Column('a_id', sqlalchemy.Integer))
|
||||
fkc = sqlalchemy.ForeignKeyConstraint([b.c.a_id], [a.c.id],
|
||||
name='b_a_id_fk')
|
||||
meta.create_all(self.engine)
|
||||
|
||||
op = sa_migration.DropForeignKey(fkc)
|
||||
op.execute(self.ddlop)
|
||||
|
||||
table = oslodbutils.get_table(self.engine, 'b')
|
||||
fkcs = {c.name: c for c in table.constraints}
|
||||
self.assertNotIn('b_a_id_fk', fkcs)
|
||||
|
||||
|
||||
class TestSchemaChangeDDLMySQL(SchemaChangeDDLCheckers,
|
||||
test_base.MySQLOpportunisticTestCase,
|
||||
test.NoDBTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestSchemaChangeDDLPostgreSQL(SchemaChangeDDLCheckers,
|
||||
test_base.PostgreSQLOpportunisticTestCase,
|
||||
test.NoDBTestCase):
|
||||
pass
|
||||
|
@ -97,14 +97,13 @@ class TestNullInstanceUuidScanDB(test.TestCase):
|
||||
|
||||
@mock.patch.object(migration, 'db_version', return_value=2)
|
||||
@mock.patch.object(migration, '_find_migrate_repo', return_value='repo')
|
||||
@mock.patch.object(migration, '_db_sync_locked', return_value=False)
|
||||
@mock.patch.object(versioning_api, 'upgrade')
|
||||
@mock.patch.object(versioning_api, 'downgrade')
|
||||
@mock.patch.object(migration, 'get_engine', return_value='engine')
|
||||
class TestDbSync(test.NoDBTestCase):
|
||||
|
||||
def test_version_none(self, mock_get_engine, mock_downgrade, mock_upgrade,
|
||||
mock_sync_locked, mock_find_repo, mock_version):
|
||||
mock_find_repo, mock_version):
|
||||
database = 'fake'
|
||||
migration.db_sync(database=database)
|
||||
mock_version.assert_called_once_with(database)
|
||||
@ -114,7 +113,7 @@ class TestDbSync(test.NoDBTestCase):
|
||||
self.assertFalse(mock_downgrade.called)
|
||||
|
||||
def test_downgrade(self, mock_get_engine, mock_downgrade, mock_upgrade,
|
||||
mock_sync_locked, mock_find_repo, mock_version):
|
||||
mock_find_repo, mock_version):
|
||||
database = 'fake'
|
||||
migration.db_sync(1, database=database)
|
||||
mock_version.assert_called_once_with(database)
|
||||
|
Loading…
x
Reference in New Issue
Block a user