From ff6b9998bb977421a5cbc94878ced8542d910c9e Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 1 Nov 2016 08:54:59 -0700 Subject: [PATCH] Require cellsv2 setup before migrating to Ocata We have code going into Ocata that needs to be sure that cell and host mappings are in place. Since this was required homework in Newton, we can land a migration to intentionally fail if this was not completed. This is, however, a little difficult to require because a first-time deployment will be initialized schema-wise with none of these records, which is also sane. So, we look to see if any flavors are defined as a sentinel to indicate that this is an upgrade of an existing deployment instead of a first-time event. Not perfect, but since this is really just a helper for the user, it seems like a reasonable risk. Depends-On: If1af9c478e8ea2420f2523a9bb8b70fafddc86b7 Change-Id: I72fb724dc13e1a5f4e97c58915b538ba761c582d --- .../versions/030_require_cell_setup.py | 57 ++++++++++ .../functional/db/api/test_migrations.py | 5 +- .../unit/db/test_sqlalchemy_migration.py | 100 ++++++++++++++++++ ...cata-requires-cellv2-96bd243be874d77f.yaml | 7 ++ 4 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 nova/db/sqlalchemy/api_migrations/migrate_repo/versions/030_require_cell_setup.py create mode 100644 releasenotes/notes/ocata-requires-cellv2-96bd243be874d77f.yaml diff --git a/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/030_require_cell_setup.py b/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/030_require_cell_setup.py new file mode 100644 index 000000000000..ffd975c3877a --- /dev/null +++ b/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/030_require_cell_setup.py @@ -0,0 +1,57 @@ +# 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 nova import exception +from nova.i18n import _ +from nova import objects + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + flavors = Table('flavors', meta, autoload=True) + count = select([func.count()]).select_from(flavors).scalar() + if count == 0: + # NOTE(danms): We need to be careful here if this is a new + # installation, which can't possibly have any mappings. Check + # to see if any flavors are defined to determine if we are + # upgrading an existing system. If not, then don't obsess over + # the lack of mappings + return + + cell_mappings = Table('cell_mappings', meta, autoload=True) + count = select([func.count()]).select_from(cell_mappings).scalar() + # Two mappings are required at a minimum, cell0 and your first cell + if count < 2: + msg = _('Cell mappings are not created, but required for Ocata. ' + 'Please run nova-manage db simple_cell_setup before ' + 'continuing.') + raise exception.ValidationError(detail=msg) + + count = select([func.count()]).select_from(cell_mappings).where( + cell_mappings.c.uuid == objects.CellMapping.CELL0_UUID).scalar() + if count != 1: + msg = _('A mapping for Cell0 was not found, but is required for ' + 'Ocata. Please run nova-manage db simple_cell_setup before ' + 'continuing.') + raise exception.ValidationError(detail=msg) + + host_mappings = Table('host_mappings', meta, autoload=True) + count = select([func.count()]).select_from(host_mappings).scalar() + if count == 0: + msg = _('No host mappings were found, but are required for Ocata. ' + 'Please run nova-manage db simple_cell_setup before ' + 'continuing.') + raise exception.ValidationError(detail=msg) diff --git a/nova/tests/functional/db/api/test_migrations.py b/nova/tests/functional/db/api/test_migrations.py index 13f2717be527..3a9171528583 100644 --- a/nova/tests/functional/db/api/test_migrations.py +++ b/nova/tests/functional/db/api/test_migrations.py @@ -164,7 +164,10 @@ class NovaAPIMigrationsWalk(test_migrations.WalkVersionsMixin): def _skippable_migrations(self): mitaka_placeholders = range(8, 13) newton_placeholders = range(21, 26) - return mitaka_placeholders + newton_placeholders + special_cases = [ + 30, # Enforcement migration, no changes to test + ] + return mitaka_placeholders + newton_placeholders + special_cases def migrate_up(self, version, with_data=False): if with_data: diff --git a/nova/tests/unit/db/test_sqlalchemy_migration.py b/nova/tests/unit/db/test_sqlalchemy_migration.py index 3b8627eda525..c4a8b8650447 100644 --- a/nova/tests/unit/db/test_sqlalchemy_migration.py +++ b/nova/tests/unit/db/test_sqlalchemy_migration.py @@ -365,3 +365,103 @@ class TestOcataCheck(test.TestCase): group = db_api.instance_group_create(self.context, self.ig_values) db_api.instance_group_delete(self.context, group['uuid']) self.migration.upgrade(self.engine) + + +class TestNewtonCellsCheck(test.NoDBTestCase): + USES_DB_SELF = True + + def setUp(self): + super(TestNewtonCellsCheck, self).setUp() + self.useFixture(nova_fixtures.DatabaseAtVersion(28, 'api')) + self.context = context.get_admin_context() + self.migration = importlib.import_module( + 'nova.db.sqlalchemy.api_migrations.migrate_repo.versions.' + '030_require_cell_setup') + self.engine = db_api.get_api_engine() + + @mock.patch('nova.objects.Flavor._ensure_migrated') + def _flavor_me(self, _): + flavor = objects.Flavor(context=self.context, + name='foo', memory_mb=123, + vcpus=1, root_gb=1, + flavorid='m1.foo') + flavor.create() + + def test_upgrade_with_no_cell_mappings(self): + self._flavor_me() + self.assertRaisesRegex(exception.ValidationError, + 'Cell mappings', + self.migration.upgrade, self.engine) + + def test_upgrade_with_only_cell0(self): + self._flavor_me() + cell0 = objects.CellMapping(context=self.context, + uuid=objects.CellMapping.CELL0_UUID, + name='cell0', + transport_url='fake', + database_connection='fake') + cell0.create() + self.assertRaisesRegex(exception.ValidationError, + 'Cell mappings', + self.migration.upgrade, self.engine) + + def test_upgrade_without_cell0(self): + self._flavor_me() + cell1 = objects.CellMapping(context=self.context, + uuid=uuidsentinel.cell1, + name='cell1', + transport_url='fake', + database_connection='fake') + cell1.create() + cell2 = objects.CellMapping(context=self.context, + uuid=uuidsentinel.cell2, + name='cell2', + transport_url='fake', + database_connection='fake') + cell2.create() + self.assertRaisesRegex(exception.ValidationError, + 'Cell0', + self.migration.upgrade, self.engine) + + def test_upgrade_with_no_host_mappings(self): + self._flavor_me() + cell0 = objects.CellMapping(context=self.context, + uuid=objects.CellMapping.CELL0_UUID, + name='cell0', + transport_url='fake', + database_connection='fake') + cell0.create() + cell1 = objects.CellMapping(context=self.context, + uuid=uuidsentinel.cell1, + name='cell1', + transport_url='fake', + database_connection='fake') + cell1.create() + + self.assertRaisesRegex(exception.ValidationError, + 'host mappings', + self.migration.upgrade, self.engine) + + def test_upgrade_with_required_mappings(self): + self._flavor_me() + cell0 = objects.CellMapping(context=self.context, + uuid=objects.CellMapping.CELL0_UUID, + name='cell0', + transport_url='fake', + database_connection='fake') + cell0.create() + cell1 = objects.CellMapping(context=self.context, + uuid=uuidsentinel.cell1, + name='cell1', + transport_url='fake', + database_connection='fake') + cell1.create() + hostmapping = objects.HostMapping(context=self.context, + cell_mapping=cell1, + host='foo') + hostmapping.create() + + self.migration.upgrade(self.engine) + + def test_upgrade_new_deploy(self): + self.migration.upgrade(self.engine) diff --git a/releasenotes/notes/ocata-requires-cellv2-96bd243be874d77f.yaml b/releasenotes/notes/ocata-requires-cellv2-96bd243be874d77f.yaml new file mode 100644 index 000000000000..7e1470034fc6 --- /dev/null +++ b/releasenotes/notes/ocata-requires-cellv2-96bd243be874d77f.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - Ocata requires that your deployment have created the + cell and host mappings in Newton. If you have not done + this, Ocata's `db sync` command will fail. Small deployments + will want to run `nova-manage db simple_cell_setup` + on Newton before upgrading.