summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2021-08-31 19:02:18 +0000
committerGerrit Code Review <review@openstack.org>2021-08-31 19:02:18 +0000
commitd024333b80c83c490ffda2d8b412baffb7ace637 (patch)
tree3fbafd27ae0e3d8cd394556c1ff0e5d6fa884e84
parent6eb9f0853dace0f8df0608716bff807aac59fd8b (diff)
parent37cff3c74d54bf0d19abe9502e5979b81e24f918 (diff)
downloadnova-d024333b80c83c490ffda2d8b412baffb7ace637.tar.gz
Merge "Add functional test for bug 1837995" into stable/ussuri
-rw-r--r--nova/tests/functional/db/test_archive.py47
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()