diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index eb3eaaaa9c2..5ebe3eabac9 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -262,9 +262,6 @@ class DhcpLocalProcess(DhcpBase): LOG.warning('Failed trying to delete interface: %s', self.interface_name) - if not ip_lib.network_namespace_exists(self.network.namespace): - LOG.debug("Namespace already deleted: %s", self.network.namespace) - return try: ip_lib.delete_network_namespace(self.network.namespace) except RuntimeError: diff --git a/neutron/privileged/agent/linux/ip_lib.py b/neutron/privileged/agent/linux/ip_lib.py index a2068d366be..59f4eeb04ff 100644 --- a/neutron/privileged/agent/linux/ip_lib.py +++ b/neutron/privileged/agent/linux/ip_lib.py @@ -50,6 +50,13 @@ class NetworkInterfaceNotFound(RuntimeError): pass +def _make_route_dict(destination, nexthop, device, scope): + return {'destination': destination, + 'nexthop': nexthop, + 'device': device, + 'scope': scope} + + @privileged.default.entrypoint def get_routing_table(ip_version, namespace=None): """Return a list of dictionaries, each representing a route. @@ -69,14 +76,32 @@ def get_routing_table(ip_version, namespace=None): if e.errno == errno.ENOENT: raise NetworkNamespaceNotFound(netns_name=namespace) raise + routes = [] with pyroute2.IPDB(nl=netns) as ipdb: ipdb_routes = ipdb.routes ipdb_interfaces = ipdb.interfaces - routes = [{'destination': route['dst'], - 'nexthop': route.get('gateway'), - 'device': ipdb_interfaces[route['oif']]['ifname'], - 'scope': _get_scope_name(route['scope'])} - for route in ipdb_routes if route['family'] == family] + for route in ipdb_routes: + if route['family'] != family: + continue + dst = route['dst'] + nexthop = route.get('gateway') + oif = route.get('oif') + scope = _get_scope_name(route['scope']) + + # If there is not a valid outgoing interface id, check if + # this is a multipath route (i.e. same destination with + # multiple outgoing interfaces) + if oif: + device = ipdb_interfaces[oif]['ifname'] + rt = _make_route_dict(dst, nexthop, device, scope) + routes.append(rt) + elif route.get('multipath'): + for mpr in route['multipath']: + oif = mpr['oif'] + device = ipdb_interfaces[oif]['ifname'] + rt = _make_route_dict(dst, nexthop, device, scope) + routes.append(rt) + return routes @@ -305,7 +330,11 @@ def remove_netns(name, **kwargs): :param name: The name of the namespace to remove """ - netns.remove(name, **kwargs) + try: + netns.remove(name, **kwargs) + except OSError as e: + if e.errno != errno.ENOENT: + raise @privileged.default.entrypoint diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index 3229e1fda18..1de87ac71fc 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -1119,9 +1119,7 @@ class TestDhcpLocalProcess(TestBase): self.assertTrue(lp.process_monitor.unregister.called) self.assertTrue(self.external_process().disable.called) - @mock.patch('neutron.agent.linux.ip_lib.network_namespace_exists') - def test_disable_not_active(self, namespace_exists): - namespace_exists.return_value = False + def test_disable_not_active(self): attrs_to_mock = dict([(a, mock.DEFAULT) for a in ['active', 'interface_name']]) with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks: @@ -1130,11 +1128,15 @@ class TestDhcpLocalProcess(TestBase): network = FakeDualNetwork() lp = LocalChild(self.conf, network) lp.device_manager = mock.Mock() - lp.disable() + with mock.patch('neutron.agent.linux.ip_lib.' + 'delete_network_namespace') as delete_ns: + lp.disable() lp.device_manager.destroy.assert_called_once_with( network, 'tap0') self._assert_disabled(lp) + delete_ns.assert_called_with('qdhcp-ns') + def test_disable_retain_port(self): attrs_to_mock = dict([(a, mock.DEFAULT) for a in ['active', 'interface_name']]) @@ -1146,9 +1148,7 @@ class TestDhcpLocalProcess(TestBase): lp.disable(retain_port=True) self._assert_disabled(lp) - @mock.patch('neutron.agent.linux.ip_lib.network_namespace_exists') - def test_disable(self, namespace_exists): - namespace_exists.return_value = True + def test_disable(self): attrs_to_mock = {'active': mock.DEFAULT} with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks: @@ -1162,9 +1162,7 @@ class TestDhcpLocalProcess(TestBase): delete_ns.assert_called_with('qdhcp-ns') - @mock.patch('neutron.agent.linux.ip_lib.network_namespace_exists') - def test_disable_config_dir_removed_after_destroy(self, namespace_exists): - namespace_exists.return_value = True + def test_disable_config_dir_removed_after_destroy(self): parent = mock.MagicMock() parent.attach_mock(self.rmtree, 'rmtree') parent.attach_mock(self.mock_mgr, 'DeviceManager') diff --git a/neutron/tests/unit/agent/linux/test_ip_lib.py b/neutron/tests/unit/agent/linux/test_ip_lib.py index 0b879b05408..f1653164a88 100644 --- a/neutron/tests/unit/agent/linux/test_ip_lib.py +++ b/neutron/tests/unit/agent/linux/test_ip_lib.py @@ -1607,6 +1607,55 @@ class TestGetRoutingTable(base.BaseTestCase): } ] + ip_db_multipath_routes = [ + { + 'dst_len': 24, + 'family': socket.AF_INET, + 'proto': 3, + 'tos': 0, + 'dst': '10.0.1.0/24', + 'flags': 16, + 'ipdb_priority': 0, + 'metrics': {}, + 'scope': 0, + 'encap': {}, + 'src_len': 0, + 'table': 254, + 'multipath': ({'oif': 1, 'family': socket.AF_INET}, + {'oif': 2, 'dst_len': 24, 'family': socket.AF_INET, + 'proto': 2, 'tos': 0, 'pref': '00', + 'priority': 256, 'flags': 0, 'encap': {}, + 'src_len': 0, 'table': 254, 'type': 1, + 'scope': 0}), + 'type': 1, + 'gateway': '10.0.0.1', + 'ipdb_scope': 'system' + }, { + 'metrics': {}, + 'dst_len': 64, + 'family': socket.AF_INET6, + 'proto': 2, + 'tos': 0, + 'dst': '1111:1111:1111:1111::/64', + 'pref': '00', + 'ipdb_priority': 0, + 'priority': 256, + 'flags': 0, + 'encap': {}, + 'src_len': 0, + 'table': 254, + 'multipath': ({'oif': 1, 'family': socket.AF_INET6}, + {'oif': 2, 'dst_len': 64, 'family': socket.AF_INET6, + 'proto': 2, 'tos': 0, 'pref': '00', + 'priority': 256, 'flags': 0, 'encap': {}, + 'src_len': 0, 'table': 254, 'type': 1, + 'scope': 0}), + 'type': 1, + 'scope': 0, + 'ipdb_scope': 'system' + } + ] + def setUp(self): super(TestGetRoutingTable, self).setUp() self.addCleanup(privileged.default.set_client_mode, True) @@ -1664,6 +1713,28 @@ class TestGetRoutingTable(base.BaseTestCase): 'scope': 'universe'}] self._test_get_routing_table(6, self.ip_db_routes, expected) + def test_get_routing_table_multipath_4(self): + expected = [{'destination': '10.0.1.0/24', + 'nexthop': '10.0.0.1', + 'device': 'lo', + 'scope': 'universe'}, + {'destination': '10.0.1.0/24', + 'nexthop': '10.0.0.1', + 'device': 'tap-1', + 'scope': 'universe'}] + self._test_get_routing_table(4, self.ip_db_multipath_routes, expected) + + def test_get_routing_table_multipath_6(self): + expected = [{'destination': '1111:1111:1111:1111::/64', + 'nexthop': None, + 'device': 'lo', + 'scope': 'universe'}, + {'destination': '1111:1111:1111:1111::/64', + 'nexthop': None, + 'device': 'tap-1', + 'scope': 'universe'}] + self._test_get_routing_table(6, self.ip_db_multipath_routes, expected) + class TestIpNeighCommand(TestIPCmdBase): def setUp(self):