From 494cd8ca5dc924dbaa3b3a185110371dfe2e59c1 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Wed, 17 Nov 2021 17:33:29 +0000 Subject: [PATCH] [OVN] Handle OVN agents when "Chassis" register is deleted If an "ovn-controller" ends not gracefully, the node "Chassis" and "Chassis_Private" registers will remain in the OVN SB database. Because there is no a mandatory procedure to delete the "Chassis" and "Chassis_Private" registers, the administrator can manually delete, from the OVN SB database, any register in any order. If the "Chassis" register is deleted and the Neutron server restarted, the updated "Chassis_Private" register will be read from the database. That won't contain the "Chassis" information as this register has been deleted. In this case, the ``NeutronAgent`` returns ``DeletedChassis``, an empty chassis register with no information. NOTE: the sequence of actions ("Chassis" register deletion, Neutron server restart) must be follow to reproduce this issue. If the "Chassis" register is deleted, the Neutron server OVN agent local cache won't update the stored information and will keep the previous value. It is when the Neutron server is restarted when the OVN agent local cache is retrieved again; at this time the "Chassis_Private" register won't have any related "Chassis" register. Closes-Bug: #1951149 Conflicts: neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py Change-Id: I17aa53cea6aba8ea83187c99102a6f25fd33cfff (cherry picked from commit f1a5511e9094d6aae7a61cd858c59c706033ca95) (cherry picked from commit 29998538cec9a696b01d84bd9573f2ebbbb15f90) --- .../ml2/drivers/ovn/agent/neutron_agent.py | 11 ++++++++- .../ovn/mech_driver/test_mech_driver.py | 23 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py index ea4290c701a..ad3aac4abc2 100644 --- a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py +++ b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py @@ -20,6 +20,12 @@ from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import utils as ovn_utils +class DeletedChassis(object): + external_ids = {} + hostname = '("Chassis" register deleted)' + name = '("Chassis" register deleted)' + + class NeutronAgent(abc.ABC): types = {} @@ -35,9 +41,12 @@ class NeutronAgent(abc.ABC): def get_chassis(chassis_private): try: return chassis_private.chassis[0] - except (AttributeError, IndexError): + except AttributeError: # No Chassis_Private support, just use Chassis return chassis_private + except IndexError: + # Chassis register has been deleted but not Chassis_Private. + return DeletedChassis @property def updated_at(self): diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index a66cfb2034c..0642504d55b 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -846,12 +846,12 @@ class AgentWaitEvent(event.WaitEvent): ONETIME = False - def __init__(self, driver, chassis_names): + def __init__(self, driver, chassis_names, events=None, timeout=None): table = driver.agent_chassis_table - events = (self.ROW_CREATE,) + events = events or (self.ROW_CREATE,) self.chassis_names = chassis_names - super().__init__(events, table, None) - self.event_name = 'AgentWaitEvent' + super().__init__(events, table, None, timeout=timeout) + self.event_name = "AgentWaitEvent" def match_fn(self, event, row, old): return row.name in self.chassis_names @@ -909,6 +909,21 @@ class TestAgentApi(base.TestOVNFunctionalBase): self.context, filters={'host': self.host})] self.assertCountEqual(list(self.agent_types.values()), agent_ids) + # "ovn-controller" ends without deleting "Chassis" and + # "Chassis_Private" registers. If "Chassis" register is deleted, + # then Chassis_Private.chassis = []; both metadata and controller + # agents will still be present in the agent list. + agent_event = AgentWaitEvent(self.mech_driver, [self.chassis], + events=(event.RowEvent.ROW_UPDATE,), + timeout=1) + self.sb_api.idl.notify_handler.watch_event(agent_event) + self.sb_api.chassis_del(self.chassis, if_exists=True).execute( + check_error=False) + agent_event.wait() + agent_ids = [a['id'] for a in self.plugin.get_agents( + self.context, filters={'host': self.host})] + self.assertCountEqual(list(self.agent_types.values()), agent_ids) + class ConnectionInactivityProbeSetEvent(event.WaitEvent): """Wait for a Connection (NB/SB) to have the inactivity probe set"""