diff --git a/doc/source/cli/nova-manage.rst b/doc/source/cli/nova-manage.rst index e2127bbfa951..a0cf0fc632f9 100644 --- a/doc/source/cli/nova-manage.rst +++ b/doc/source/cli/nova-manage.rst @@ -230,9 +230,6 @@ Nova Database arguments are not provided, 2 if an invalid date is provided, 3 if no data was deleted, 4 if the list of cells cannot be obtained. -``nova-manage db null_instance_uuid_scan [--delete]`` - Lists and optionally deletes database records where instance_uuid is NULL. - ``nova-manage db online_data_migrations [--max-count]`` Perform data migration to update all live data. diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 3f0cb198465a..5199dc9e8836 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -463,35 +463,6 @@ Error: %s""") % str(e)) else: return 3 - @args('--delete', action='store_true', dest='delete', - help='If specified, automatically delete any records found where ' - 'instance_uuid is NULL.') - def null_instance_uuid_scan(self, delete=False): - """Lists and optionally deletes database records where - instance_uuid is NULL. - """ - hits = migration.db_null_instance_uuid_scan(delete) - records_found = False - for table_name, records in hits.items(): - # Don't print anything for 0 hits - if records: - records_found = True - if delete: - print(_("Deleted %(records)d records " - "from table '%(table_name)s'.") % - {'records': records, 'table_name': table_name}) - else: - print(_("There are %(records)d records in the " - "'%(table_name)s' table where the uuid or " - "instance_uuid column is NULL. Run this " - "command again with the --delete option after you " - "have backed up any necessary data.") % - {'records': records, 'table_name': table_name}) - # check to see if we didn't find anything - if not records_found: - print(_('There were no records found where ' - 'instance_uuid was NULL.')) - def _run_migration(self, ctxt, max_count): ran = 0 exceptions = False diff --git a/nova/db/migration.py b/nova/db/migration.py index 4765e457c760..c4aa47612c4f 100644 --- a/nova/db/migration.py +++ b/nova/db/migration.py @@ -34,23 +34,3 @@ def db_version(database='main', context=None): def db_initial_version(database='main'): """The starting version for the database.""" return IMPL.db_initial_version(database=database) - - -def db_null_instance_uuid_scan(delete=False): - """Utility for scanning the database to look for NULL instance uuid rows. - - Scans the backing nova database to look for table entries where - instances.uuid or instance_uuid columns are NULL (except for the - fixed_ips table since that can contain NULL instance_uuid entries by - design). Dumps the tables that have NULL instance_uuid entries or - optionally deletes them based on usage. - - This tool is meant to be used in conjunction with the 267 database - migration script to detect and optionally cleanup NULL instance_uuid - records. - - :param delete: If true, delete NULL instance_uuid records found, else - just query to see if they exist for reporting. - :returns: dict of table name to number of hits for NULL instance_uuid rows. - """ - return IMPL.db_null_instance_uuid_scan(delete=delete) diff --git a/nova/db/sqlalchemy/migration.py b/nova/db/sqlalchemy/migration.py index b4e1309c6542..b8174d8d1bc2 100644 --- a/nova/db/sqlalchemy/migration.py +++ b/nova/db/sqlalchemy/migration.py @@ -19,7 +19,6 @@ import os from migrate import exceptions as versioning_exceptions from migrate.versioning import api as versioning_api from migrate.versioning.repository import Repository -from oslo_db.sqlalchemy import utils as db_utils from oslo_log import log as logging import sqlalchemy from sqlalchemy.sql import null @@ -159,38 +158,6 @@ def _process_null_records(table, col_name, check_fkeys, delete=False): return records -def db_null_instance_uuid_scan(delete=False): - """Scans the database for NULL instance_uuid records. - - :param delete: If true, delete NULL instance_uuid records found, else - just query to see if they exist for reporting. - :returns: dict of table name to number of hits for NULL instance_uuid rows. - """ - engine = get_engine() - meta = sqlalchemy.MetaData(bind=engine) - # NOTE(mriedem): We're going to load up all of the tables so we can find - # any with an instance_uuid column since those may be foreign keys back - # to the instances table and we want to cleanup those records first. We - # have to do this explicitly because the foreign keys in nova aren't - # defined with cascading deletes. - meta.reflect(engine) - # Keep track of all of the tables that had hits in the query. - processed = {} - for table in reversed(meta.sorted_tables): - # Ignore the fixed_ips table by design. - if table.name not in ('fixed_ips', 'shadow_fixed_ips'): - processed[table.name] = _process_null_records( - table, 'instance_uuid', check_fkeys=True, delete=delete) - - # Now process the *instances tables. - for table_name in ('instances', 'shadow_instances'): - table = db_utils.get_table(engine, table_name) - processed[table.name] = _process_null_records( - table, 'uuid', check_fkeys=False, delete=delete) - - return processed - - def db_version_control(version=None, database='main', context=None): repository = _find_migrate_repo(database) versioning_api.version_control(get_engine(database, context=context), diff --git a/nova/tests/unit/cmd/test_manage.py b/nova/tests/unit/cmd/test_manage.py index 76fcf70f9f3d..29c9b6063dc5 100644 --- a/nova/tests/unit/cmd/test_manage.py +++ b/nova/tests/unit/cmd/test_manage.py @@ -30,7 +30,6 @@ from nova.cmd import manage from nova import conf from nova import context from nova.db import api as db -from nova.db import migration from nova.db.sqlalchemy import migration as sqla_migration from nova import exception from nova import objects @@ -584,32 +583,6 @@ Cell %s: 456 self.assertEqual(4, ret) self.assertIn('Unable to get cell list', self.output.getvalue()) - @mock.patch.object(migration, 'db_null_instance_uuid_scan', - return_value={'foo': 0}) - def test_null_instance_uuid_scan_no_records_found(self, mock_scan): - self.commands.null_instance_uuid_scan() - self.assertIn("There were no records found", self.output.getvalue()) - - @mock.patch.object(migration, 'db_null_instance_uuid_scan', - return_value={'foo': 1, 'bar': 0}) - def _test_null_instance_uuid_scan(self, mock_scan, delete): - self.commands.null_instance_uuid_scan(delete) - output = self.output.getvalue() - - if delete: - self.assertIn("Deleted 1 records from table 'foo'.", output) - self.assertNotIn("Deleted 0 records from table 'bar'.", output) - else: - self.assertIn("1 records in the 'foo' table", output) - self.assertNotIn("0 records in the 'bar' table", output) - self.assertNotIn("There were no records found", output) - - def test_null_instance_uuid_scan_readonly(self): - self._test_null_instance_uuid_scan(delete=False) - - def test_null_instance_uuid_scan_delete(self): - self._test_null_instance_uuid_scan(delete=True) - @mock.patch.object(sqla_migration, 'db_version', return_value=2) def test_version(self, sqla_migrate): self.commands.version() diff --git a/nova/tests/unit/db/test_sqlalchemy_migration.py b/nova/tests/unit/db/test_sqlalchemy_migration.py index c28e2d8be2c9..d692d8b6947c 100644 --- a/nova/tests/unit/db/test_sqlalchemy_migration.py +++ b/nova/tests/unit/db/test_sqlalchemy_migration.py @@ -15,7 +15,6 @@ import importlib from migrate import exceptions as versioning_exceptions -from migrate import UniqueConstraint from migrate.versioning import api as versioning_api import mock from oslo_db.sqlalchemy import utils as db_utils @@ -32,71 +31,6 @@ from nova import test from nova.tests import fixtures as nova_fixtures -class TestNullInstanceUuidScanDB(test.TestCase): - - # NOTE(mriedem): Copied from the 267 database migration. - def downgrade(self, migrate_engine): - UniqueConstraint('uuid', - table=db_utils.get_table(migrate_engine, 'instances'), - name='uniq_instances0uuid').drop() - for table_name in ('instances', 'shadow_instances'): - table = db_utils.get_table(migrate_engine, table_name) - table.columns.uuid.alter(nullable=True) - - def setUp(self): - super(TestNullInstanceUuidScanDB, self).setUp() - - self.engine = db_api.get_engine() - # When this test runs, we've already run the schema migration to make - # instances.uuid non-nullable, so we have to alter the table here - # so we can test against a real database. - self.downgrade(self.engine) - # Now create fake entries in the fixed_ips, consoles and - # instances table where (instance_)uuid is None for testing. - for table_name in ('fixed_ips', 'instances', 'consoles'): - table = db_utils.get_table(self.engine, table_name) - fake_record = {'id': 1} - table.insert().execute(fake_record) - - def test_db_null_instance_uuid_scan_readonly(self): - results = migration.db_null_instance_uuid_scan(delete=False) - self.assertEqual(1, results.get('instances')) - self.assertEqual(1, results.get('consoles')) - # The fixed_ips table should be ignored. - self.assertNotIn('fixed_ips', results) - # Now pick a random table with an instance_uuid column and show it's - # in the results but with 0 hits. - self.assertEqual(0, results.get('instance_info_caches')) - # Make sure nothing was deleted. - for table_name in ('fixed_ips', 'instances', 'consoles'): - table = db_utils.get_table(self.engine, table_name) - record = table.select(table.c.id == 1).execute().first() - self.assertIsNotNone(record) - - def test_db_null_instance_uuid_scan_delete(self): - results = migration.db_null_instance_uuid_scan(delete=True) - self.assertEqual(1, results.get('instances')) - self.assertEqual(1, results.get('consoles')) - # The fixed_ips table should be ignored. - self.assertNotIn('fixed_ips', results) - # Now pick a random table with an instance_uuid column and show it's - # in the results but with 0 hits. - self.assertEqual(0, results.get('instance_info_caches')) - # Make sure fixed_ips wasn't touched, but instances and instance_faults - # records were deleted. - fixed_ips = db_utils.get_table(self.engine, 'fixed_ips') - record = fixed_ips.select(fixed_ips.c.id == 1).execute().first() - self.assertIsNotNone(record) - - consoles = db_utils.get_table(self.engine, 'consoles') - record = consoles.select(consoles.c.id == 1).execute().first() - self.assertIsNone(record) - - instances = db_utils.get_table(self.engine, 'instances') - record = instances.select(instances.c.id == 1).execute().first() - self.assertIsNone(record) - - @mock.patch.object(migration, 'db_version', return_value=2) @mock.patch.object(migration, '_find_migrate_repo', return_value='repo') @mock.patch.object(versioning_api, 'upgrade') diff --git a/releasenotes/notes/remove-nova-manage-db-null_instance_uuid_scan-f34494b316ff181c.yaml b/releasenotes/notes/remove-nova-manage-db-null_instance_uuid_scan-f34494b316ff181c.yaml new file mode 100644 index 000000000000..e74d2cea2a1c --- /dev/null +++ b/releasenotes/notes/remove-nova-manage-db-null_instance_uuid_scan-f34494b316ff181c.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The ``nova-manage db null_instance_uuid_scan`` command has been removed. + A blocking migration has been in place since the 12.0.0 (Liberty) release + making this check unnecessary.