Replace "get_routing_table" with "list_ip_routes"

"get_routing_table" uses "pyroute2.IPDB" that has been deprecated.
"list_ip_routes" has been improved to be able to read multipath
routes.

Closes-Bug: #1926476

Change-Id: I0299fa11a7afefbd2999f81cd4ed3beed572009c
This commit is contained in:
Rodolfo Alonso Hernandez 2021-04-30 10:42:31 +00:00
parent 2cf4314553
commit c511964d70
6 changed files with 72 additions and 454 deletions

View File

@ -822,21 +822,6 @@ def flush_ip_addresses(ip_version, device, namespace=None):
privileged.flush_ip_addresses(ip_version, device, namespace)
def get_routing_table(ip_version, namespace=None):
"""Return a list of dictionaries, each representing a route.
@param ip_version: the routes of version to return, for example 4
@param namespace
@return: a list of dictionaries, each representing a route.
The dictionary format is: {'destination': cidr,
'nexthop': ip,
'device': device_name,
'scope': scope}
"""
# oslo.privsep turns lists to tuples in its IPC code. Change it back
return list(privileged.get_routing_table(ip_version, namespace))
# NOTE(haleyb): These neighbour functions live outside the IpNeighCommand
# class since not all callers require it.
def add_neigh_entry(ip_address, mac_address, device, namespace=None,
@ -1544,12 +1529,24 @@ def list_ip_routes(namespace, ip_version, scope=None, via=None, table=None,
'source_prefix': get_attr(route, 'RTA_PREFSRC'),
'cidr': cidr,
'scope': IP_ADDRESS_SCOPE[int(route['scope'])],
'device': get_device(int(get_attr(route, 'RTA_OIF')), devices),
'via': get_attr(route, 'RTA_GATEWAY'),
'metric': metric,
'proto': proto,
}
multipath = get_attr(route, 'RTA_MULTIPATH')
if multipath:
value['device'] = None
mp_via = []
for mp in multipath:
mp_via.append({'device': get_device(int(mp['oif']), devices),
'via': get_attr(mp, 'RTA_GATEWAY'),
'weight': int(mp['hops']) + 1})
value['via'] = mp_via
else:
value['device'] = get_device(int(get_attr(route, 'RTA_OIF')),
devices)
value['via'] = get_attr(route, 'RTA_GATEWAY')
ret.append(value)
if scope:

View File

@ -39,7 +39,7 @@ NETNS_RUN_DIR = '/var/run/netns'
NUD_STATES = {state[1]: state[0] for state in ndmsg.states.items()}
def _get_scope_name(scope):
def get_scope_name(scope):
"""Return the name of the scope (given as a number), or the scope number
if the name is unknown.
@ -138,54 +138,6 @@ def _make_route_dict(destination, nexthop, device, scope):
'scope': scope}
@privileged.default.entrypoint
def get_routing_table(ip_version, namespace=None):
"""Return a list of dictionaries, each representing a route.
:param ip_version: IP version of routes to return, for example 4
:param namespace: The name of the namespace from which to get the routes
:return: a list of dictionaries, each representing a route.
The dictionary format is: {'destination': cidr,
'nexthop': ip,
'device': device_name,
'scope': scope}
"""
family = _IP_VERSION_FAMILY_MAP[ip_version]
try:
netns = pyroute2.NetNS(namespace, flags=0) if namespace else None
except OSError as e:
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
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
def get_iproute(namespace):
# From iproute.py:
# `IPRoute` -- RTNL API to the current network namespace
@ -312,7 +264,7 @@ def add_ip_address(ip_version, ip, prefixlen, device, namespace, scope,
mask=prefixlen,
family=family,
broadcast=broadcast,
scope=_get_scope_name(scope))
scope=get_scope_name(scope))
except NetlinkError as e:
if e.code == errno.EEXIST:
raise IpAddressAlreadyExists(ip=ip, device=device)
@ -714,7 +666,7 @@ def _make_pyroute2_route_args(namespace, ip_version, cidr, device, via, table,
args = {'family': _IP_VERSION_FAMILY_MAP[ip_version]}
if not scope:
scope = 'global' if via else 'link'
scope = _get_scope_name(scope)
scope = get_scope_name(scope)
if scope:
args['scope'] = scope
if cidr:

View File

@ -563,9 +563,9 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
def _assert_extra_routes(self, router, namespace=None):
if namespace is None:
namespace = router.ns_name
routes = ip_lib.get_routing_table(4, namespace=namespace)
routes = [{'nexthop': route['nexthop'],
'destination': route['destination']} for route in routes]
routes = ip_lib.list_ip_routes(namespace, constants.IP_VERSION_4)
routes = [{'nexthop': route['via'],
'destination': route['cidr']} for route in routes]
for extra_route in router.router['routes']:
self.assertIn(extra_route, routes)
@ -575,10 +575,9 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
ns_name = namespace or router.ns_name
routes = []
for ip_version in ip_versions:
_routes = ip_lib.get_routing_table(ip_version,
namespace=ns_name)
_routes = ip_lib.list_ip_routes(ns_name, ip_version)
routes.extend(_routes)
routes = set(route['destination'] for route in routes)
routes = set(route['cidr'] for route in routes)
extra_subnets = router.get_ex_gw_port()['extra_subnets']
for extra_subnet in (route['cidr'] for route in extra_subnets):
self.assertIn(extra_subnet, routes)

View File

@ -370,49 +370,6 @@ class IpLibTestCase(IpLibTestFramework):
self.assertIsNone(
device.route.get_gateway(ip_version=ip_version))
def test_get_routing_table(self):
attr = self.generate_device_details(
ip_cidrs=["%s/24" % TEST_IP, "fd00::1/64"]
)
device = self.manage_device(attr)
device_ip = attr.ip_cidrs[0].split('/')[0]
destination = '8.8.8.0/24'
device.route.add_route(destination, device_ip)
destination6 = 'fd01::/64'
device.route.add_route(destination6, "fd00::2")
expected_routes = [{'nexthop': device_ip,
'device': attr.name,
'destination': destination,
'scope': 'universe'},
{'nexthop': None,
'device': attr.name,
'destination': str(
netaddr.IPNetwork(attr.ip_cidrs[0]).cidr),
'scope': 'link'}]
routes = ip_lib.get_routing_table(4, namespace=attr.namespace)
self.assertCountEqual(expected_routes, routes)
self.assertIsInstance(routes, list)
expected_routes6 = [{'nexthop': "fd00::2",
'device': attr.name,
'destination': destination6,
'scope': 'universe'},
{'nexthop': None,
'device': attr.name,
'destination': str(
netaddr.IPNetwork(attr.ip_cidrs[1]).cidr),
'scope': 'universe'}]
routes6 = ip_lib.get_routing_table(6, namespace=attr.namespace)
self.assertCountEqual(expected_routes6, routes6)
self.assertIsInstance(routes6, list)
def test_get_routing_table_no_namespace(self):
with testtools.ExpectedException(ip_lib.NetworkNamespaceNotFound):
ip_lib.get_routing_table(4, namespace="nonexistent-netns")
def test_get_neigh_entries(self):
attr = self.generate_device_details(
ip_cidrs=["%s/24" % TEST_IP, "fd00::1/64"]
@ -1137,3 +1094,52 @@ class GetDevicesWithIpTestCase(functional_base.BaseSudoTestCase):
ip_addresses = self._remove_loopback_interface(ip_addresses)
ip_addresses = self._remove_ipv6_scope_link(ip_addresses)
self.assertEqual(0, len(ip_addresses))
class ListIpRoutesTestCase(functional_base.BaseSudoTestCase):
def setUp(self):
super().setUp()
self.namespace = self.useFixture(net_helpers.NamespaceFixture()).name
self.device_names = ['test_device1', 'test_device2']
self.device_ips = ['10.0.0.1/24', '10.0.1.1/24']
self.device_cidrs = [netaddr.IPNetwork(ip_address).cidr for ip_address
in self.device_ips]
for idx, dev in enumerate(self.device_names):
ip_lib.IPWrapper(self.namespace).add_dummy(dev)
device = ip_lib.IPDevice(dev, namespace=self.namespace)
device.link.set_up()
device.addr.add(self.device_ips[idx])
def test_list_ip_routes_multipath(self):
multipath = [
{'device': self.device_names[0],
'via': str(self.device_cidrs[0].ip + 100), 'weight': 10},
{'device': self.device_names[1],
'via': str(self.device_cidrs[1].ip + 100), 'weight': 20},
{'via': str(self.device_cidrs[1].ip + 101), 'weight': 30},
{'via': str(self.device_cidrs[1].ip + 102)}]
ip_lib.add_ip_route(self.namespace, '1.2.3.0/24',
constants.IP_VERSION_4, via=multipath)
routes = ip_lib.list_ip_routes(self.namespace, constants.IP_VERSION_4)
multipath[2]['device'] = self.device_names[1]
multipath[3]['device'] = self.device_names[1]
multipath[3]['weight'] = 1
for route in (route for route in routes if
route['cidr'] == '1.2.3.0/24'):
if not isinstance(route['via'], list):
continue
self.assertEqual(len(multipath), len(route['via']))
for nexthop in multipath:
for mp in route['via']:
if nexthop != mp:
continue
break
else:
self.fail('Not matching route, routes: %s' % routes)
return
self.fail('Not matching route, routes: %s' % routes)

View File

@ -509,7 +509,7 @@ class RouteTestCase(functional_base.BaseSudoTestCase):
table = table or iproute_linux.DEFAULT_TABLE
if not scope:
scope = 'universe' if gateway else 'link'
scope = priv_ip_lib._get_scope_name(scope)
scope = priv_ip_lib.get_scope_name(scope)
for cidr in cidrs:
ip_version = common_utils.get_ip_version(cidr)
if ip_version == n_cons.IP_VERSION_6 and not metric:

View File

@ -914,342 +914,6 @@ class TestDeviceExists(base.BaseTestCase):
self.assertFalse(ip_lib.ensure_device_is_ready("lo"))
class TestGetRoutingTable(base.BaseTestCase):
ip_db_interfaces = {
1: {
'family': 0,
'txqlen': 0,
'ipdb_scope': 'system',
'index': 1,
'operstate': 'DOWN',
'num_tx_queues': 1,
'group': 0,
'carrier_changes': 0,
'ipaddr': [],
'neighbours': [],
'ifname': 'lo',
'promiscuity': 0,
'linkmode': 0,
'broadcast': '00:00:00:00:00:00',
'address': '00:00:00:00:00:00',
'vlans': [],
'ipdb_priority': 0,
'qdisc': 'noop',
'mtu': 65536,
'num_rx_queues': 1,
'carrier': 1,
'flags': 8,
'ifi_type': 772,
'ports': []
},
2: {
'family': 0,
'txqlen': 500,
'ipdb_scope': 'system',
'index': 2,
'operstate': 'DOWN',
'num_tx_queues': 1,
'group': 0,
'carrier_changes': 1,
'ipaddr': ['1111:1111:1111:1111::3/64', '10.0.0.3/24'],
'neighbours': [],
'ifname': 'tap-1',
'promiscuity': 0,
'linkmode': 0,
'broadcast': 'ff:ff:ff:ff:ff:ff',
'address': 'b6:d5:f6:a8:2e:62',
'vlans': [],
'ipdb_priority': 0,
'kind': 'tun',
'qdisc': 'fq_codel',
'mtu': 1500,
'num_rx_queues': 1,
'carrier': 0,
'flags': 4099,
'ifi_type': 1,
'ports': []
},
'tap-1': {
'family': 0,
'txqlen': 500,
'ipdb_scope': 'system',
'index': 2,
'operstate': 'DOWN',
'num_tx_queues': 1,
'group': 0,
'carrier_changes': 1,
'ipaddr': ['1111:1111:1111:1111::3/64', '10.0.0.3/24'],
'neighbours': [],
'ifname': 'tap-1',
'promiscuity': 0,
'linkmode': 0,
'broadcast': 'ff:ff:ff:ff:ff:ff',
'address': 'b6:d5:f6:a8:2e:62',
'vlans': [],
'ipdb_priority': 0,
'kind': 'tun',
'qdisc': 'fq_codel',
'mtu': 1500,
'num_rx_queues': 1,
'carrier': 0,
'flags': 4099,
'ifi_type': 1,
'ports': []
},
'lo': {
'family': 0,
'txqlen': 0,
'ipdb_scope': 'system',
'index': 1,
'operstate': 'DOWN',
'num_tx_queues': 1,
'group': 0,
'carrier_changes': 0,
'ipaddr': [],
'neighbours': [],
'ifname': 'lo',
'promiscuity': 0,
'linkmode': 0,
'broadcast': '00:00:00:00:00:00',
'address': '00:00:00:00:00:00',
'vlans': [],
'ipdb_priority': 0,
'qdisc': 'noop',
'mtu': 65536,
'num_rx_queues': 1,
'carrier': 1,
'flags': 8,
'ifi_type': 772,
'ports': []
}
}
ip_db_routes = [
{
'oif': 2,
'dst_len': 24,
'family': 2,
'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': [],
'type': 1,
'gateway': '10.0.0.1',
'ipdb_scope': 'system'
}, {
'oif': 2,
'type': 1,
'dst_len': 24,
'family': 2,
'proto': 2,
'tos': 0,
'dst': '10.0.0.0/24',
'ipdb_priority': 0,
'metrics': {},
'flags': 16,
'encap': {},
'src_len': 0,
'table': 254,
'multipath': [],
'prefsrc': '10.0.0.3',
'scope': 253,
'ipdb_scope': 'system'
}, {
'oif': 2,
'dst_len': 0,
'family': 2,
'proto': 3,
'tos': 0,
'dst': 'default',
'flags': 16,
'ipdb_priority': 0,
'metrics': {},
'scope': 0,
'encap': {},
'src_len': 0,
'table': 254,
'multipath': [],
'type': 1,
'gateway': '10.0.0.2',
'ipdb_scope': 'system'
}, {
'metrics': {},
'oif': 2,
'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': [],
'type': 1,
'scope': 0,
'ipdb_scope': 'system'
}, {
'metrics': {},
'oif': 2,
'dst_len': 64,
'family': socket.AF_INET6,
'proto': 3,
'tos': 0,
'dst': '1111:1111:1111:1112::/64',
'pref': '00',
'flags': 0,
'ipdb_priority': 0,
'priority': 1024,
'scope': 0,
'encap': {},
'src_len': 0,
'table': 254,
'multipath': [],
'type': 1,
'gateway': '1111:1111:1111:1111::1',
'ipdb_scope': 'system'
}
]
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)
privileged.default.set_client_mode(False)
@mock.patch.object(pyroute2, 'IPDB')
@mock.patch.object(pyroute2, 'NetNS')
def test_get_routing_table_nonexistent_namespace(self,
mock_netns, mock_ip_db):
mock_netns.side_effect = OSError(errno.ENOENT, None)
with testtools.ExpectedException(ip_lib.NetworkNamespaceNotFound):
ip_lib.get_routing_table(4, 'ns')
@mock.patch.object(pyroute2, 'IPDB')
@mock.patch.object(pyroute2, 'NetNS')
def test_get_routing_table_other_error(self, mock_netns, mock_ip_db):
expected_exception = OSError(errno.EACCES, None)
mock_netns.side_effect = expected_exception
with testtools.ExpectedException(expected_exception.__class__):
ip_lib.get_routing_table(4, 'ns')
@mock.patch.object(pyroute2, 'IPDB')
@mock.patch.object(pyroute2, 'NetNS')
def _test_get_routing_table(self, version, ip_db_routes, expected,
mock_netns, mock_ip_db):
mock_ip_db_instance = mock_ip_db.return_value
mock_ip_db_enter = mock_ip_db_instance.__enter__.return_value
mock_ip_db_enter.interfaces = self.ip_db_interfaces
mock_ip_db_enter.routes = ip_db_routes
self.assertEqual(expected, ip_lib.get_routing_table(version))
def test_get_routing_table_4(self):
expected = [{'destination': '10.0.1.0/24',
'nexthop': '10.0.0.1',
'device': 'tap-1',
'scope': 'universe'},
{'destination': '10.0.0.0/24',
'nexthop': None,
'device': 'tap-1',
'scope': 'link'},
{'destination': 'default',
'nexthop': '10.0.0.2',
'device': 'tap-1',
'scope': 'universe'}]
self._test_get_routing_table(4, self.ip_db_routes, expected)
def test_get_routing_table_6(self):
expected = [{'destination': '1111:1111:1111:1111::/64',
'nexthop': None,
'device': 'tap-1',
'scope': 'universe'},
{'destination': '1111:1111:1111:1112::/64',
'nexthop': '1111:1111:1111:1111::1',
'device': 'tap-1',
'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):
super(TestIpNeighCommand, self).setUp()