diff --git a/ovn_bgp_agent/tests/functional/utils/test_linux_net.py b/ovn_bgp_agent/tests/functional/utils/test_linux_net.py index db694a35..544fbef0 100644 --- a/ovn_bgp_agent/tests/functional/utils/test_linux_net.py +++ b/ovn_bgp_agent/tests/functional/utils/test_linux_net.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import socket + +import netaddr from oslo_utils import uuidutils from ovn_bgp_agent import exceptions as agent_exc @@ -22,7 +25,10 @@ from ovn_bgp_agent.tests.functional.privileged import test_linux_net as \ from ovn_bgp_agent.utils import linux_net -class GetInterfaceAddressTestCase(base_functional.BaseFunctionalTestCase): +_IP_VERSION_FAMILY_MAP = {4: socket.AF_INET, 6: socket.AF_INET6} + + +class GetInterfaceTestCase(base_functional.BaseFunctionalTestCase): def _delete_interfaces(self, dev_names): for dev_name in dev_names: @@ -37,6 +43,25 @@ class GetInterfaceAddressTestCase(base_functional.BaseFunctionalTestCase): if device['name'] == device_name: return device + def test_get_interfaces(self): + dev_names = list(map(lambda x: uuidutils.generate_uuid()[:15], + range(3))) + self.addCleanup(self._delete_interfaces, dev_names) + for dev_name in dev_names: + priv_linux_net.create_interface(dev_name, 'dummy') + ret = linux_net.get_interfaces() + for dev in dev_names: + self.assertIn(dev, ret) + + def test_get_interface_index(self): + dev_name = uuidutils.generate_uuid()[:15] + self.addCleanup(self._delete_interfaces, [dev_name]) + priv_linux_net.create_interface(dev_name, 'dummy') + device = self._get_device(dev_name) + + ret = linux_net.get_interface_index(dev_name) + self.assertEqual(device['index'], ret) + def test_get_interface_address(self): dev_names = list(map(lambda x: uuidutils.generate_uuid()[:15], range(5))) @@ -50,3 +75,59 @@ class GetInterfaceAddressTestCase(base_functional.BaseFunctionalTestCase): def test_get_interface_address_no_interface(self): self.assertRaises(agent_exc.NetworkInterfaceNotFound, linux_net.get_interface_address, 'no_interface_name') + + def test_get_exposed_ips(self): + ips = ['240.0.0.1', 'fd00::1'] + dev_name = uuidutils.generate_uuid()[:15] + self.addCleanup(self._delete_interfaces, [dev_name]) + priv_linux_net.create_interface(dev_name, 'dummy') + for ip in ips: + priv_linux_net.add_ip_address(ip, dev_name) + + ret = linux_net.get_exposed_ips(dev_name) + self.assertEqual(ips, ret) + + def test_get_nic_ip(self): + ips = ['240.0.0.1', 'fd00::1'] + dev_name = uuidutils.generate_uuid()[:15] + self.addCleanup(self._delete_interfaces, [dev_name]) + priv_linux_net.create_interface(dev_name, 'dummy') + for ip in ips: + priv_linux_net.add_ip_address(ip, dev_name) + + ret = linux_net.get_nic_ip(dev_name) + self.assertEqual(ips, ret) + + +class GetRulesTestCase(base_functional.BaseFunctionalTestCase): + + def _delete_rules(self, rules): + for rule in rules: + try: + priv_linux_net.rule_delete(rule) + except Exception: + pass + + def test_get_ovn_ip_rules(self): + cidrs = ['192.168.0.0/24', '172.90.0.0/16', 'fd00::1/128'] + table = 100 + expected_rules = {} + rules_added = [] + for cidr in cidrs: + _ip = netaddr.IPNetwork(cidr) + ip_version = linux_net.get_ip_version(cidr) + rule = {'dst': str(_ip.ip), + 'dst_len': _ip.netmask.netmask_bits(), + 'table': table, + 'family': _IP_VERSION_FAMILY_MAP[ip_version]} + dst = "{}/{}".format(str(_ip.ip), _ip.netmask.netmask_bits()) + rules_added.append(rule) + expected_rules[dst] = { + 'table': table, + 'family': _IP_VERSION_FAMILY_MAP[ip_version]} + self.addCleanup(self._delete_rules, rules_added) + for rule in rules_added: + priv_linux_net.rule_create(rule) + + ret = linux_net.get_ovn_ip_rules([table]) + self.assertEqual(expected_rules, ret) diff --git a/ovn_bgp_agent/tests/unit/utils/test_linux_net.py b/ovn_bgp_agent/tests/unit/utils/test_linux_net.py index 0986af0d..6643a0da 100644 --- a/ovn_bgp_agent/tests/unit/utils/test_linux_net.py +++ b/ovn_bgp_agent/tests/unit/utils/test_linux_net.py @@ -25,6 +25,14 @@ from ovn_bgp_agent.tests import base as test_base from ovn_bgp_agent.utils import linux_net +class IPRouteDict(dict): + def get_attr(self, attr_name): + for attr in self['attrs']: + if attr[0] == attr_name: + return attr[1] + return None + + class TestLinuxNet(test_base.TestCase): def setUp(self): @@ -57,19 +65,24 @@ class TestLinuxNet(test_base.TestCase): self.assertEqual(6, linux_net.get_ip_version(self.ipv6)) def test_get_interfaces(self): - iface0 = mock.Mock(ifname='ethfake0') - iface1 = mock.Mock(ifname='ethfake1') - iface2 = mock.Mock(ifname='ethfake2') - self.fake_ndb.interfaces = [iface0, iface1, iface2] + iface0 = IPRouteDict({'attrs': [('IFLA_IFNAME', 'ethfake0')]}) + iface1 = IPRouteDict({'attrs': [('IFLA_IFNAME', 'ethfake1')]}) + iface2 = IPRouteDict({'attrs': [('IFLA_IFNAME', 'ethfake2')]}) + self.fake_ipr.get_links.return_value = [iface0, iface1, iface2] ret = linux_net.get_interfaces(filter_out='ethfake1') self.assertEqual(['ethfake0', 'ethfake2'], ret) def test_get_interface_index(self): - self.fake_ndb.interfaces = {'fake-nic': {'index': 7}} + self.fake_ipr.link_lookup.return_value = [7] ret = linux_net.get_interface_index('fake-nic') self.assertEqual(7, ret) + def test_get_interface_index_error(self): + self.fake_ipr.link_lookup.return_value = '' + self.assertRaises(agent_exc.NetworkInterfaceNotFound, + linux_net.get_interface_index, 'fake-nic') + def test_get_interface_address(self): device_idx = 7 self.fake_ipr.link_lookup.return_value = [device_idx] @@ -105,16 +118,6 @@ class TestLinuxNet(test_base.TestCase): linux_net.ensure_veth('fake-veth', 'fake-veth-peer') mock_ensure_veth.assert_called_once_with('fake-veth', 'fake-veth-peer') - def test_set_master_for_device_already_set(self): - dev = mock.MagicMock() - dev.get.return_value = 5 - - self.fake_ndb.interfaces = { - 'fake-dev': dev, 'fake-master': {'index': 5}} - linux_net.set_master_for_device('fake-dev', 'fake-master') - # Both values were the same, assert set() is not called - self.assertFalse(dev.__enter__().set.called) - @mock.patch('ovn_bgp_agent.privileged.linux_net.ensure_dummy_device') def test_ensure_dummy_device(self, mock_ensure_dummy_device): linux_net.ensure_dummy_device('fake-dev') @@ -202,13 +205,16 @@ class TestLinuxNet(test_base.TestCase): mock_flag.assert_called_once_with(expected_flag, 1) def test_get_exposed_ips(self): - ip0 = mock.Mock(address=self.ip, prefixlen=32) - ip1 = mock.Mock(address=self.ipv6, prefixlen=128) - ip2 = mock.Mock(address='10.10.1.18', prefixlen=24) - ip3 = mock.Mock(address='2001:0DB8:0000:000b::', prefixlen=64) - iface = mock.Mock() - iface.ipaddr.summary.return_value = [ip0, ip1, ip2, ip3] - self.fake_ndb.interfaces = {self.dev: iface} + ip0 = IPRouteDict({'prefixlen': 32, + 'attrs': [('IFA_ADDRESS', self.ip)]}) + ip1 = IPRouteDict({'prefixlen': 128, + 'attrs': [('IFA_ADDRESS', self.ipv6)]}) + ip2 = IPRouteDict({'prefixlen': 24, + 'attrs': [('IFA_ADDRESS', '10.10.1.18')]}) + ip3 = IPRouteDict( + {'prefixlen': 64, + 'attrs': [('IFA_ADDRESS', '2001:0DB8:0000:000b::')]}) + self.fake_ipr.get_addr.return_value = [ip0, ip1, ip2, ip3] ips = linux_net.get_exposed_ips(self.dev) @@ -216,36 +222,28 @@ class TestLinuxNet(test_base.TestCase): self.assertEqual(expected_ips, ips) def test_get_nic_ip(self): - ip0 = mock.Mock(address='10.10.1.16') - ip1 = mock.Mock(address='10.10.1.17') - iface = mock.Mock() - iface.ipaddr.summary.return_value = [ip0, ip1] - self.fake_ndb.interfaces = {self.dev: iface} + ip0 = IPRouteDict({'attrs': [('IFA_ADDRESS', '10.10.1.16')]}) + ip1 = IPRouteDict({'attrs': [('IFA_ADDRESS', '10.10.1.17')]}) + self.fake_ipr.get_addr.return_value = [ip0, ip1] ips = linux_net.get_nic_ip(self.dev) expected_ips = ['10.10.1.16', '10.10.1.17'] self.assertEqual(expected_ips, ips) - def test_get_nic_ip_prefixlen(self): - ip = mock.Mock(address=self.ip, prefixlen=32) - iface = mock.Mock() - iface.ipaddr.summary.return_value.filter.return_value = [ip] - self.fake_ndb.interfaces = {self.dev: iface} - - linux_net.get_nic_ip(self.dev, prefixlen_filter=32) - iface.ipaddr.summary.return_value.filter.assert_called_once_with( - prefixlen=32) - def test_get_exposed_ips_on_network(self): - ip0 = mock.Mock(address=self.ip, prefixlen=32) - ip1 = mock.Mock(address='10.10.1.17', prefixlen=128) - ip2 = mock.Mock(address=self.ipv6, prefixlen=128) - ip3 = mock.Mock( - address='2001:db8:3333:4444:5555:6666:7777:8888', prefixlen=128) - iface = mock.Mock() - iface.ipaddr.summary.return_value = [ip0, ip1, ip2, ip3] - self.fake_ndb.interfaces = {self.dev: iface} + ip0 = IPRouteDict({'prefixlen': 32, + 'attrs': [('IFA_ADDRESS', self.ip)]}) + ip1 = IPRouteDict({'prefixlen': 128, + 'attrs': [('IFA_ADDRESS', '10.10.1.17')]}) + ip2 = IPRouteDict({'prefixlen': 128, + 'attrs': [('IFA_ADDRESS', self.ipv6)]}) + ip3 = IPRouteDict({ + 'prefixlen': 128, + 'attrs': [('IFA_ADDRESS', '2001:db8:3333:4444:5555:6666:7777:8888') + ]}) + + self.fake_ipr.get_addr.return_value = [ip0, ip1, ip2, ip3] network_ips = [ipaddress.ip_address(self.ip), ipaddress.ip_address(self.ipv6)] @@ -314,16 +312,24 @@ class TestLinuxNet(test_base.TestCase): self.assertEqual([route1], ret) def test_get_ovn_ip_rules(self): - rule0 = mock.Mock(table=7, dst=10, dst_len=128, family='fake') - rule1 = mock.Mock(table=7, dst=11, dst_len=32, family='fake') - rule2 = mock.Mock(table=9, dst=5, dst_len=24, family='fake') - rule3 = mock.Mock(table=10, dst=6, dst_len=128, family='fake') - self.fake_ndb.rules.dump.return_value = [rule0, rule1, rule2, rule3] + rule0 = IPRouteDict({'dst_len': 128, 'family': 10, + 'attrs': [('FRA_TABLE', 7), + ('FRA_DST', 10)]}) + rule1 = IPRouteDict({'dst_len': 32, 'family': 2, + 'attrs': [('FRA_TABLE', 7), + ('FRA_DST', 11)]}) + rule2 = IPRouteDict({'dst_len': 24, 'family': 2, + 'attrs': [('FRA_TABLE', 9), + ('FRA_DST', 5)]}) + rule3 = IPRouteDict({'dst_len': 128, 'family': 10, + 'attrs': [('FRA_TABLE', 10), + ('FRA_DST', 6)]}) + self.fake_ipr.get_rules.side_effect = [[rule1, rule2], [rule0, rule3]] ret = linux_net.get_ovn_ip_rules([7, 10]) - expected_ret = {'10/128': {'table': 7, 'family': 'fake'}, - '11/32': {'table': 7, 'family': 'fake'}, - '6/128': {'table': 10, 'family': 'fake'}} + expected_ret = {'10/128': {'table': 7, 'family': 10}, + '11/32': {'table': 7, 'family': 2}, + '6/128': {'table': 10, 'family': 10}} self.assertEqual(expected_ret, ret) @mock.patch('ovn_bgp_agent.privileged.linux_net.delete_exposed_ips') @@ -338,15 +344,13 @@ class TestLinuxNet(test_base.TestCase): linux_net.delete_ip_rules(ip_rules) mock_delete_ip_rules.assert_called_once_with(ip_rules) - def _test_delete_bridge_ip_routes(self, mock_route_delete, is_vlan=False, - has_gateway=False): + @mock.patch.object(linux_net, 'get_interface_index') + def _test_delete_bridge_ip_routes(self, mock_route_delete, mock_get_index, + is_vlan=False, has_gateway=False): gateway = '1.1.1.1' oif = 11 vlan = 30 if is_vlan else None - vlan_dev = '%s.%s' % (self.bridge, vlan) if is_vlan else None - self.fake_ndb.interfaces = {self.bridge: {'index': oif}} - if is_vlan: - self.fake_ndb.interfaces.update({vlan_dev: {'index': oif}}) + mock_get_index.return_value = oif route = {'route': {'dst': self.ip, 'dst_len': 32, @@ -358,19 +362,28 @@ class TestLinuxNet(test_base.TestCase): routing_tables = {self.bridge: 20} routing_tables_routes = {self.bridge: [route]} # extra_route0 matches with the route - extra_route0 = {'dst': self.ip, 'dst_len': 32, - 'family': AF_INET, 'oif': oif, - 'gateway': gateway, 'table': 20} + extra_route0 = IPRouteDict({ + 'dst_len': 32, 'family': AF_INET, 'table': 20, + 'attrs': [('RTA_DST', self.ip), + ('RTA_OIF', oif), + ('RTA_GATEWAY', gateway)]}) # extra_route1 does not match with route and should be removed - extra_route1 = copy.deepcopy(extra_route0) - extra_route1['dst'] = '10.10.1.17' + extra_route1 = IPRouteDict({ + 'dst_len': 32, 'family': AF_INET, 'table': 20, + 'attrs': [('RTA_DST', '10.10.1.17'), + ('RTA_OIF', oif), + ('RTA_GATEWAY', gateway)]}) extra_routes = {self.bridge: [extra_route0, extra_route1]} linux_net.delete_bridge_ip_routes( routing_tables, routing_tables_routes, extra_routes) # Assert extra_route1 has been removed - mock_route_delete.assert_called_once_with(extra_route1) + expected_route = {'dst': '10.10.1.17', 'dst_len': 32, + 'family': AF_INET, 'oif': oif, + 'gateway': gateway, 'table': 20} + + mock_route_delete.assert_called_once_with(expected_route) @mock.patch('ovn_bgp_agent.privileged.linux_net.route_delete') def test_delete_bridge_ip_routes(self, mock_route_delete): @@ -386,30 +399,33 @@ class TestLinuxNet(test_base.TestCase): @mock.patch('ovn_bgp_agent.utils.linux_net.delete_ip_routes') def test_delete_routes_from_table(self, mock_delete_ip_routes): - route0 = mock.MagicMock(scope=1, proto=11) - route1 = mock.MagicMock(scope=2, proto=22) - route2 = mock.MagicMock(scope=254, proto=186) - self.fake_ndb.routes.dump().filter.return_value = [ + route0 = {'scope': 1, 'proto': 11} + route1 = {'scope': 2, 'proto': 22} + route2 = {'scope': 254, 'proto': 186} + self.fake_ipr.get_routes.return_value = [ route0, route1, route2] - self.fake_ndb.routes.__getitem__.side_effect = ( - route0, route1) - linux_net.delete_routes_from_table('fake-table') mock_delete_ip_routes.assert_called_once_with([route0, route1]) def test_get_routes_on_tables(self): - route0 = mock.MagicMock(table=10, dst='10.10.10.10', proto=10) + route0 = IPRouteDict({ + 'proto': 10, 'table': 10, + 'attrs': [('RTA_DST', '10.10.10.10')]}) # Route1 has proto 186, should be ignored - route1 = mock.MagicMock(table=11, dst='11.11.11.11', proto=186) - route2 = mock.MagicMock(table=11, dst='12.12.12.12', proto=12) - # Route3 is not in the table list, should be ignored - route3 = mock.MagicMock(table=99, dst='14.14.14.14', proto=14) - # Route4 is in the list but dst is empty - route4 = mock.MagicMock(table=22, dst='', proto=10) - self.fake_ndb.routes.dump.return_value = [ - route0, route1, route2, route3, route4] + route1 = IPRouteDict({ + 'proto': 186, 'table': 11, + 'attrs': [('RTA_DST', '11.11.11.11')]}) + route2 = IPRouteDict({ + 'proto': 12, 'table': 11, + 'attrs': [('RTA_DST', '12.12.12.12')]}) + # Route3 is in the list but dst is empty + route3 = IPRouteDict({ + 'proto': 10, 'table': 22, + 'attrs': [('RTA_DST', '')]}) + self.fake_ipr.get_routes.side_effect = [ + [route0], [route1, route2], [route3]] ret = linux_net.get_routes_on_tables([10, 11, 22]) @@ -424,7 +440,6 @@ class TestLinuxNet(test_base.TestCase): table=11, dst='11.11.11.11', proto=11, dst_len=64, oif='ethout', family='fake', gateway='2.2.2.2') routes = [route0, route1] - self.fake_ndb.routes.__getitem__.side_effect = routes linux_net.delete_ip_routes(routes) @@ -443,9 +458,6 @@ class TestLinuxNet(test_base.TestCase): @mock.patch('ovn_bgp_agent.privileged.linux_net.route_delete') @mock.patch('ovn_bgp_agent.privileged.linux_net.add_ip_to_dev') def test_add_ips_to_dev(self, mock_add_ip_to_dev, mock_route_delete): - iface = mock.MagicMock(index=7) - self.fake_ndb.interfaces = {self.dev: iface} - ips = [self.ip, self.ipv6] linux_net.add_ips_to_dev( self.dev, ips, clear_local_route_at_table=123) @@ -464,9 +476,6 @@ class TestLinuxNet(test_base.TestCase): @mock.patch('ovn_bgp_agent.privileged.linux_net.del_ip_from_dev') def test_del_ips_from_dev(self, mock_del_ip_from_dev): - iface = mock.MagicMock() - self.fake_ndb.interfaces = {self.dev: iface} - ips = [self.ip, self.ipv6] linux_net.del_ips_from_dev(self.dev, ips) @@ -508,9 +517,6 @@ class TestLinuxNet(test_base.TestCase): @mock.patch.object(linux_net, 'del_ip_nei') @mock.patch('ovn_bgp_agent.privileged.linux_net.rule_delete') def test_del_ip_rule(self, mock_rule_delete, mock_del_ip_nei): - rule = mock.MagicMock() - self.fake_ndb.rules.__getitem__.return_value = rule - linux_net.del_ip_rule(self.ip, 7, dev=self.dev, lladdr=self.mac) expected_args = {'dst': self.ip, 'table': 7, 'dst_len': 32} @@ -520,9 +526,6 @@ class TestLinuxNet(test_base.TestCase): @mock.patch.object(linux_net, 'del_ip_nei') @mock.patch('ovn_bgp_agent.privileged.linux_net.rule_delete') def test_del_ip_rule_ipv6(self, mock_rule_delete, mock_del_ip_nei): - rule = mock.MagicMock() - self.fake_ndb.rules.__getitem__.return_value = rule - linux_net.del_ip_rule(self.ipv6, 7, dev=self.dev, lladdr=self.mac) expected_args = {'dst': self.ipv6, 'table': 7, @@ -533,9 +536,6 @@ class TestLinuxNet(test_base.TestCase): @mock.patch.object(linux_net, 'del_ip_nei') @mock.patch('ovn_bgp_agent.privileged.linux_net.rule_delete') def test_del_ip_rule_invalid_ip(self, mock_rule_delete, mock_del_ip_nei): - rule = mock.MagicMock() - self.fake_ndb.rules.__getitem__.return_value = rule - self.assertRaises(agent_exc.InvalidPortIP, linux_net.del_ip_rule, '10.10.1.6/30/128', 7) @@ -565,7 +565,6 @@ class TestLinuxNet(test_base.TestCase): 'table': 7}, 'vlan': None}]} self.assertEqual(expected_routes, routes) - self.assertFalse(self.fake_ndb.routes.create.called) mock_route_create.assert_not_called() @mock.patch('ovn_bgp_agent.privileged.linux_net.route_create') @@ -614,14 +613,15 @@ class TestLinuxNet(test_base.TestCase): self.assertEqual(expected_routes, routes) mock_route_create.assert_not_called() + @mock.patch.object(linux_net, 'get_interface_index') @mock.patch.object(linux_net, 'ensure_vlan_device_for_network') @mock.patch('ovn_bgp_agent.privileged.linux_net.route_create') def test_add_ip_route_vlan_keyerror(self, mock_route_create, - mock_ensure_vlan_device): + mock_ensure_vlan_device, + mock_get_index): routes = {} oif = '5' - self.fake_ndb.interfaces.__getitem__.side_effect = ( - KeyError('No index'), {'index': oif}) + mock_get_index.side_effect = [agent_exc.NetworkInterfaceNotFound, oif] linux_net.add_ip_route(routes, self.ip, 7, self.dev, vlan=10) expected_routes = { self.dev: [{'route': {'dst': self.ip, @@ -651,8 +651,8 @@ class TestLinuxNet(test_base.TestCase): mock_route_create.assert_not_called() @mock.patch('ovn_bgp_agent.privileged.linux_net.route_create') - def test_add_ip_route_keyerror(self, mock_route_create): - self.fake_ndb.routes.__getitem__.side_effect = KeyError('Nite Expo') + def test_add_ip_route_no_route(self, mock_route_create): + self.fake_ipr.route.return_value = () routes = {} linux_net.add_ip_route(routes, self.ip, 7, self.dev) expected_routes = { diff --git a/ovn_bgp_agent/utils/linux_net.py b/ovn_bgp_agent/utils/linux_net.py index f5c84623..665a9a1b 100644 --- a/ovn_bgp_agent/utils/linux_net.py +++ b/ovn_bgp_agent/utils/linux_net.py @@ -43,9 +43,9 @@ def get_ip_version(ip): stop=tenacity.stop_after_delay(8), reraise=True) def get_interfaces(filter_out=[]): - with pyroute2.NDB() as ndb: - return [iface.ifname for iface in ndb.interfaces - if iface.ifname not in filter_out] + with pyroute2.IPRoute() as ipr: + return [iface.get_attr('IFLA_IFNAME') for iface in ipr.get_links() + if iface.get_attr('IFLA_IFNAME') not in filter_out] @tenacity.retry( @@ -55,8 +55,11 @@ def get_interfaces(filter_out=[]): stop=tenacity.stop_after_delay(8), reraise=True) def get_interface_index(nic): - with pyroute2.NDB() as ndb: - return ndb.interfaces[nic]['index'] + try: + with pyroute2.IPRoute() as ipr: + return ipr.link_lookup(ifname=nic)[0] + except IndexError: + raise agent_exc.NetworkInterfaceNotFound(device=nic) @tenacity.retry( @@ -69,8 +72,7 @@ def get_interface_address(nic): try: with pyroute2.IPRoute() as ipr: idx = ipr.link_lookup(ifname=nic)[0] - mac = ipr.get_links(idx)[0].get_attr('IFLA_ADDRESS') - return mac + return ipr.get_links(idx)[0].get_attr('IFLA_ADDRESS') except IndexError: raise agent_exc.NetworkInterfaceNotFound(device=nic) @@ -186,24 +188,23 @@ def ensure_routing_table_for_bridge(ovn_routing_tables, bridge, vrf_table): def _ensure_routing_table_routes(ovn_routing_tables, bridge): # add default route on that table if it does not exist extra_routes = [] + bridge_idx = get_interface_index(bridge) - with pyroute2.NDB() as ndb: + with pyroute2.IPRoute() as ip: table_route_dsts = set( [ - (r.dst, r.dst_len) - for r in ndb.routes.summary().filter( - table=ovn_routing_tables[bridge] - ) + (r.get_attr('RTA_DST'), r['dst_len']) + for r in ip.get_routes(table=ovn_routing_tables[bridge]) ] ) if not table_route_dsts: - r1 = {'dst': 'default', 'oif': ndb.interfaces[bridge]['index'], + r1 = {'dst': 'default', 'oif': bridge_idx, 'table': ovn_routing_tables[bridge], 'scope': 253, 'proto': 3} ovn_bgp_agent.privileged.linux_net.route_create(r1) - r2 = {'dst': 'default', 'oif': ndb.interfaces[bridge]['index'], + r2 = {'dst': 'default', 'oif': bridge_idx, 'table': ovn_routing_tables[bridge], 'family': AF_INET6, 'proto': 3} ovn_bgp_agent.privileged.linux_net.route_create(r2) @@ -213,54 +214,52 @@ def _ensure_routing_table_routes(ovn_routing_tables, bridge): for (dst, dst_len) in table_route_dsts: if not dst: # default route try: - route = ndb.routes[ - {'table': ovn_routing_tables[bridge], - 'dst': '', - 'family': AF_INET}] - if (bridge == - ndb.interfaces[{'index': route['oif']}][ - 'ifname']): + route = [ + r for r in ip.get_routes( + table=ovn_routing_tables[bridge], + family=AF_INET) + if not r.get_attr('RTA_DST')][0] + if bridge_idx == route.get_attr('RTA_OIF'): route_missing = False else: extra_routes.append(route) - except KeyError: + except IndexError: pass # no ipv4 default rule try: - route_6 = ndb.routes[ - {'table': ovn_routing_tables[bridge], - 'dst': '', - 'family': AF_INET6}] - if (bridge == - ndb.interfaces[{'index': route_6['oif']}][ - 'ifname']): + route_6 = [ + r for r in ip.get_routes( + table=ovn_routing_tables[bridge], + family=AF_INET6) + if not r.get_attr('RTA_DST')][0] + if bridge_idx == route_6.get_attr('RTA_OIF'): route6_missing = False else: extra_routes.append(route_6) - except KeyError: + except IndexError: pass # no ipv6 default rule else: if get_ip_version(dst) == constants.IP_VERSION_6: extra_routes.append( - ndb.routes[{'table': ovn_routing_tables[bridge], - 'dst': dst, - 'dst_len': dst_len, - 'family': AF_INET6}] - ) + ip.get_routes( + table=ovn_routing_tables[bridge], + dst=dst, + dst_len=dst_len, + family=AF_INET6)[0]) else: extra_routes.append( - ndb.routes[{'table': ovn_routing_tables[bridge], - 'dst': dst, - 'dst_len': dst_len, - 'family': AF_INET}] - ) + ip.get_routes( + table=ovn_routing_tables[bridge], + dst=dst, + dst_len=dst_len, + family=AF_INET)[0]) if route_missing: - r = {'dst': 'default', 'oif': ndb.interfaces[bridge]['index'], + r = {'dst': 'default', 'oif': bridge_idx, 'table': ovn_routing_tables[bridge], 'scope': 253, 'proto': 3} ovn_bgp_agent.privileged.linux_net.route_create(r) if route6_missing: - r = {'dst': 'default', 'oif': ndb.interfaces[bridge]['index'], + r = {'dst': 'default', 'oif': bridge_idx, 'table': ovn_routing_tables[bridge], 'family': AF_INET6, 'proto': 3} ovn_bgp_agent.privileged.linux_net.route_create(r) @@ -275,14 +274,12 @@ def _ensure_routing_table_routes(ovn_routing_tables, bridge): reraise=True) def get_extra_routing_table_for_bridge(ovn_routing_tables, bridge): extra_routes = [] - - with pyroute2.NDB() as ndb: + bridge_idx = get_interface_index(bridge) + with pyroute2.IPRoute() as ip: table_route_dsts = set( [ - (r.dst, r.dst_len) - for r in ndb.routes.summary().filter( - table=ovn_routing_tables[bridge] - ) + (r.get_attr('RTA_DST'), r['dst_len']) + for r in ip.get_routes(table=ovn_routing_tables[bridge]) ] ) @@ -292,40 +289,40 @@ def get_extra_routing_table_for_bridge(ovn_routing_tables, bridge): for (dst, dst_len) in table_route_dsts: if not dst: # default route try: - route = ndb.routes[ - {'table': ovn_routing_tables[bridge], - 'dst': '', - 'family': AF_INET}] - if (bridge != ndb.interfaces[{'index': route['oif']}][ - 'ifname']): + route = [ + r for r in ip.get_routes( + table=ovn_routing_tables[bridge], + family=AF_INET) + if not r.get_attr('RTA_DST')][0] + if bridge_idx != route.get_attr('RTA_OIF'): extra_routes.append(route) - except KeyError: - pass # no ipv4 default rule + except IndexError: + pass # no IPv4 default rule try: - route_6 = ndb.routes[ - {'table': ovn_routing_tables[bridge], - 'dst': '', - 'family': AF_INET6}] - if (bridge != ndb.interfaces[{'index': route_6['oif']}][ - 'ifname']): + route_6 = [ + r for r in ip.get_routes( + table=ovn_routing_tables[bridge], + family=AF_INET6) + if not r.get_attr('RTA_DST')][0] + if bridge_idx != route_6.get_attr('RTA_OIF'): extra_routes.append(route_6) - except KeyError: - pass # no ipv6 default rule + except IndexError: + pass # no IPv6 default rule else: if get_ip_version(dst) == constants.IP_VERSION_6: extra_routes.append( - ndb.routes[{'table': ovn_routing_tables[bridge], - 'dst': dst, - 'dst_len': dst_len, - 'family': AF_INET6}] - ) + ip.get_routes( + table=ovn_routing_tables[bridge], + dst=dst, + dst_len=dst_len, + family=AF_INET6)[0]) else: extra_routes.append( - ndb.routes[{'table': ovn_routing_tables[bridge], - 'dst': dst, - 'dst_len': dst_len, - 'family': AF_INET}] - ) + ip.get_routes( + table=ovn_routing_tables[bridge], + dst=dst, + dst_len=dst_len, + family=AF_INET)[0]) return extra_routes @@ -359,12 +356,17 @@ def enable_proxy_arp(device): stop=tenacity.stop_after_delay(8), reraise=True) def get_exposed_ips(nic): - exposed_ips = [] - with pyroute2.NDB() as ndb: - exposed_ips = [ip.address - for ip in ndb.interfaces[nic].ipaddr.summary() - if ip.prefixlen == 32 or ip.prefixlen == 128] - return exposed_ips + nic_idx = get_interface_index(nic) + try: + with pyroute2.IPRoute() as ipr: + return [ip.get_attr('IFA_ADDRESS') + for ip in ipr.get_addr(index=nic_idx) + if ip['prefixlen'] in (32, 128)] + except pyroute2.netlink.exceptions.NetlinkError: + # Nic does not exist + LOG.debug("NIC %s does not yet exist, so it does not have exposed IPs", + nic) + return [] @tenacity.retry( @@ -374,38 +376,24 @@ def get_exposed_ips(nic): stop=tenacity.stop_after_delay(8), reraise=True) def get_nic_ip(nic, prefixlen_filter=None): - exposed_ips = [] - with pyroute2.NDB() as ndb: + nic_idx = get_interface_index(nic) + with pyroute2.IPRoute() as ipr: if prefixlen_filter: - exposed_ips = [ip.address - for ip in ndb.interfaces[nic].ipaddr.summary( - ).filter(prefixlen=prefixlen_filter)] + return [ + ip.get_attr('IFA_ADDRESS') + for ip in ipr.get_addr(index=nic_idx, + prefixlen=prefixlen_filter) + ] else: - exposed_ips = [ip.address - for ip in ndb.interfaces[nic].ipaddr.summary()] - - return exposed_ips + return [ + ip.get_attr('IFA_ADDRESS') + for ip in ipr.get_addr(index=nic_idx) + ] -@tenacity.retry( - retry=tenacity.retry_if_exception_type( - netlink_exceptions.NetlinkDumpInterrupted), - wait=tenacity.wait_exponential(multiplier=0.02, max=1), - stop=tenacity.stop_after_delay(8), - reraise=True) def get_exposed_ips_on_network(nic, network): - exposed_ips = [] - with pyroute2.NDB() as ndb: - try: - exposed_ips = [ip.address - for ip in ndb.interfaces[nic].ipaddr.summary() - if ((ip.prefixlen == 32 or ip.prefixlen == 128) and - ipaddress.ip_address(ip.address) in network)] - except KeyError: - # Nic does not exists - LOG.debug("Nic %s does not yet exists, so it does not have " - "exposed IPs", nic) - return exposed_ips + exposed_ips = get_exposed_ips(nic) + return [ip for ip in exposed_ips if ipaddress.ip_address(ip) in network] @tenacity.retry( @@ -434,14 +422,17 @@ def get_exposed_routes_on_network(table_ids, network): wait=tenacity.wait_exponential(multiplier=0.02, max=1), stop=tenacity.stop_after_delay(8), reraise=True) -def get_ovn_ip_rules(routing_table): - # get the rules pointing to ovn bridges +def get_ovn_ip_rules(routing_tables): ovn_ip_rules = {} - with pyroute2.NDB() as ndb: - rules_info = [(rule.table, - "{}/{}".format(rule.dst, rule.dst_len), - rule.family) for rule in ndb.rules.dump() - if rule.table in routing_table] + with pyroute2.IPRoute() as ipr: + rules_info = [ + (rule.get_attr('FRA_TABLE'), + "{}/{}".format(rule.get_attr('FRA_DST'), rule['dst_len']), + rule['family']) + for rule in ( + ipr.get_rules(family=AF_INET) + ipr.get_rules(family=AF_INET6)) + if rule.get_attr('FRA_TABLE') in routing_tables + ] for table, dst, family in rules_info: ovn_ip_rules[dst] = {'table': table, 'family': family} return ovn_ip_rules @@ -457,39 +448,40 @@ def delete_ip_rules(ip_rules): def delete_bridge_ip_routes(routing_tables, routing_tables_routes, extra_routes): - with pyroute2.NDB() as ndb: - for device, routes_info in routing_tables_routes.items(): - if not extra_routes.get(device): - continue - for route_info in routes_info: - oif = ndb.interfaces[device]['index'] - if route_info['vlan']: - vlan_device_name = '{}.{}'.format(device, - route_info['vlan']) - oif = ndb.interfaces[vlan_device_name]['index'] - if 'gateway' in route_info['route'].keys(): # subnet route - possible_matchings = [ - r for r in extra_routes[device] - if (r['dst'] == route_info['route']['dst'] and - r['dst_len'] == route_info['route']['dst_len'] and - r['gateway'] == route_info['route']['gateway'])] - else: # cr-lrp - possible_matchings = [ - r for r in extra_routes[device] - if (r['dst'] == route_info['route']['dst'] and - r['dst_len'] == route_info['route']['dst_len'] and - r['oif'] == oif)] - for r in possible_matchings: - extra_routes[device].remove(r) + for device, routes_info in routing_tables_routes.items(): + if not extra_routes.get(device): + continue + for route_info in routes_info: + oif = get_interface_index(device) + if route_info['vlan']: + vlan_device_name = '{}.{}'.format(device, + route_info['vlan']) + oif = get_interface_index(vlan_device_name) + if 'gateway' in route_info['route'].keys(): # subnet route + possible_matchings = [ + r for r in extra_routes[device] + if (r.get_attr('RTA_DST') == route_info['route']['dst'] and + r['dst_len'] == route_info['route']['dst_len'] and + r.get_attr('RTA_GATEWAY') == route_info['route'][ + 'gateway'])] + else: # cr-lrp + possible_matchings = [ + r for r in extra_routes[device] + if (r.get_attr('RTA_DST') == route_info['route']['dst'] and + r['dst_len'] == route_info['route']['dst_len'] and + r.get_attr('RTA_OIF') == oif)] + for r in possible_matchings: + extra_routes[device].remove(r) for bridge, routes in extra_routes.items(): for route in routes: - r_info = {'dst': route['dst'], + r_info = {'dst': route.get_attr('RTA_DST'), 'dst_len': route['dst_len'], 'family': route['family'], - 'oif': route['oif'], - 'gateway': route['gateway'], + 'oif': route.get_attr('RTA_OIF'), 'table': routing_tables[bridge]} + if route.get_attr('RTA_GATEWAY'): + r_info['gateway'] = route.get_attr('RTA_GATEWAY') ovn_bgp_agent.privileged.linux_net.route_delete(r_info) @@ -505,10 +497,11 @@ def delete_routes_from_table(table): stop=tenacity.stop_after_delay(8), reraise=True) def _get_table_routes(table): - with pyroute2.NDB() as ndb: - # FIXME: problem in pyroute2 removing routes with local (254) scope - return [r for r in ndb.routes.dump().filter(table=table) - if r.scope != 254 and r.proto != 186] + with pyroute2.IPRoute() as ipr: + return [ + r for r in ipr.get_routes(table=table) + if r['scope'] != 254 and r['proto'] != 186 + ] @tenacity.retry( @@ -518,10 +511,15 @@ def _get_table_routes(table): stop=tenacity.stop_after_delay(8), reraise=True) def get_routes_on_tables(table_ids): - with pyroute2.NDB() as ndb: - # NOTE: skip bgp routes (proto 186) - return [r for r in ndb.routes.dump() - if r.table in table_ids and r.dst != '' and r.proto != 186] + routes = [] + with pyroute2.IPRoute() as ipr: + for table_id in table_ids: + table_routes = [ + r for r in ipr.get_routes(table=table_id) + if r.get_attr('RTA_DST') and r['proto'] != 186 + ] + routes.extend(table_routes) + return routes def delete_ip_routes(routes): @@ -549,22 +547,19 @@ def add_ips_to_dev(nic, ips, clear_local_route_at_table=False): try: ovn_bgp_agent.privileged.linux_net.add_ip_to_dev(ip, nic) except agent_exc.IpAddressAlreadyExists: - # NDB raises KeyError: 'object exists' - # if the ip is already added already_added_ips.append(ip) if clear_local_route_at_table: for ip in ips: if ip in already_added_ips: continue - with pyroute2.NDB() as ndb: - oif = ndb.interfaces[nic]['index'] - route = {'table': clear_local_route_at_table, - 'proto': 2, - 'scope': 254, - 'dst': ip, - 'oif': oif} - ovn_bgp_agent.privileged.linux_net.route_delete(route) + oif = get_interface_index(nic) + route = {'table': clear_local_route_at_table, + 'proto': 2, + 'scope': 254, + 'dst': ip, + 'oif': oif} + ovn_bgp_agent.privileged.linux_net.route_delete(route) def del_ips_from_dev(nic, ips): @@ -601,8 +596,6 @@ def add_ip_nei(ip, lladdr, dev): param lladdr: link layer address of the neighbor to associate to that IP param dev: the interface to which the neighbor is attached """ - # FIXME: There is no support for creating neighbours in NDB - # So we are using iproute here ovn_bgp_agent.privileged.linux_net.add_ip_nei(ip, lladdr, dev) @@ -635,8 +628,6 @@ def del_ip_nei(ip, lladdr, dev): param lladdr: link layer address of the neighbor to disassociate param dev: the interface to which the neighbor is attached """ - # FIXME: There is no support for deleting neighbours in NDB - # So we are using iproute here ovn_bgp_agent.privileged.linux_net.del_ip_nei(ip, lladdr, dev) @@ -644,6 +635,12 @@ def add_unreachable_route(vrf_name): ovn_bgp_agent.privileged.linux_net.add_unreachable_route(vrf_name) +@tenacity.retry( + retry=tenacity.retry_if_exception_type( + netlink_exceptions.NetlinkDumpInterrupted), + wait=tenacity.wait_exponential(multiplier=0.02, max=1), + stop=tenacity.stop_after_delay(8), + reraise=True) def add_ip_route(ovn_routing_tables_routes, ip_address, route_table, dev, vlan=None, mask=None, via=None): net_ip = ip_address @@ -661,20 +658,19 @@ def add_ip_route(ovn_routing_tables_routes, ip_address, route_table, dev, net_ip = '{}'.format(ipaddress.IPv4Network( ip, strict=False).network_address) - with pyroute2.NDB() as ndb: - if vlan: - oif_name = '{}.{}'.format(dev, vlan) - try: - oif = ndb.interfaces[oif_name]['index'] - except KeyError: - # Most provider network was recently created an - # there has not been a sync since then, therefore - # the vlan device has not yet been created - # Trying to create the device and retrying - ensure_vlan_device_for_network(dev, vlan) - oif = ndb.interfaces[oif_name]['index'] - else: - oif = ndb.interfaces[dev]['index'] + if vlan: + oif_name = '{}.{}'.format(dev, vlan) + try: + oif = get_interface_index(oif_name) + except agent_exc.NetworkInterfaceNotFound: + # Most provider network was recently created an + # there has not been a sync since then, therefore + # the vlan device has not yet been created + # Trying to create the device and retrying + ensure_vlan_device_for_network(dev, vlan) + oif = get_interface_index(oif_name) + else: + oif = get_interface_index(dev) route = {'dst': net_ip, 'dst_len': int(mask), 'oif': oif, 'table': int(route_table), 'proto': 3} @@ -687,14 +683,13 @@ def add_ip_route(ovn_routing_tables_routes, ip_address, route_table, dev, route['family'] = AF_INET6 del route['scope'] - with pyroute2.NDB() as ndb: - try: - with ndb.routes[route]: - LOG.debug("Route already existing: %s", route) - except KeyError: + with pyroute2.IPRoute() as ipr: + if not ipr.route('show', **route): LOG.debug("Creating route at table %s: %s", route_table, route) ovn_bgp_agent.privileged.linux_net.route_create(route) LOG.debug("Route created at table %s: %s", route_table, route) + else: + LOG.debug("Route already existing: %s", route) route_info = {'vlan': vlan, 'route': route} ovn_routing_tables_routes.setdefault(dev, []).append(route_info) @@ -716,18 +711,17 @@ def del_ip_route(ovn_routing_tables_routes, ip_address, route_table, dev, net_ip = '{}'.format(ipaddress.IPv4Network( ip, strict=False).network_address) - with pyroute2.NDB() as ndb: - try: - if vlan: - oif_name = '{}.{}'.format(dev, vlan) - oif = ndb.interfaces[oif_name]['index'] - else: - oif = ndb.interfaces[dev]['index'] - except KeyError: - LOG.debug("Device %s does not exists, so the associated " - "routes should have been automatically deleted.", dev) - ovn_routing_tables_routes.pop(dev, None) - return + try: + if vlan: + oif_name = '{}.{}'.format(dev, vlan) + oif = get_interface_index(oif_name) + else: + oif = get_interface_index(dev) + except agent_exc.NetworkInterfaceNotFound: + LOG.debug("Device %s does not exists, so the associated " + "routes should have been automatically deleted.", dev) + ovn_routing_tables_routes.pop(dev, None) + return route = {'dst': net_ip, 'dst_len': int(mask), 'oif': oif, 'table': int(route_table), 'proto': 3}