diff options
author | Zuul <zuul@review.opendev.org> | 2021-08-31 19:02:18 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2021-08-31 19:02:18 +0000 |
commit | d024333b80c83c490ffda2d8b412baffb7ace637 (patch) | |
tree | 3fbafd27ae0e3d8cd394556c1ff0e5d6fa884e84 | |
parent | 6eb9f0853dace0f8df0608716bff807aac59fd8b (diff) | |
parent | 37cff3c74d54bf0d19abe9502e5979b81e24f918 (diff) | |
download | nova-d024333b80c83c490ffda2d8b412baffb7ace637.tar.gz |
Merge "Add functional test for bug 1837995" into stable/ussuri
-rw-r--r-- | nova/tests/functional/db/test_archive.py | 47 |
1 files changed, 47 insertions, 0 deletions
diff --git a/nova/tests/functional/db/test_archive.py b/nova/tests/functional/db/test_archive.py index b38a3072da..e4d703cd2a 100644 --- a/nova/tests/functional/db/test_archive.py +++ b/nova/tests/functional/db/test_archive.py @@ -128,6 +128,53 @@ class TestDatabaseArchive(test_servers.ServersTestBase): # Verify that the pci_devices record has not been dropped self.assertNotIn('pci_devices', results) + def test_archive_deleted_rows_incomplete(self): + """This tests a scenario where archive_deleted_rows is run with + --max_rows and does not run to completion. + + That is, the archive is stopped before all archivable records have been + archived. Specifically, the problematic state is when a single instance + becomes partially archived (example: 'instance_extra' record for one + instance has been archived while its 'instances' record remains). Any + access of the instance (example: listing deleted instances) that + triggers the retrieval of a dependent record that has been archived + away, results in undefined behavior that may raise an error. + + We will force the system into a state where a single deleted instance + is partially archived. We want to verify that we can, for example, + successfully do a GET /servers/detail at any point between partial + archive_deleted_rows runs without errors. + """ + # Boots a server, deletes it, and then tries to archive it. + server = self._create_server() + server_id = server['id'] + # Assert that there are instance_actions. instance_actions are + # interesting since we don't soft delete them but they have a foreign + # key back to the instances table. + actions = self.api.get_instance_actions(server_id) + self.assertTrue(len(actions), + 'No instance actions for server: %s' % server_id) + self._delete_server(server) + # Archive deleted records iteratively, 1 row at a time, and try to do a + # GET /servers/detail between each run. All should succeed. + exceptions = [] + while True: + _, _, archived = db.archive_deleted_rows(max_rows=1) + try: + # Need to use the admin API to list deleted servers. + self.admin_api.get_servers(search_opts={'deleted': True}) + except Exception as ex: + exceptions.append(ex) + if archived == 0: + break + # FIXME(melwitt): OrphanedObjectError is raised because of the bug. + self.assertTrue(exceptions) + for ex in exceptions: + self.assertEqual(500, ex.response.status_code) + self.assertIn('OrphanedObjectError', str(ex)) + # FIXME(melwitt): Uncomment when the bug is fixed. + # self.assertFalse(exceptions) + def _get_table_counts(self): engine = sqlalchemy_api.get_engine() conn = engine.connect() |