Skip to content

Commit 833baa7

Browse files
Merge pull request #293 from stackhpc/upstream/2026.1-2026-06-08
Synchronise 2026.1 with upstream This PR contains a snapshot of 2026.1 from upstream stable/2026.1. Reviewed-by: Michał Nasiadka
2 parents 857a21a + cc7c7bf commit 833baa7

6 files changed

Lines changed: 178 additions & 23 deletions

File tree

neutron/agent/linux/openvswitch_firewall/firewall.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ def __init__(self, integration_bridge):
557557
self._initialize_sg()
558558
self._update_cookie = None
559559
self._deferred = False
560+
self._ports_pending_invalid_ct_cleanup = []
560561
self.iptables_helper = iptables.Helper(self.int_br.br)
561562
self.iptables_helper.load_driver_if_needed()
562563
self.ipconntrack = ip_conntrack.OvsIpConntrackManager()
@@ -702,11 +703,22 @@ def _get_port_physical_network(self, port_name):
702703
return get_physical_network_from_other_config(
703704
self.int_br.br, port_name)
704705

705-
def _delete_invalid_conntrack_entries_for_port(self, port, of_port):
706-
port['of_port'] = of_port
706+
def _delete_invalid_conntrack_entries_for_port(self, ports):
707707
for ethertype in [lib_const.IPv4, lib_const.IPv6]:
708708
self.ipconntrack.delete_conntrack_state_by_remote_ips(
709-
[port], ethertype, set(), mark=ovsfw_consts.CT_MARK_INVALID)
709+
ports, ethertype, set(), mark=ovsfw_consts.CT_MARK_INVALID)
710+
711+
def _schedule_invalid_conntrack_entries_cleanup(self, port):
712+
if self._deferred:
713+
self._ports_pending_invalid_ct_cleanup.append(port)
714+
else:
715+
self._delete_invalid_conntrack_entries_for_port([port])
716+
717+
def _flush_pending_invalid_conntrack_cleanup(self):
718+
self._delete_invalid_conntrack_entries_for_port(
719+
self._ports_pending_invalid_ct_cleanup
720+
)
721+
self._ports_pending_invalid_ct_cleanup = []
710722

