[ovn] Add neutron network to metadata namespace names

Until this patch the metadata namespace had the format
'ovnmeta-<OVN datapath uuid>' which makes it hard to
relate to the Neutron network for which metadata is
provisioned to. This applies to the name of the tap
interface that connects the network namespace to the
integration bridge.

This patch is changing the name convention and providing
an upgrade path to include the Neutron network ID to make
debugging and troubleshooting easier for developers and
operators.

The new name pattern is:
'ovnmeta-<Neutron network uuid>'

Please, note that with this patch, the old namespaces will
be deleted and new ones will be recreated.

(Manually cherry picked from Neutron e4fb06b24299a7ecf10b05ef6ddc2d883c40e5a1)

Conflicts:
    networking_ovn/agent/metadata/agent.py
    networking_ovn/common/utils.py
    networking_ovn/ovsdb/impl_idl_ovn.py

Signed-off-by: Daniel Alvarez <dalvarez@redhat.com>
Change-Id: Ic8ffa9c4437aab6fb1878b3a1ebf2c3ab86e3d0c
This commit is contained in:
Lucas Alvares Gomes 2021-04-07 15:21:13 +01:00 committed by Rodolfo Alonso
parent 249e530913
commit e7e0796179
5 changed files with 78 additions and 42 deletions
networking_ovn
agent/metadata
common
ovsdb
tests/unit/agent/metadata
releasenotes/notes

