diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index f3c5a9d36d3..f4f00d91853 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -987,14 +987,24 @@ def list_network_namespaces(**kwargs): return netns.listnetns(**kwargs) -def network_namespace_exists(namespace, **kwargs): +def network_namespace_exists(namespace, try_is_ready=False, **kwargs): """Check if a network namespace exists. :param namespace: The name of the namespace to check + :param try_is_ready: Try to open the namespace to know if the namespace + is ready to be operated. :param kwargs: Callers add any filters they use as kwargs """ - output = list_network_namespaces(**kwargs) - return namespace in output + if not try_is_ready: + output = list_network_namespaces(**kwargs) + return namespace in output + + try: + privileged.open_namespace(namespace) + return True + except (RuntimeError, OSError): + pass + return False def ensure_device_is_ready(device_name, namespace=None): diff --git a/neutron/privileged/agent/linux/ip_lib.py b/neutron/privileged/agent/linux/ip_lib.py index 6fef7b7b8ed..44257539514 100644 --- a/neutron/privileged/agent/linux/ip_lib.py +++ b/neutron/privileged/agent/linux/ip_lib.py @@ -176,6 +176,17 @@ def get_iproute(namespace): return pyroute2.IPRoute() +@privileged.default.entrypoint +# NOTE(slaweq): Because of issue with pyroute2.NetNS objects running in threads +# we need to lock this function to workaround this issue. +# For details please check https://bugs.launchpad.net/neutron/+bug/1811515 +@lockutils.synchronized("privileged-ip-lib") +def open_namespace(namespace): + """Open namespace to test if the namespace is ready to be manipulated""" + with pyroute2.NetNS(namespace, flags=0): + pass + + def _translate_ip_device_exception(e, device=None, namespace=None): if e.code == errno.ENODEV: raise NetworkInterfaceNotFound(device=device, namespace=namespace) diff --git a/neutron/tests/fullstack/base.py b/neutron/tests/fullstack/base.py index 2b7883ee00d..c710a09c33f 100644 --- a/neutron/tests/fullstack/base.py +++ b/neutron/tests/fullstack/base.py @@ -19,6 +19,7 @@ import netaddr from oslo_config import cfg from oslo_log import log as logging +from neutron.agent.linux import ip_lib from neutron.common import utils as common_utils from neutron.conf.agent import common as config from neutron.tests import base as tests_base @@ -150,3 +151,8 @@ class BaseFullStackTestCase(testlib_api.MySQLTestCaseMixin, "Port", vm.port.name, "tag", network.get("provider:segmentation_id")) return vm + + def assert_namespace_exists(self, ns_name): + common_utils.wait_until_true( + lambda: ip_lib.network_namespace_exists(ns_name, + try_is_ready=True)) diff --git a/neutron/tests/fullstack/test_dhcp_agent.py b/neutron/tests/fullstack/test_dhcp_agent.py index c4e5d8a9a9b..851eb9217b3 100644 --- a/neutron/tests/fullstack/test_dhcp_agent.py +++ b/neutron/tests/fullstack/test_dhcp_agent.py @@ -107,8 +107,9 @@ class TestDhcpAgentNoHA(BaseDhcpAgentTest): namespace = dhcp_agent._get_namespace_name( self.network['id'], suffix=self.environment.hosts[0].dhcp_agent.get_namespace_suffix()) - ip = ip_lib.IPWrapper(namespace) + self.assert_namespace_exists(namespace) + ip = ip_lib.IPWrapper(namespace) devices = ip.get_devices() self.assertEqual(1, len(devices)) diff --git a/neutron/tests/fullstack/test_l3_agent.py b/neutron/tests/fullstack/test_l3_agent.py index f7f6a9c5bd9..a98f1703f28 100644 --- a/neutron/tests/fullstack/test_l3_agent.py +++ b/neutron/tests/fullstack/test_l3_agent.py @@ -130,10 +130,6 @@ class TestLegacyL3Agent(TestL3Agent): def _get_namespace(self, router_id): return namespaces.build_ns_name(namespaces.NS_PREFIX, router_id) - def _assert_namespace_exists(self, ns_name): - common_utils.wait_until_true( - lambda: ip_lib.network_namespace_exists(ns_name)) - def test_namespace_exists(self): tenant_id = uuidutils.generate_uuid() @@ -146,7 +142,7 @@ class TestLegacyL3Agent(TestL3Agent): namespace = "%s@%s" % ( self._get_namespace(router['id']), self.environment.hosts[0].l3_agent.get_namespace_suffix(), ) - self._assert_namespace_exists(namespace) + self.assert_namespace_exists(namespace) def test_mtu_update(self): tenant_id = uuidutils.generate_uuid() @@ -160,7 +156,7 @@ class TestLegacyL3Agent(TestL3Agent): namespace = "%s@%s" % ( self._get_namespace(router['id']), self.environment.hosts[0].l3_agent.get_namespace_suffix(), ) - self._assert_namespace_exists(namespace) + self.assert_namespace_exists(namespace) ip = ip_lib.IPWrapper(namespace) common_utils.wait_until_true(lambda: ip.get_devices()) diff --git a/neutron/tests/functional/agent/linux/test_ip_lib.py b/neutron/tests/functional/agent/linux/test_ip_lib.py index 43451452959..b4bb5fa40ec 100644 --- a/neutron/tests/functional/agent/linux/test_ip_lib.py +++ b/neutron/tests/functional/agent/linux/test_ip_lib.py @@ -21,6 +21,7 @@ from neutron_lib.utils import net from oslo_config import cfg from oslo_log import log as logging from oslo_utils import importutils +from oslo_utils import uuidutils import testtools from neutron.agent.linux import ip_lib @@ -634,3 +635,29 @@ class TestSetIpNonlocalBind(functional_base.BaseSudoTestCase): self.assertFalse(failed) self.assertEqual(expected, observed) + + +class NamespaceTestCase(functional_base.BaseSudoTestCase): + + def setUp(self): + super(NamespaceTestCase, self).setUp() + self.namespace = 'test_ns_' + uuidutils.generate_uuid() + ip_lib.create_network_namespace(self.namespace) + self.addCleanup(self._delete_namespace) + + def _delete_namespace(self): + ip_lib.delete_network_namespace(self.namespace) + + def test_network_namespace_exists_ns_exists(self): + self.assertTrue(ip_lib.network_namespace_exists(self.namespace)) + + def test_network_namespace_exists_ns_doesnt_exists(self): + self.assertFalse(ip_lib.network_namespace_exists('another_ns')) + + def test_network_namespace_exists_ns_exists_try_is_ready(self): + self.assertTrue(ip_lib.network_namespace_exists(self.namespace, + try_is_ready=True)) + + def test_network_namespace_exists_ns_doesnt_exists_try_is_ready(self): + self.assertFalse(ip_lib.network_namespace_exists('another_ns', + try_is_ready=True))