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