diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index 5c95b14a4..f649f7b80 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -480,7 +480,8 @@ def list_all_block_devices(block_type='disk', ignore_raid=False, ignore_floppy=True, ignore_empty=True, - ignore_multipath=False): + ignore_multipath=False, + all_serial_and_wwn=False): """List all physical block devices The switches we use for lsblk: P for KEY="value" output, b for size output @@ -500,6 +501,13 @@ def list_all_block_devices(block_type='disk', :param ignore_multipath: Whether to ignore devices backing multipath devices. Default is to consider multipath devices, if possible. + :param all_serial_and_wwn: Don't collect serial and wwn numbers based + on a priority order, instead collect wwn + numbers from both udevadm and lsblk. When + enabled this option will allso collect both + the short and the long serial from udevadm if + possible. + :return: A list of BlockDevices """ @@ -646,8 +654,15 @@ def list_all_block_devices(block_type='disk', extra = {} lsblk_serial = device_raw.get('serial') - if lsblk_serial: - extra['serial'] = lsblk_serial + lsblk_wwn = device_raw.get('wwn') + if all_serial_and_wwn: + extra['serial'] = [lsblk_serial] + extra['wwn'] = [lsblk_wwn] + else: + if lsblk_serial: + extra['serial'] = lsblk_serial + if lsblk_wwn: + extra['wwn'] = lsblk_wwn try: udev = pyudev.Devices.from_device_file(context, name) except pyudev.DeviceNotFoundByFileError as e: @@ -667,18 +682,23 @@ def list_all_block_devices(block_type='disk', ] # Only check device serial information from udev # when lsblk returned None - if not lsblk_serial: + if all_serial_and_wwn or not lsblk_serial: udev_property_mappings += [ ('serial', 'SERIAL_SHORT'), ('serial', 'SERIAL') ] for key, udev_key in udev_property_mappings: - if key in extra: - continue - value = (udev.get(f'ID_{udev_key}') - or udev.get(f'DM_{udev_key}')) # devicemapper - if value: - extra[key] = value + if all_serial_and_wwn and (key == 'wwn' or key == 'serial'): + value = (udev.get(f'ID_{udev_key}') + or udev.get(f'DM_{udev_key}')) # devicemapper + extra[key].append(value) + else: + if key in extra: + continue + value = (udev.get(f'ID_{udev_key}') + or udev.get(f'DM_{udev_key}')) # devicemapper + if value: + extra[key] = value # NOTE(lucasagomes): Newer versions of the lsblk tool supports # HCTL as a parameter but let's get it from sysfs to avoid breaking @@ -1546,8 +1566,10 @@ class GenericHardwareManager(HardwareManager): return Memory(total=total, physical_mb=physical) - def list_block_devices(self, include_partitions=False): - block_devices = list_all_block_devices() + def list_block_devices(self, include_partitions=False, + all_serial_and_wwn=False): + block_devices = \ + list_all_block_devices(all_serial_and_wwn=all_serial_and_wwn) if include_partitions: block_devices.extend( list_all_block_devices(block_type='part', @@ -1581,9 +1603,11 @@ class GenericHardwareManager(HardwareManager): return skip_list def list_block_devices_check_skip_list(self, node, - include_partitions=False): + include_partitions=False, + all_serial_and_wwn=False): block_devices = self.list_block_devices( - include_partitions=include_partitions) + include_partitions=include_partitions, + all_serial_and_wwn=all_serial_and_wwn) skip_list = self.get_skip_list_from_node( node, block_devices) if skip_list is not None: @@ -1606,13 +1630,29 @@ class GenericHardwareManager(HardwareManager): LOG.debug('Looking for a device matching root hints %s', root_device_hints) block_devices = self.list_block_devices_check_skip_list( - cached_node) + cached_node, all_serial_and_wwn=True) else: - block_devices = self.list_block_devices() + block_devices = self.list_block_devices(all_serial_and_wwn=True) if not root_device_hints: dev_name = utils.guess_root_disk(block_devices).name else: serialized_devs = [dev.serialize() for dev in block_devices] + orig_size = len(serialized_devs) + for dev_idx in range(orig_size): + ser_dev = serialized_devs.pop(0) + serials = ser_dev.get('serial') + wwns = ser_dev.get('wwn') + # (rozzi) static serial and static wwn are used to avoid + # reundancy in the number of wwns and serials, if the code + # would just loop over both serials and wwns it could be that + # there would be an uncesarry duplication of the first wwn + # number + for serial in serials: + for wwn in wwns: + tmp_ser_dev = ser_dev.copy() + tmp_ser_dev['wwn'] = wwn + tmp_ser_dev['serial'] = serial + serialized_devs.append(tmp_ser_dev) try: device = il_utils.match_root_device_hints(serialized_devs, root_device_hints) diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index 246c461e7..a37496afb 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -833,7 +833,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock.call('udevadm', 'settle'), mock.call('lsblk', '-bia', '--json', '-oKNAME,MODEL,SIZE,ROTA,' - + 'TYPE,UUID,PARTUUID,SERIAL', + + 'TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), @@ -956,7 +956,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock.call('udevadm', 'settle'), mock.call('lsblk', '-bia', '--json', '-oKNAME,MODEL,SIZE,ROTA,' - + 'TYPE,UUID,PARTUUID,SERIAL', + + 'TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), diff --git a/ironic_python_agent/tests/unit/samples/hardware_samples.py b/ironic_python_agent/tests/unit/samples/hardware_samples.py index a109e837b..914485f41 100644 --- a/ironic_python_agent/tests/unit/samples/hardware_samples.py +++ b/ironic_python_agent/tests/unit/samples/hardware_samples.py @@ -102,35 +102,42 @@ BLK_DEVICE_TEMPLATE = """ "blockdevices": [ {"kname":"sda", "model":"TinyUSB Drive", "size":3116853504, "rota":false, "type":"disk", "serial":"sda123", "uuid":"F531-BDC3", - "partuuid":null}, + "partuuid":null, "wwn":"wwn0"}, {"kname":"sdb", "model":"Fastable SD131 7", "size":10737418240, - "rota":false, "type":"disk", "serial":"sdb123", + "rota":false, "type":"disk", "serial":"sdb123", "wwn":"wwn1", "uuid":"9a5e5cca-e03d-4cbd-9054-9e6ca9048222", "partuuid":null}, {"kname":"sdc", "model":"NWD-BLP4-1600", "size":1765517033472, "rota":false, "type":"disk", "serial":"sdc123", "uuid":null, - "partuuid":null}, + "partuuid":null, "wwn":"wwn2"}, {"kname":"sdd", "model":"NWD-BLP4-1600", "size":1765517033472, "rota":false, "type":"disk", "serial":"sdd123", "uuid":null, - "partuuid":null}, + "partuuid":null, "wwn":"wwn3"}, {"kname":"loop0", "model":null, "size":109109248, "rota":true, - "type":"loop", "serial":null, "uuid":null, "partuuid": null}, + "type":"loop", "serial":null, "uuid":null, "partuuid": null, + "wwn":"wwn03"}, {"kname":"zram0", "model":null, "size":0, "rota":false, "type":"disk", - "serial":null, "uuid":null, "partuuid":null}, + "serial":null, "uuid":null, "partuuid":null, "wwn":"wwn04"}, {"kname":"ram0", "model":null, "size":8388608, "rota":false, - "type":"disk", "serial":null, "uuid":null, "partuuid":null}, + "type":"disk", "serial":null, "uuid":null, "partuuid":null, "wwn":null + }, {"kname":"ram1", "model":null, "size":8388608, "rota":false, - "type":"disk", "serial":null, "uuid":null, "partuuid":null}, + "type":"disk", "serial":null, "uuid":null, "partuuid":null, "wwn":null + }, {"kname":"ram2", "model":null, "size":8388608, "rota":false, - "type":"disk", "serial":null, "uuid":null, "partuuid":null}, + "type":"disk", "serial":null, "uuid":null, "partuuid":null,"wwn":null + }, {"kname":"ram3", "model":null, "size":8388608, "rota":false, - "type":"disk", "serial":null, "uuid":null, "partuuid":null}, + "type":"disk", "serial":null, "uuid":null, "partuuid":null, "wwn":null + }, {"kname":"fd1", "model":"magic", "size":4096, "rota":true, - "type":"disk", "serial":null, "uuid":null, "partuuid":null}, + "type":"disk", "serial":null, "uuid":null, "partuuid":null, "wwn":null + }, {"kname":"sdf", "model":"virtual floppy", "size":0, "rota":true, - "type":"disk", "serial":null, "uuid":null, "partuuid":null}, + "type":"disk", "serial":null, "uuid":null, "partuuid":null, "wwn":null + }, {"kname":"dm-0", "model":"NWD-BLP4-1600", "size":"1765517033472", "rota":false, "type":"mpath", "serial":null, "uuid":null, - "partuuid":null} + "partuuid":null, "wwn":null } ] } """ @@ -140,9 +147,11 @@ BLK_DEVICE_TEMPLATE_SMALL = """ { "blockdevices": [ {"kname":"sda", "model":"TinyUSB Drive", "size":3116853504, "rota":false, - "type":"disk", "serial":"123", "uuid":"F531-BDC", "partuuid":null}, + "type":"disk", "serial":"123", "uuid":"F531-BDC", "partuuid":null, + "wwn":"wwn0" }, {"kname":"sdb", "model":"AlmostBigEnough Drive", "size":"4294967295", - "rota":false, "type":"disk", "serial":"456", "uuid":null, "partuuid":null} + "rota":false, "type":"disk", "serial":"456", "uuid":null, "partuuid":null, + "wwn":"wwn1" } ] } """ @@ -153,9 +162,10 @@ BLK_INCOMPLETE_DEVICE_TEMPLATE_SMALL = """ { "blockdevices": [ {"kname":"sda", "model":"TinyUSB Drive", "size":3116853504, "rota":false, - "type":"disk", "serial":"", "uuid":"F531-BDC", "partuuid":null}, + "type":"disk", "serial":"", "uuid":"F531-BDC", "partuuid":null, "wwn":""}, {"kname":"sdb", "model":"AlmostBigEnough Drive", "size":"4294967295", - "rota":false, "type":"disk", "serial":"", "uuid":null, "partuuid":null} + "rota":false, "type":"disk", "serial":"", "uuid":null, "partuuid":null, + "wwn":null } ] } """ @@ -171,23 +181,27 @@ RAID_BLK_DEVICE_TEMPLATE = (""" { "blockdevices": [ {"kname":"sda", "model":"DRIVE 0", "size":1765517033472, "rota":true, - "type":"disk", "serial":"sda123", "uuid":null, "partuuid":null}, + "type":"disk", "serial":"sda123", "uuid":null, "partuuid":null, + "wwn":"wwn1234" }, {"kname":"sda1", "model":"DRIVE 0", "size":107373133824, "rota":true, - "type":"part", "serial":"sda1123", "uuid":null, "partuuid":null}, + "type":"part", "serial":"sda1123", "uuid":null, "partuuid":null, + "wwn":"wwn2222"}, {"kname":"sdb", "model":"DRIVE 1", "size":1765517033472, "rota":true, - "type":"disk", "serial":"sdb123", "uuid":null, "partuuid":null}, + "type":"disk", "serial":"sdb123", "uuid":null, "partuuid":null, + "wwn":"wwn333"}, {"kname":"sdb", "model":"DRIVE 1", "size":1765517033472, "rota":true, - "type":"disk", "uuid":null, "partuuid":null}, + "type":"disk", "uuid":null, "partuuid":null, "wwn":"444"}, {"kname":"sdb1", "model":"DRIVE 1", "size":107373133824, "rota":true, - "type":"part", "serial":"sdb1123", "uuid":null, "partuuid":null}, + "type":"part", "serial":"sdb1123", "uuid":null, "partuuid":null, + "wwn":"wwn5"}, {"kname":"md0p1", "model":"RAID", "size":107236818944, "rota":false, - "type":"md", "serial":null, "uuid":null, "partuuid":null}, + "type":"md", "serial":null, "uuid":null, "partuuid":null, "wwn":"wwn6"}, {"kname":"md0", "model":"RAID", "size":1765517033470, "rota":false, - "type":"raid1", "serial":null, "uuid":null, "partuuid":null}, + "type":"raid1", "serial":null, "uuid":null, "partuuid":null, "wwn":"12"}, {"kname":"md0", "model":"RAID", "size":1765517033470, "rota":false, - "type":"raid1", "serial":null, "uuid":null, "partuuid":null}, + "type":"raid1", "serial":null, "uuid":null, "partuuid":null, "wwn":"33"}, {"kname":"md1", "model":"RAID", "size":0, "rota":false, "type":"raid1", - "serial":null, "uuid":null, "partuuid":null} + "serial":null, "uuid":null, "partuuid":null, "wwn":null} ] } """) @@ -197,51 +211,59 @@ MULTIPATH_BLK_DEVICE_TEMPLATE = (""" "blockdevices": [ {"kname":"sda", "model":"INTEL_SSDSC2CT060A3", "size":"60022480896", "rota":false, "type":"disk", "serial":"sda123", "uuid":null, - "partuuid":null}, + "partuuid":null, "wwn":null }, {"kname":"sda2", "model":null, "size":"59162722304", "rota":false, "type":"part", "uuid":"f8b55d59-96c3-3982-b129-1b6b2ee8da86", - "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3", "serial":"sda2123"}, + "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3", "serial":"sda2123", + "wwn":"" }, {"kname":"sda3", "model":null, "size":"650002432", "rota":false, "type":"part", "uuid":"b3b03565-5f13-3c93-b2a6-6d90e25be926", - "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d", "serial":"sda3123"}, + "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d", "serial":"sda3123", + "wwn":"wwn1" }, {"kname":"sda1", "model":null, "size":"209715200", "rota":false, "type":"part", "uuid":"0a83355d-7500-3f5f-9abd-66f6fd03714c", - "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485", "serial":"sda1123"}, + "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485", "serial":"sda1123", + "wwn":"123" }, {"kname":"dm-0", "model":null, "size":"60022480896", "rota":false, - "type":"mpath", "serial":null, "uuid":null, "partuuid":null}, + "type":"mpath", "serial":null, "uuid":null, "partuuid":null, + "wwn":"123aa" }, {"kname":"dm-4", "model":null, "size":"650002432", "rota":false, "type":"part", "uuid":"b3b03565-5f13-3c93-b2a6-6d90e25be926", - "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d", "serial":null}, + "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d", "serial":null, + "wwn":"123bb" }, {"kname":"dm-2", "model":null, "size":"209715200", "rota":false, "type":"part", "uuid":"0a83355d-7500-3f5f-9abd-66f6fd03714c", - "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485", "serial":null}, + "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485", "serial":null, + "wwn":"123cc" }, {"kname":"dm-3", "model":null, "size":"59162722304", "rota":false, "type":"part", "uuid":"f8b55d59-96c3-3982-b129-1b6b2ee8da86", - "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3", "serial":null}, + "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3", "serial":null, + "wwn":"123dd" }, {"kname":"sdb", "model":"INTEL_SSDSC2CT060A3", "size":"60022480896", "rota":false, "type":"disk", "serial":"sdb123", "uuid":null, - "partuuid":null}, + "partuuid":null, "wwn":"123ee" }, {"kname":"sdb2", "model":null, "size":"59162722304", "rota":false, "type":"part", "serial":"sdb2123", - "uuid":"f8b55d59-96c3-3982-b129-1b6b2ee8da86", + "uuid":"f8b55d59-96c3-3982-b129-1b6b2ee8da86", "wwn":"123gg", "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3"}, {"kname":"sdb3", "model":null, "size":"650002432", "rota":false, "type":"part", "serial":"sdv3123", - "uuid":"b3b03565-5f13-3c93-b2a6-6d90e25be926", + "uuid":"b3b03565-5f13-3c93-b2a6-6d90e25be926", "wwn":"123zz", "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d"}, {"kname":"sdb1", "model":null, "size":"209715200", "rota":false, "type":"part", "serial":"sdb1123", - "uuid":"0a83355d-7500-3f5f-9abd-66f6fd03714c", + "uuid":"0a83355d-7500-3f5f-9abd-66f6fd03714c", "wwn":"123ll", "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485"}, {"kname":"sdc", "model":"ST1000DM003-1CH162", "size":"1000204886016", - "rota":true, "type":"disk", "serial":"sdc123", "uuid":null, + "rota":true, "type":"disk", "serial":"sdc123", "uuid":null, "wwn":"123g", "partuuid":null}, {"kname":"sdc1", "model":null, "size":"899999072256", "rota":true, "type":"part", "serial":"sdc1123", - "uuid":"457f7d3c-9376-4997-89bd-d1a7c8b04060", + "uuid":"457f7d3c-9376-4997-89bd-d1a7c8b04060", "wwn":"123kc", "partuuid":"c9433d2e-3bbc-47b4-92bf-43c1d80f06e0"}, {"kname":"dm-1", "model":null, "size":"1000204886016", "rota":false, - "type":"mpath", "serial":null, "uuid":null, "partuuid":null} + "type":"mpath", "serial":null, "uuid":null, "partuuid":null, + "wwn":"sp0ng3b0b" } ] } """) @@ -250,9 +272,10 @@ PARTUUID_DEVICE_TEMPLATE = (""" { "blockdevices": [ {"kname":"sda", "model":"DRIVE 0", "size":1765517033472, "rota":true, - "type":"disk", "serial":"sda123", "uuid":null, "partuuid":null}, + "type":"disk", "serial":"sda123", "uuid":null, "partuuid":null, + "wwn":"4d4m" }, {"kname":"sda1", "model":"DRIVE 0", "size":107373133824, "rota":true, - "type":"part", "serial":"sda1123", "uuid":"987654-3210", + "type":"part", "serial":"sda1123", "uuid":"987654-3210", "wwn":"k4k1", "partuuid":"1234-5678"} ] } diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index fca8c3773..4448064dc 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -49,37 +49,37 @@ BLK_DEVICE_TEMPLATE_SMALL_DEVICES = [ hardware.BlockDevice(name='/dev/sda', model='TinyUSB Drive', size=3116853504, rotational=False, vendor="FooTastic", uuid="F531-BDC3", - serial="123"), + serial="123", wwn="wwn0"), hardware.BlockDevice(name='/dev/sdb', model='AlmostBigEnough Drive', size=4294967295, rotational=False, vendor="FooTastic", uuid="", - serial="456"), + serial="456", wwn="wwn1"), ] RAID_BLK_DEVICE_TEMPLATE_DEVICES = [ hardware.BlockDevice(name='/dev/sda', model='DRIVE 0', size=1765517033472, rotational=True, vendor="FooTastic", uuid="", - serial="sda123"), + serial="sda123", wwn="wwn1234"), hardware.BlockDevice(name='/dev/sdb', model='DRIVE 1', size=1765517033472, rotational=True, vendor="FooTastic", uuid="", - serial="sdb123"), + serial="sdb123", wwn="wwn333"), hardware.BlockDevice(name='/dev/md0', model='RAID', size=1765517033470, rotational=False, vendor="FooTastic", uuid="", - serial=None), + serial=None, wwn="12"), hardware.BlockDevice(name='/dev/md1', model='RAID', size=0, rotational=False, vendor="FooTastic", uuid="", - serial=None), + serial=None, wwn=None), ] BLK_DEVICE_TEMPLATE_PARTUUID_DEVICE = [ hardware.BlockDevice(name='/dev/sda1', model='DRIVE 0', size=107373133824, rotational=True, vendor="FooTastic", uuid="987654-3210", - partuuid="1234-5678", serial="sda1123"), + partuuid="1234-5678", serial="sda1123", wwn="k4k1"), ] @@ -406,7 +406,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): ] expected = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('multipath', '-c', '/dev/sda'), mock.call('multipath', '-ll', '/dev/sda'), @@ -486,7 +486,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): ] expected = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('multipath', '-c', '/dev/sda'), mock.call('multipath', '-ll', '/dev/sda'), @@ -541,7 +541,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual('/dev/md0', self.hardware.get_os_install_device()) expected = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), ] @@ -567,14 +567,14 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.hardware.get_os_install_device) expected = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), ] mocked_execute.assert_has_calls(expected) mocked_execute.assert_called_once_with( 'lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]) self.assertIn(str(4 * units.Gi), ex.details) mock_cached_node.assert_called_once_with() @@ -594,26 +594,26 @@ class TestGenericHardwareManager(base.IronicAgentTest): size=3116853504, rotational=False, vendor='Super Vendor', - wwn='wwn0', + wwn=['strangewwn', 'wwn0'], wwn_with_extension='wwn0ven0', wwn_vendor_extension='ven0', - serial='serial0'), + serial=['wongserial', 'wrng0', 'serial0']), hardware.BlockDevice(name='/dev/sdb', model=model, size=10737418240, rotational=True, vendor='fake-vendor', - wwn='fake-wwn', + wwn=['fake-wwn'], wwn_with_extension='fake-wwnven0', wwn_vendor_extension='ven0', - serial='fake-serial', + serial=['fake-serial', 'serial1'], by_path='/dev/disk/by-path/1:0:0:0'), ] self.assertEqual(expected_device, self.hardware.get_os_install_device()) mock_cached_node.assert_called_once_with() - mock_dev.assert_called_once_with() + mock_dev.assert_called_once_with(all_serial_and_wwn=True) def test_get_os_install_device_root_device_hints_model(self): self._get_os_install_device_root_device_hints( @@ -691,7 +691,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertRaises(errors.DeviceNotFound, self.hardware.get_os_install_device) mock_cached_node.assert_called_once_with() - mock_dev.assert_called_once_with() + mock_dev.assert_called_once_with(all_serial_and_wwn=True) @mock.patch.object(hardware, 'list_all_block_devices', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @@ -727,7 +727,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual('/dev/sdb', self.hardware.get_os_install_device()) mock_cached_node.assert_called_once_with() - mock_dev.assert_called_once_with() + mock_dev.assert_called_once_with(all_serial_and_wwn=True) @mock.patch.object(hardware, 'update_cached_node', autospec=True) @mock.patch.object(hardware, 'list_all_block_devices', autospec=True) @@ -767,7 +767,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.hardware.get_os_install_device( permit_refresh=True)) self.assertEqual(1, mock_cached_node.call_count) - mock_dev.assert_called_once_with() + mock_dev.assert_called_once_with(all_serial_and_wwn=True) @mock.patch.object(hardware, 'list_all_block_devices', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @@ -801,7 +801,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual('/dev/sdb', self.hardware.get_os_install_device()) mock_cached_node.assert_called_once_with() - mock_dev.assert_called_once_with() + mock_dev.assert_called_once_with(all_serial_and_wwn=True) @mock.patch.object(hardware, 'list_all_block_devices', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @@ -832,7 +832,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertRaises(errors.DeviceNotFound, self.hardware.get_os_install_device) mock_cached_node.assert_called_once_with() - mock_dev.assert_called_once_with() + mock_dev.assert_called_once_with(all_serial_and_wwn=True) @mock.patch.object(hardware, 'list_all_block_devices', autospec=True) @mock.patch.object(hardware, 'get_cached_node', autospec=True) @@ -864,7 +864,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertRaises(errors.DeviceNotFound, self.hardware.get_os_install_device) mock_cached_node.assert_called_once_with() - mock_dev.assert_called_once_with() + mock_dev.assert_called_once_with(all_serial_and_wwn=True) def test__get_device_info(self): fileobj = mock.mock_open(read_data='fake-vendor') @@ -1070,7 +1070,7 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual([device], devices) - list_mock.assert_called_once_with() + list_mock.assert_called_once_with(all_serial_and_wwn=False) @mock.patch.object(hardware, 'list_all_block_devices', autospec=True) def test_list_block_devices_including_partitions(self, list_mock): @@ -1081,8 +1081,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual([device, partition], devices) - self.assertEqual([mock.call(), mock.call(block_type='part', - ignore_raid=True)], + self.assertEqual([mock.call(all_serial_and_wwn=False), + mock.call(block_type='part', ignore_raid=True)], list_mock.call_args_list) def test_get_skip_list_from_node_block_devices_with_skip_list(self): @@ -1156,7 +1156,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual([device], returned_devices) mock_list_devs.assert_called_once_with(self.hardware, - include_partitions=False) + include_partitions=False, + all_serial_and_wwn=False) @mock.patch.object(hardware.GenericHardwareManager, 'list_block_devices', autospec=True) @@ -1175,7 +1176,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual(devices, returned_devices) mock_list_devs.assert_called_once_with(self.hardware, - include_partitions=False) + include_partitions=False, + all_serial_and_wwn=False) @mock.patch.object(hardware.GenericHardwareManager, 'list_block_devices', autospec=True) @@ -1202,7 +1204,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual(devices, returned_devices) mock_list_devs.assert_called_once_with(self.hardware, - include_partitions=False) + include_partitions=False, + all_serial_and_wwn=False) @mock.patch.object(hardware.GenericHardwareManager, 'list_block_devices', autospec=True) @@ -1229,7 +1232,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual([], returned_devices) mock_list_devs.assert_called_once_with(self.hardware, - include_partitions=False) + include_partitions=False, + all_serial_and_wwn=False) @mock.patch.object(hardware, 'get_multipath_status', lambda *_: True) @mock.patch.object(os, 'readlink', autospec=True) @@ -1292,7 +1296,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): vendor='Super Vendor', hctl='1:0:0:0', by_path='/dev/disk/by-path/1:0:0:0', - serial='sda123'), + serial='sda123', + wwn='wwn0'), hardware.BlockDevice(name='/dev/sdb', model='Fastable SD131 7', size=10737418240, @@ -1300,7 +1305,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): vendor='Super Vendor', hctl='1:0:0:0', by_path='/dev/disk/by-path/1:0:0:1', - serial='sdb123'), + serial='sdb123', + wwn='wwn1'), hardware.BlockDevice(name='/dev/sdc', model='NWD-BLP4-1600', size=1765517033472, @@ -1308,13 +1314,15 @@ class TestGenericHardwareManager(base.IronicAgentTest): vendor='Super Vendor', hctl='1:0:0:0', by_path='/dev/disk/by-path/1:0:0:2', - serial='sdc123'), + serial='sdc123', + wwn='wwn2'), hardware.BlockDevice(name='/dev/dm-0', model='NWD-BLP4-1600', size=1765517033472, rotational=False, vendor='Super Vendor', - hctl='1:0:0:0'), + hctl='1:0:0:0', + wwn=None), ] self.assertEqual(4, len(devices)) @@ -1333,7 +1341,139 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock_readlink.assert_has_calls(expected_calls) expected_calls = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', + check_exit_code=[0]), + mock.call('multipath', '-c', '/dev/sda'), + mock.call('multipath', '-c', '/dev/sdb'), + mock.call('multipath', '-c', '/dev/sdc'), + mock.call('multipath', '-c', '/dev/sdd'), + mock.call('multipath', '-ll', '/dev/sdd'), + mock.call('multipath', '-c', '/dev/loop0'), + mock.call('multipath', '-c', '/dev/zram0'), + mock.call('multipath', '-c', '/dev/ram0'), + mock.call('multipath', '-c', '/dev/ram1'), + mock.call('multipath', '-c', '/dev/ram2'), + mock.call('multipath', '-c', '/dev/ram3'), + mock.call('multipath', '-c', '/dev/sdf'), + mock.call('multipath', '-c', '/dev/dm-0') + ] + mocked_execute.assert_has_calls(expected_calls) + + @mock.patch.object(hardware, 'get_multipath_status', lambda *_: True) + @mock.patch.object(os, 'readlink', autospec=True) + @mock.patch.object(os, 'listdir', autospec=True) + @mock.patch.object(hardware, '_get_device_info', autospec=True) + @mock.patch.object(pyudev.Devices, 'from_device_file', autospec=False) + @mock.patch.object(il_utils, 'execute', autospec=True) + def test_list_all_block_device_all_serial(self, mocked_execute, + mocked_udev, mocked_dev_vendor, + mock_listdir, mock_readlink): + by_path_map = { + '/dev/disk/by-path/1:0:0:0': '../../dev/sda', + '/dev/disk/by-path/1:0:0:1': '../../dev/sdb', + '/dev/disk/by-path/1:0:0:2': '../../dev/sdc', + # pretend that the by-path link to ../../dev/sdd is missing + } + mock_readlink.side_effect = lambda x, m=by_path_map: m[x] + mock_listdir.return_value = [os.path.basename(x) + for x in sorted(by_path_map)] + mocked_execute.side_effect = [ + (hws.BLK_DEVICE_TEMPLATE, ''), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb'), + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdc'), + # Pretend sdd is a multipath device... because why not. + (hws.MULTIPATH_VALID_PATH % '/dev/sdd', ''), + (hws.MULTIPATH_LINKS_DM % 'dm-0', ''), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # loop0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # zram0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram0 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram1 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram2 + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # ram3 + processutils.ProcessExecutionError( + stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdf'), + processutils.ProcessExecutionError( + stderr='the -c option requires a path to check'), # dm-0 + ] + mocked_udev.side_effect = [ + {'ID_WWN': 'badwwn%d' % i, 'ID_SERIAL_SHORT': 'badserial%d' % i, + 'ID_SERIAL': 'longserial%d' % i, + 'ID_WWN_WITH_EXTENSION': 'wwn-ext%d' % i, + 'ID_WWN_VENDOR_EXTENSION': 'wwn-vendor-ext%d' % i} + for i in range(3) + ] + [ + {'DM_WWN': 'wwn3', 'DM_SERIAL': 'serial3'} + ] + mocked_dev_vendor.return_value = 'Super Vendor' + devices = hardware.list_all_block_devices(all_serial_and_wwn=True) + expected_devices = [ + hardware.BlockDevice(name='/dev/sda', + model='TinyUSB Drive', + size=3116853504, + rotational=False, + vendor='Super Vendor', + hctl='1:0:0:0', + by_path='/dev/disk/by-path/1:0:0:0', + serial=['sda123', 'badserial0', + 'longserial0'], + wwn=['wwn0', 'badwwn0']), + hardware.BlockDevice(name='/dev/sdb', + model='Fastable SD131 7', + size=10737418240, + rotational=False, + vendor='Super Vendor', + hctl='1:0:0:0', + by_path='/dev/disk/by-path/1:0:0:1', + serial=['sdb123', 'badserial1', + 'longserial1'], + wwn=['wwn1', 'badwwn1']), + hardware.BlockDevice(name='/dev/sdc', + model='NWD-BLP4-1600', + size=1765517033472, + rotational=False, + vendor='Super Vendor', + hctl='1:0:0:0', + by_path='/dev/disk/by-path/1:0:0:2', + serial=['sdc123', 'badserial2', + 'longserial2'], + wwn=['wwn2', 'badwwn2']), + hardware.BlockDevice(name='/dev/dm-0', + model='NWD-BLP4-1600', + size=1765517033472, + rotational=False, + vendor='Super Vendor', + hctl='1:0:0:0', + wwn=[None, 'wwn3'], + serial=[None, None, 'serial3']), + ] + + self.assertEqual(4, len(devices)) + for expected, device in zip(expected_devices, devices): + # Compare all attrs of the objects + for attr in ['name', 'model', 'size', 'rotational', + 'wwn', 'vendor', 'serial', 'hctl']: + self.assertEqual(getattr(expected, attr), + getattr(device, attr)) + expected_calls = [mock.call('/sys/block/%s/device/scsi_device' % dev) + for dev in ('sda', 'sdb', 'sdc', 'dm-0')] + mock_listdir.assert_has_calls(expected_calls) + + expected_calls = [mock.call('/dev/disk/by-path/1:0:0:%d' % dev) + for dev in range(3)] + mock_readlink.assert_has_calls(expected_calls) + expected_calls = [ + mock.call('lsblk', '-bia', '--json', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('multipath', '-c', '/dev/sda'), mock.call('multipath', '-c', '/dev/sdb'), @@ -2540,7 +2680,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('/dev/md0', self.node['uuid'])], mock_metadata.call_args_list) mock_list_devs.assert_called_with(self.hardware, - include_partitions=True) + include_partitions=True, + all_serial_and_wwn=False) self.assertEqual([mock.call(self.hardware, block_devices[0]), mock.call(self.hardware, block_devices[1]), mock.call(self.hardware, block_devices[4]), @@ -2599,7 +2740,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): self.assertEqual([mock.call('/dev/sda1', self.node['uuid'])], mock_metadata.call_args_list) mock_list_devs.assert_called_with(self.hardware, - include_partitions=True) + include_partitions=True, + all_serial_and_wwn=False) mock_safety_check.assert_has_calls([ mock.call(self.node, '/dev/sda1'), mock.call(self.node, '/dev/sda'), @@ -2648,7 +2790,8 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('/dev/sda', self.node['uuid'])], mock_metadata.call_args_list) mock_list_devs.assert_called_with(self.hardware, - include_partitions=True) + include_partitions=True, + all_serial_and_wwn=False) self.assertEqual([mock.call(self.hardware, block_devices[1]), mock.call(self.hardware, block_devices[0])], mock__is_vmedia.call_args_list) @@ -5229,7 +5372,7 @@ class TestModuleFunctions(base.IronicAgentTest): result = hardware.list_all_block_devices() expected_calls = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('multipath', '-c', '/dev/sda'), mock.call('multipath', '-c', '/dev/sdb') @@ -5276,7 +5419,7 @@ class TestModuleFunctions(base.IronicAgentTest): ] expected_calls = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('multipath', '-c', '/dev/sda'), mock.call('multipath', '-c', '/dev/sda1'), @@ -5315,7 +5458,7 @@ class TestModuleFunctions(base.IronicAgentTest): result = hardware.list_all_block_devices(block_type='part') expected_calls = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), mock.call('multipath', '-c', '/dev/sda'), mock.call('multipath', '-c', '/dev/sda1'), @@ -5338,7 +5481,7 @@ class TestModuleFunctions(base.IronicAgentTest): result = hardware.list_all_block_devices() mocked_execute.assert_called_once_with( 'lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]) self.assertEqual([], result) mocked_udev.assert_called_once_with() @@ -5352,15 +5495,15 @@ class TestModuleFunctions(base.IronicAgentTest): mocked_mpath.return_value = False expected_calls = [ mock.call('lsblk', '-bia', '--json', - '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL,WWN', check_exit_code=[0]), ] mocked_execute.return_value = ( '{"blockdevices": [{"type":"disk", "model":"model"}]}', '') self.assertRaisesRegex( errors.BlockDeviceError, - r'^Block device caused unknown error: kname, partuuid, rota, ' - r'serial, size, uuid must be returned by lsblk.$', + r'Block device caused unknown error: kname, partuuid, rota, ' + r'serial, size, uuid, wwn must be returned by lsblk.', hardware.list_all_block_devices) mocked_udev.assert_called_once_with() mocked_execute.assert_has_calls(expected_calls) diff --git a/ironic_python_agent/utils.py b/ironic_python_agent/utils.py index f3dbe35fc..e2f6db787 100644 --- a/ironic_python_agent/utils.py +++ b/ironic_python_agent/utils.py @@ -59,7 +59,7 @@ AGENT_PARAMS_CACHED = dict() LSBLK_COLUMNS = ['KNAME', 'MODEL', 'SIZE', 'ROTA', - 'TYPE', 'UUID', 'PARTUUID', 'SERIAL'] + 'TYPE', 'UUID', 'PARTUUID', 'SERIAL', 'WWN'] DEVICE_EXTRACTOR = re.compile(r'^(?:(.*\d)p|(.*\D))(?:\d+)$') diff --git a/releasenotes/notes/mix-and-match-disk-detection-58db04403ce220a0.yaml b/releasenotes/notes/mix-and-match-disk-detection-58db04403ce220a0.yaml new file mode 100644 index 000000000..5abbf22db --- /dev/null +++ b/releasenotes/notes/mix-and-match-disk-detection-58db04403ce220a0.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + For a long time block device information originating form lsblk and udev + was handled in a mutually exclusive way during root disk selection. + The new ``mix and match`` approach allows IPA to collect and match + ``disk serial`` and ``wwn`` root device hints against values coming + from both ``lsblk`` and ``udev`` at the same time. The ``mix and match`` + approach is necesarry to handle edge cases where the serial and/or wwn + information is different in ``lsblk`` compared to ``udev``.