From 3fd68c0848e2d3877e4297f63a1c00232bc70c5d Mon Sep 17 00:00:00 2001 From: Damien Rannou Date: Wed, 6 Dec 2023 13:50:45 +0100 Subject: [PATCH] USB device discovery The idea is to retreive USB devices informations via 'lshw' and return the list to ironic in order to be able to create introspection rules based on USB devices. Change-Id: I39d60cb467614fca7a7f701dbe576154213580a5 --- doc/source/admin/how_it_works.rst | 6 ++++ ironic_python_agent/hardware.py | 35 +++++++++++++++++++ ironic_python_agent/inspector.py | 9 +++++ .../tests/unit/samples/hardware_samples.py | 22 ++++++++++++ .../tests/unit/test_hardware.py | 10 ++++++ .../usb-autodiscovery-ab5a4a40ba096bb8.yaml | 5 +++ setup.cfg | 1 + 7 files changed, 88 insertions(+) create mode 100644 releasenotes/notes/usb-autodiscovery-ab5a4a40ba096bb8.yaml diff --git a/doc/source/admin/how_it_works.rst b/doc/source/admin/how_it_works.rst index 831013264..7162bf888 100644 --- a/doc/source/admin/how_it_works.rst +++ b/doc/source/admin/how_it_works.rst @@ -152,6 +152,12 @@ collectors are: * ``lldp_raw`` - mapping of interface names to lists of raw type-length-value (TLV) records. +``usb-devices`` + Collects USB devices information. Adds one key: + + * ``usb_devices`` - list of objects with keys ``product``, ``vendor`` and + ``handle`` + .. _hardware: https://pypi.org/project/hardware/ .. _NUMA: https://en.wikipedia.org/wiki/Non-uniform_memory_access .. _LLDP: https://en.wikipedia.org/wiki/Link_Layer_Discovery_Protocol diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index c8627e605..b996922b3 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -848,6 +848,15 @@ class SystemVendorInfo(encoding.SerializableComparable): self.firmware = firmware +class USBInfo(encoding.SerializableComparable): + serializable_fields = ('product', 'vendor', 'handle') + + def __init__(self, product, vendor, handle): + self.product = product + self.vendor = vendor + self.handle = handle + + class BootInfo(encoding.SerializableComparable): serializable_fields = ('current_boot_mode', 'pxe_interface') @@ -925,6 +934,15 @@ class HardwareManager(object, metaclass=abc.ABCMeta): def generate_tls_certificate(self, ip_address): raise errors.IncompatibleHardwareMethodError() + def get_usb_devices(self): + """Collect USB devices + + List all USB final devices, based on lshw information + + :return: a dict, containing product, vendor, and handle information + """ + raise errors.IncompatibleHardwareMethodError() + def erase_block_device(self, node, block_device): """Attempt to erase a block device. @@ -1615,6 +1633,23 @@ class GenericHardwareManager(HardwareManager): 'node': cached_node['uuid'] if cached_node else None}) return dev_name + def get_usb_devices(self): + sys_dict = self._get_system_lshw_dict() + try: + usb_dict = utils.find_in_lshw(sys_dict, by_id='usb', + by_class='generic', recursive=True) + + except StopIteration: + LOG.warning('Cannot find detailed information about USB') + return None + devices = [] + for dev in usb_dict: + usb_info = USBInfo(product=dev.get('product', ''), + vendor=dev.get('vendor', ''), + handle=dev.get('handle', '')) + devices.append(usb_info) + return devices + def get_system_vendor_info(self): try: sys_dict = self._get_system_lshw_dict() diff --git a/ironic_python_agent/inspector.py b/ironic_python_agent/inspector.py index 2b6f2b593..64db34d75 100644 --- a/ironic_python_agent/inspector.py +++ b/ironic_python_agent/inspector.py @@ -387,3 +387,12 @@ def collect_lldp(data, failures): :param failures: AccumulatedFailures object """ data['lldp_raw'] = hardware.dispatch_to_managers('collect_lldp_data') + + +def collect_usb_devices(data, failures): + """Collect USB information for connected devices. + + :param data: mutable data that we'll send to inspector + :param failures: AccumulatedFailures object + """ + data['usb_devices'] = hardware.dispatch_to_managers('get_usb_devices') diff --git a/ironic_python_agent/tests/unit/samples/hardware_samples.py b/ironic_python_agent/tests/unit/samples/hardware_samples.py index 93a63ac56..7cd062d1d 100644 --- a/ironic_python_agent/tests/unit/samples/hardware_samples.py +++ b/ironic_python_agent/tests/unit/samples/hardware_samples.py @@ -447,6 +447,28 @@ LSHW_JSON_OUTPUT_V1 = (""" "ethernet": true, "physical": "Physical interface" } + }, + { + "id": "usb", + "class": "bus", + "children": [ + { + "id": "usbhost:0", + "class": "bus", + "children": [ + { + "id": "usb", + "class": "generic", + "handle": "USB:1:2", + "description": "Generic USB device", + "product": "MyProduct", + "vendor": "MyVendor", + "physid": "1", + "businfo": "usb@1:1" + } + ] + } + ] } ] } diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index 827a2cdfc..a5f5e9acb 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -4897,6 +4897,16 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual('', vendor_info.firmware.build_date) self.assertEqual('', vendor_info.firmware.version) + @mock.patch.object(il_utils, 'execute', autospec=True) + def test_get_usb_devices(self, mocked_execute): + + device = hardware.USBInfo('MyProduct', 'MyVendor', 'USB:1:2') + + mocked_execute.return_value = hws.LSHW_JSON_OUTPUT_V1 + detected_usb_devices = self.hardware.get_usb_devices() + + self.assertEqual([device], detected_usb_devices) + @mock.patch.object(utils, 'get_agent_params', lambda: {'BOOTIF': 'boot:if'}) @mock.patch.object(os.path, 'isdir', autospec=True) diff --git a/releasenotes/notes/usb-autodiscovery-ab5a4a40ba096bb8.yaml b/releasenotes/notes/usb-autodiscovery-ab5a4a40ba096bb8.yaml new file mode 100644 index 000000000..0ff0de98c --- /dev/null +++ b/releasenotes/notes/usb-autodiscovery-ab5a4a40ba096bb8.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add attached USB device auto discovery. The information is + retrieived from `lshw` tool and store in introspection data result. + diff --git a/setup.cfg b/setup.cfg index e5bed2d4d..9751d8c07 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,6 +58,7 @@ ironic_python_agent.inspector.collectors = numa-topology = ironic_python_agent.numa_inspector:collect_numa_topology_info dmi-decode = ironic_python_agent.dmi_inspector:collect_dmidecode_info lldp = ironic_python_agent.inspector:collect_lldp + usb-devices = ironic_python_agent.inspector:collect_usb_devices [pbr] autodoc_index_modules = True