diff --git a/neutron/db/ovn_hash_ring_db.py b/neutron/db/ovn_hash_ring_db.py index 5a585432028..55430a55a45 100644 --- a/neutron/db/ovn_hash_ring_db.py +++ b/neutron/db/ovn_hash_ring_db.py @@ -60,6 +60,15 @@ def remove_node_by_uuid(context, node_uuid): LOG.info('Node "%s" removed from the Hash Ring', node_uuid) +@db_api.retry_if_session_inactive() +def cleanup_old_nodes(context, days): + age = timeutils.utcnow() - datetime.timedelta(days=days) + with db_api.CONTEXT_WRITER.using(context): + context.session.query(ovn_models.OVNHashRing).filter( + ovn_models.OVNHashRing.updated_at < age).delete() + LOG.info('Cleaned up Hash Ring nodes older than %d days', days) + + @db_api.retry_if_session_inactive() def _touch(context, updated_at=None, **filter_args): if updated_at is None: diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py index 2a26c691689..03c81c01c0c 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py @@ -1103,6 +1103,20 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase): for table in ('Chassis_Private', 'Chassis'): txn.add(self._sb_idl.db_destroy(table, ch.name)) + @periodics.periodic(spacing=86400, run_immediately=True) + def cleanup_old_hash_ring_nodes(self): + """Daily task to cleanup old stable Hash Ring node entries. + + Runs once a day and clean up Hash Ring entries that haven't + been updated in more than 5 days. See LP #2033281 for more + information. + + """ + if not self.has_lock: + return + context = n_context.get_admin_context() + hash_ring_db.cleanup_old_nodes(context, days=5) + class HashRingHealthCheckPeriodics(object): diff --git a/neutron/tests/unit/db/test_ovn_hash_ring_db.py b/neutron/tests/unit/db/test_ovn_hash_ring_db.py index ba7010bc3e7..9aac99acc9c 100644 --- a/neutron/tests/unit/db/test_ovn_hash_ring_db.py +++ b/neutron/tests/unit/db/test_ovn_hash_ring_db.py @@ -285,3 +285,29 @@ class TestHashRing(testlib_api.SqlTestCaseLight): self.admin_ctx, interval=60, group_name=HASH_RING_TEST_GROUP) self.assertEqual(2, len(active_nodes)) self.assertNotIn(node_to_remove, [n.node_uuid for n in active_nodes]) + + def test_cleanup_old_nodes(self): + # Add 2 new nodes + self._add_nodes_and_assert_exists(count=2) + + # Subtract 5 days from utcnow() and touch the nodes to make + # them to appear stale + fake_utcnow = timeutils.utcnow() - datetime.timedelta(days=5) + with mock.patch.object(timeutils, 'utcnow') as mock_utcnow: + mock_utcnow.return_value = fake_utcnow + ovn_hash_ring_db.touch_nodes_from_host(self.admin_ctx, + HASH_RING_TEST_GROUP) + + # Add 3 new nodes + self._add_nodes_and_assert_exists(count=3) + + # Assert we have 5 nodes in the hash ring + self.assertEqual(5, ovn_hash_ring_db.count_nodes_from_host( + self.admin_ctx, HASH_RING_TEST_GROUP)) + + # Clean up the 2 stale nodes + ovn_hash_ring_db.cleanup_old_nodes(self.admin_ctx, days=5) + + # Assert we only have 3 node entries after the clean up + self.assertEqual(3, ovn_hash_ring_db.count_nodes_from_host( + self.admin_ctx, HASH_RING_TEST_GROUP)) diff --git a/releasenotes/notes/hash-ring-cleanup-1079d2375082cebe.yaml b/releasenotes/notes/hash-ring-cleanup-1079d2375082cebe.yaml new file mode 100644 index 00000000000..082fca1e431 --- /dev/null +++ b/releasenotes/notes/hash-ring-cleanup-1079d2375082cebe.yaml @@ -0,0 +1,6 @@ +--- +other: + - | + Adds a maintenance task that runs once a day and is responsible for + cleaning up Hash Ring nodes that haven't been updated in 5 days or + more. See LP #2033281 for more information. \ No newline at end of file