From c4fdba258287e6087b4d023c1286f89869624316 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Wed, 30 Jul 2025 09:01:43 +0000 Subject: [PATCH] [OVN] The OVN agent should handle its own tags and status The OVN agent should handle its own tags, written in the "Chassis_Private.external_ids" dictionary. These are: * neutron:ovn-neutron-agent-sb-cfg * neutron:description-neutron-agent * neutron:ovn-neutron-agent-id The "metadata" extension should not create nor update these tags. This patch removes from the "metadata" extension the ``SbGlobalUpdateEvent`` and ``ChassisPrivateCreateEvent`` events. Partial-Bug: #2119097 Signed-off-by: Rodolfo Alonso Hernandez Change-Id: I7f0ecf8f8727e363d98e981948c999305ae33426 --- neutron/agent/ovn/agent/ovn_neutron_agent.py | 30 ++++++++++++- .../agent/ovn/extensions/extension_manager.py | 30 +++++++++++++ neutron/agent/ovn/extensions/metadata.py | 3 -- neutron/agent/ovn/metadata/agent.py | 45 +++---------------- .../agent/ovn/agent/test_ovn_neutron_agent.py | 24 ++++++++++ 5 files changed, 90 insertions(+), 42 deletions(-) diff --git a/neutron/agent/ovn/agent/ovn_neutron_agent.py b/neutron/agent/ovn/agent/ovn_neutron_agent.py index 1e7f2317227..c4d84c64e1c 100644 --- a/neutron/agent/ovn/agent/ovn_neutron_agent.py +++ b/neutron/agent/ovn/agent/ovn_neutron_agent.py @@ -49,6 +49,32 @@ class SbGlobalUpdateEvent(row_event.RowEvent): ('external_ids', ext_ids)).execute() +class ChassisPrivateCreateEvent(row_event.RowEvent): + """Row create event - Chassis name == our_chassis. + + On connection, we get a dump of all chassis so if we catch a creation + of our own chassis it has to be a reconnection. In this case, we need + to do a full sync to make sure that we capture all changes while the + connection to OVSDB was down. + """ + def __init__(self, ovn_agent): + self._first_time = True + self.ovn_agent = ovn_agent + events = (self.ROW_CREATE,) + super().__init__(events, 'Chassis_Private', None) + self.conditions = (('name', '=', self.ovn_agent.chassis),) + self.event_name = self.__class__.__name__ + + def run(self, event, row, old): + if self._first_time: + self._first_time = False + return + + # Re-register the OVN agent with the local chassis in case its + # entry was re-created (happens when restarting the ovn-controller) + self.ovn_agent.register_ovn_agent() + + class OVNNeutronAgent(service.Service): def __init__(self, conf): @@ -139,7 +165,9 @@ class OVNNeutronAgent(service.Service): return ovsdb.MonitorAgentOvnNbIdl(tables, events).start() def _load_sb_idl(self): - events = [SbGlobalUpdateEvent] + events = [SbGlobalUpdateEvent, + ChassisPrivateCreateEvent, + ] tables = ['SB_Global', 'Chassis_Private'] for extension in self.ext_manager: events += extension.obj.sb_idl_events diff --git a/neutron/agent/ovn/extensions/extension_manager.py b/neutron/agent/ovn/extensions/extension_manager.py index d427b0f4d66..f8b258ee544 100644 --- a/neutron/agent/ovn/extensions/extension_manager.py +++ b/neutron/agent/ovn/extensions/extension_manager.py @@ -19,6 +19,7 @@ import threading from neutron_lib.agent import extension from neutron_lib import exceptions from oslo_log import log as logging +from oslo_service import service from neutron._i18n import _ from neutron.agent import agent_extensions_manager as agent_ext_mgr @@ -33,6 +34,35 @@ class ConfigException(exceptions.NeutronException): message = _('Error configuring the OVN Neutron Agent: %(description)s.') +class OVNExtensionEvent(metaclass=abc.ABCMeta): + """Implements a method to retrieve the correct caller agent + + The events inheriting from this class could be called from the OVN metadata + agent or as part of an extension of the OVN agent ("metadata" extension, + for example). In future releases, the OVN metadata agent will be superseded + by the OVN agent (with the "metadata" extension) and this class removed, + keeping only the compatibility with the OVN agent (to be removed in C+2). + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._agent_or_extension = None + self._agent = None + + @property + def agent(self): + """This method provide support for the OVN agent + + This event can be used in the OVN metadata agent and in the OVN + agent metadata extension. + """ + if not self._agent_or_extension: + if isinstance(self._agent, service.Service): + self._agent_or_extension = self._agent['metadata'] + else: + self._agent_or_extension = self._agent + return self._agent_or_extension + + class OVNAgentExtensionManager(agent_ext_mgr.AgentExtensionsManager): def __init__(self, conf): diff --git a/neutron/agent/ovn/extensions/metadata.py b/neutron/agent/ovn/extensions/metadata.py index db7700249c7..fdd73535c8a 100644 --- a/neutron/agent/ovn/extensions/metadata.py +++ b/neutron/agent/ovn/extensions/metadata.py @@ -115,8 +115,6 @@ class MetadataExtension(extension_manager.OVNAgentExtension, def sb_idl_events(self): return [metadata_agent.PortBindingUpdatedEvent, metadata_agent.PortBindingDeletedEvent, - metadata_agent.SbGlobalUpdateEvent, - metadata_agent.ChassisPrivateCreateEvent, ] # NOTE(ralonsoh): the following properties are needed during the migration @@ -162,7 +160,6 @@ class MetadataExtension(extension_manager.OVNAgentExtension, """ self.agent_api.load_config() self._update_chassis_private_config() - self.agent_api.update_neutron_sb_cfg_key() self.sync() def start(self): diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py index d9b0d9ce11c..3ec41267205 100644 --- a/neutron/agent/ovn/metadata/agent.py +++ b/neutron/agent/ovn/metadata/agent.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import abc import collections import functools import re @@ -31,7 +30,7 @@ from ovsdbapp.backend.ovs_idl import vlog from neutron.agent.linux import external_process from neutron.agent.linux import ip_lib from neutron.agent.linux import iptables_manager -from neutron.agent.ovn.agent import ovn_neutron_agent +from neutron.agent.ovn.extensions import extension_manager from neutron.agent.ovn.metadata import driver as metadata_driver from neutron.agent.ovn.metadata import ovsdb from neutron.agent.ovn.metadata import server_socket as metadata_server @@ -87,36 +86,8 @@ class ConfigException(Exception): """ -class _OVNExtensionEvent(metaclass=abc.ABCMeta): - """Implements a method to retrieve the correct caller agent - - The events inheriting from this class could be called from the OVN metadata - agent or as part of an extension of the OVN agent ("metadata" extension, - for example). In future releases, the OVN metadata agent will be superseded - by the OVN agent (with the "metadata" extension) and this class removed, - keeping only the compatibility with the OVN agent (to be removed in C+2). - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._agent_or_extension = None - self._agent = None - - @property - def agent(self): - """This method provide support for the OVN agent - - This event can be used in the OVN metadata agent and in the OVN - agent metadata extension. - """ - if not self._agent_or_extension: - if isinstance(self._agent, ovn_neutron_agent.OVNNeutronAgent): - self._agent_or_extension = self._agent['metadata'] - else: - self._agent_or_extension = self._agent - return self._agent_or_extension - - -class PortBindingEvent(_OVNExtensionEvent, row_event.RowEvent): +class PortBindingEvent(extension_manager.OVNExtensionEvent, + row_event.RowEvent): def __init__(self, agent): table = 'Port_Binding' super().__init__((self.__class__.EVENT,), table, None) @@ -301,7 +272,7 @@ class PortBindingDeletedEvent(PortBindingEvent): return True -class ChassisPrivateCreateEvent(_OVNExtensionEvent, row_event.RowEvent): +class ChassisPrivateCreateEvent(row_event.RowEvent): """Row create event - Chassis name == our_chassis. On connection, we get a dump of all chassis so if we catch a creation @@ -314,9 +285,7 @@ class ChassisPrivateCreateEvent(_OVNExtensionEvent, row_event.RowEvent): self.first_time = True events = (self.ROW_CREATE,) super().__init__(events, 'Chassis_Private', None) - # NOTE(ralonsoh): ``self._agent`` needs to be assigned before being - # used in the property ``self.agent``. - self._agent = agent + self.agent = agent self.conditions = (('name', '=', self.agent.chassis),) self.event_name = self.__class__.__name__ @@ -332,14 +301,14 @@ class ChassisPrivateCreateEvent(_OVNExtensionEvent, row_event.RowEvent): self.agent.sync() -class SbGlobalUpdateEvent(_OVNExtensionEvent, row_event.RowEvent): +class SbGlobalUpdateEvent(row_event.RowEvent): """Row update event on SB_Global table.""" def __init__(self, agent): table = 'SB_Global' events = (self.ROW_UPDATE,) super().__init__(events, table, None) - self._agent = agent + self.agent = agent self.event_name = self.__class__.__name__ self.first_run = True diff --git a/neutron/tests/functional/agent/ovn/agent/test_ovn_neutron_agent.py b/neutron/tests/functional/agent/ovn/agent/test_ovn_neutron_agent.py index 4cd4181e5a8..08680e8cc2c 100644 --- a/neutron/tests/functional/agent/ovn/agent/test_ovn_neutron_agent.py +++ b/neutron/tests/functional/agent/ovn/agent/test_ovn_neutron_agent.py @@ -18,6 +18,7 @@ import uuid from oslo_config import fixture as fixture_config from oslo_utils import uuidutils +from ovsdbapp.backend.ovs_idl import idlutils from neutron.agent.ovn.agent import ovn_neutron_agent from neutron.agent.ovn.agent import ovsdb as agent_ovsdb @@ -94,6 +95,29 @@ class TestOVNNeutronAgentBase(base.TestOVNFunctionalBase): return agt +class TestOVNNeutronAgent(TestOVNNeutronAgentBase): + def setUp(self, **kwargs): + super().setUp(extensions=[METADATA_EXTENSION], **kwargs) + + def test_chassis_private_create_event(self): + def _check_chassis_private(): + try: + ext_ids = self.ovn_agent.sb_idl.db_get( + 'Chassis_Private', self.chassis_name, + 'external_ids').execute(check_error=True) + return (ext_ids.get(ovn_const.OVN_AGENT_NEUTRON_ID_KEY) + is not None) + except idlutils.RowNotFound: + return False + + # If the "Chassis_Private" register is deleted and created again, + # the agent should be able to re-register itself. + self.ovn_agent.sb_idl.chassis_del(self.chassis_name).execute( + check_error=True) + self.add_fake_chassis(self.host_name, name=self.chassis_name) + n_utils.wait_until_true(_check_chassis_private, timeout=10) + + class TestOVNNeutronAgentFakeAgent(TestOVNNeutronAgentBase): def setUp(self, **kwargs):