From e7a2b6d1793cd08c30b78693a4f419ab3955a437 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 10 Jan 2019 18:19:23 +0000 Subject: [PATCH] Add IPWrapper.get_devices_info using PyRoute2 This function returns the attributes of a list of devices. Change-Id: I322fc7db9c71e7c21fd03d616937d172da856428 Related-Bug: #1804274 --- neutron/agent/linux/ip_lib.py | 52 +++++++- .../privileged/agent/linux/test_ip_lib.py | 124 ++++++++++++++++-- neutron/tests/unit/agent/linux/test_ip_lib.py | 112 ++++++++++++++++ 3 files changed, 274 insertions(+), 14 deletions(-) diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index 09a371f1834..f38c20ab15b 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -144,6 +144,19 @@ class IPWrapper(SubProcessBase): def device(self, name): return IPDevice(name, namespace=self.namespace) + def get_devices_info(self, exclude_loopback=True, + exclude_fb_tun_devices=True): + devices = get_devices_info(self.namespace) + + retval = [] + for device in devices: + if (exclude_loopback and device['name'] == LOOPBACK_DEVNAME or + exclude_fb_tun_devices and + device['name'] in FB_TUNNEL_DEVICE_NAMES): + continue + retval.append(device) + return retval + def get_devices(self, exclude_loopback=True, exclude_fb_tun_devices=True): retval = [] try: @@ -1330,6 +1343,13 @@ def delete_ip_rule(namespace, ip, iif=None, table=None, priority=None, privileged.delete_ip_rule(namespace, **cmd_args) +def get_attr(pyroute2_obj, attr_name): + """Get an attribute from a PyRoute2 object""" + rule_attrs = pyroute2_obj.get('attrs', []) + for attr in (attr for attr in rule_attrs if attr[0] == attr_name): + return attr[1] + + def _parse_link_device(namespace, device, **kwargs): """Parse pytoute2 link device information @@ -1337,12 +1357,6 @@ def _parse_link_device(namespace, device, **kwargs): in a dictionary. IP address scope: http://linux-ip.net/html/tools-ip-address.html """ - def get_attr(pyroute2_obj, attr_name): - rule_attrs = pyroute2_obj.get('attrs', []) - for attr in (attr for attr in rule_attrs if attr[0] == attr_name): - return attr[1] - return - retval = [] name = get_attr(device, 'IFLA_IFNAME') ip_addresses = privileged.get_ip_addresses(namespace, @@ -1377,3 +1391,29 @@ def get_devices_with_ip(namespace, name=None, **kwargs): for device in devices): retval += parsed_ips return retval + + +def get_devices_info(namespace, **kwargs): + devices = privileged.get_link_devices(namespace, **kwargs) + retval = [] + for device in devices: + ret = {'index': device['index'], + 'name': get_attr(device, 'IFLA_IFNAME'), + 'operstate': get_attr(device, 'IFLA_OPERSTATE'), + 'linkmode': get_attr(device, 'IFLA_LINKMODE'), + 'mtu': get_attr(device, 'IFLA_MTU'), + 'promiscuity': get_attr(device, 'IFLA_PROMISCUITY'), + 'mac': get_attr(device, 'IFLA_ADDRESS'), + 'broadcast': get_attr(device, 'IFLA_BROADCAST')} + ifla_linkinfo = get_attr(device, 'IFLA_LINKINFO') + if ifla_linkinfo: + ret['kind'] = get_attr(ifla_linkinfo, 'IFLA_INFO_KIND') + ifla_data = get_attr(ifla_linkinfo, 'IFLA_INFO_DATA') + if ret['kind'] == 'vxlan': + ret['vxlan_id'] = get_attr(ifla_data, 'IFLA_VXLAN_ID') + ret['vxlan_group'] = get_attr(ifla_data, 'IFLA_VXLAN_GROUP') + elif ret['kind'] == 'vlan': + ret['vlan_id'] = get_attr(ifla_data, 'IFLA_VLAN_ID') + retval.append(ret) + + return retval 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 ebc8c95ef62..89caab81cae 100644 --- a/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py +++ b/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py @@ -21,13 +21,6 @@ from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.tests.functional import base as functional_base -def _get_attr(pyroute2_obj, attr_name): - rule_attrs = pyroute2_obj.get('attrs', []) - for attr in (attr for attr in rule_attrs if attr[0] == attr_name): - return attr[1] - return - - class GetDeviceNamesTestCase(functional_base.BaseSudoTestCase): def _remove_ns(self, namespace): @@ -57,6 +50,121 @@ class GetDeviceNamesTestCase(functional_base.BaseSudoTestCase): self.assertNotIn(name, interfaces) +class GetDevicesInfoTestCase(functional_base.BaseSudoTestCase): + + def setUp(self): + super(GetDevicesInfoTestCase, self).setUp() + self.namespace = 'ns_test-' + uuidutils.generate_uuid() + priv_ip_lib.create_netns(self.namespace) + self.addCleanup(self._remove_ns, self.namespace) + self.interfaces = ['int_01', 'int_02'] + self.interfaces_to_exclude = (ip_lib.FB_TUNNEL_DEVICE_NAMES + + [ip_lib.LOOPBACK_DEVNAME]) + + def _remove_ns(self, namespace): + priv_ip_lib.remove_netns(namespace) + + def test_get_devices_info_lo(self): + devices = priv_ip_lib.get_link_devices(self.namespace) + self.assertGreater(len(devices), 0) + for device in devices: + if ip_lib.get_attr(device, 'IFLA_IFNAME') != 'lo': + continue + self.assertIsNone(ip_lib.get_attr(device, 'IFLA_LINKINFO')) + break + else: + self.fail('Device "lo" not found') + + def test_get_devices_info_dummy(self): + interfaces_tested = [] + for interface in self.interfaces: + priv_ip_lib.create_interface(interface, self.namespace, 'dummy') + + devices = priv_ip_lib.get_link_devices(self.namespace) + self.assertGreater(len(devices), 0) + for device in devices: + name = ip_lib.get_attr(device, 'IFLA_IFNAME') + if name in self.interfaces_to_exclude: + continue + self.assertIn(name, self.interfaces) + ifla_linkinfo = ip_lib.get_attr(device, 'IFLA_LINKINFO') + self.assertEqual(ip_lib.get_attr(ifla_linkinfo, 'IFLA_INFO_KIND'), + 'dummy') + interfaces_tested.append(name) + self.assertEqual(sorted(interfaces_tested), sorted(self.interfaces)) + + def test_get_devices_info_vlan(self): + interfaces_tested = [] + vlan_interfaces = [] + vlan_id = 1000 + for interface in self.interfaces: + priv_ip_lib.create_interface(interface, self.namespace, 'dummy') + vlan_interface = interface + '_' + str(vlan_id) + vlan_interfaces.append(vlan_interface) + priv_ip_lib.create_interface( + vlan_interface, self.namespace, 'vlan', + physical_interface=interface, vlan_id=vlan_id) + vlan_id += 1 + + devices = priv_ip_lib.get_link_devices(self.namespace) + self.assertGreater(len(devices), 0) + for device in devices: + name = ip_lib.get_attr(device, 'IFLA_IFNAME') + if name in self.interfaces_to_exclude: + continue + self.assertIn(name, self.interfaces + vlan_interfaces) + ifla_linkinfo = ip_lib.get_attr(device, 'IFLA_LINKINFO') + if name in vlan_interfaces: + self.assertEqual( + ip_lib.get_attr(ifla_linkinfo, 'IFLA_INFO_KIND'), 'vlan') + ifla_infodata = ip_lib.get_attr(ifla_linkinfo, + 'IFLA_INFO_DATA') + vlan_id = int(name.split('_')[-1]) + self.assertEqual( + ip_lib.get_attr(ifla_infodata, 'IFLA_VLAN_ID'), vlan_id) + interfaces_tested.append(name) + self.assertEqual(sorted(interfaces_tested), + sorted(self.interfaces + vlan_interfaces)) + + def test_get_devices_info_vxlan(self): + interfaces_tested = [] + vxlan_interfaces = [] + vxlan_id = 1000 + for interface in self.interfaces: + priv_ip_lib.create_interface(interface, self.namespace, 'dummy') + vxlan_interface = interface + '_' + str(vxlan_id) + vxlan_interfaces.append(vxlan_interface) + priv_ip_lib.create_interface( + vxlan_interface, self.namespace, 'vxlan', + physical_interface=interface, vxlan_id=vxlan_id, + vxlan_group='239.1.1.1') + vxlan_id += 1 + + devices = priv_ip_lib.get_link_devices(self.namespace) + self.assertGreater(len(devices), 0) + for device in devices: + name = ip_lib.get_attr(device, 'IFLA_IFNAME') + if name in self.interfaces_to_exclude: + continue + self.assertIn(name, self.interfaces + vxlan_interfaces) + ifla_linkinfo = ip_lib.get_attr(device, 'IFLA_LINKINFO') + if name in vxlan_interfaces: + self.assertEqual( + ip_lib.get_attr(ifla_linkinfo, 'IFLA_INFO_KIND'), + 'vxlan') + ifla_infodata = ip_lib.get_attr(ifla_linkinfo, + 'IFLA_INFO_DATA') + vxlan_id = int(name.split('_')[-1]) + self.assertEqual( + ip_lib.get_attr(ifla_infodata, 'IFLA_VXLAN_ID'), vxlan_id) + self.assertEqual( + ip_lib.get_attr(ifla_infodata, 'IFLA_VXLAN_GROUP'), + '239.1.1.1') + interfaces_tested.append(name) + self.assertEqual(sorted(interfaces_tested), + sorted(self.interfaces + vxlan_interfaces)) + + class ListIpRulesTestCase(functional_base.BaseSudoTestCase): RULE_TABLES = {'default': 253, 'main': 254, 'local': 255} @@ -276,7 +384,7 @@ class GetIpAddressesTestCase(functional_base.BaseSudoTestCase): ip_addresses = priv_ip_lib.get_ip_addresses(namespace) for ip_address in ip_addresses: int_name = str(ip_address['index']) - ip = _get_attr(ip_address, 'IFA_ADDRESS') + ip = ip_lib.get_attr(ip_address, 'IFA_ADDRESS') mask = ip_address['prefixlen'] cidr = common_utils.ip_to_cidr(ip, mask) self.assertEqual(interfaces[int_name]['cidr'], cidr) diff --git a/neutron/tests/unit/agent/linux/test_ip_lib.py b/neutron/tests/unit/agent/linux/test_ip_lib.py index 27ebbd22813..620ce35609d 100644 --- a/neutron/tests/unit/agent/linux/test_ip_lib.py +++ b/neutron/tests/unit/agent/linux/test_ip_lib.py @@ -1906,3 +1906,115 @@ class ParseLinkDeviceTestCase(base.BaseTestCase): 'dynamic': False, 'dadfailed': False, 'name': 'int_name', 'broadcast': None, 'tentative': False}] self.assertEqual(expected, retval) + + +class GetDevicesInfoTestCase(base.BaseTestCase): + + DEVICE_LO = { + 'index': 2, + 'attrs': (('IFLA_IFNAME', 'lo'), ('IFLA_OPERSTATE', 'UP'), + ('IFLA_LINKMODE', 0), ('IFLA_MTU', 1000), + ('IFLA_PROMISCUITY', 0), + ('IFLA_ADDRESS', '5a:76:ed:cc:ce:90'), + ('IFLA_BROADCAST', 'ff:ff:ff:ff:ff:f0'), ) + } + + DEVICE_DUMMY = { + 'index': 2, + 'attrs': (('IFLA_IFNAME', 'int_01'), ('IFLA_OPERSTATE', 'DOWN'), + ('IFLA_LINKMODE', 0), ('IFLA_MTU', 1500), + ('IFLA_PROMISCUITY', 0), + ('IFLA_ADDRESS', '5a:76:ed:cc:ce:90'), + ('IFLA_BROADCAST', 'ff:ff:ff:ff:ff:f0'), + ('IFLA_LINKINFO', { + 'attrs': (('IFLA_INFO_KIND', 'dummy'), )})) + } + DEVICE_VLAN = { + 'index': 5, + 'attrs': (('IFLA_IFNAME', 'int_02'), ('IFLA_OPERSTATE', 'DOWN'), + ('IFLA_LINKMODE', 0), ('IFLA_MTU', 1400), + ('IFLA_PROMISCUITY', 0), + ('IFLA_ADDRESS', '5a:76:ed:cc:ce:91'), + ('IFLA_BROADCAST', 'ff:ff:ff:ff:ff:f1'), + ('IFLA_LINKINFO', {'attrs': ( + ('IFLA_INFO_KIND', 'vlan'), + ('IFLA_INFO_DATA', {'attrs': (('IFLA_VLAN_ID', 1000), )}) + )})) + } + DEVICE_VXLAN = { + 'index': 9, + 'attrs': (('IFLA_IFNAME', 'int_03'), ('IFLA_OPERSTATE', 'UP'), + ('IFLA_LINKMODE', 0), ('IFLA_MTU', 1300), + ('IFLA_PROMISCUITY', 0), + ('IFLA_ADDRESS', '5a:76:ed:cc:ce:92'), + ('IFLA_BROADCAST', 'ff:ff:ff:ff:ff:f2'), + ('IFLA_LINKINFO', {'attrs': ( + ('IFLA_INFO_KIND', 'vxlan'), + ('IFLA_INFO_DATA', {'attrs': ( + ('IFLA_VXLAN_ID', 1001), + ('IFLA_VXLAN_GROUP', '239.1.1.1'))}) + )})) + } + + def setUp(self): + super(GetDevicesInfoTestCase, self).setUp() + self.mock_getdevs = mock.patch.object(priv_lib, + 'get_link_devices').start() + + def test_get_devices_info_lo(self): + self.mock_getdevs.return_value = (self.DEVICE_LO, ) + ret = ip_lib.get_devices_info('namespace') + expected = {'index': 2, + 'name': 'lo', + 'operstate': 'UP', + 'linkmode': 0, + 'mtu': 1000, + 'promiscuity': 0, + 'mac': '5a:76:ed:cc:ce:90', + 'broadcast': 'ff:ff:ff:ff:ff:f0'} + self.assertEqual(expected, ret[0]) + + def test_get_devices_info_dummy(self): + self.mock_getdevs.return_value = (self.DEVICE_DUMMY, ) + ret = ip_lib.get_devices_info('namespace') + expected = {'index': 2, + 'name': 'int_01', + 'operstate': 'DOWN', + 'linkmode': 0, + 'mtu': 1500, + 'promiscuity': 0, + 'mac': '5a:76:ed:cc:ce:90', + 'broadcast': 'ff:ff:ff:ff:ff:f0', + 'kind': 'dummy'} + self.assertEqual(expected, ret[0]) + + def test_get_devices_info_vlan(self): + self.mock_getdevs.return_value = (self.DEVICE_VLAN, ) + ret = ip_lib.get_devices_info('namespace') + expected = {'index': 5, + 'name': 'int_02', + 'operstate': 'DOWN', + 'linkmode': 0, + 'mtu': 1400, + 'promiscuity': 0, + 'mac': '5a:76:ed:cc:ce:91', + 'broadcast': 'ff:ff:ff:ff:ff:f1', + 'kind': 'vlan', + 'vlan_id': 1000} + self.assertEqual(expected, ret[0]) + + def test_get_devices_info_vxlan(self): + self.mock_getdevs.return_value = (self.DEVICE_VXLAN, ) + ret = ip_lib.get_devices_info('namespace') + expected = {'index': 9, + 'name': 'int_03', + 'operstate': 'UP', + 'linkmode': 0, + 'mtu': 1300, + 'promiscuity': 0, + 'mac': '5a:76:ed:cc:ce:92', + 'broadcast': 'ff:ff:ff:ff:ff:f2', + 'kind': 'vxlan', + 'vxlan_id': 1001, + 'vxlan_group': '239.1.1.1'} + self.assertEqual(expected, ret[0])