711723
def get_ofport(self, port):
712724
port_id = port['device']
@@ -768,7 +780,8 @@ def prepare_port_filter(self, port):
768780
self._update_flows_for_port(of_port, old_of_port)
769781
else:
770782
self._set_port_filters(of_port)
771-
self._delete_invalid_conntrack_entries_for_port(port, of_port)
783+
port['of_port'] = of_port
784+
self._schedule_invalid_conntrack_entries_cleanup(port)
772785
except exceptions.OVSFWPortNotFound as not_found_error:
773786
LOG.info("port %(port_id)s does not exist in ovsdb: %(err)s.",
774787
{'port_id': port['device'],
@@ -808,7 +821,8 @@ def update_port_filter(self, port):
808821
else:
809822
self._set_port_filters(of_port)
810823

811-
self._delete_invalid_conntrack_entries_for_port(port, of_port)
824+
port['of_port'] = of_port
825+
self._schedule_invalid_conntrack_entries_cleanup(port)
812826

813827
except exceptions.OVSFWPortNotFound as not_found_error:
814828
LOG.info("port %(port_id)s does not exist in ovsdb: %(err)s.",
@@ -897,11 +911,13 @@ def remove_trusted_ports(self, port_ids):
897911

898912
def filter_defer_apply_on(self):
899913
self._deferred = True
914+
self._ports_pending_invalid_ct_cleanup = []
900915

901916
def filter_defer_apply_off(self):
902917
if self._deferred:
903918
self._cleanup_stale_sg()
904919
self.int_br.apply_flows()
920+
self._flush_pending_invalid_conntrack_cleanup()
905921
self._deferred = False
906922

907923
@property

neutron/agent/ovn/metadata/agent.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,11 @@ def _get_port_ip4_ips_and_ip6_flag(self, port):
629629
LOG.warning("Port %s MAC column is empty, cannot retrieve IP "
630630
"addresses", port.uuid)
631631
return [], False
632+
if port.mac == [ovn_const.UNKNOWN_ADDR]:
633+
LOG.warning("Port %s MAC column value is %s, this port will "
634+
"not have metadata service", port.uuid, port.mac)
635+
return [], False
636+
632637
mac, ips = ovn_utils.get_mac_and_ips_from_port_binding(port)
633638
if not ips:
634639
LOG.debug("Port %s IP addresses were not retrieved from the "

neutron/plugins/ml2/extensions/dns_integration.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -351,35 +351,42 @@ class DNSExtensionDriverML2(DNSExtensionDriver):
351351
def __init__(self):
352352
super().__init__()
353353
self._vlan_driver = None
354+
self._tunnel_drivers = {}
354355
self._plugin = None
355356

356357
def initialize(self):
357358
LOG.info("DNSExtensionDriverML2 initialization complete")
358359

360+
@property
361+
def plugin(self):
362+
if not self._plugin:
363+
self._plugin = directory.get_plugin()
364+
return self._plugin
365+
359366
@property
360367
def vlan_driver(self):
361368
if not self._vlan_driver:
362-
if not self._plugin:
363-
self._plugin = directory.get_plugin()
364-
self._vlan_driver = self._plugin.type_manager.drivers.get(
369+
self._vlan_driver = self.plugin.type_manager.drivers.get(
365370
lib_const.TYPE_VLAN)
366371
return self._vlan_driver
367372

368-
def _is_tunnel_project_network(self, provider_net):
369-
if provider_net['network_type'] == lib_const.TYPE_GENEVE:
370-
tunnel_ranges = cfg.CONF.ml2_type_geneve.vni_ranges
371-
elif provider_net['network_type'] == lib_const.TYPE_VXLAN:
372-
tunnel_ranges = cfg.CONF.ml2_type_vxlan.vni_ranges
373-
else:
374-
tunnel_ranges = cfg.CONF.ml2_type_gre.tunnel_id_ranges
373+
def get_tunnel_driver(self, network_type):
374+
if network_type not in self._tunnel_drivers:
375+
self._tunnel_drivers[network_type] = (
376+
self.plugin.type_manager.drivers.get(network_type))
377+
return self._tunnel_drivers[network_type]
375378

379+
def _is_tunnel_project_network(self, provider_net):
380+
network_type = provider_net['network_type']
381+
tunnel_driver = self.get_tunnel_driver(network_type)
382+
if not tunnel_driver:
383+
return False
384+
tunnel_ranges = tunnel_driver.obj.get_network_segment_ranges()
385+
if not tunnel_ranges:
386+
return False
376387
segmentation_id = int(provider_net['segmentation_id'])
377-
for entry in tunnel_ranges:
378-
entry = entry.strip()
379-
tun_min, tun_max = entry.split(':')
380-
tun_min = tun_min.strip()
381-
tun_max = tun_max.strip()
382-
return int(tun_min) <= segmentation_id <= int(tun_max)
388+
return any(tun_min <= segmentation_id <= tun_max
389+
for tun_min, tun_max in tunnel_ranges)
383390

384391
def _is_vlan_project_network(self, provider_net):
385392
if not self.vlan_driver:

neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,15 +1032,23 @@ def test_update_port_filter_port_security_disabled(self):
10321032
self.assertTrue(self.mock_bridge.br.delete_flows.called)
10331033
self.delete_invalid_conntrack_entries_mock.assert_not_called()
10341034

1035-
def test_update_port_filter_applies_added_flows(self):
1036-
"""Check flows are applied right after _set_flows is called."""
1035+
def test_update_port_filter_applies_added_flows_and_clean_conntrack(self):
1036+
"""Check flows are applied right after _set_flows is called.
1037+
1038+
Additionally this test checks also if conntrack entries marked as
1039+
CT_MARK_INVALID are cleaned once after new flows are set.
1040+
"""
1041+
10371042
port_dict = {'device': 'port-id',
10381043
'security_groups': [1]}
10391044
self._prepare_security_group()
10401045
self.firewall.prepare_port_filter(port_dict)
1046+
self.delete_invalid_conntrack_entries_mock.reset_mock()
10411047
with self.firewall.defer_apply():
10421048
self.firewall.update_port_filter(port_dict)
1049+
self.delete_invalid_conntrack_entries_mock.assert_not_called()
10431050
self.mock_bridge.apply_flows.assert_called_once()
1051+
self._assert_invalid_conntrack_entries_deleted(port_dict)
10441052

10451053
def test_update_port_filter_clean_when_port_not_found(self):
10461054
"""Check flows are cleaned if port is not found in the bridge."""

neutron/tests/unit/agent/ovn/metadata/test_agent.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,36 @@ def test__get_provision_params_returns_provision_parameters_ipv6(self):
483483
self._test__get_provision_params_returns_provision_parameters(
484484
'fe80::f816:3eff:feb6:c0c0')
485485

486+
def test__get_provision_params_skips_port_with_unknown_mac(self):
487+
"""Should skip ports with mac=['unknown'] and return valid ports."""
488+
network_id = '1'
489+
datapath = DatapathInfo(uuid='test123',
490+
external_ids={'name': f'neutron-{network_id}'})
491+
valid_port = makePort(datapath,
492+
mac=['fa:16:3e:e7:ac:ab 1.2.3.4'])
493+
unknown_mac_port = mock.Mock()
494+
unknown_mac_port.type = ''
495+
unknown_mac_port.mac = [ovn_const.UNKNOWN_ADDR]
496+
unknown_mac_port.datapath = datapath
497+
unknown_mac_port.uuid = 'fake-uuid'
498+
metadadata_port = makePort(
499+
datapath,
500+
mac=['fa:16:3e:22:65:18 10.204.0.1'],
501+
external_ids={'neutron:cidrs': '10.204.0.10/29'},
502+
logical_port='3b66c176-199b-48ec-8331-c1fd3f6e2b44')
503+
504+
with mock.patch.object(self.agent.sb_idl, 'get_metadata_port',
505+
return_value=metadadata_port),\
506+
mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis',
507+
return_value=[valid_port, unknown_mac_port]):
508+
actual_params = self.agent._get_provision_params(datapath)
509+
510+
net_name, datapath_port_ips, any_ip6, metadata_port_info = (
511+
actual_params)
512+
self.assertEqual(network_id, net_name)
513+
self.assertListEqual(['1.2.3.4'], datapath_port_ips)
514+
self.assertFalse(any_ip6)
515+
486516
def test__get_port_ip4_ips_and_ip6_flag_empty_mac(self):
487517
"""Should return empty IPs for ports with empty MAC column."""
488518
port = mock.Mock()
@@ -492,6 +522,15 @@ def test__get_port_ip4_ips_and_ip6_flag_empty_mac(self):
492522
self.assertEqual([], ip4_ips)
493523
self.assertFalse(any_ip6)
494524

525+
def test__get_port_ip4_ips_and_ip6_flag_unknown_mac(self):
526+
"""Should return empty IPs for ports with mac=['unknown']."""
527+
port = mock.Mock()
528+
port.mac = [ovn_const.UNKNOWN_ADDR]
529+
port.uuid = 'fake-uuid'
530+
ip4_ips, any_ip6 = self.agent._get_port_ip4_ips_and_ip6_flag(port)
531+
self.assertEqual([], ip4_ips)
532+
self.assertFalse(any_ip6)
533+
495534
def _test_provision_datapath(self, ipv6_enabled):
496535
"""Test datapath provisioning.
497536

neutron/tests/unit/plugins/ml2/extensions/test_dns_integration.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,86 @@ def test__is_vlan_project_network_no_vlan_driver(self):
864864
}
865865
self.assertFalse(self.driver._is_vlan_project_network(provider_net))
866866

867+
def _mock_tunnel_driver(self, network_type, ranges):
868+
mock_tunnel_driver = mock.Mock()
869+
mock_tunnel_driver.obj.get_network_segment_ranges.return_value = ranges
870+
return mock.patch.object(
871+
self.driver, 'get_tunnel_driver',
872+
return_value=mock_tunnel_driver)
873+
874+
def test__is_tunnel_project_network_with_multiple_ranges(self):
875+
with self._mock_tunnel_driver(
876+
constants.TYPE_VXLAN, [(100, 200), (300, 400)]):
877+
provider_net_in_range = {
878+
'network_type': constants.TYPE_VXLAN,
879+
'segmentation_id': 150,
880+
}
881+
self.assertTrue(
882+
self.driver._is_tunnel_project_network(provider_net_in_range))
883+
884+
provider_net_between_ranges = {
885+
'network_type': constants.TYPE_VXLAN,
886+
'segmentation_id': 250,
887+
}
888+
self.assertFalse(self.driver._is_tunnel_project_network(
889+
provider_net_between_ranges))
890+
891+
provider_net_second_range = {
892+
'network_type': constants.TYPE_VXLAN,
893+
'segmentation_id': 350,
894+
}
895+
self.assertTrue(self.driver._is_tunnel_project_network(
896+
provider_net_second_range))
897+
898+
def test__is_tunnel_project_network_boundary_values(self):
899+
with self._mock_tunnel_driver(constants.TYPE_GENEVE, [(100, 200)]):
900+
provider_net_min = {
901+
'network_type': constants.TYPE_GENEVE,
902+
'segmentation_id': 100,
903+
}
904+
self.assertTrue(
905+
self.driver._is_tunnel_project_network(provider_net_min))
906+
907+
provider_net_max = {
908+
'network_type': constants.TYPE_GENEVE,
909+
'segmentation_id': 200,
910+
}
911+
self.assertTrue(
912+
self.driver._is_tunnel_project_network(provider_net_max))
913+
914+
provider_net_below = {
915+
'network_type': constants.TYPE_GENEVE,
916+
'segmentation_id': 99,
917+
}
918+
self.assertFalse(
919+
self.driver._is_tunnel_project_network(provider_net_below))
920+
921+
provider_net_above = {
922+
'network_type': constants.TYPE_GENEVE,
923+
'segmentation_id': 201,
924+
}
925+
self.assertFalse(
926+
self.driver._is_tunnel_project_network(provider_net_above))
927+
928+
def test__is_tunnel_project_network_empty_ranges(self):
929+
with self._mock_tunnel_driver(constants.TYPE_GRE, []):
930+
provider_net = {
931+
'network_type': constants.TYPE_GRE,
932+
'segmentation_id': 100,
933+
}
934+
self.assertFalse(
935+
self.driver._is_tunnel_project_network(provider_net))
936+
937+
def test__is_tunnel_project_network_no_tunnel_driver(self):
938+
with mock.patch.object(
939+
self.driver, 'get_tunnel_driver', return_value=None):
940+
provider_net = {
941+
'network_type': constants.TYPE_VXLAN,
942+
'segmentation_id': 100,
943+
}
944+
self.assertFalse(
945+
self.driver._is_tunnel_project_network(provider_net))
946+
867947

868948
class TestDesignateClientKeystoneV3(testtools.TestCase):
869949
"""Test case for designate clients """

0 commit comments

Comments
 (0)