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
This commit is contained in:
Mark Doffman 2016-02-01 14:37:58 -06:00 committed by Andrey Volkov
parent 9a1f48403d
commit 24f0c5b9d6
8 changed files with 96 additions and 34 deletions

View File

@ -797,8 +797,26 @@ class DbCommands(object):
pass
@args('--version', metavar='<version>', 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):

View File

@ -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'):

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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',

View File

@ -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):

View File

@ -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.