From a9ed835e69102397b192a3d23d80084b5d76a7f8 Mon Sep 17 00:00:00 2001 From: Daniel Alvarez Sanchez Date: Wed, 21 Apr 2021 11:41:57 +0200 Subject: [PATCH] [OVN] Only account for bound ports in metadata agent Due to bug #1922934 there might be situations with stale ports in the OVN database. When instances request metadata, the agent will try to fetch all ports for that particular network with the same IP address and there should be only one. However, when there are stale ports in OVN database but not in Neutron, those ports may have the same IP address and the result is that metadata won't work. As the stale ports will never be bound to any hypervisor, this patch is accounting only for bound ports so that those ports don't interfere until they eventually get fixed by the maintenance task. Conflicts: neutron/agent/ovn/metadata/server.py Related-Bug: #1922934 Signed-off-by: Daniel Alvarez Sanchez Change-Id: I708904b982d243359f2eeda809beae0321f1a7db (cherry picked from commit 32e938e698f260af6896f6179a5ffc0838677124) --- neutron/agent/ovn/metadata/agent.py | 3 ++- neutron/agent/ovn/metadata/server.py | 11 +++++--- .../ovn/mech_driver/ovsdb/impl_idl_ovn.py | 27 ++++++++++++------- .../unit/agent/ovn/metadata/test_server.py | 18 +++++++------ 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py index d31265b5bfa..2fa8e2d5ba4 100644 --- a/neutron/agent/ovn/metadata/agent.py +++ b/neutron/agent/ovn/metadata/agent.py @@ -203,7 +203,8 @@ class MetadataAgent(object): self._load_config() # Launch the server that will act as a proxy between the VM's and Nova. - proxy = metadata_server.UnixDomainMetadataProxy(self.conf) + proxy = metadata_server.UnixDomainMetadataProxy(self.conf, + self.chassis) proxy.run() tables = ('Encap', 'Port_Binding', 'Datapath_Binding', 'SB_Global', diff --git a/neutron/agent/ovn/metadata/server.py b/neutron/agent/ovn/metadata/server.py index 6784baada05..76b573a943c 100644 --- a/neutron/agent/ovn/metadata/server.py +++ b/neutron/agent/ovn/metadata/server.py @@ -44,8 +44,9 @@ MODE_MAP = { class MetadataProxyHandler(object): - def __init__(self, conf): + def __init__(self, conf, chassis): self.conf = conf + self.chassis = chassis self.subscribe() def subscribe(self): @@ -57,7 +58,8 @@ class MetadataProxyHandler(object): # We need to open a connection to OVN SouthBound database for # each worker so that we can process the metadata requests. self.sb_idl = ovsdb.MetadataAgentOvnSbIdl( - tables=('Port_Binding', 'Datapath_Binding')).start() + tables=('Port_Binding', 'Datapath_Binding', 'Chassis'), + chassis=self.chassis).start() @webob.dec.wsgify(RequestClass=webob.Request) def __call__(self, req): @@ -174,8 +176,9 @@ class MetadataProxyHandler(object): class UnixDomainMetadataProxy(object): - def __init__(self, conf): + def __init__(self, conf, chassis): self.conf = conf + self.chassis = chassis agent_utils.ensure_directory_exists_without_file( cfg.CONF.metadata_proxy_socket) @@ -200,7 +203,7 @@ class UnixDomainMetadataProxy(object): def run(self): self.server = agent_utils.UnixDomainWSGIServer( 'networking-ovn-metadata-agent') - self.server.start(MetadataProxyHandler(self.conf), + self.server.start(MetadataProxyHandler(self.conf, self.chassis), self.conf.metadata_proxy_socket, workers=self.conf.metadata_workers, backlog=self.conf.metadata_backlog, diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py index 697040792bf..08c54c2b40b 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py @@ -803,15 +803,24 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend): rows = self.db_list_rows('Port_Binding').execute(check_error=True) # TODO(twilson) It would be useful to have a db_find that takes a # comparison function - # TODO(dalvarez): Remove the comparison to r.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. - return [r for r in rows - if (r.mac and ( - str(r.datapath.uuid) == network or - utils.get_network_name_from_datapath( - r.datapath) == network)) and - ip_address in r.mac[0].split(' ')] + + def check_net_and_ip(port): + # If the port is not bound to any chassis it is not relevant + if not port.chassis: + return False + + # 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)] def set_port_cidrs(self, name, cidrs): # TODO(twilson) add if_exists to db commands diff --git a/neutron/tests/unit/agent/ovn/metadata/test_server.py b/neutron/tests/unit/agent/ovn/metadata/test_server.py index 1380fa2fd2b..ec06eb3fc40 100644 --- a/neutron/tests/unit/agent/ovn/metadata/test_server.py +++ b/neutron/tests/unit/agent/ovn/metadata/test_server.py @@ -27,7 +27,8 @@ from neutron.conf.agent.metadata import config as meta_conf from neutron.conf.agent.ovn.metadata import config as ovn_meta_conf from neutron.tests import base -OvnPortInfo = collections.namedtuple('OvnPortInfo', 'external_ids') +OvnPortInfo = collections.namedtuple( + 'OvnPortInfo', ['external_ids', 'chassis']) class ConfFixture(config_fixture.Config): @@ -54,7 +55,7 @@ class TestMetadataProxyHandler(base.BaseTestCase): self.useFixture(self.fake_conf_fixture) self.log_p = mock.patch.object(agent, 'LOG') self.log = self.log_p.start() - self.handler = agent.MetadataProxyHandler(self.fake_conf) + self.handler = agent.MetadataProxyHandler(self.fake_conf, 'chassis1') self.handler.sb_idl = mock.Mock() def test_call(self): @@ -113,7 +114,8 @@ class TestMetadataProxyHandler(base.BaseTestCase): ovn_port = OvnPortInfo( external_ids={'neutron:device_id': 'device_id', - 'neutron:project_id': 'project_id'}) + 'neutron:project_id': 'project_id'}, + chassis=['chassis1']) ports = [[ovn_port]] self.assertEqual( @@ -220,14 +222,14 @@ class TestUnixDomainMetadataProxy(base.BaseTestCase): @mock.patch.object(fileutils, 'ensure_tree') def test_init_doesnot_exists(self, ensure_dir): - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') ensure_dir.assert_called_once_with('/the', mode=0o755) def test_init_exists(self): with mock.patch('os.path.isdir') as isdir: with mock.patch('os.unlink') as unlink: isdir.return_value = True - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') unlink.assert_called_once_with('/the/path') def test_init_exists_unlink_no_file(self): @@ -238,7 +240,7 @@ class TestUnixDomainMetadataProxy(base.BaseTestCase): exists.return_value = False unlink.side_effect = OSError - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') unlink.assert_called_once_with('/the/path') def test_init_exists_unlink_fails_file_still_exists(self): @@ -250,14 +252,14 @@ class TestUnixDomainMetadataProxy(base.BaseTestCase): unlink.side_effect = OSError with testtools.ExpectedException(OSError): - agent.UnixDomainMetadataProxy(mock.Mock()) + agent.UnixDomainMetadataProxy(mock.Mock(), 'chassis1') unlink.assert_called_once_with('/the/path') @mock.patch.object(agent, 'MetadataProxyHandler') @mock.patch.object(agent_utils, 'UnixDomainWSGIServer') @mock.patch.object(fileutils, 'ensure_tree') def test_run(self, ensure_dir, server, handler): - p = agent.UnixDomainMetadataProxy(self.cfg.CONF) + p = agent.UnixDomainMetadataProxy(self.cfg.CONF, 'chassis1') p.run() ensure_dir.assert_called_once_with('/the', mode=0o755)