Wedge DB migrations if flavor migrations are not complete
In Kilo, we moved per-instance flavor information from sysmeta to a new location. We have a lot of compatibity code as a result, which we want to drop ASAP. In order to do that, we need to be sure that nobody can run the post-drop code with unmigrated instances. This adds a new DB migration that just does a sanity check and blocks moving forward if any unmigrated instances are found. NOTE: This does not operate on deleted instances, which matches the behavior of the nova-manage command to push migrations. Depends-On: Ib38f4bdb2b8e478973e0e5aaaeb4a25771af720e Change-Id: Ide5492b8b52dec665088d7bca78c386418eba0fe
This commit is contained in:
parent
de8d19dec1
commit
bf8b3946e6
|
@ -0,0 +1,35 @@
|
|||
# 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 sqlalchemy import MetaData, Table, func, select
|
||||
from sqlalchemy.sql import and_
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData(migrate_engine)
|
||||
instances = Table('instances', meta, autoload=True)
|
||||
sysmeta = Table('instance_system_metadata', meta, autoload=True)
|
||||
count = select([func.count()]).select_from(sysmeta).where(
|
||||
and_(instances.c.uuid == sysmeta.c.instance_uuid,
|
||||
sysmeta.c.key == 'instance_type_id',
|
||||
sysmeta.c.deleted != sysmeta.c.id,
|
||||
instances.c.deleted != instances.c.id)).execute().scalar()
|
||||
if count > 0:
|
||||
msg = _('There are still %(count)i unmigrated flavor records. '
|
||||
'Migration cannot continue until all instance flavor '
|
||||
'records have been migrated to the new format. Please run '
|
||||
'`nova-manage db migrate_flavor_data\' first.') % {
|
||||
'count': count}
|
||||
raise exception.ValidationError(detail=msg)
|
|
@ -669,6 +669,11 @@ class NovaMigrationsCheckers(test_migrations.ModelsMigrationsSync,
|
|||
key_pairs = oslodbutils.get_table(engine, 'key_pairs')
|
||||
self.assertFalse(key_pairs.c.name.nullable)
|
||||
|
||||
def _check_291(self, engine, data):
|
||||
# NOTE(danms): This is a dummy migration that just does a consistency
|
||||
# check
|
||||
pass
|
||||
|
||||
|
||||
class TestNovaMigrationsSQLite(NovaMigrationsCheckers,
|
||||
test_base.DbTestCase,
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import importlib
|
||||
import mock
|
||||
import uuid
|
||||
|
||||
from migrate import exceptions as versioning_exceptions
|
||||
from migrate import UniqueConstraint
|
||||
|
@ -20,8 +22,12 @@ from migrate.versioning import api as versioning_api
|
|||
from oslo_db.sqlalchemy import utils as db_utils
|
||||
import sqlalchemy
|
||||
|
||||
from nova.compute import flavors
|
||||
from nova import context
|
||||
from nova.db.sqlalchemy import api as db_api
|
||||
from nova.db.sqlalchemy import migration
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova import test
|
||||
|
||||
|
||||
|
@ -178,3 +184,85 @@ class TestGetEngine(test.NoDBTestCase):
|
|||
engine = migration.get_engine('api')
|
||||
self.assertEqual('api_engine', engine)
|
||||
mock_get_engine.assert_called_once_with()
|
||||
|
||||
|
||||
class TestFlavorCheck(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestFlavorCheck, self).setUp()
|
||||
self.context = context.get_admin_context()
|
||||
self.migration = importlib.import_module(
|
||||
'nova.db.sqlalchemy.migrate_repo.versions.'
|
||||
'291_enforce_flavors_migrated')
|
||||
self.engine = db_api.get_engine()
|
||||
|
||||
def test_upgrade_clean(self):
|
||||
inst = objects.Instance(context=self.context,
|
||||
uuid=uuid.uuid4(),
|
||||
user_id=self.context.user_id,
|
||||
project_id=self.context.project_id,
|
||||
system_metadata={'foo': 'bar'})
|
||||
inst.create()
|
||||
self.migration.upgrade(self.engine)
|
||||
|
||||
def test_upgrade_dirty(self):
|
||||
inst = objects.Instance(context=self.context,
|
||||
uuid=uuid.uuid4(),
|
||||
user_id=self.context.user_id,
|
||||
project_id=self.context.project_id,
|
||||
system_metadata={'foo': 'bar',
|
||||
'instance_type_id': 'foo'})
|
||||
inst.create()
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.migration.upgrade, self.engine)
|
||||
|
||||
def test_upgrade_flavor_deleted_instances(self):
|
||||
inst = objects.Instance(context=self.context,
|
||||
uuid=uuid.uuid4(),
|
||||
user_id=self.context.user_id,
|
||||
project_id=self.context.project_id,
|
||||
system_metadata={'foo': 'bar',
|
||||
'instance_type_id': 'foo'})
|
||||
inst.create()
|
||||
inst.destroy()
|
||||
self.migration.upgrade(self.engine)
|
||||
|
||||
def test_upgrade_flavor_deleted_sysmeta(self):
|
||||
flavor = flavors.get_default_flavor()
|
||||
sysmeta = flavors.save_flavor_info({}, flavor)
|
||||
sysmeta['foo'] = 'bar'
|
||||
inst = objects.Instance(context=self.context,
|
||||
uuid=uuid.uuid4(),
|
||||
user_id=self.context.user_id,
|
||||
project_id=self.context.project_id,
|
||||
system_metadata=sysmeta)
|
||||
inst.create()
|
||||
|
||||
sysmeta = db_api.instance_system_metadata_get(self.context,
|
||||
inst.uuid)
|
||||
sysmeta = {k: v for k, v in sysmeta.items()
|
||||
if not k.startswith('instance_type_')}
|
||||
db_api.instance_system_metadata_update(self.context, inst.uuid,
|
||||
sysmeta, True)
|
||||
inst.refresh()
|
||||
|
||||
self.assertEqual({'foo': 'bar'}, inst.system_metadata)
|
||||
self.migration.upgrade(self.engine)
|
||||
|
||||
def test_upgrade_flavor_already_migrated(self):
|
||||
flavor = flavors.get_default_flavor()
|
||||
sysmeta = flavors.save_flavor_info({}, flavor)
|
||||
sysmeta['foo'] = 'bar'
|
||||
inst = objects.Instance(context=self.context,
|
||||
uuid=uuid.uuid4(),
|
||||
user_id=self.context.user_id,
|
||||
project_id=self.context.project_id,
|
||||
system_metadata=sysmeta)
|
||||
inst.create()
|
||||
# Trigger the migration by lazy-loading flavor
|
||||
inst.flavor
|
||||
inst.save()
|
||||
self.assertNotIn('instance_type_id', inst.system_metadata)
|
||||
sysmeta = db_api.instance_system_metadata_get(self.context,
|
||||
inst.uuid)
|
||||
self.assertEqual({'foo': 'bar'}, sysmeta)
|
||||
self.migration.upgrade(self.engine)
|
||||
|
|
Loading…
Reference in New Issue