diff --git a/nova/db/main/api.py b/nova/db/main/api.py index 4c40be905ef..39775d4f461 100644 --- a/nova/db/main/api.py +++ b/nova/db/main/api.py @@ -4176,6 +4176,12 @@ def _get_fk_stmts(metadata, conn, table, column, records): fk_column = fk_table.c.id for fk in fk_table.foreign_keys: + if table != fk.column.table: + # if the foreign key doesn't actually point to the table we're + # archiving entries from then it's not relevant; trying to + # resolve this would result in a cartesian product + continue + # We need to find the records in the referring (child) table that # correspond to the records in our (parent) table so we can archive # them. @@ -4225,6 +4231,7 @@ def _get_fk_stmts(metadata, conn, table, column, records): # deque. fk_delete = fk_table.delete().where(fk_column.in_(fk_records)) deletes.appendleft(fk_delete) + # Repeat for any possible nested child tables. i, d = _get_fk_stmts(metadata, conn, fk_table, fk_column, fk_records) inserts.extendleft(i) diff --git a/nova/objects/cell_mapping.py b/nova/objects/cell_mapping.py index 595ec43e480..13551824205 100644 --- a/nova/objects/cell_mapping.py +++ b/nova/objects/cell_mapping.py @@ -279,11 +279,15 @@ def _get_by_project_id_from_db(context, project_id): # SELECT DISTINCT cell_id FROM instance_mappings \ # WHERE project_id = $project_id; cell_ids = context.session.query( - api_db_models.InstanceMapping.cell_id).filter_by( - project_id=project_id).distinct().subquery() + api_db_models.InstanceMapping.cell_id + ).filter_by( + project_id=project_id + ).distinct() # SELECT cell_mappings WHERE cell_id IN ($cell_ids); - return context.session.query(api_db_models.CellMapping).filter( - api_db_models.CellMapping.id.in_(cell_ids)).all() + return context.session.query( + api_db_models.CellMapping).filter( + api_db_models.CellMapping.id.in_(cell_ids) + ).all() @classmethod def get_by_project_id(cls, context, project_id): diff --git a/nova/tests/fixtures/nova.py b/nova/tests/fixtures/nova.py index 27ca2fd77d4..f9e011dd67d 100644 --- a/nova/tests/fixtures/nova.py +++ b/nova/tests/fixtures/nova.py @@ -904,6 +904,16 @@ def setUp(self): message='Implicit coercion of SELECT and textual SELECT .*', category=sqla_exc.SADeprecationWarning) + # Enable general SQLAlchemy warnings also to ensure we're not doing + # silly stuff. It's possible that we'll need to filter things out here + # with future SQLAlchemy versions, but that's a good thing + + warnings.filterwarnings( + 'error', + module='nova', + category=sqla_exc.SAWarning, + ) + self.addCleanup(self._reset_warning_filters) def _reset_warning_filters(self): diff --git a/nova/tests/functional/regressions/test_bug_1888395.py b/nova/tests/functional/regressions/test_bug_1888395.py index e582ad3e851..8f2e2a0eeb4 100644 --- a/nova/tests/functional/regressions/test_bug_1888395.py +++ b/nova/tests/functional/regressions/test_bug_1888395.py @@ -23,14 +23,8 @@ from nova.tests.functional.libvirt import base as libvirt_base -class TestLiveMigrationWithoutMultiplePortBindings( +class TestLiveMigrationWithoutMultiplePortBindingsBase( libvirt_base.ServersTestBase): - """Regression test for bug 1888395. - - This regression test asserts that Live migration works when - neutron does not support the binding-extended api extension - and the legacy single port binding workflow is used. - """ ADMIN_API = True microversion = 'latest' @@ -72,6 +66,16 @@ def setUp(self): 'nova.tests.fixtures.libvirt.Domain.migrateToURI3', self._migrate_stub)) + +class TestLiveMigrationWithoutMultiplePortBindings( + TestLiveMigrationWithoutMultiplePortBindingsBase): + """Regression test for bug 1888395. + + This regression test asserts that Live migration works when + neutron does not support the binding-extended api extension + and the legacy single port binding workflow is used. + """ + def _migrate_stub(self, domain, destination, params, flags): """Stub out migrateToURI3.""" @@ -124,3 +128,30 @@ def test_live_migrate(self): server, {'OS-EXT-SRV-ATTR:host': 'end_host', 'status': 'ACTIVE'}) msg = "NotImplementedError: Cannot load 'vif_type' in the base class" self.assertNotIn(msg, self.stdlog.logger.output) + + +class TestLiveMigrationRollbackWithoutMultiplePortBindings( + TestLiveMigrationWithoutMultiplePortBindingsBase): + + def _migrate_stub(self, domain, destination, params, flags): + source = self.computes['start_host'] + conn = source.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server['id']) + dom.fail_job() + + def test_live_migration_rollback(self): + self.server = self._create_server( + host='start_host', + networks=[{'port': self.neutron.port_1['id']}]) + + self.assertFalse( + self.neutron_api.has_port_binding_extension(self.ctxt)) + # FIXME(artom) Until bug 1969980 is fixed, this will fail with a + # NotImplementedError. + self._live_migrate(self.server, migration_expected_state='error', + server_expected_state='ERROR') + server = self.api.get_server(self.server['id']) + self.assertIn( + "NotImplementedError: Cannot load 'vifs' in the base class", + server['fault']['details'] + )