From 24f0c5b9d6136fe18c3fba0ddd64dab99f6f1aa5 Mon Sep 17 00:00:00 2001 From: Mark Doffman Date: Mon, 1 Feb 2016 14:37:58 -0600 Subject: [PATCH] Manage db sync command for cell0 Add the ability to sync the cell0 database using nova-manage. The `db sync` command will be used to sync the cell0 database. This ensures that operators will only have two db sync commands to perform in the single cell case. blueprint cells-cell0 Change-Id: I21ae13a6c029e8ac89484faa212434911160fd51 --- nova/cmd/manage.py | 20 +++++++++- nova/db/migration.py | 8 ++-- nova/db/sqlalchemy/migration.py | 36 ++++++++++-------- .../functional/db/api/test_migrations.py | 2 +- nova/tests/unit/db/test_migrations.py | 2 +- .../unit/db/test_sqlalchemy_migration.py | 16 ++++---- nova/tests/unit/test_nova_manage.py | 38 ++++++++++++++++--- ...-db-sync-nova-manage-8504b54dd115a2e9.yaml | 8 ++++ 8 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 releasenotes/notes/cell-id-db-sync-nova-manage-8504b54dd115a2e9.yaml diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 23cdf613098b..6c8c9f31e94d 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -797,8 +797,26 @@ class DbCommands(object): pass @args('--version', metavar='', help='Database version') - def sync(self, version=None): + @args('--local_cell', action='store_true', + help='Only sync db in the local cell: do not attempt to fan-out' + 'to all cells') + def sync(self, version=None, local_cell=False): """Sync the database up to the most recent version.""" + if not local_cell: + ctxt = context.RequestContext() + # NOTE(mdoff): Multiple cells not yet implemented. Currently + # fanout only looks for cell0. + try: + cell_mapping = objects.CellMapping.get_by_uuid(ctxt, + objects.CellMapping.CELL0_UUID) + with context.target_cell(ctxt, cell_mapping): + migration.db_sync(version, context=ctxt) + except exception.CellMappingNotFound: + print(_('WARNING: cell0 mapping not found - not' + ' syncing cell0.')) + except Exception: + print(_('ERROR: could not access cell mapping database - has' + ' api db been created?')) return migration.db_sync(version) def version(self): diff --git a/nova/db/migration.py b/nova/db/migration.py index 0575b4528edf..4765e457c760 100644 --- a/nova/db/migration.py +++ b/nova/db/migration.py @@ -21,14 +21,14 @@ from nova.db.sqlalchemy import migration IMPL = migration -def db_sync(version=None, database='main'): +def db_sync(version=None, database='main', context=None): """Migrate the database to `version` or the most recent version.""" - return IMPL.db_sync(version=version, database=database) + return IMPL.db_sync(version=version, database=database, context=context) -def db_version(database='main'): +def db_version(database='main', context=None): """Display the current database version.""" - return IMPL.db_version(database=database) + return IMPL.db_version(database=database, context=context) def db_initial_version(database='main'): diff --git a/nova/db/sqlalchemy/migration.py b/nova/db/sqlalchemy/migration.py index 3c8d3d7939db..f457186ac740 100644 --- a/nova/db/sqlalchemy/migration.py +++ b/nova/db/sqlalchemy/migration.py @@ -37,44 +37,48 @@ _REPOSITORY = {} LOG = logging.getLogger(__name__) -def get_engine(database='main'): +def get_engine(database='main', context=None): if database == 'main': - return db_session.get_engine() + return db_session.get_engine(context=context) if database == 'api': return db_session.get_api_engine() if database == 'placement': return db_session.get_placement_engine() -def db_sync(version=None, database='main'): +def db_sync(version=None, database='main', context=None): if version is not None: try: version = int(version) except ValueError: raise exception.NovaException(_("version should be an integer")) - current_version = db_version(database) + current_version = db_version(database, context=context) repository = _find_migrate_repo(database) if version is None or version > current_version: - return versioning_api.upgrade(get_engine(database), repository, - version) + return versioning_api.upgrade(get_engine(database, context=context), + repository, version) else: - return versioning_api.downgrade(get_engine(database), repository, - version) + return versioning_api.downgrade(get_engine(database, context=context), + repository, version) -def db_version(database='main'): +def db_version(database='main', context=None): repository = _find_migrate_repo(database) try: - return versioning_api.db_version(get_engine(database), repository) + return versioning_api.db_version(get_engine(database, context=context), + repository) except versioning_exceptions.DatabaseNotControlledError as exc: meta = sqlalchemy.MetaData() - engine = get_engine(database) + engine = get_engine(database, context=context) meta.reflect(bind=engine) tables = meta.tables if len(tables) == 0: - db_version_control(INIT_VERSION[database], database) - return versioning_api.db_version(get_engine(database), repository) + db_version_control(INIT_VERSION[database], + database, + context=context) + return versioning_api.db_version( + get_engine(database, context=context), repository) else: LOG.exception(exc) # Some pre-Essex DB's may not be version controlled. @@ -156,9 +160,11 @@ def db_null_instance_uuid_scan(delete=False): return processed -def db_version_control(version=None, database='main'): +def db_version_control(version=None, database='main', context=None): repository = _find_migrate_repo(database) - versioning_api.version_control(get_engine(database), repository, version) + versioning_api.version_control(get_engine(database, context=context), + repository, + version) return version diff --git a/nova/tests/functional/db/api/test_migrations.py b/nova/tests/functional/db/api/test_migrations.py index dcbf183750a0..616ffe3b0fe5 100644 --- a/nova/tests/functional/db/api/test_migrations.py +++ b/nova/tests/functional/db/api/test_migrations.py @@ -58,7 +58,7 @@ class NovaAPIModelsSync(test_migrations.ModelsMigrationsSync): def migrate_engine(self): return self.engine - def get_engine(self): + def get_engine(self, context=None): return self.migrate_engine def get_metadata(self): diff --git a/nova/tests/unit/db/test_migrations.py b/nova/tests/unit/db/test_migrations.py index 8be5297c159a..d34fed2d4599 100644 --- a/nova/tests/unit/db/test_migrations.py +++ b/nova/tests/unit/db/test_migrations.py @@ -143,7 +143,7 @@ class NovaMigrationsCheckers(test_migrations.ModelsMigrationsSync, return_value=engine): sa_migration.db_sync() - def get_engine(self): + def get_engine(self, context=None): return self.migrate_engine def get_metadata(self): diff --git a/nova/tests/unit/db/test_sqlalchemy_migration.py b/nova/tests/unit/db/test_sqlalchemy_migration.py index 023055b09f36..9f2af9d21656 100644 --- a/nova/tests/unit/db/test_sqlalchemy_migration.py +++ b/nova/tests/unit/db/test_sqlalchemy_migration.py @@ -107,9 +107,9 @@ class TestDbSync(test.NoDBTestCase): mock_find_repo, mock_version): database = 'fake' migration.db_sync(database=database) - mock_version.assert_called_once_with(database) + mock_version.assert_called_once_with(database, context=None) mock_find_repo.assert_called_once_with(database) - mock_get_engine.assert_called_once_with(database) + mock_get_engine.assert_called_once_with(database, context=None) mock_upgrade.assert_called_once_with('engine', 'repo', None) self.assertFalse(mock_downgrade.called) @@ -117,9 +117,9 @@ class TestDbSync(test.NoDBTestCase): mock_find_repo, mock_version): database = 'fake' migration.db_sync(1, database=database) - mock_version.assert_called_once_with(database) + mock_version.assert_called_once_with(database, context=None) mock_find_repo.assert_called_once_with(database) - mock_get_engine.assert_called_once_with(database) + mock_get_engine.assert_called_once_with(database, context=None) mock_downgrade.assert_called_once_with('engine', 'repo', 1) self.assertFalse(mock_upgrade.called) @@ -149,10 +149,12 @@ class TestDbVersion(test.NoDBTestCase): metadata), mock.patch.object(migration, 'db_version_control') as mock_version_control: migration.db_version(database) - mock_version_control.assert_called_once_with(0, database) + mock_version_control.assert_called_once_with(0, + database, + context=None) db_version_calls = [mock.call('engine', 'repo')] * 2 self.assertEqual(db_version_calls, mock_db_version.call_args_list) - engine_calls = [mock.call(database)] * 3 + engine_calls = [mock.call(database, context=None)] * 3 self.assertEqual(engine_calls, mock_get_engine.call_args_list) @@ -176,7 +178,7 @@ class TestGetEngine(test.NoDBTestCase): return_value='engine') as mock_get_engine: engine = migration.get_engine() self.assertEqual('engine', engine) - mock_get_engine.assert_called_once_with() + mock_get_engine.assert_called_once_with(context=None) def test_get_api_engine(self): with mock.patch.object(db_api, 'get_api_engine', diff --git a/nova/tests/unit/test_nova_manage.py b/nova/tests/unit/test_nova_manage.py index 20870a206187..97163c43a32a 100644 --- a/nova/tests/unit/test_nova_manage.py +++ b/nova/tests/unit/test_nova_manage.py @@ -544,12 +544,38 @@ class DBCommandsTestCase(test.NoDBTestCase): @mock.patch.object(sqla_migration, 'db_version', return_value=2) def test_version(self, sqla_migrate): self.commands.version() - sqla_migrate.assert_called_once_with(database='main') + sqla_migrate.assert_called_once_with(context=None, database='main') @mock.patch.object(sqla_migration, 'db_sync') def test_sync(self, sqla_sync): - self.commands.sync(version=4) - sqla_sync.assert_called_once_with(version=4, database='main') + self.commands.sync(version=4, local_cell=True) + sqla_sync.assert_called_once_with(context=None, + version=4, database='main') + + @mock.patch('nova.db.migration.db_sync') + @mock.patch.object(objects.CellMapping, 'get_by_uuid', return_value='map') + def test_sync_cell0(self, mock_get_by_uuid, mock_db_sync): + ctxt = context.get_admin_context() + cell_ctxt = context.get_admin_context() + with test.nested( + mock.patch('nova.context.RequestContext', + return_value=ctxt), + mock.patch('nova.context.target_cell')) \ + as (mock_get_context, + mock_target_cell): + fake_target_cell_mock = mock.MagicMock() + fake_target_cell_mock.__enter__.return_value = cell_ctxt + mock_target_cell.return_value = fake_target_cell_mock + self.commands.sync(version=4) + mock_get_by_uuid.assert_called_once_with(ctxt, + objects.CellMapping.CELL0_UUID) + mock_target_cell.assert_called_once_with(ctxt, 'map') + + db_sync_calls = [ + mock.call(4, context=ctxt), + mock.call(4) + ] + mock_db_sync.assert_has_calls(db_sync_calls) def _fake_db_command(self, migrations=None): if migrations is None: @@ -635,12 +661,14 @@ class ApiDbCommandsTestCase(test.NoDBTestCase): @mock.patch.object(sqla_migration, 'db_version', return_value=2) def test_version(self, sqla_migrate): self.commands.version() - sqla_migrate.assert_called_once_with(database='api') + sqla_migrate.assert_called_once_with(context=None, + database='api') @mock.patch.object(sqla_migration, 'db_sync') def test_sync(self, sqla_sync): self.commands.sync(version=4) - sqla_sync.assert_called_once_with(version=4, database='api') + sqla_sync.assert_called_once_with(context=None, + version=4, database='api') class CellCommandsTestCase(test.NoDBTestCase): diff --git a/releasenotes/notes/cell-id-db-sync-nova-manage-8504b54dd115a2e9.yaml b/releasenotes/notes/cell-id-db-sync-nova-manage-8504b54dd115a2e9.yaml new file mode 100644 index 000000000000..8c616dcc8b18 --- /dev/null +++ b/releasenotes/notes/cell-id-db-sync-nova-manage-8504b54dd115a2e9.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + 'nova-manage db sync' can now sync the cell0 database. + The cell0 db is required to store instances that cannot be scheduled to + any cell. Before the 'db sync' command is called a cell mapping + for cell0 must have been created using 'nova-manage cell_v2 map_cell0'. + This command only needs to be called when upgrading to CellsV2.