Fix support of IPv6 only networks in OVN metadata agent

When an IPv6 only network is used as the sole network for a VM and
there are no other bound ports on the same network in the same chassis,
the OVN metadata agent concludes that the associated namespace is not
needed and deletes it. As a consequence, the VM cannot access the
metadata service. With this change, the namespace is preserved if there
is at least one bound port on the chassis with either IPv4 or IPv6
addresses.

Closes-Bug: #2069482

Change-Id: Ie15c3344161ad521bf10b98303c7bb730351e2d8
(cherry picked from commit f7000f3d57bc59732522c4943d6ff2e9dfcf7d31)
This commit is contained in:
Miguel Lavalle 2024-06-18 19:36:13 -05:00 committed by Miro Tomaska
parent d0f11ca346
commit b98003cf9f
2 changed files with 41 additions and 17 deletions
neutron
agent/ovn/metadata
tests/unit/agent/ovn/metadata

@ -617,9 +617,10 @@ class MetadataAgent(object):
iptables_mgr.ipv4['mangle'].add_rule('POSTROUTING', rule, wrap=False)
iptables_mgr.apply()
def _get_port_ip4_ips(self, port):
def _get_port_ip4_ips_and_ip6_flag(self, port):
# Retrieve IPv4 addresses from the port mac column which is in form
# ["<port_mac> <ip1> <ip2> ... <ipN>"]
# ["<port_mac> <ip1> <ip2> ... <ipN>"]. Also return True if the port
# has at least one IPv6 address
if not port.mac:
LOG.warning("Port %s MAC column is empty, cannot retrieve IP "
"addresses", port.uuid)
@ -629,10 +630,17 @@ class MetadataAgent(object):
if not ips:
LOG.debug("Port %s IP addresses were not retrieved from the "
"Port_Binding MAC column %s", port.uuid, mac_field_attrs)
return [ip for ip in ips if (
utils.get_ip_version(ip) == n_const.IP_VERSION_4)]
ip4_ips = []
any_ip6 = False
for ip in ips:
if utils.get_ip_version(ip) == n_const.IP_VERSION_4:
ip4_ips.append(ip)
else:
any_ip6 = True
return ip4_ips, any_ip6
def _active_subnets_cidrs(self, datapath_ports_ips, metadata_port_cidrs):
def _active_subnets_cidrs(self, datapath_ports_ip4_ips,
metadata_port_cidrs):
active_subnets_cidrs = set()
# Prepopulate a dictionary where each metadata_port_cidr(string) maps
# to its netaddr.IPNetwork object. This is so we dont have to
@ -642,7 +650,7 @@ class MetadataAgent(object):
for metadata_port_cidr in metadata_port_cidrs if metadata_port_cidr
}
for datapath_port_ip in datapath_ports_ips:
for datapath_port_ip in datapath_ports_ip4_ips:
ip_obj = netaddr.IPAddress(datapath_port_ip)
for metadata_cidr, metadata_cidr_obj in \
metadata_cidrs_to_network_objects.items():
@ -652,9 +660,10 @@ class MetadataAgent(object):
return active_subnets_cidrs
def _process_cidrs(self, current_namespace_cidrs,
datapath_ports_ips, metadata_port_subnet_cidrs, lla):
datapath_ports_ip4_ips,
metadata_port_subnet_cidrs, lla):
active_subnets_cidrs = self._active_subnets_cidrs(
datapath_ports_ips, metadata_port_subnet_cidrs)
datapath_ports_ip4_ips, metadata_port_subnet_cidrs)
cidrs_to_add = active_subnets_cidrs - current_namespace_cidrs
@ -713,18 +722,22 @@ class MetadataAgent(object):
chassis_ports = self.sb_idl.get_ports_on_chassis(
self._chassis, include_additional_chassis=True)
datapath_ports_ips = []
datapath_ports_ip4_ips = []
any_ip6 = False
for chassis_port in self._vif_ports(chassis_ports):
if str(chassis_port.datapath.uuid) == datapath_uuid:
datapath_ports_ips.extend(self._get_port_ip4_ips(chassis_port))
ip4_ips, ip6_flag = self._get_port_ip4_ips_and_ip6_flag(
chassis_port)
datapath_ports_ip4_ips.extend(ip4_ips)
any_ip6 = any_ip6 or ip6_flag
if not datapath_ports_ips:
if not (datapath_ports_ip4_ips or any_ip6):
LOG.debug("No valid VIF ports were found for network %s, "
"tearing the namespace down if needed", net_name)
self.teardown_datapath(net_name)
return
return net_name, datapath_ports_ips, metadata_port_info
return net_name, datapath_ports_ip4_ips, metadata_port_info
def provision_datapath(self, port_binding):
"""Provision the datapath so that it can serve metadata.
@ -744,7 +757,7 @@ class MetadataAgent(object):
provision_params = self._get_provision_params(datapath)
if not provision_params:
return
net_name, datapath_ports_ips, metadata_port_info = provision_params
net_name, datapath_ports_ip4_ips, metadata_port_info = provision_params
LOG.info("Provisioning metadata for network %s", net_name)
# Create the VETH pair if it's not created. Also the add_veth function
@ -780,7 +793,7 @@ class MetadataAgent(object):
cidrs_to_add, cidrs_to_delete = self._process_cidrs(
{dev['cidr'] for dev in ip2.addr.list()},
datapath_ports_ips,
datapath_ports_ip4_ips,
metadata_port_info.ip_addresses,
ip_lib.get_ipv6_lladdr(metadata_port_info.mac)
)

@ -30,6 +30,7 @@ from neutron.agent.linux import utils as linux_utils
from neutron.agent.ovn.metadata import agent
from neutron.agent.ovn.metadata import driver
from neutron.common.ovn import constants as ovn_const
from neutron.common import utils
from neutron.conf.agent.metadata import config as meta_conf
from neutron.conf.agent.ovn.metadata import config as ovn_meta_conf
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
@ -357,12 +358,12 @@ class TestMetadataAgent(base.BaseTestCase):
self.assertIsNone(self.agent._get_provision_params(datapath))
tdp.assert_called_once_with(network_id)
def test__get_provision_params_returns_provision_parameters(self):
def _test__get_provision_params_returns_provision_parameters(self,
port_ip):
"""The happy path when datapath has ports with "external" or ""(blank)
types and metadata port contains MAC and subnet CIDRs.
"""
network_id = '1'
port_ip = '1.2.3.4'
metada_port_mac = "fa:16:3e:22:65:18"
metada_port_subnet_cidr = "10.204.0.10/29"
metada_port_logical_port = "3b66c176-199b-48ec-8331-c1fd3f6e2b44"
@ -388,13 +389,23 @@ class TestMetadataAgent(base.BaseTestCase):
net_name, datapath_port_ips, metadata_port_info = actual_params
self.assertEqual(network_id, net_name)
self.assertListEqual([port_ip], datapath_port_ips)
if utils.get_ip_version(port_ip) == n_const.IP_VERSION_4:
self.assertListEqual([port_ip], datapath_port_ips)
self.assertEqual(metada_port_mac, metadata_port_info.mac)
self.assertSetEqual(set([metada_port_subnet_cidr]),
metadata_port_info.ip_addresses)
self.assertEqual(metada_port_logical_port,
metadata_port_info.logical_port)
def test__get_provision_params_returns_provision_parameters(self):
self._test__get_provision_params_returns_provision_parameters(
'1.2.3.4')
def test__get_provision_params_returns_provision_parameters_ipv6(self):
self._test__get_provision_params_returns_provision_parameters(
'fe80::f816:3eff:feb6:c0c0')
def test_provision_datapath(self):
"""Test datapath provisioning.