diff -Nru neutron-20.5.0/debian/changelog neutron-20.5.0/debian/changelog --- neutron-20.5.0/debian/changelog 2024-01-06 07:49:46.000000000 +0800 +++ neutron-20.5.0/debian/changelog 2024-03-26 14:15:56.000000000 +0800 @@ -1,3 +1,12 @@ +neutron (2:20.5.0-0ubuntu1~cloud1) focal; urgency=medium + + * ovnmeta namespaces missing during scalability test causing DHCP issues + * d/p/0001-Update-tap-ip-in-metadata-agent-when-metadata-port-i.patch + * d/p/0001-ovn-metadata-Refactor-events.patch + * d/p/0001-Handle-creation-of-Port_Binding-with-chassis-set.patch + + -- Zhang Hua Mon, 26 Mar 2024 14:18:42 +0800 + neutron (2:20.5.0-0ubuntu1~cloud0) focal-yoga; urgency=medium * New upstream release for the Ubuntu Cloud Archive. diff -Nru neutron-20.5.0/debian/patches/0001-Handle-creation-of-Port_Binding-with-chassis-set.patch neutron-20.5.0/debian/patches/0001-Handle-creation-of-Port_Binding-with-chassis-set.patch --- neutron-20.5.0/debian/patches/0001-Handle-creation-of-Port_Binding-with-chassis-set.patch 1970-01-01 08:00:00.000000000 +0800 +++ neutron-20.5.0/debian/patches/0001-Handle-creation-of-Port_Binding-with-chassis-set.patch 2024-03-26 12:24:45.000000000 +0800 @@ -0,0 +1,376 @@ +From 84f77e90a7eafbad5594dcc4e90d2365e6c80cbb Mon Sep 17 00:00:00 2001 +From: Terry Wilson +Date: Fri, 15 Dec 2023 21:00:43 +0000 +Subject: [PATCH] Handle creation of Port_Binding with chassis set + +When there is a backlog of notifications to be sent, it is possible +that ovsdb-server will merge insert and update notifications. Due +to this, we need to handle the situation where we see a Port_Binding +created with the chassis set. + +Closes-Bug: #2017748 + +Change-Id: Idfae87cf6c60e9e18ede91ea20857cea5322738c +(cherry picked from commit a641e8aec09c1e33a15a34b19d92675ed2c85682) +(cherry picked from commit b992d639b974f35612d6bb0057f35c452129aed3) +Signed-off-by: Zhang Hua +--- + neutron/agent/ovn/extensions/qos_hwol.py | 289 +++++++++++++++++++++++ + neutron/agent/ovn/metadata/agent.py | 21 +- + 2 files changed, 306 insertions(+), 4 deletions(-) + create mode 100644 neutron/agent/ovn/extensions/qos_hwol.py + +Index: neutron-20.5.0/neutron/agent/ovn/extensions/qos_hwol.py +=================================================================== +--- /dev/null ++++ neutron-20.5.0/neutron/agent/ovn/extensions/qos_hwol.py +@@ -0,0 +1,289 @@ ++# Copyright (c) 2023 Red Hat, Inc. ++# All Rights Reserved. ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); you may ++# not use this file except in compliance with the License. You may obtain ++# a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++# License for the specific language governing permissions and limitations ++# under the License. ++ ++from oslo_log import log as logging ++from ovsdbapp.backend.ovs_idl import event as row_event ++ ++from neutron.agent.linux import devlink ++from neutron.agent.ovn.agent import ovsdb as agent_ovsdb ++from neutron.agent.ovn.extensions import extension_manager ++from neutron.common.ovn import constants as ovn_const ++from neutron.common.ovn import utils as ovn_utils ++from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager ++# NOTE(ralonsoh): move ``pci_lib`` to ``neutron.agent.linux``. ++from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib ++ ++ ++LOG = logging.getLogger(__name__) ++# NOTE(ralonsoh): move these constants from ``eswitch_manager`` to ``pci_lib``. ++MAX_TX_RATE = eswitch_manager.IP_LINK_CAPABILITY_RATE ++MIN_TX_RATE = eswitch_manager.IP_LINK_CAPABILITY_MIN_TX_RATE ++TX_RATES = eswitch_manager.IP_LINK_CAPABILITY_RATES ++NB_IDL_TABLES = ['QoS', ++ 'Logical_Switch_Port', ++ 'Logical_Switch', ++ ] ++SB_IDL_TABLES = ['Chassis', ++ 'Chassis_Private', ++ 'Encap', ++ 'Port_Binding', ++ 'Datapath_Binding', ++ ] ++ ++ ++class OVSInterfaceEvent(row_event.RowEvent): ++ LOG_MSG = 'Port ID %s, port name %s (event: %s)' ++ ++ def __init__(self, ovn_agent): ++ self.ovn_agent = ovn_agent ++ events = (self.ROW_CREATE, self.ROW_DELETE) ++ table = 'Interface' ++ super().__init__(events, table, None) ++ ++ def match_fn(self, event, row, old): ++ if not row.external_ids.get('iface-id'): ++ return False ++ return True ++ ++ def run(self, event, row, old): ++ if event == self.ROW_CREATE: ++ self.ovn_agent.qos_hwol_ext.add_port( ++ row.external_ids['iface-id'], row.name) ++ elif event == self.ROW_DELETE: ++ self.ovn_agent.qos_hwol_ext.remove_egress( ++ row.external_ids['iface-id']) ++ LOG.debug(self.LOG_MSG, row.external_ids['iface-id'], row.name, event) ++ ++ ++class QoSBandwidthLimitEvent(row_event.RowEvent): ++ LOG_MSG = 'QoS register %s, port ID %s, max_kbps: %s (event: %s)' ++ ++ def __init__(self, ovn_agent): ++ self.ovn_agent = ovn_agent ++ table = 'QoS' ++ events = (self.ROW_CREATE, self.ROW_UPDATE, self.ROW_DELETE) ++ super().__init__(events, table, None) ++ ++ def match_fn(self, event, row, old): ++ if not self.ovn_agent.sb_post_fork_event.is_set(): ++ return False ++ ++ # Check if the port has a Port ID and if this ID is bound to this host. ++ port_id = row.external_ids.get(ovn_const.OVN_PORT_EXT_ID_KEY) ++ if not port_id or not self.ovn_agent.qos_hwol_ext.get_port(port_id): ++ return False ++ ++ if event in (self.ROW_CREATE, self.ROW_DELETE): ++ # Check direction, only egress rules ('from-lport') accepted. ++ if row.direction != 'from-lport': ++ return False ++ elif event == self.ROW_UPDATE: ++ try: ++ if row.bandwidth['rate'] == old.bandwidth['rate']: ++ return False ++ except (KeyError, AttributeError): ++ # No "rate" update. ++ return False ++ ++ return True ++ ++ def run(self, event, row, old): ++ port_id = row.external_ids[ovn_const.OVN_PORT_EXT_ID_KEY] ++ max_kbps, min_kbps = agent_ovsdb.get_port_qos(self.ovn_agent.nb_idl, ++ port_id) ++ LOG.debug(self.LOG_MSG, str(row.uuid), port_id, max_kbps, event) ++ self.ovn_agent.qos_hwol_ext.update_egress(port_id, max_kbps, min_kbps) ++ ++ ++class QoSMinimumBandwidthEvent(row_event.RowEvent): ++ LOG_MSG = 'Port ID %s, min_kbps: %s (event: %s)' ++ ++ def __init__(self, ovn_agent): ++ self.ovn_agent = ovn_agent ++ table = 'Logical_Switch_Port' ++ events = (self.ROW_UPDATE, ) ++ super().__init__(events, table, None) ++ ++ def match_fn(self, event, row, old): ++ if not self.ovn_agent.sb_post_fork_event.is_set(): ++ return False ++ ++ # The "qos_min_rate" set on the LSP has always egress direction. ++ # Check if "options:qos_min_rate" has changed. ++ try: ++ ovn_min_rate = ovn_const.LSP_OPTIONS_QOS_MIN_RATE ++ if row.options.get(ovn_min_rate) == old.options.get(ovn_min_rate): ++ return False ++ except (KeyError, AttributeError): ++ return False ++ ++ if not self.ovn_agent.qos_hwol_ext.get_port(row.name): ++ return False ++ ++ return True ++ ++ def run(self, event, row, old): ++ max_kbps, min_kbps = agent_ovsdb.get_port_qos(self.ovn_agent.nb_idl, ++ row.name) ++ LOG.debug(self.LOG_MSG, row.name, min_kbps, event) ++ self.ovn_agent.qos_hwol_ext.update_egress(row.name, max_kbps, min_kbps) ++ ++ ++class _PortBindingChassisEvent(row_event.RowEvent): ++ ++ def __init__(self, ovn_agent, events): ++ self.ovn_agent = ovn_agent ++ self.ovs_idl = self.ovn_agent.ovs_idl ++ table = 'Port_Binding' ++ super().__init__(events, table, None) ++ self.event_name = self.__class__.__name__ ++ ++ def run(self, event, row, old): ++ pass ++ ++ ++class PortBindingChassisCreatedEvent(_PortBindingChassisEvent): ++ LOG_MSG = 'Port ID %s, datapath %s, OVS port name: %s (event: %s)' ++ ++ def __init__(self, ovn_agent): ++ events = (self.ROW_CREATE, self.ROW_UPDATE,) ++ super().__init__(ovn_agent, events) ++ ++ def match_fn(self, event, row, old): ++ try: ++ return (row.chassis[0].name == self.ovn_agent.chassis and ++ (event == self.ROW_CREATE or not old.chassis)) ++ except (IndexError, AttributeError): ++ return False ++ ++ def run(self, event, row, old): ++ ovs_port_name = agent_ovsdb.get_ovs_port_name(self.ovs_idl, ++ row.logical_port) ++ net_name = ovn_utils.get_network_name_from_datapath(row.datapath) ++ LOG.debug(self.LOG_MSG, row.logical_port, net_name, ovs_port_name, ++ event) ++ max_kbps, min_kbps = agent_ovsdb.get_port_qos(self.ovn_agent.nb_idl, ++ row.logical_port) ++ self.ovn_agent.qos_hwol_ext.update_egress(row.logical_port, max_kbps, ++ min_kbps) ++ ++ ++class QoSHardwareOffloadExtension(extension_manager.OVNAgentExtension): ++ ++ def __init__(self): ++ super().__init__() ++ # _ovs_ports = {Neutron port ID: OVS port name} ++ self._ovs_ports = {} ++ ++ @property ++ def ovs_idl_events(self): ++ return [OVSInterfaceEvent, ++ ] ++ ++ @property ++ def nb_idl_tables(self): ++ return NB_IDL_TABLES ++ ++ @property ++ def nb_idl_events(self): ++ return [QoSBandwidthLimitEvent, ++ QoSMinimumBandwidthEvent, ++ ] ++ ++ @property ++ def sb_idl_tables(self): ++ return SB_IDL_TABLES ++ ++ @property ++ def sb_idl_events(self): ++ return [PortBindingChassisCreatedEvent, ++ ] ++ ++ def add_port(self, port_id, port_name): ++ self._ovs_ports[port_id] = port_name ++ ++ def del_port(self, port_id): ++ return self._ovs_ports.pop(port_id, None) ++ ++ def get_port(self, port_id): ++ return self._ovs_ports.get(port_id) ++ ++ @staticmethod ++ def _set_device_rate(pf_name, vf_index, rates): ++ """Set device rate: max_tx_rate, min_tx_rate ++ ++ @param pf_name: Physical Function name ++ @param vf_index: Virtual Function index ++ @param rates: dictionary with rate type (str) and the value (int) ++ in Kbps. Example: ++ {'max_tx_rate': 20000, 'min_tx_rate': 10000} ++ {'max_tx_rate': 30000} ++ {'min_tx_rate': 5000} ++ """ ++ LOG.debug('Setting rates on device %(pf_name)s, VF number ' ++ '%(vf_index)s: %(rates)s', ++ {'pf_name': pf_name, 'vf_index': vf_index, 'rates': rates}) ++ if not pf_name: ++ LOG.warning('Empty PF name, rates cannot be set') ++ return ++ ++ pci_dev_wrapper = pci_lib.PciDeviceIPWrapper(pf_name) ++ return pci_dev_wrapper.set_vf_rate(vf_index, rates) ++ ++ @staticmethod ++ def _kbps_2_mbps(rate_kbps): ++ if rate_kbps == 0: # Delete the BW setting. ++ return 0 ++ elif 0 < rate_kbps < 1000: # Any value under 1000kbps --> 1Mbps ++ return 1 ++ else: ++ return int(rate_kbps / 1000.0) ++ ++ def _get_port_representor(self, port_id): ++ port_name = self.get_port(port_id) ++ if not port_name: ++ return ++ ++ pr = devlink.get_port(port_name) ++ if not pr: ++ return ++ ++ return pr ++ ++ def update_egress(self, port_id, max_kbps, min_kbps): ++ pr = self._get_port_representor(port_id) ++ if not pr: ++ return ++ ++ _qos = {MAX_TX_RATE: self._kbps_2_mbps(int(max_kbps)), ++ MIN_TX_RATE: self._kbps_2_mbps(int(min_kbps))} ++ self._set_device_rate(pr['pf_name'], pr['vf_num'], _qos) ++ ++ def reset_egress(self, port_id): ++ pr = self._get_port_representor(port_id) ++ if not pr: ++ return ++ ++ self._set_device_rate(pr['pf_name'], pr['vf_num'], ++ {MAX_TX_RATE: 0, MIN_TX_RATE: 0}) ++ ++ def remove_egress(self, port_id): ++ pr = self._get_port_representor(port_id) ++ self.del_port(port_id) ++ if not pr: ++ return ++ ++ self._set_device_rate(pr['pf_name'], pr['vf_num'], ++ {MAX_TX_RATE: 0, MIN_TX_RATE: 0}) +Index: neutron-20.5.0/neutron/agent/ovn/metadata/agent.py +=================================================================== +--- neutron-20.5.0.orig/neutron/agent/ovn/metadata/agent.py ++++ neutron-20.5.0/neutron/agent/ovn/metadata/agent.py +@@ -109,6 +109,16 @@ class PortBindingEvent(row_event.RowEven + self.agent.resync() + + ++class PortBindingCreateWithChassis(PortBindingEvent): ++ EVENT = PortBindingEvent.ROW_CREATE ++ ++ def match_fn(self, event, row, old): ++ self._log_msg = "Port %s in datapath %s bound to our chassis on insert" ++ if not (super().match_fn(event, row, old) and row.chassis): ++ return False ++ return row.chassis[0].name == self.agent.chassis ++ ++ + class PortBindingUpdatedEvent(PortBindingEvent): + EVENT = PortBindingEvent.ROW_UPDATE + +@@ -316,6 +326,7 @@ class MetadataAgent(object): + tables = ('Encap', 'Port_Binding', 'Datapath_Binding', 'SB_Global', + 'Chassis') + events = (PortBindingUpdatedEvent(self), ++ PortBindingCreateWithChassis(self), + PortBindingDeletedEvent(self), + SbGlobalUpdateEvent(self), + ) +@@ -345,7 +356,8 @@ class MetadataAgent(object): + self._proxy.run() + + # Do the initial sync. +- self.sync() ++ # Provisioning handled by PortBindingCreateWithChassis ++ self.sync(provision=False) + + # Register the agent with its corresponding Chassis + self.register_metadata_agent() +@@ -396,7 +408,7 @@ class MetadataAgent(object): + return set(p.datapath for p in self._vif_ports(ports)) + + @_sync_lock +- def sync(self): ++ def sync(self, provision=True): + """Agent sync. + + This function will make sure that all networks with ports in our +@@ -423,8 +435,9 @@ class MetadataAgent(object): + # resync all network namespaces based on the associated datapaths, + # even those that are already running. This is to make sure + # everything within each namespace is up to date. +- for datapath in net_datapaths: +- self.provision_datapath(datapath) ++ if provision: ++ for datapath in net_datapaths: ++ self.provision_datapath(datapath) + + @staticmethod + def _get_veth_name(datapath): diff -Nru neutron-20.5.0/debian/patches/0001-ovn-metadata-Refactor-events.patch neutron-20.5.0/debian/patches/0001-ovn-metadata-Refactor-events.patch --- neutron-20.5.0/debian/patches/0001-ovn-metadata-Refactor-events.patch 1970-01-01 08:00:00.000000000 +0800 +++ neutron-20.5.0/debian/patches/0001-ovn-metadata-Refactor-events.patch 2024-03-26 12:24:45.000000000 +0800 @@ -0,0 +1,432 @@ +From 915021497eafc4ec694fb499f07912588dffc6b8 Mon Sep 17 00:00:00 2001 +From: Jakub Libosvar +Date: Thu, 21 Sep 2023 19:40:36 +0000 +Subject: [PATCH] ovn-metadata: Refactor events + +The agent had multiple events matching the same event type and calling +the same run() method. Also the same things as in match_fn() were +checked later again in run() method. This patch squashes the events, as +a result there are only Port Binding Update and Port Binding Delete +events that either provision or teardown the resources. + +Related bug: #2036118 + +Change-Id: I13f4e95eacb3cbdd3170f9707b2310bfae13a2bc +Signed-off-by: Jakub Libosvar +(cherry picked from commit 6801589510242affc78497660d34377603774074) +(cherry picked from commit 62051588314b66ba884e5df1cf2e6f39dfd50506) +Signed-off-by: Zhang Hua +--- + neutron/agent/ovn/metadata/agent.py | 142 ++++++++------- + .../agent/ovn/metadata/test_metadata_agent.py | 166 +++++++++--------- + 2 files changed, 157 insertions(+), 151 deletions(-) + +diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py +index 4234bd116a..f3b8426920 100644 +--- a/neutron/agent/ovn/metadata/agent.py ++++ b/neutron/agent/ovn/metadata/agent.py +@@ -48,7 +48,8 @@ CHASSIS_METADATA_LOCK = 'chassis_metadata_lock' + + NS_PREFIX = 'ovnmeta-' + MAC_PATTERN = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) +-OVN_VIF_PORT_TYPES = ("", "external", ovn_const.LSP_TYPE_LOCALPORT, ) ++OVN_VIF_PORT_TYPES = ( ++ "", ovn_const.LSP_TYPE_EXTERNAL, ovn_const.LSP_TYPE_LOCALPORT) + + MetadataPortInfo = collections.namedtuple('MetadataPortInfo', ['mac', + 'ip_addresses', +@@ -74,39 +75,31 @@ class ConfigException(Exception): + """ + + +-class PortBindingChassisEvent(row_event.RowEvent): +- def __init__(self, metadata_agent, events): ++class PortBindingEvent(row_event.RowEvent): ++ def __init__(self, metadata_agent): + self.agent = metadata_agent + table = 'Port_Binding' +- super(PortBindingChassisEvent, self).__init__( +- events, table, None) ++ super().__init__((self.__class__.EVENT,), table, None) + self.event_name = self.__class__.__name__ ++ self._log_msg = ( ++ "PortBindingEvent matched for logical port %s and network %s") ++ ++ def log_row(self, row): ++ net_name = ovn_utils.get_network_name_from_datapath( ++ row.datapath) ++ LOG.info(self._log_msg, row.logical_port, net_name) ++ ++ def match_fn(self, event, row, old): ++ return row.type in OVN_VIF_PORT_TYPES + + def run(self, event, row, old): + # Check if the port has been bound/unbound to our chassis and update + # the metadata namespace accordingly. + resync = False +- if row.type not in OVN_VIF_PORT_TYPES: +- return +- if row.type == ovn_const.LSP_TYPE_LOCALPORT: +- new_ext_ids = row.external_ids +- old_ext_ids = old.external_ids +- device_id = row.external_ids.get( +- ovn_const.OVN_DEVID_EXT_ID_KEY, "") +- if not device_id.startswith(NS_PREFIX): +- return +- new_cidrs = new_ext_ids.get(ovn_const.OVN_CIDRS_EXT_ID_KEY, "") +- old_cidrs = old_ext_ids.get(ovn_const.OVN_CIDRS_EXT_ID_KEY, "") +- # If old_cidrs is "", it is create event, +- # nothing needs to be done. +- # If old_cidrs equals new_cidrs, the ip does not change. +- if old_cidrs in ("", new_cidrs, ): +- return ++ + with _SYNC_STATE_LOCK.read_lock(): ++ self.log_row(row) + try: +- net_name = ovn_utils.get_network_name_from_datapath( +- row.datapath) +- LOG.info(self.LOG_MSG, row.logical_port, net_name) + self.agent.provision_datapath(row.datapath) + except ConfigException: + # We're now in the reader lock mode, we need to exit the +@@ -116,63 +109,84 @@ class PortBindingChassisEvent(row_event.RowEvent): + self.agent.resync() + + +-class PortBindingMetaPortUpdatedEvent(PortBindingChassisEvent): +- LOG_MSG = "Metadata Port %s in datapath %s updated." ++class PortBindingUpdatedEvent(PortBindingEvent): ++ EVENT = PortBindingEvent.ROW_UPDATE + +- def __init__(self, metadata_agent): +- events = (self.ROW_UPDATE,) +- super(PortBindingMetaPortUpdatedEvent, self).__init__( +- metadata_agent, events) ++ def __init__(self, *args, **kwargs): ++ super().__init__(*args, **kwargs) ++ self._match_checks = [ ++ self._is_localport_ext_ids_update, ++ self._is_new_chassis_set, ++ self._is_chassis_removed, ++ ] + + def match_fn(self, event, row, old): +- if row.type == ovn_const.LSP_TYPE_LOCALPORT: +- if hasattr(row, 'external_ids') and hasattr(old, 'external_ids'): +- device_id = row.external_ids.get( +- ovn_const.OVN_DEVID_EXT_ID_KEY, "") +- if device_id.startswith(NS_PREFIX): +- return True +- return False ++ if not super().match_fn(event, row, old): ++ return False ++ # if any of the check functions is true, the event should be triggered ++ return any(check(row, old) for check in self._match_checks) ++ ++ def _is_localport_ext_ids_update(self, row, old): ++ if row.type != ovn_const.LSP_TYPE_LOCALPORT: ++ return False + ++ if not hasattr(old, 'external_ids'): ++ return False + +-class PortBindingChassisCreatedEvent(PortBindingChassisEvent): +- LOG_MSG = "Port %s in datapath %s bound to our chassis" ++ device_id = row.external_ids.get( ++ ovn_const.OVN_DEVID_EXT_ID_KEY, "") ++ if not device_id.startswith(NS_PREFIX): ++ return False + +- def __init__(self, metadata_agent): +- events = (self.ROW_UPDATE,) +- super(PortBindingChassisCreatedEvent, self).__init__( +- metadata_agent, events) ++ new_cidrs = row.external_ids.get( ++ ovn_const.OVN_CIDRS_EXT_ID_KEY, "") ++ old_cidrs = old.external_ids.get( ++ ovn_const.OVN_CIDRS_EXT_ID_KEY, "") ++ # If old_cidrs is "", it is create event, ++ # nothing needs to be done. ++ # If old_cidrs equals new_cidrs, the ip does not change. ++ if old_cidrs not in ("", new_cidrs): ++ self._log_msg = ( ++ "Metadata Port %s in datapath %s updated") ++ return True ++ return False + +- def match_fn(self, event, row, old): ++ def _is_new_chassis_set(self, row, old): ++ self._log_msg = "Port %s in datapath %s bound to our chassis" + try: + return (row.chassis[0].name == self.agent.chassis and + not old.chassis) + except (IndexError, AttributeError): + return False + ++ def _is_chassis_removed(self, row, old): ++ self._log_msg = "Port %s in datapath %s unbound from our chassis" ++ try: ++ return (old.chassis[0].name == self.agent.chassis and ++ not row.chassis) ++ except (IndexError, AttributeError): ++ return False + +-class PortBindingChassisDeletedEvent(PortBindingChassisEvent): +- LOG_MSG = "Port %s in datapath %s unbound from our chassis" + +- def __init__(self, metadata_agent): +- events = (self.ROW_UPDATE, self.ROW_DELETE) +- super(PortBindingChassisDeletedEvent, self).__init__( +- metadata_agent, events) ++class PortBindingDeletedEvent(PortBindingEvent): ++ EVENT = PortBindingEvent.ROW_DELETE + + def match_fn(self, event, row, old): ++ if not super().match_fn(event, row, old): ++ return False + try: +- if event == self.ROW_UPDATE: +- return (old.chassis[0].name == self.agent.chassis and +- not row.chassis) +- else: +- if row.chassis[0].name == self.agent.chassis: +- if row.type != "external": +- LOG.warning( +- 'Removing non-external type port %(port_id)s with ' +- 'type "%(type)s"', +- {"port_id": row.uuid, "type": row.type}) +- return True ++ if row.chassis[0].name != self.agent.chassis: ++ return False + except (IndexError, AttributeError): + return False ++ if row.type != ovn_const.LSP_TYPE_EXTERNAL: ++ LOG.warning( ++ 'Removing non-external type port %(port_id)s with ' ++ 'type "%(type)s"', ++ {"port_id": row.uuid, "type": row.type}) ++ self._log_msg = ( ++ "Port %s in datapath %s unbound from our chassis") ++ return True + + + class ChassisCreateEventBase(row_event.RowEvent): +@@ -301,10 +315,10 @@ class MetadataAgent(object): + + tables = ('Encap', 'Port_Binding', 'Datapath_Binding', 'SB_Global', + 'Chassis') +- events = (PortBindingChassisCreatedEvent(self), +- PortBindingChassisDeletedEvent(self), ++ events = (PortBindingUpdatedEvent(self), ++ PortBindingDeletedEvent(self), + SbGlobalUpdateEvent(self), +- PortBindingMetaPortUpdatedEvent(self)) ++ ) + + # TODO(lucasagomes): Remove this in the future. Try to register + # the Chassis_Private table, if not present, fallback to the normal +diff --git a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py b/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py +index bfa4747038..4ff15ccc30 100644 +--- a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py ++++ b/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py +@@ -209,108 +209,66 @@ class TestMetadataAgent(base.TestOVNFunctionalBase): + timeout=10, + exception=exc) + +- def _test_agent_events(self, delete, type_=None, update=False): +- m_pb_created = mock.patch.object( +- agent.PortBindingChassisCreatedEvent, 'run').start() +- m_pb_deleted = mock.patch.object( +- agent.PortBindingChassisDeletedEvent, 'run').start() +- m_pb_updated = mock.patch.object( +- agent.PortBindingMetaPortUpdatedEvent, 'run').start() +- ++ def _test_agent_events_prepare(self, lsp_type=None): + lswitchport_name, lswitch_name = self._create_logical_switch_port( +- type_) +- self.sb_api.lsp_bind(lswitchport_name, self.chassis_name).execute( +- check_error=True, log_errors=True) +- if update and type_ == ovn_const.LSP_TYPE_LOCALPORT: +- with self.nb_api.transaction( +- check_error=True, log_errors=True) as txn: +- mdt_port_name = self._create_metadata_port(txn, lswitch_name) +- self.sb_api.lsp_bind(mdt_port_name, self.chassis_name).execute( ++ lsp_type) ++ with mock.patch.object( ++ agent.MetadataAgent, 'provision_datapath') as m_provision: ++ self.sb_api.lsp_bind(lswitchport_name, self.chassis_name).execute( + check_error=True, log_errors=True) +- self._update_metadata_port_ip(mdt_port_name) +- +- def pb_created(): +- if m_pb_created.call_count < 1: +- return False +- args = m_pb_created.call_args[0] +- self.assertEqual('update', args[0]) +- self.assertEqual(self.chassis_name, args[1].chassis[0].name) +- self.assertFalse(args[2].chassis) +- return True + +- n_utils.wait_until_true( +- pb_created, +- timeout=10, +- exception=Exception( +- "PortBindingChassisCreatedEvent didn't happen on port " +- "binding.")) +- +- def pb_updated(): +- if m_pb_updated.call_count < 1: +- return False +- args = m_pb_updated.call_args[0] +- self.assertEqual('update', args[0]) +- self.assertTrue(args[1].external_ids) +- self.assertTrue(args[2].external_ids) +- device_id = args[1].external_ids.get( +- ovn_const.OVN_DEVID_EXT_ID_KEY, "") +- self.assertTrue(device_id.startswith("ovnmeta-")) +- new_cidrs = args[1].external_ids.get( +- ovn_const.OVN_CIDRS_EXT_ID_KEY, "") +- old_cidrs = args[2].external_ids.get( +- ovn_const.OVN_CIDRS_EXT_ID_KEY, "") +- self.assertNotEqual(new_cidrs, old_cidrs) +- self.assertNotEqual(old_cidrs, "") +- return True +- if update and type_ == ovn_const.LSP_TYPE_LOCALPORT: ++ # Wait until port is bound + n_utils.wait_until_true( +- pb_updated, ++ lambda: m_provision.called, + timeout=10, + exception=Exception( +- "PortBindingMetaPortUpdatedEvent didn't happen on " +- "metadata port ip address updated.")) ++ "Datapath provisioning did not happen on port binding")) + +- if delete: +- self.nb_api.delete_lswitch_port( +- lswitchport_name, lswitch_name).execute( +- check_error=True, log_errors=True) +- else: ++ return lswitchport_name, lswitch_name ++ ++ def test_agent_unbind_port(self): ++ lswitchport_name, lswitch_name = self._test_agent_events_prepare() ++ ++ with mock.patch.object( ++ agent.MetadataAgent, 'provision_datapath') as m_provision: + self.sb_api.lsp_unbind(lswitchport_name).execute( + check_error=True, log_errors=True) + +- def pb_deleted(): +- if m_pb_deleted.call_count < 1: +- return False +- args = m_pb_deleted.call_args[0] +- if delete: +- self.assertEqual('delete', args[0]) +- self.assertTrue(args[1].chassis) +- self.assertEqual(self.chassis_name, args[1].chassis[0].name) +- else: +- self.assertEqual('update', args[0]) +- self.assertFalse(args[1].chassis) +- self.assertEqual(self.chassis_name, args[2].chassis[0].name) +- return True ++ n_utils.wait_until_true( ++ lambda: m_provision.called, ++ timeout=10, ++ exception=Exception( ++ "Datapath teardown did not happen after the port was " ++ "unbound")) + +- n_utils.wait_until_true( +- pb_deleted, +- timeout=10, +- exception=Exception( +- "PortBindingChassisDeletedEvent didn't happen on port " +- "unbind or delete.")) ++ def _test_agent_delete_bound_external_port(self, lsp_type=None): ++ lswitchport_name, lswitch_name = self._test_agent_events_prepare( ++ lsp_type) + +- self.assertEqual(1, m_pb_deleted.call_count) ++ with mock.patch.object( ++ agent.MetadataAgent, 'provision_datapath') as m_provision,\ ++ mock.patch.object(agent.LOG, 'warning') as m_log_warn: ++ self.nb_api.delete_lswitch_port( ++ lswitchport_name, lswitch_name).execute( ++ check_error=True, log_errors=True) + +- def test_agent_unbind_port(self): +- self._test_agent_events(delete=False) ++ n_utils.wait_until_true( ++ lambda: m_provision.called, ++ timeout=10, ++ exception=Exception( ++ "Datapath teardown did not happen after external port was " ++ "deleted")) ++ if lsp_type == ovn_const.LSP_TYPE_EXTERNAL: ++ m_log_warn.assert_not_called() ++ else: ++ m_log_warn.assert_called() + + def test_agent_delete_bound_external_port(self): +- self._test_agent_events(delete=True, type_='external') ++ self._test_agent_delete_bound_external_port( ++ lsp_type=ovn_const.LSP_TYPE_EXTERNAL) + + def test_agent_delete_bound_nonexternal_port(self): +- with mock.patch.object(agent.LOG, 'warning') as m_warn: +- self._test_agent_events(delete=True) +- self.assertTrue(m_warn.called) ++ self._test_agent_delete_bound_external_port() + + def test_agent_registration_at_chassis_create_event(self): + def check_for_metadata(): +@@ -342,8 +300,42 @@ class TestMetadataAgent(base.TestOVNFunctionalBase): + exception=exc) + + def test_agent_metadata_port_ip_update_event(self): +- self._test_agent_events( +- delete=False, type_=ovn_const.LSP_TYPE_LOCALPORT, update=True) ++ lswitch_name = 'ovn-' + uuidutils.generate_uuid() ++ mdt_port_name = 'ovn-mdt-' + uuidutils.generate_uuid() ++ ++ mdt_pb_event = test_event.WaitForPortBindingEvent(mdt_port_name) ++ self.handler.watch_event(mdt_pb_event) ++ ++ with self.nb_api.transaction( ++ check_error=True, log_errors=True) as txn: ++ txn.add( ++ self.nb_api.ls_add(lswitch_name)) ++ self._create_metadata_port(txn, lswitch_name, mdt_port_name) ++ ++ self.assertTrue(mdt_pb_event.wait()) ++ ++ with mock.patch.object( ++ agent.MetadataAgent, 'provision_datapath') as m_provision: ++ self.sb_api.lsp_bind(mdt_port_name, self.chassis_name).execute( ++ check_error=True, log_errors=True) ++ ++ # Wait until port is bound ++ n_utils.wait_until_true( ++ lambda: m_provision.called, ++ timeout=10, ++ exception=Exception( ++ "Datapath provisioning did not happen on port binding")) ++ ++ m_provision.reset_mock() ++ ++ self._update_metadata_port_ip(mdt_port_name) ++ ++ n_utils.wait_until_true( ++ lambda: m_provision.called, ++ timeout=10, ++ exception=Exception( ++ "Datapath provisioning not called after external ids was " ++ "changed")) + + def test_metadata_agent_only_monitors_own_chassis(self): + # We already have the fake chassis which we should be monitoring, so +-- +2.34.1 + diff -Nru neutron-20.5.0/debian/patches/0001-Update-tap-ip-in-metadata-agent-when-metadata-port-i.patch neutron-20.5.0/debian/patches/0001-Update-tap-ip-in-metadata-agent-when-metadata-port-i.patch --- neutron-20.5.0/debian/patches/0001-Update-tap-ip-in-metadata-agent-when-metadata-port-i.patch 1970-01-01 08:00:00.000000000 +0800 +++ neutron-20.5.0/debian/patches/0001-Update-tap-ip-in-metadata-agent-when-metadata-port-i.patch 2024-03-26 12:24:45.000000000 +0800 @@ -0,0 +1,200 @@ +From e656d14ffe1bf1bd36c53d0f92a585bc8cf1eec2 Mon Sep 17 00:00:00 2001 +From: "hailun.huang" +Date: Wed, 16 Nov 2022 09:35:22 +0000 +Subject: [PATCH] Update tap ip in metadata agent when metadata port ip updated + +Update neutron-ovn-metadata-agent, catch port_binding update event +of monitoring localport type, and judge if the neutron:cidrs field in +the external_ids of port_binding table has changed, +then update_datapath. + +Closes-Bug: #1996677 +Change-Id: Ibdc1b385b07a2ab1ca8e4b6278f6d39fb5839509 +(cherry picked from commit 686698284b3553001398f65614c6521359bc45aa) +Signed-off-by: Zhang Hua +--- + neutron/agent/ovn/metadata/agent.py | 37 ++++++++++++- + .../agent/ovn/metadata/test_metadata_agent.py | 54 ++++++++++++++++++- + .../notes/bug-1996677-64851b476a0c5a37.yaml | 6 +++ + 3 files changed, 93 insertions(+), 4 deletions(-) + create mode 100644 releasenotes/notes/bug-1996677-64851b476a0c5a37.yaml + +diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py +index f0be39d235..4234bd116a 100644 +--- a/neutron/agent/ovn/metadata/agent.py ++++ b/neutron/agent/ovn/metadata/agent.py +@@ -48,7 +48,7 @@ CHASSIS_METADATA_LOCK = 'chassis_metadata_lock' + + NS_PREFIX = 'ovnmeta-' + MAC_PATTERN = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) +-OVN_VIF_PORT_TYPES = ("", "external", ) ++OVN_VIF_PORT_TYPES = ("", "external", ovn_const.LSP_TYPE_LOCALPORT, ) + + MetadataPortInfo = collections.namedtuple('MetadataPortInfo', ['mac', + 'ip_addresses', +@@ -88,6 +88,20 @@ class PortBindingChassisEvent(row_event.RowEvent): + resync = False + if row.type not in OVN_VIF_PORT_TYPES: + return ++ if row.type == ovn_const.LSP_TYPE_LOCALPORT: ++ new_ext_ids = row.external_ids ++ old_ext_ids = old.external_ids ++ device_id = row.external_ids.get( ++ ovn_const.OVN_DEVID_EXT_ID_KEY, "") ++ if not device_id.startswith(NS_PREFIX): ++ return ++ new_cidrs = new_ext_ids.get(ovn_const.OVN_CIDRS_EXT_ID_KEY, "") ++ old_cidrs = old_ext_ids.get(ovn_const.OVN_CIDRS_EXT_ID_KEY, "") ++ # If old_cidrs is "", it is create event, ++ # nothing needs to be done. ++ # If old_cidrs equals new_cidrs, the ip does not change. ++ if old_cidrs in ("", new_cidrs, ): ++ return + with _SYNC_STATE_LOCK.read_lock(): + try: + net_name = ovn_utils.get_network_name_from_datapath( +@@ -102,6 +116,24 @@ class PortBindingChassisEvent(row_event.RowEvent): + self.agent.resync() + + ++class PortBindingMetaPortUpdatedEvent(PortBindingChassisEvent): ++ LOG_MSG = "Metadata Port %s in datapath %s updated." ++ ++ def __init__(self, metadata_agent): ++ events = (self.ROW_UPDATE,) ++ super(PortBindingMetaPortUpdatedEvent, self).__init__( ++ metadata_agent, events) ++ ++ def match_fn(self, event, row, old): ++ if row.type == ovn_const.LSP_TYPE_LOCALPORT: ++ if hasattr(row, 'external_ids') and hasattr(old, 'external_ids'): ++ device_id = row.external_ids.get( ++ ovn_const.OVN_DEVID_EXT_ID_KEY, "") ++ if device_id.startswith(NS_PREFIX): ++ return True ++ return False ++ ++ + class PortBindingChassisCreatedEvent(PortBindingChassisEvent): + LOG_MSG = "Port %s in datapath %s bound to our chassis" + +@@ -271,7 +303,8 @@ class MetadataAgent(object): + 'Chassis') + events = (PortBindingChassisCreatedEvent(self), + PortBindingChassisDeletedEvent(self), +- SbGlobalUpdateEvent(self)) ++ SbGlobalUpdateEvent(self), ++ PortBindingMetaPortUpdatedEvent(self)) + + # TODO(lucasagomes): Remove this in the future. Try to register + # the Chassis_Private table, if not present, fallback to the normal +diff --git a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py b/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py +index 2ce06ec550..bfa4747038 100644 +--- a/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py ++++ b/neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py +@@ -141,7 +141,19 @@ class TestMetadataAgent(base.TestOVNFunctionalBase): + type=ovn_const.LSP_TYPE_LOCALPORT, + addresses='AA:AA:AA:AA:AA:AA 192.168.122.123', + external_ids={ +- ovn_const.OVN_CIDRS_EXT_ID_KEY: '192.168.122.123/24'})) ++ ovn_const.OVN_CIDRS_EXT_ID_KEY: '192.168.122.123/24', ++ ovn_const.OVN_DEVID_EXT_ID_KEY: 'ovnmeta-' + lswitch_name ++ })) ++ return mdt_port_name ++ ++ def _update_metadata_port_ip(self, metadata_port_name): ++ external_ids = { ++ ovn_const.OVN_CIDRS_EXT_ID_KEY: "192.168.122.2/24", ++ ovn_const.OVN_DEVID_EXT_ID_KEY: ++ 'ovnmeta-' + uuidutils.generate_uuid() ++ } ++ self.nb_api.set_lswitch_port(lport_name=metadata_port_name, ++ external_ids=external_ids).execute() + + def _create_logical_switch_port(self, type_=None, addresses=None): + lswitch_name = 'ovn-' + uuidutils.generate_uuid() +@@ -197,16 +209,25 @@ class TestMetadataAgent(base.TestOVNFunctionalBase): + timeout=10, + exception=exc) + +- def _test_agent_events(self, delete, type_=None): ++ def _test_agent_events(self, delete, type_=None, update=False): + m_pb_created = mock.patch.object( + agent.PortBindingChassisCreatedEvent, 'run').start() + m_pb_deleted = mock.patch.object( + agent.PortBindingChassisDeletedEvent, 'run').start() ++ m_pb_updated = mock.patch.object( ++ agent.PortBindingMetaPortUpdatedEvent, 'run').start() + + lswitchport_name, lswitch_name = self._create_logical_switch_port( + type_) + self.sb_api.lsp_bind(lswitchport_name, self.chassis_name).execute( + check_error=True, log_errors=True) ++ if update and type_ == ovn_const.LSP_TYPE_LOCALPORT: ++ with self.nb_api.transaction( ++ check_error=True, log_errors=True) as txn: ++ mdt_port_name = self._create_metadata_port(txn, lswitch_name) ++ self.sb_api.lsp_bind(mdt_port_name, self.chassis_name).execute( ++ check_error=True, log_errors=True) ++ self._update_metadata_port_ip(mdt_port_name) + + def pb_created(): + if m_pb_created.call_count < 1: +@@ -224,6 +245,31 @@ class TestMetadataAgent(base.TestOVNFunctionalBase): + "PortBindingChassisCreatedEvent didn't happen on port " + "binding.")) + ++ def pb_updated(): ++ if m_pb_updated.call_count < 1: ++ return False ++ args = m_pb_updated.call_args[0] ++ self.assertEqual('update', args[0]) ++ self.assertTrue(args[1].external_ids) ++ self.assertTrue(args[2].external_ids) ++ device_id = args[1].external_ids.get( ++ ovn_const.OVN_DEVID_EXT_ID_KEY, "") ++ self.assertTrue(device_id.startswith("ovnmeta-")) ++ new_cidrs = args[1].external_ids.get( ++ ovn_const.OVN_CIDRS_EXT_ID_KEY, "") ++ old_cidrs = args[2].external_ids.get( ++ ovn_const.OVN_CIDRS_EXT_ID_KEY, "") ++ self.assertNotEqual(new_cidrs, old_cidrs) ++ self.assertNotEqual(old_cidrs, "") ++ return True ++ if update and type_ == ovn_const.LSP_TYPE_LOCALPORT: ++ n_utils.wait_until_true( ++ pb_updated, ++ timeout=10, ++ exception=Exception( ++ "PortBindingMetaPortUpdatedEvent didn't happen on " ++ "metadata port ip address updated.")) ++ + if delete: + self.nb_api.delete_lswitch_port( + lswitchport_name, lswitch_name).execute( +@@ -295,6 +341,10 @@ class TestMetadataAgent(base.TestOVNFunctionalBase): + timeout=10, + exception=exc) + ++ def test_agent_metadata_port_ip_update_event(self): ++ self._test_agent_events( ++ delete=False, type_=ovn_const.LSP_TYPE_LOCALPORT, update=True) ++ + def test_metadata_agent_only_monitors_own_chassis(self): + # We already have the fake chassis which we should be monitoring, so + # create an event looking for a change to another chassis +diff --git a/releasenotes/notes/bug-1996677-64851b476a0c5a37.yaml b/releasenotes/notes/bug-1996677-64851b476a0c5a37.yaml +new file mode 100644 +index 0000000000..0743a61788 +--- /dev/null ++++ b/releasenotes/notes/bug-1996677-64851b476a0c5a37.yaml +@@ -0,0 +1,6 @@ ++--- ++fixes: ++ - | ++ `1996677 `_ ++ When the fixed_ips of metadata port is modified, the ip address of ++ tap device in metadata agent is modified. +-- +2.34.1 + diff -Nru neutron-20.5.0/debian/patches/series neutron-20.5.0/debian/patches/series --- neutron-20.5.0/debian/patches/series 2023-12-14 03:22:29.000000000 +0800 +++ neutron-20.5.0/debian/patches/series 2024-03-26 12:24:59.000000000 +0800 @@ -1 +1,4 @@ skip-iptest.patch +0001-Update-tap-ip-in-metadata-agent-when-metadata-port-i.patch +0001-ovn-metadata-Refactor-events.patch +0001-Handle-creation-of-Port_Binding-with-chassis-set.patch