From 2cf431455395c6c87e174073eac3557f4820eb0f Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 29 Apr 2021 17:18:09 +0000 Subject: [PATCH] Implement multipath routing in route commands Related-Bug: #1926476 Change-Id: I598da266905a5645b744d7ffcc47b417ff64a5e7 --- neutron/privileged/agent/linux/ip_lib.py | 29 ++++++++++-- .../privileged/agent/linux/test_ip_lib.py | 47 ++++++++++++++++++- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/neutron/privileged/agent/linux/ip_lib.py b/neutron/privileged/agent/linux/ip_lib.py index b736fe2bb3a..5db83168c56 100644 --- a/neutron/privileged/agent/linux/ip_lib.py +++ b/neutron/privileged/agent/linux/ip_lib.py @@ -295,6 +295,11 @@ def _run_iproute_addr(command, device, namespace, **kwargs): raise +@privileged.default.entrypoint +def privileged_get_link_id(device, namespace, raise_exception=True): + return get_link_id(device, namespace, raise_exception=raise_exception) + + @privileged.default.entrypoint def add_ip_address(ip_version, ip, prefixlen, device, namespace, scope, broadcast=None): @@ -698,7 +703,8 @@ def _make_pyroute2_route_args(namespace, ip_version, cidr, device, via, table, :param ip_version: (int) [4, 6] :param cidr: (string) source IP or CIDR address (IPv4, IPv6) :param device: (string) input interface name - :param via: (string) gateway IP address + :param via: (string) gateway IP address or (list of dicts) for multipath + definition. :param table: (string, int) table number or name :param metric: (int) route metric :param scope: (int) route scope @@ -713,16 +719,29 @@ def _make_pyroute2_route_args(namespace, ip_version, cidr, device, via, table, args['scope'] = scope if cidr: args['dst'] = cidr - if device: - args['oif'] = get_link_id(device, namespace) - if via: - args['gateway'] = via if table: args['table'] = int(table) if metric: args['priority'] = int(metric) if protocol: args['proto'] = protocol + if isinstance(via, (list, tuple)): + args['multipath'] = [] + for mp in via: + multipath = {} + if mp.get('device'): + multipath['oif'] = get_link_id(mp['device'], namespace) + if mp.get('via'): + multipath['gateway'] = mp['via'] + if mp.get('weight'): + multipath['hops'] = mp['weight'] - 1 + args['multipath'].append(multipath) + else: + if via: + args['gateway'] = via + if device: + args['oif'] = get_link_id(device, namespace) + return args diff --git a/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py b/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py index 81d08ca4f93..25238217ddb 100644 --- a/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py +++ b/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py @@ -476,6 +476,34 @@ class RouteTestCase(functional_base.BaseSudoTestCase): self.device = ip_lib.IPDevice(self.device_name, self.namespace) self.device.link.set_up() + def _check_gateway_or_multipath(self, route, gateway): + if gateway is None or isinstance(gateway, str): + self.assertEqual(gateway, ip_lib.get_attr(route, 'RTA_GATEWAY')) + return + + rta_multipath = ip_lib.get_attr(route, 'RTA_MULTIPATH') + self.assertEqual(len(gateway), len(rta_multipath)) + for nexthop in gateway: + to_check = {'hops': 0} + if nexthop.get('device'): + to_check['oif'] = priv_ip_lib.privileged_get_link_id( + nexthop['device'], self.namespace) + if nexthop.get('weight'): + to_check['hops'] = nexthop['weight'] - 1 + if nexthop.get('via'): + to_check['gateway'] = nexthop['via'] + + for mp in rta_multipath: + mp['gateway'] = ip_lib.get_attr(mp, 'RTA_GATEWAY') + for key in to_check: + if to_check[key] != mp[key]: + break + else: + break + else: + self.fail('Route nexthops %s do not match with defined ones ' + '%s' % (rta_multipath, gateway)) + def _check_routes(self, cidrs, table=None, gateway=None, metric=None, scope=None, proto='static'): table = table or iproute_linux.DEFAULT_TABLE @@ -499,8 +527,7 @@ class RouteTestCase(functional_base.BaseSudoTestCase): self.assertEqual( priv_ip_lib._IP_VERSION_FAMILY_MAP[ip_version], route['family']) - self.assertEqual(gateway, - ip_lib.get_attr(route, 'RTA_GATEWAY')) + self._check_gateway_or_multipath(route, gateway) self.assertEqual(metric, ip_lib.get_attr(route, 'RTA_PRIORITY')) self.assertEqual(scope, route['scope']) @@ -616,6 +643,22 @@ class RouteTestCase(functional_base.BaseSudoTestCase): device=self.device_name, via=_ip) self._check_gateway(_ip) + def test_add_multipath_route(self): + self.device_name2 = 'test_device2' + ip_lib.IPWrapper(self.namespace).add_dummy(self.device_name2) + self.device2 = ip_lib.IPDevice(self.device_name2, self.namespace) + self.device2.link.set_up() + self.device.addr.add('10.1.0.1/24') + self.device2.addr.add('10.2.0.1/24') + multipath = [ + {'device': self.device_name, 'via': '10.1.0.100', 'weight': 10}, + {'device': self.device_name2, 'via': '10.2.0.100', 'weight': 20}, + {'via': '10.2.0.101', 'weight': 30}, + {'via': '10.2.0.102'}] + priv_ip_lib.add_ip_route(self.namespace, '192.168.0.0/24', + n_cons.IP_VERSION_4, via=multipath) + self._check_routes(['192.168.0.0/24'], gateway=multipath) + class GetLinkAttributesTestCase(functional_base.BaseSudoTestCase):