@ -34,6 +34,7 @@ from networking_ovn.agent.metadata import ovsdb
from networking_ovn.agent.metadata import server as metadata_server
from networking_ovn.common import config
from networking_ovn.common import constants as ovn_const
from networking_ovn.common import utils as ovn_utils
LOG = log.getLogger(__name__)
@ -87,9 +88,10 @@ class PortBindingChassisEvent(row_event.RowEvent):
return
with _SYNC_STATE_LOCK.read_lock():
try:
LOG.info(self.LOG_MSG, row.logical_port,
str(row.datapath.uuid))
self.agent.update_datapath(str(row.datapath.uuid))
net_name = ovn_utils.get_network_name_from_datapath(
row.datapath)
LOG.info(self.LOG_MSG, row.logical_port, net_name)
self.agent.update_datapath(str(row.datapath.uuid), net_name)
except ConfigException:
# We're now in the reader lock mode, we need to exit the
# context and then use writer lock
@ -348,14 +350,20 @@ class MetadataAgent(object):
def _vif_ports(self, ports):
return (p for p in ports if p.type in OVN_VIF_PORT_TYPES)
def teardown_datapath(self, datapath):
def teardown_datapath(self, datapath, net_name=None):
"""Unprovision this datapath to stop serving metadata.
This function will shutdown metadata proxy if it's running and delete
the VETH pair, the OVS port and the namespace.
"""
self.update_chassis_metadata_networks(datapath, remove=True)
namespace = self._get_namespace_name(datapath)
# TODO(dalvarez): Remove this in Y cycle when we are sure that all
# namespaces will be created with the Neutron network UUID and not
# anymore with the OVN datapath UUID.
dp = net_name or datapath
namespace = self._get_namespace_name(dp)
ip = ip_lib.IPWrapper(namespace)
# If the namespace doesn't exist, return
if not ip.netns.exists(namespace):
@ -365,7 +373,8 @@ class MetadataAgent(object):
namespace)
metadata_driver.MetadataDriver.destroy_monitored_metadata_proxy(
self._process_monitor, datapath, self.conf, namespace)
self._process_monitor, dp, self.conf, namespace)
veth_name = self._get_veth_name(dp)
veth_name = self._get_veth_name(datapath)
self.ovs_idl.del_port(veth_name[0]).execute()
@ -374,7 +383,7 @@ class MetadataAgent(object):
ip.garbage_collect_namespace()
def update_datapath(self, datapath):
def update_datapath(self, datapath, net_name):
"""Update the metadata service for this datapath.
This function will:
@ -389,9 +398,9 @@ class MetadataAgent(object):
datapath_ports = [p for p in self._vif_ports(ports) if
str(p.datapath.uuid) == datapath]
if datapath_ports:
self.provision_datapath(datapath)
self.provision_datapath(datapath, net_name)
else:
self.teardown_datapath(datapath)
self.teardown_datapath(datapath, net_name)
def _ensure_datapath_checksum(self, namespace):
"""Ensure the correct checksum in the metadata packets in DPDK bridges
@ -411,7 +420,7 @@ class MetadataAgent(object):
iptables_mgr.ipv4['mangle'].add_rule('POSTROUTING', rule, wrap=False)
iptables_mgr.apply()
def provision_datapath(self, datapath):
def provision_datapath(self, datapath, net_name):
"""Provision the datapath so that it can serve metadata.
This function will create the namespace and VETH pair if needed
@ -421,7 +430,7 @@ class MetadataAgent(object):
:return: The metadata namespace name of this datapath
"""
LOG.debug("Provisioning datapath %s", datapath)
LOG.debug("Provisioning metadata for network %s", net_name)
port = self.sb_idl.get_metadata_port_network(datapath)
# If there's no metadata port or it doesn't have a MAC or IP
# addresses, then tear the namespace down if needed. This might happen
@ -429,10 +438,10 @@ class MetadataAgent(object):
# an IP address.
if not (port and port.mac and
port.external_ids.get(ovn_const.OVN_CIDRS_EXT_ID_KEY, None)):
LOG.debug("There is no metadata port for datapath %s or it has no "
LOG.debug("There is no metadata port for network %s or it has no "
"MAC or IP addresses configured, tearing the namespace "
"down if needed", datapath)
self.teardown_datapath(datapath)
"down if needed", net_name)
self.teardown_datapath(datapath, net_name)
return
# First entry of the mac field must be the MAC address.
@ -440,9 +449,9 @@ class MetadataAgent(object):
# If it is not, we can't provision the namespace. Tear it down if
# needed and log the error.
if not match:
LOG.error("Metadata port for datapath %s doesn't have a MAC "
LOG.error("Metadata port for network %s doesn't have a MAC "
"address, tearing the namespace down if needed",
datapath)
net_name)
self.teardown_datapath(datapath)
return
@ -454,8 +463,8 @@ class MetadataAgent(object):
# Create the VETH pair if it's not created. Also the add_veth function
# will create the namespace for us.
namespace = self._get_namespace_name(datapath)
veth_name = self._get_veth_name(datapath)
namespace = self._get_namespace_name(net_name)
veth_name = self._get_veth_name(net_name)
ip1 = ip_lib.IPDevice(veth_name[0])
if ip_lib.device_exists(veth_name[1], namespace):
@ -524,9 +533,9 @@ class MetadataAgent(object):
# Spawn metadata proxy if it's not already running.
metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy(
self._process_monitor, namespace, METADATA_PORT,
self.conf, bind_address=METADATA_DEFAULT_IP, network_id=datapath)
self.update_chassis_metadata_networks(datapath)
self.conf, bind_address=METADATA_DEFAULT_IP,
network_id=net_name)
self.update_chassis_metadata_networks(net_name)
return namespace
def ensure_all_networks_provisioned(self):
@ -541,11 +550,13 @@ class MetadataAgent(object):
"""
# Retrieve all VIF ports in our Chassis
ports = self.sb_idl.get_ports_on_chassis(self.chassis)
datapaths = {str(p.datapath.uuid) for p in self._vif_ports(ports)}
nets = {(str(p.datapath.uuid),
ovn_utils.get_network_name_from_datapath(p.datapath))
for p in self._vif_ports(ports)}
namespaces = []
# Make sure that all those datapaths are serving metadata
for datapath in datapaths:
netns = self.provision_datapath(datapath)
for datapath, net_name in nets:
netns = self.provision_datapath(datapath, net_name)
if netns:
namespaces.append(netns)

@ -559,3 +559,7 @@ def connection_config_to_target_string(connection_config):
_dict['ip'])
elif _dict['file']:
return 'p' + _dict['proto'] + ':' + _dict['file']
def get_network_name_from_datapath(datapath):
return datapath.external_ids['name'].replace('neutron-', '')

@ -892,7 +892,15 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
if not port.chassis:
return False
return port.mac and str(port.datapath.uuid) == network and (
# TODO(dalvarez): Remove the comparison to port.datapath.uuid in Y
# cycle when we are sure that all namespaces will be created with
# the Neutron network UUID and not anymore with the OVN datapath
# UUID.
is_in_network = lambda port: (
str(port.datapath.uuid) == network or
utils.get_network_name_from_datapath(port.datapath) == network)
return port.mac and is_in_network(port) and (
ip_address in port.mac[0].split(' '))
return [r for r in rows if check_net_and_ip(r)]

@ -31,7 +31,7 @@ from networking_ovn.conf.agent.metadata import config as meta_conf
OvnPortInfo = collections.namedtuple(
'OvnPortInfo', ['datapath', 'type', 'mac', 'external_ids', 'logical_port'])
DatapathInfo = collections.namedtuple('DatapathInfo', 'uuid')
DatapathInfo = collections.namedtuple('DatapathInfo', ['uuid', 'external_ids'])
def makePort(datapath=None, type='', mac=None, external_ids=None,
@ -117,11 +117,14 @@ class TestMetadataAgent(base.BaseTestCase):
ports = []
for i in range(0, 3):
ports.append(makePort(datapath=DatapathInfo(uuid=str(i))))
ports.append(makePort(datapath=DatapathInfo(uuid='1')))
ports.append(makePort(datapath=DatapathInfo(uuid='3'),
type='external'))
ports.append(makePort(datapath=DatapathInfo(uuid='5'), type='unknown'))
ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
external_ids={'name': 'neutron-%d' % i})))
ports.append(makePort(datapath=DatapathInfo(uuid='1',
external_ids={'name': 'neutron-1'})))
ports.append(makePort(datapath=DatapathInfo(uuid='3',
external_ids={'name': 'neutron-3'}), type='external'))
ports.append(makePort(datapath=DatapathInfo(uuid='5',
external_ids={'name': 'neutron-5'}), type='unknown'))
with mock.patch.object(self.agent, 'provision_datapath',
return_value=None) as pdp,\
@ -129,40 +132,42 @@ class TestMetadataAgent(base.BaseTestCase):
return_value=ports):
self.agent.ensure_all_networks_provisioned()
expected_calls = [mock.call(str(i)) for i in range(0, 4)]
expected_calls = [mock.call(str(i), str(i)) for i in range(0, 4)]
self.assertEqual(sorted(expected_calls),
sorted(pdp.call_args_list))
def test_update_datapath_provision(self):
ports = []
for i in range(0, 3):
ports.append(makePort(datapath=DatapathInfo(uuid=str(i))))
ports.append(makePort(datapath=DatapathInfo(uuid='3'),
type='external'))
ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
external_ids={'name': 'neutron-%d' % i})))
ports.append(makePort(datapath=DatapathInfo(uuid='3',
external_ids={'name': 'neutron-3'}), type='external'))
with mock.patch.object(self.agent, 'provision_datapath',
return_value=None) as pdp,\
mock.patch.object(self.agent, 'teardown_datapath') as tdp,\
mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis',
return_value=ports):
self.agent.update_datapath('1')
self.agent.update_datapath('3')
expected_calls = [mock.call('1'), mock.call('3')]
self.agent.update_datapath('1', 'a')
self.agent.update_datapath('3', 'b')
expected_calls = [mock.call('1', 'a'), mock.call('3', 'b')]
pdp.assert_has_calls(expected_calls)
tdp.assert_not_called()
def test_update_datapath_teardown(self):
ports = []
for i in range(0, 3):
ports.append(makePort(datapath=DatapathInfo(uuid=str(i))))
ports.append(makePort(datapath=DatapathInfo(uuid=str(i),
external_ids={'name': 'neutron-%d' % i})))
with mock.patch.object(self.agent, 'provision_datapath',
return_value=None) as pdp,\
mock.patch.object(self.agent, 'teardown_datapath') as tdp,\
mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis',
return_value=ports):
self.agent.update_datapath('5')
tdp.assert_called_once_with('5')
self.agent.update_datapath('5', 'a')
tdp.assert_called_once_with('5', 'a')
pdp.assert_not_called()
def test_teardown_datapath(self):
@ -240,7 +245,7 @@ class TestMetadataAgent(base.BaseTestCase):
# We need to assert that it was deleted first.
self.agent.ovs_idl.list_br.return_value.execute.return_value = (
['br-int', 'br-fake'])
self.agent.provision_datapath('1')
self.agent.provision_datapath('1', '1')
# Check that the port was deleted from br-fake
self.agent.ovs_idl.del_port.assert_called_once_with(

@ -0,0 +1,8 @@
---
other:
- |
The ``OVN Metadata Agent`` now creates the network namespaces including the
Neutron network UUID in its name. Previously, the OVN datapath UUID was used
and it was not obvious for operators and during debugging to figure out which
namespace corresponded to what Neutron network.