summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/admin/hardware_managers.rst9
-rw-r--r--ironic_python_agent/hardware.py267
-rw-r--r--ironic_python_agent/raid_utils.py36
-rw-r--r--ironic_python_agent/tests/unit/extensions/test_image.py6
-rw-r--r--ironic_python_agent/tests/unit/samples/hardware_samples.py155
-rw-r--r--ironic_python_agent/tests/unit/test_hardware.py556
-rw-r--r--ironic_python_agent/tests/unit/test_raid_utils.py14
-rw-r--r--ironic_python_agent/utils.py19
-rw-r--r--releasenotes/notes/enable-skipping-raids-40263cc3a19cfd27.yaml6
-rw-r--r--releasenotes/notes/prioritize-lsblk-device-serials-8cae406ca5164a01.yaml8
-rw-r--r--releasenotes/source/index.rst1
-rw-r--r--releasenotes/source/yoga.rst6
-rw-r--r--releasenotes/source/zed.rst6
-rw-r--r--zuul.d/project.yaml3
14 files changed, 952 insertions, 140 deletions
diff --git a/doc/source/admin/hardware_managers.rst b/doc/source/admin/hardware_managers.rst
index 6c72ba1a..90e6c298 100644
--- a/doc/source/admin/hardware_managers.rst
+++ b/doc/source/admin/hardware_managers.rst
@@ -121,6 +121,15 @@ containing hints to identify the drives. For example::
'skip_block_devices': [{'name': '/dev/vda', 'vendor': '0x1af4'}]
+To prevent software RAID devices from being deleted, put their volume name
+(defined in the ``target_raid_config``) to the list.
+
+Note: one dictionary with one value for each of the logical disks.
+For example::
+
+ 'skip_block_devices': [{'volume_name': 'large'}, {'volume_name': 'temp'}]
+
+
Shared Disk Cluster Filesystems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py
index eda78f4a..0fbdab3b 100644
--- a/ironic_python_agent/hardware.py
+++ b/ironic_python_agent/hardware.py
@@ -648,6 +648,9 @@ def list_all_block_devices(block_type='disk',
name = os.path.join('/dev', device_raw['kname'])
extra = {}
+ lsblk_serial = device_raw.get('serial')
+ if lsblk_serial:
+ extra['serial'] = lsblk_serial
try:
udev = pyudev.Devices.from_device_file(context, name)
except pyudev.DeviceNotFoundByFileError as e:
@@ -658,17 +661,21 @@ def list_all_block_devices(block_type='disk',
"skipping... Error: %(error)s",
{'dev': name, 'error': e})
else:
- # TODO(lucasagomes): Since lsblk only supports
- # returning the short serial we are using
- # ID_SERIAL_SHORT first to keep compatibility with the
- # bash deploy ramdisk
- for key, udev_key in [
- ('serial', 'SERIAL_SHORT'),
- ('serial', 'SERIAL'),
+ # lsblk serial information is prioritized over
+ # udev serial information
+ udev_property_mappings = [
('wwn', 'WWN'),
('wwn_with_extension', 'WWN_WITH_EXTENSION'),
('wwn_vendor_extension', 'WWN_VENDOR_EXTENSION')
- ]:
+ ]
+ # Only check device serial information from udev
+ # when lsblk returned None
+ if 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}')
@@ -863,6 +870,17 @@ class HardwareManager(object, metaclass=abc.ABCMeta):
"""
raise errors.IncompatibleHardwareMethodError
+ def get_skip_list_from_node(self, node,
+ block_devices=None, just_raids=False):
+ """Get the skip block devices list from the node
+
+ :param block_devices: a list of BlockDevices
+ :param just_raids: a boolean to signify that only RAID devices
+ are important
+ :return: A set of names of devices on the skip list
+ """
+ raise errors.IncompatibleHardwareMethodError
+
def list_block_devices_check_skip_list(self, node,
include_partitions=False):
"""List physical block devices without the ones listed in
@@ -1391,17 +1409,22 @@ class GenericHardwareManager(HardwareManager):
)
return block_devices
- def list_block_devices_check_skip_list(self, node,
- include_partitions=False):
- block_devices = self.list_block_devices(
- include_partitions=include_partitions)
+ def get_skip_list_from_node(self, node,
+ block_devices=None, just_raids=False):
properties = node.get('properties', {})
skip_list_hints = properties.get("skip_block_devices", [])
if not skip_list_hints:
- return block_devices
+ return None
+ if just_raids:
+ return {d['volume_name'] for d in skip_list_hints
+ if 'volume_name' in d}
+ if not block_devices:
+ return None
skip_list = set()
serialized_devs = [dev.serialize() for dev in block_devices]
for hint in skip_list_hints:
+ if 'volume_name' in hint:
+ continue
found_devs = il_utils.find_devices_by_hints(serialized_devs, hint)
excluded_devs = {dev['name'] for dev in found_devs}
skipped_devices = excluded_devs.difference(skip_list)
@@ -1409,8 +1432,17 @@ class GenericHardwareManager(HardwareManager):
if skipped_devices:
LOG.warning("Using hint %(hint)s skipping devices: %(devs)s",
{'hint': hint, 'devs': ','.join(skipped_devices)})
- block_devices = [d for d in block_devices
- if d.name not in skip_list]
+ return skip_list
+
+ def list_block_devices_check_skip_list(self, node,
+ include_partitions=False):
+ block_devices = self.list_block_devices(
+ include_partitions=include_partitions)
+ skip_list = self.get_skip_list_from_node(
+ node, block_devices)
+ if skip_list is not None:
+ block_devices = [d for d in block_devices
+ if d.name not in skip_list]
return block_devices
def get_os_install_device(self, permit_refresh=False):
@@ -2341,15 +2373,41 @@ class GenericHardwareManager(HardwareManager):
return self._do_create_configuration(node, ports, raid_config)
def _do_create_configuration(self, node, ports, raid_config):
+ def _get_volume_names_of_existing_raids():
+ list_of_raids = []
+ raid_devices = list_all_block_devices(block_type='raid',
+ ignore_raid=False,
+ ignore_empty=False)
+ raid_devices.extend(
+ list_all_block_devices(block_type='md',
+ ignore_raid=False,
+ ignore_empty=False)
+ )
+ for raid_device in raid_devices:
+ device = raid_device.name
+ try:
+ il_utils.execute('mdadm', '--examine',
+ device, use_standard_locale=True)
+ except processutils.ProcessExecutionError as e:
+ if "No md superblock detected" in str(e):
+ continue
+ volume_name = raid_utils.get_volume_name_of_raid_device(device)
+ if volume_name:
+ list_of_raids.append(volume_name)
+ else:
+ list_of_raids.append("unnamed_raid")
+ return list_of_raids
+
# No 'software' controller: do nothing. If 'controller' is
# set to 'software' on only one of the drives, the validation
# code will catch it.
software_raid = False
logical_disks = raid_config.get('logical_disks')
+ software_raid_disks = []
for logical_disk in logical_disks:
if logical_disk.get('controller') == 'software':
software_raid = True
- break
+ software_raid_disks.append(logical_disk)
if not software_raid:
LOG.debug("No Software RAID config found")
return {}
@@ -2359,24 +2417,51 @@ class GenericHardwareManager(HardwareManager):
# Check if the config is compliant with current limitations.
self.validate_configuration(raid_config, node)
+ # Remove any logical disk from being eligible for inclusion in the
+ # RAID if it's on the skip list
+ skip_list = self.get_skip_list_from_node(
+ node, just_raids=True)
+ rm_from_list = []
+ if skip_list:
+ present_raids = _get_volume_names_of_existing_raids()
+ if present_raids:
+ for ld in logical_disks:
+ volume_name = ld.get('volume_name', None)
+ if volume_name in skip_list \
+ and volume_name in present_raids:
+ rm_from_list.append(ld)
+ LOG.debug("Software RAID device with volume name %s "
+ "exists and is, therefore, not going to be "
+ "created", volume_name)
+ present_raids.remove(volume_name)
+ # NOTE(kubajj): Raise an error if there is an existing software
+ # RAID device that either does not have a volume name or does not
+ # match one on the skip list
+ if present_raids:
+ msg = ("Existing Software RAID device detected that should"
+ " not")
+ raise errors.SoftwareRAIDError(msg)
+ logical_disks = [d for d in logical_disks if d not in rm_from_list]
+
# Log the validated target_raid_configuration.
LOG.debug("Target Software RAID configuration: %s", raid_config)
block_devices, logical_disks = raid_utils.get_block_devices_for_raid(
self.list_block_devices(), logical_disks)
- # Make sure there are no partitions yet (or left behind).
- with_parts = []
- for dev_name in block_devices:
- try:
- if disk_utils.list_partitions(dev_name):
- with_parts.append(dev_name)
- except processutils.ProcessExecutionError:
- # Presumably no partitions (or no partition table)
- continue
- if with_parts:
- msg = ("Partitions detected on devices %s during RAID config" %
- ', '.join(with_parts))
- raise errors.SoftwareRAIDError(msg)
+ if not rm_from_list:
+ # Make sure there are no partitions yet (or left behind).
+ with_parts = []
+ for dev_name in block_devices:
+ try:
+ if disk_utils.list_partitions(dev_name):
+ with_parts.append(dev_name)
+ except processutils.ProcessExecutionError:
+ # Presumably no partitions (or no partition table)
+ continue
+ if with_parts:
+ msg = ("Partitions detected on devices %s during RAID config" %
+ ', '.join(with_parts))
+ raise errors.SoftwareRAIDError(msg)
partition_table_type = utils.get_partition_table_type_from_specs(node)
target_boot_mode = utils.get_node_boot_mode(node)
@@ -2484,10 +2569,12 @@ class GenericHardwareManager(HardwareManager):
return raid_devices
raid_devices = _scan_raids()
+ skip_list = self.get_skip_list_from_node(
+ node, just_raids=True)
attempts = 0
while attempts < 2:
attempts += 1
- self._delete_config_pass(raid_devices)
+ self._delete_config_pass(raid_devices, skip_list)
raid_devices = _scan_raids()
if not raid_devices:
break
@@ -2497,9 +2584,22 @@ class GenericHardwareManager(HardwareManager):
LOG.error(msg)
raise errors.SoftwareRAIDError(msg)
- def _delete_config_pass(self, raid_devices):
+ def _delete_config_pass(self, raid_devices, skip_list):
all_holder_disks = []
+ do_not_delete_devices = set()
+ delete_partitions = {}
for raid_device in raid_devices:
+ do_not_delete = False
+ volume_name = raid_utils.get_volume_name_of_raid_device(
+ raid_device.name)
+ if volume_name:
+ LOG.info("Software RAID device %(dev)s has volume name"
+ "%(name)s", {'dev': raid_device.name,
+ 'name': volume_name})
+ if skip_list and volume_name in skip_list:
+ LOG.warning("RAID device %s will not be deleted",
+ raid_device.name)
+ do_not_delete = True
component_devices = get_component_devices(raid_device.name)
if not component_devices:
# A "Software RAID device" without components is usually
@@ -2511,52 +2611,73 @@ class GenericHardwareManager(HardwareManager):
continue
holder_disks = get_holder_disks(raid_device.name)
- LOG.info("Deleting Software RAID device %s", raid_device.name)
+ if do_not_delete:
+ LOG.warning("Software RAID device %(dev)s is not going to be "
+ "deleted as its volume name - %(vn)s - is on the "
+ "skip list", {'dev': raid_device.name,
+ 'vn': volume_name})
+ else:
+ LOG.info("Deleting Software RAID device %s", raid_device.name)
LOG.debug('Found component devices %s', component_devices)
LOG.debug('Found holder disks %s', holder_disks)
- # Remove md devices.
- try:
- il_utils.execute('wipefs', '-af', raid_device.name)
- except processutils.ProcessExecutionError as e:
- LOG.warning('Failed to wipefs %(device)s: %(err)s',
- {'device': raid_device.name, 'err': e})
- try:
- il_utils.execute('mdadm', '--stop', raid_device.name)
- except processutils.ProcessExecutionError as e:
- LOG.warning('Failed to stop %(device)s: %(err)s',
- {'device': raid_device.name, 'err': e})
-
- # Remove md metadata from component devices.
- for component_device in component_devices:
+ if not do_not_delete:
+ # Remove md devices.
try:
- il_utils.execute('mdadm', '--examine', component_device,
- use_standard_locale=True)
+ il_utils.execute('wipefs', '-af', raid_device.name)
except processutils.ProcessExecutionError as e:
- if "No md superblock detected" in str(e):
- # actually not a component device
- continue
- else:
- msg = "Failed to examine device {}: {}".format(
- component_device, e)
- raise errors.SoftwareRAIDError(msg)
-
- LOG.debug('Deleting md superblock on %s', component_device)
+ LOG.warning('Failed to wipefs %(device)s: %(err)s',
+ {'device': raid_device.name, 'err': e})
try:
- il_utils.execute('mdadm', '--zero-superblock',
- component_device)
+ il_utils.execute('mdadm', '--stop', raid_device.name)
except processutils.ProcessExecutionError as e:
- LOG.warning('Failed to remove superblock from'
- '%(device)s: %(err)s',
+ LOG.warning('Failed to stop %(device)s: %(err)s',
{'device': raid_device.name, 'err': e})
+ # Remove md metadata from component devices.
+ for component_device in component_devices:
+ try:
+ il_utils.execute('mdadm', '--examine',
+ component_device,
+ use_standard_locale=True)
+ except processutils.ProcessExecutionError as e:
+ if "No md superblock detected" in str(e):
+ # actually not a component device
+ continue
+ else:
+ msg = "Failed to examine device {}: {}".format(
+ component_device, e)
+ raise errors.SoftwareRAIDError(msg)
+
+ LOG.debug('Deleting md superblock on %s', component_device)
+ try:
+ il_utils.execute('mdadm', '--zero-superblock',
+ component_device)
+ except processutils.ProcessExecutionError as e:
+ LOG.warning('Failed to remove superblock from'
+ '%(device)s: %(err)s',
+ {'device': raid_device.name, 'err': e})
+ if skip_list:
+ dev, part = utils.split_device_and_partition_number(
+ component_device)
+ if dev in delete_partitions:
+ delete_partitions[dev].append(part)
+ else:
+ delete_partitions[dev] = [part]
+ else:
+ for component_device in component_devices:
+ do_not_delete_devices.add(component_device)
+
# NOTE(arne_wiebalck): We cannot delete the partitions right
# away since there may be other partitions on the same disks
# which are members of other RAID devices. So we remember them
# for later.
all_holder_disks.extend(holder_disks)
-
- LOG.info('Deleted Software RAID device %s', raid_device.name)
+ if do_not_delete:
+ LOG.warning("Software RAID device %s was not deleted",
+ raid_device.name)
+ else:
+ LOG.info('Deleted Software RAID device %s', raid_device.name)
# Remove all remaining raid traces from any drives, in case some
# drives or partitions have been member of some raid once
@@ -2581,7 +2702,13 @@ class GenericHardwareManager(HardwareManager):
# mdadm: Couldn't open /dev/block for write - not zeroing
# mdadm -E /dev/block1: still shows superblocks
all_blks = reversed(self.list_block_devices(include_partitions=True))
+ do_not_delete_disks = set()
for blk in all_blks:
+ if blk.name in do_not_delete_devices:
+ do_not_delete_disks.add(utils.extract_device(blk.name))
+ continue
+ if blk.name in do_not_delete_disks:
+ continue
try:
il_utils.execute('mdadm', '--examine', blk.name,
use_standard_locale=True)
@@ -2604,6 +2731,20 @@ class GenericHardwareManager(HardwareManager):
all_holder_disks_uniq = list(
collections.OrderedDict.fromkeys(all_holder_disks))
for holder_disk in all_holder_disks_uniq:
+ if holder_disk in do_not_delete_disks:
+ # Remove just partitions not listed in keep_partitions
+ del_list = delete_partitions[holder_disk]
+ if del_list:
+ LOG.warning('Holder disk %(dev)s contains logical disk '
+ 'on the skip list. Deleting just partitions: '
+ '%(parts)s', {'dev': holder_disk,
+ 'parts': del_list})
+ for part in del_list:
+ il_utils.execute('parted', holder_disk, 'rm', part)
+ else:
+ LOG.warning('Holder disk %(dev)s contains only logical '
+ 'disk(s) on the skip list', holder_disk)
+ continue
LOG.info('Removing partitions on holder disk %s', holder_disk)
try:
il_utils.execute('wipefs', '-af', holder_disk)
diff --git a/ironic_python_agent/raid_utils.py b/ironic_python_agent/raid_utils.py
index 3d5f260f..84c6941f 100644
--- a/ironic_python_agent/raid_utils.py
+++ b/ironic_python_agent/raid_utils.py
@@ -267,10 +267,44 @@ def get_next_free_raid_device():
name = f'/dev/md{idx}'
if name not in names:
return name
-
raise errors.SoftwareRAIDError("No free md (RAID) devices are left")
+def get_volume_name_of_raid_device(raid_device):
+ """Get the volume name of a RAID device
+
+ :param raid_device: A Software RAID block device name.
+ :returns: volume name of the device, or None
+ """
+ if not raid_device:
+ return None
+ try:
+ out, _ = utils.execute('mdadm', '--detail', raid_device,
+ use_standard_locale=True)
+ except processutils.ProcessExecutionError as e:
+ LOG.warning('Could not retrieve the volume name of %(dev)s: %(err)s',
+ {'dev': raid_device, 'err': e})
+ return None
+ lines = out.splitlines()
+ for line in lines:
+ if re.search(r'Name', line) is not None:
+ split_array = line.split(':')
+ # expecting format:
+ # Name : <host>:name (optional comment)
+ if len(split_array) == 3:
+ candidate = split_array[2]
+ else:
+ return None
+ # if name is followed by some other text
+ # such as (local to host <domain>) remove
+ # everything after " "
+ if " " in candidate:
+ candidate = candidate.split(" ")[0]
+ volume_name = candidate
+ return volume_name
+ return None
+
+
# TODO(rg): handle PreP boot parts relocation as well
def prepare_boot_partitions_for_softraid(device, holders, efi_part,
target_boot_mode):
diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py
index 6958036f..46afe0ef 100644
--- a/ironic_python_agent/tests/unit/extensions/test_image.py
+++ b/ironic_python_agent/tests/unit/extensions/test_image.py
@@ -827,7 +827,8 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640
use_standard_locale=True),
mock.call('udevadm', 'settle'),
mock.call('lsblk', '-bia', '--json',
- '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
+ '-oKNAME,MODEL,SIZE,ROTA,'
+ + 'TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
mock.call('umount', self.fake_dir + '/boot/efi',
attempts=3, delay_on_retry=True),
@@ -949,7 +950,8 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640
use_standard_locale=True),
mock.call('udevadm', 'settle'),
mock.call('lsblk', '-bia', '--json',
- '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
+ '-oKNAME,MODEL,SIZE,ROTA,'
+ + 'TYPE,UUID,PARTUUID,SERIAL',
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 f9635e43..c00d637b 100644
--- a/ironic_python_agent/tests/unit/samples/hardware_samples.py
+++ b/ironic_python_agent/tests/unit/samples/hardware_samples.py
@@ -101,33 +101,36 @@ BLK_DEVICE_TEMPLATE = """
{
"blockdevices": [
{"kname":"sda", "model":"TinyUSB Drive", "size":3116853504,
- "rota":false, "type":"disk", "serial":123, "uuid":"F531-BDC3",
+ "rota":false, "type":"disk", "serial":"sda123", "uuid":"F531-BDC3",
"partuuid":null},
{"kname":"sdb", "model":"Fastable SD131 7", "size":10737418240,
- "rota":false, "type":"disk",
+ "rota":false, "type":"disk", "serial":"sdb123",
"uuid":"9a5e5cca-e03d-4cbd-9054-9e6ca9048222", "partuuid":null},
{"kname":"sdc", "model":"NWD-BLP4-1600", "size":1765517033472,
- "rota":false, "type":"disk", "uuid":null, "partuuid":null},
+ "rota":false, "type":"disk", "serial":"sdc123", "uuid":null,
+ "partuuid":null},
{"kname":"sdd", "model":"NWD-BLP4-1600", "size":1765517033472,
- "rota":false, "type":"disk", "uuid":null, "partuuid":null},
+ "rota":false, "type":"disk", "serial":"sdd123", "uuid":null,
+ "partuuid":null},
{"kname":"loop0", "model":null, "size":109109248, "rota":true,
- "type":"loop", "uuid":null, "partuuid": null},
+ "type":"loop", "serial":null, "uuid":null, "partuuid": null},
{"kname":"zram0", "model":null, "size":0, "rota":false, "type":"disk",
- "uuid":null, "partuuid":null},
+ "serial":null, "uuid":null, "partuuid":null},
{"kname":"ram0", "model":null, "size":8388608, "rota":false,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":null, "uuid":null, "partuuid":null},
{"kname":"ram1", "model":null, "size":8388608, "rota":false,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":null, "uuid":null, "partuuid":null},
{"kname":"ram2", "model":null, "size":8388608, "rota":false,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":null, "uuid":null, "partuuid":null},
{"kname":"ram3", "model":null, "size":8388608, "rota":false,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":null, "uuid":null, "partuuid":null},
{"kname":"fd1", "model":"magic", "size":4096, "rota":true,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":null, "uuid":null, "partuuid":null},
{"kname":"sdf", "model":"virtual floppy", "size":0, "rota":true,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":null, "uuid":null, "partuuid":null},
{"kname":"dm-0", "model":"NWD-BLP4-1600", "size":"1765517033472",
- "rota":false, "type":"mpath", "uuid":null, "partuuid":null}
+ "rota":false, "type":"mpath", "serial":null, "uuid":null,
+ "partuuid":null}
]
}
"""
@@ -137,9 +140,22 @@ BLK_DEVICE_TEMPLATE_SMALL = """
{
"blockdevices": [
{"kname":"sda", "model":"TinyUSB Drive", "size":3116853504, "rota":false,
- "type":"disk", "uuid":"F531-BDC", "partuuid":null},
+ "type":"disk", "serial":"123", "uuid":"F531-BDC", "partuuid":null},
+ {"kname":"sdb", "model":"AlmostBigEnough Drive", "size":"4294967295",
+ "rota":false, "type":"disk", "serial":"456", "uuid":null, "partuuid":null}
+ ]
+}
+"""
+
+
+# NOTE This is intentionally have serials removed
+BLK_INCOMPLETE_DEVICE_TEMPLATE_SMALL = """
+{
+ "blockdevices": [
+ {"kname":"sda", "model":"TinyUSB Drive", "size":3116853504, "rota":false,
+ "type":"disk", "serial":"", "uuid":"F531-BDC", "partuuid":null},
{"kname":"sdb", "model":"AlmostBigEnough Drive", "size":"4294967295",
- "rota":false, "type":"disk", "uuid":null, "partuuid":null}
+ "rota":false, "type":"disk", "serial":"", "uuid":null, "partuuid":null}
]
}
"""
@@ -155,23 +171,23 @@ RAID_BLK_DEVICE_TEMPLATE = ("""
{
"blockdevices": [
{"kname":"sda", "model":"DRIVE 0", "size":1765517033472, "rota":true,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":"sda123", "uuid":null, "partuuid":null},
{"kname":"sda1", "model":"DRIVE 0", "size":107373133824, "rota":true,
- "type":"part", "uuid":null, "partuuid":null},
+ "type":"part", "serial":"sda1123", "uuid":null, "partuuid":null},
{"kname":"sdb", "model":"DRIVE 1", "size":1765517033472, "rota":true,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":"sdb123", "uuid":null, "partuuid":null},
{"kname":"sdb", "model":"DRIVE 1", "size":1765517033472, "rota":true,
"type":"disk", "uuid":null, "partuuid":null},
{"kname":"sdb1", "model":"DRIVE 1", "size":107373133824, "rota":true,
- "type":"part", "uuid":null, "partuuid":null},
+ "type":"part", "serial":"sdb1123", "uuid":null, "partuuid":null},
{"kname":"md0p1", "model":"RAID", "size":107236818944, "rota":false,
- "type":"md", "uuid":null, "partuuid":null},
+ "type":"md", "serial":null, "uuid":null, "partuuid":null},
{"kname":"md0", "model":"RAID", "size":1765517033470, "rota":false,
- "type":"raid1", "uuid":null, "partuuid":null},
+ "type":"raid1", "serial":null, "uuid":null, "partuuid":null},
{"kname":"md0", "model":"RAID", "size":1765517033470, "rota":false,
- "type":"raid1", "uuid":null, "partuuid":null},
+ "type":"raid1", "serial":null, "uuid":null, "partuuid":null},
{"kname":"md1", "model":"RAID", "size":0, "rota":false, "type":"raid1",
- "uuid":null, "partuuid":null}
+ "serial":null, "uuid":null, "partuuid":null}
]
}
""")
@@ -180,49 +196,52 @@ MULTIPATH_BLK_DEVICE_TEMPLATE = ("""
{
"blockdevices": [
{"kname":"sda", "model":"INTEL_SSDSC2CT060A3", "size":"60022480896",
- "rota":false, "type":"disk", "uuid":null, "partuuid":null},
+ "rota":false, "type":"disk", "serial":"sda123", "uuid":null,
+ "partuuid":null},
{"kname":"sda2", "model":null, "size":"59162722304", "rota":false,
"type":"part", "uuid":"f8b55d59-96c3-3982-b129-1b6b2ee8da86",
- "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3"},
+ "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3", "serial":"sda2123"},
{"kname":"sda3", "model":null, "size":"650002432", "rota":false,
"type":"part", "uuid":"b3b03565-5f13-3c93-b2a6-6d90e25be926",
- "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d"},
+ "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d", "serial":"sda3123"},
{"kname":"sda1", "model":null, "size":"209715200", "rota":false,
"type":"part", "uuid":"0a83355d-7500-3f5f-9abd-66f6fd03714c",
- "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485"},
+ "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485", "serial":"sda1123"},
{"kname":"dm-0", "model":null, "size":"60022480896", "rota":false,
- "type":"mpath", "uuid":null, "partuuid":null},
+ "type":"mpath", "serial":null, "uuid":null, "partuuid":null},
{"kname":"dm-4", "model":null, "size":"650002432", "rota":false,
"type":"part", "uuid":"b3b03565-5f13-3c93-b2a6-6d90e25be926",
- "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d"},
+ "partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d", "serial":null},
{"kname":"dm-2", "model":null, "size":"209715200", "rota":false,
"type":"part", "uuid":"0a83355d-7500-3f5f-9abd-66f6fd03714c",
- "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485"},
+ "partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485", "serial":null},
{"kname":"dm-3", "model":null, "size":"59162722304", "rota":false,
"type":"part", "uuid":"f8b55d59-96c3-3982-b129-1b6b2ee8da86",
- "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3"},
+ "partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3", "serial":null},
{"kname":"sdb", "model":"INTEL_SSDSC2CT060A3", "size":"60022480896",
- "rota":false, "type":"disk", "uuid":null, "partuuid":null},
+ "rota":false, "type":"disk", "serial":"sdb123", "uuid":null,
+ "partuuid":null},
{"kname":"sdb2", "model":null, "size":"59162722304",
- "rota":false, "type":"part",
+ "rota":false, "type":"part", "serial":"sdb2123",
"uuid":"f8b55d59-96c3-3982-b129-1b6b2ee8da86",
"partuuid":"c97c8aac-7796-4433-b1fc-9b5fac43edf3"},
{"kname":"sdb3", "model":null, "size":"650002432",
- "rota":false, "type":"part",
+ "rota":false, "type":"part", "serial":"sdv3123",
"uuid":"b3b03565-5f13-3c93-b2a6-6d90e25be926",
"partuuid":"6c85beff-b2bd-4a1c-91b7-8abb5256459d"},
{"kname":"sdb1", "model":null, "size":"209715200",
- "rota":false, "type":"part",
+ "rota":false, "type":"part", "serial":"sdb1123",
"uuid":"0a83355d-7500-3f5f-9abd-66f6fd03714c",
"partuuid":"eba28b26-b76a-402c-94dd-0b66a523a485"},
{"kname":"sdc", "model":"ST1000DM003-1CH162", "size":"1000204886016",
- "rota":true, "type":"disk", "uuid":null, "partuuid":null},
+ "rota":true, "type":"disk", "serial":"sdc123", "uuid":null,
+ "partuuid":null},
{"kname":"sdc1", "model":null, "size":"899999072256",
- "rota":true, "type":"part",
+ "rota":true, "type":"part", "serial":"sdc1123",
"uuid":"457f7d3c-9376-4997-89bd-d1a7c8b04060",
"partuuid":"c9433d2e-3bbc-47b4-92bf-43c1d80f06e0"},
{"kname":"dm-1", "model":null, "size":"1000204886016", "rota":false,
- "type":"mpath", "uuid":null, "partuuid":null}
+ "type":"mpath", "serial":null, "uuid":null, "partuuid":null}
]
}
""")
@@ -231,9 +250,10 @@ PARTUUID_DEVICE_TEMPLATE = ("""
{
"blockdevices": [
{"kname":"sda", "model":"DRIVE 0", "size":1765517033472, "rota":true,
- "type":"disk", "uuid":null, "partuuid":null},
+ "type":"disk", "serial":"sda123", "uuid":null, "partuuid":null},
{"kname":"sda1", "model":"DRIVE 0", "size":107373133824, "rota":true,
- "type":"part", "uuid":"987654-3210", "partuuid":"1234-5678"}
+ "type":"part", "serial":"sda1123", "uuid":"987654-3210",
+ "partuuid":"1234-5678"}
]
}
""")
@@ -1031,6 +1051,61 @@ Working Devices : 2
1 259 3 1 active sync /dev/nvme1n1p1
""")
+MDADM_DETAIL_OUTPUT_VOLUME_NAME = ("""/dev/md0:
+ Version : 1.0
+ Creation Time : Fri Feb 15 12:37:44 2019
+ Raid Level : raid1
+ Array Size : 1048512 (1023.94 MiB 1073.68 MB)
+ Used Dev Size : 1048512 (1023.94 MiB 1073.68 MB)
+ Raid Devices : 2
+ Total Devices : 2
+ Persistence : Superblock is persistent
+
+ Update Time : Fri Feb 15 12:38:02 2019
+ State : clean
+ Active Devices : 2
+ Working Devices : 2
+ Failed Devices : 0
+ Spare Devices : 0
+
+Consistency Policy : resync
+
+ Name : abc.xyz.com:this_name (local to host abc.xyz.com)
+ UUID : 83143055:2781ddf5:2c8f44c7:9b45d92e
+ Events : 17
+
+ Number Major Minor RaidDevice State
+ 0 253 64 0 active sync /dev/vde1
+ 1 253 80 1 active sync /dev/vdf1
+""")
+
+MDADM_DETAIL_OUTPUT_VOLUME_NAME_INVALID = ("""/dev/md0:
+ Version : 1.0
+ Creation Time : Fri Feb 15 12:37:44 2019
+ Raid Level : raid1
+ Array Size : 1048512 (1023.94 MiB 1073.68 MB)
+ Used Dev Size : 1048512 (1023.94 MiB 1073.68 MB)
+ Raid Devices : 2
+ Total Devices : 2
+ Persistence : Superblock is persistent
+
+ Update Time : Fri Feb 15 12:38:02 2019
+ State : clean
+ Active Devices : 2
+ Working Devices : 2
+ Failed Devices : 0
+ Spare Devices : 0
+
+Consistency Policy : resync
+
+ UUID : 83143055:2781ddf5:2c8f44c7:9b45d92e
+ Events : 17
+
+ Number Major Minor RaidDevice State
+ 0 253 64 0 active sync /dev/vde1
+ 1 253 80 1 active sync /dev/vdf1
+""")
+
MDADM_DETAIL_OUTPUT_BROKEN_RAID0 = ("""/dev/md126:
Version : 1.2
Raid Level : raid0
diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py
index 0d6d28f7..9fcf775c 100644
--- a/ironic_python_agent/tests/unit/test_hardware.py
+++ b/ironic_python_agent/tests/unit/test_hardware.py
@@ -45,32 +45,38 @@ CONF.import_opt('disk_wait_delay', 'ironic_python_agent.config')
BLK_DEVICE_TEMPLATE_SMALL_DEVICES = [
hardware.BlockDevice(name='/dev/sda', model='TinyUSB Drive',
size=3116853504, rotational=False,
- vendor="FooTastic", uuid="F531-BDC3"),
+ vendor="FooTastic", uuid="F531-BDC3",
+ serial="123"),
hardware.BlockDevice(name='/dev/sdb', model='AlmostBigEnough Drive',
size=4294967295, rotational=False,
- vendor="FooTastic", uuid=""),
+ vendor="FooTastic", uuid="",
+ serial="456"),
]
RAID_BLK_DEVICE_TEMPLATE_DEVICES = [
hardware.BlockDevice(name='/dev/sda', model='DRIVE 0',
size=1765517033472, rotational=True,
- vendor="FooTastic", uuid=""),
+ vendor="FooTastic", uuid="",
+ serial="sda123"),
hardware.BlockDevice(name='/dev/sdb', model='DRIVE 1',
size=1765517033472, rotational=True,
- vendor="FooTastic", uuid=""),
+ vendor="FooTastic", uuid="",
+ serial="sdb123"),
hardware.BlockDevice(name='/dev/md0', model='RAID',
size=1765517033470, rotational=False,
- vendor="FooTastic", uuid=""),
+ vendor="FooTastic", uuid="",
+ serial=None),
hardware.BlockDevice(name='/dev/md1', model='RAID',
size=0, rotational=False,
- vendor="FooTastic", uuid=""),
+ vendor="FooTastic", uuid="",
+ serial=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"),
+ partuuid="1234-5678", serial="sda1123"),
]
@@ -869,7 +875,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
]
expected = [
mock.call('lsblk', '-bia', '--json',
- '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
mock.call('multipath', '-c', '/dev/sda'),
mock.call('multipath', '-ll', '/dev/sda'),
@@ -949,7 +955,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
]
expected = [
mock.call('lsblk', '-bia', '--json',
- '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
mock.call('multipath', '-c', '/dev/sda'),
mock.call('multipath', '-ll', '/dev/sda'),
@@ -1004,7 +1010,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',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
]
@@ -1030,14 +1036,14 @@ class TestGenericHardwareManager(base.IronicAgentTest):
self.hardware.get_os_install_device)
expected = [
mock.call('lsblk', '-bia', '--json',
- '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
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',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0])
self.assertIn(str(4 * units.Gi), ex.details)
mock_cached_node.assert_called_once_with()
@@ -1542,6 +1548,54 @@ class TestGenericHardwareManager(base.IronicAgentTest):
ignore_raid=True)],
list_mock.call_args_list)
+ def test_get_skip_list_from_node_block_devices_with_skip_list(self):
+ block_devices = [
+ hardware.BlockDevice('/dev/sdj', 'big', 1073741824, True),
+ hardware.BlockDevice('/dev/hdaa', 'small', 65535, False),
+ ]
+ expected_skip_list = {'/dev/sdj'}
+ node = self.node
+
+ node['properties'] = {
+ 'skip_block_devices': [{
+ 'name': '/dev/sdj'
+ }]
+ }
+
+ skip_list = self.hardware.get_skip_list_from_node(node,
+ block_devices)
+
+ self.assertEqual(expected_skip_list, skip_list)
+
+ def test_get_skip_list_from_node_block_devices_just_raids(self):
+ expected_skip_list = {'large'}
+ node = self.node
+
+ node['properties'] = {
+ 'skip_block_devices': [{
+ 'name': '/dev/sdj'
+ }, {
+ 'volume_name': 'large'
+ }]
+ }
+
+ skip_list = self.hardware.get_skip_list_from_node(node,
+ just_raids=True)
+
+ self.assertEqual(expected_skip_list, skip_list)
+
+ def test_get_skip_list_from_node_block_devices_no_skip_list(self):
+ block_devices = [
+ hardware.BlockDevice('/dev/sdj', 'big', 1073741824, True),
+ hardware.BlockDevice('/dev/hdaa', 'small', 65535, False),
+ ]
+ node = self.node
+
+ skip_list = self.hardware.get_skip_list_from_node(node,
+ block_devices)
+
+ self.assertIsNone(skip_list)
+
@mock.patch.object(hardware.GenericHardwareManager,
'list_block_devices', autospec=True)
def test_list_block_devices_check_skip_list_with_skip_list(self,
@@ -1700,21 +1754,24 @@ class TestGenericHardwareManager(base.IronicAgentTest):
rotational=False,
vendor='Super Vendor',
hctl='1:0:0:0',
- by_path='/dev/disk/by-path/1:0:0:0'),
+ by_path='/dev/disk/by-path/1:0:0:0',
+ serial='sda123'),
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'),
+ by_path='/dev/disk/by-path/1:0:0:1',
+ serial='sdb123'),
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'),
+ by_path='/dev/disk/by-path/1:0:0:2',
+ serial='sdc123'),
hardware.BlockDevice(name='/dev/dm-0',
model='NWD-BLP4-1600',
size=1765517033472,
@@ -1739,7 +1796,7 @@ 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',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
mock.call('multipath', '-c', '/dev/sda'),
mock.call('multipath', '-c', '/dev/sdb'),
@@ -1845,7 +1902,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
'ID_WWN_VENDOR_EXTENSION': 'wwn-vendor-ext%d' % i}
for i in range(3)
] + [
- {'DM_WWN': 'wwn3', 'DM_SERIAL': 'serial3'},
+ {'DM_WWN': 'wwn3', 'DM_SERIAL': 'serial3'}
]
mocked_dev_vendor.return_value = 'Super Vendor'
devices = hardware.list_all_block_devices()
@@ -1858,7 +1915,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
wwn='wwn0',
wwn_with_extension='wwn-ext0',
wwn_vendor_extension='wwn-vendor-ext0',
- serial='serial0',
+ serial='sda123',
hctl='1:0:0:0'),
hardware.BlockDevice(name='/dev/sdb',
model='Fastable SD131 7',
@@ -1868,7 +1925,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
wwn='wwn1',
wwn_with_extension='wwn-ext1',
wwn_vendor_extension='wwn-vendor-ext1',
- serial='serial1',
+ serial='sdb123',
hctl='1:0:0:0'),
hardware.BlockDevice(name='/dev/sdc',
model='NWD-BLP4-1600',
@@ -1878,7 +1935,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
wwn='wwn2',
wwn_with_extension='wwn-ext2',
wwn_vendor_extension='wwn-vendor-ext2',
- serial='serial2',
+ serial='sdc123',
hctl='1:0:0:0'),
hardware.BlockDevice(name='/dev/dm-0',
model='NWD-BLP4-1600',
@@ -1905,6 +1962,72 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mocked_listdir.assert_has_calls(expected_calls)
mocked_mpath.assert_called_once_with()
+ @mock.patch.object(hardware, 'get_multipath_status', autospec=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_with_only_udev(self,
+ mocked_execute,
+ mocked_udev,
+ mocked_dev_vendor,
+ mocked_listdir,
+ mocked_readlink,
+ mocked_mpath):
+ mocked_readlink.return_value = '../../sda'
+ mocked_listdir.return_value = ['1:0:0:0']
+ mocked_execute.side_effect = [
+ (hws.BLK_INCOMPLETE_DEVICE_TEMPLATE_SMALL, ''),
+ processutils.ProcessExecutionError(
+ stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sda'),
+ processutils.ProcessExecutionError(
+ stderr=hws.MULTIPATH_INVALID_PATH % '/dev/sdb'),
+ ]
+
+ mocked_mpath.return_value = True
+ mocked_udev.side_effect = [
+ {'ID_WWN': 'wwn%d' % i, 'ID_SERIAL_SHORT': 'serial%d' % i,
+ 'ID_SERIAL': 'do not use me',
+ 'ID_WWN_WITH_EXTENSION': 'wwn-ext%d' % i,
+ 'ID_WWN_VENDOR_EXTENSION': 'wwn-vendor-ext%d' % i}
+ for i in range(2)
+ ]
+ devices = hardware.list_all_block_devices()
+ expected_devices = [
+ hardware.BlockDevice(name='/dev/sda',
+ model='TinyUSB Drive',
+ size=3116853504,
+ rotational=False,
+ wwn='wwn0',
+ wwn_with_extension='wwn-ext0',
+ wwn_vendor_extension='wwn-vendor-ext0',
+ serial='serial0',
+ hctl='1:0:0:0'),
+ hardware.BlockDevice(name='/dev/sdb',
+ model='AlmostBigEnough Drive',
+ size=4294967295,
+ rotational=False,
+ wwn='wwn1',
+ wwn_with_extension='wwn-ext1',
+ wwn_vendor_extension='wwn-vendor-ext1',
+ serial='serial1',
+ hctl='1:0:0:0')
+ ]
+
+ self.assertEqual(2, len(devices))
+ for expected, device in zip(expected_devices, devices):
+ # Compare all attrs of the objects
+ for attr in ['name', 'model', 'size', 'rotational',
+ 'wwn', 'serial', 'wwn_with_extension',
+ 'wwn_vendor_extension', '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')]
+ mocked_listdir.assert_has_calls(expected_calls)
+ mocked_mpath.assert_called_once_with()
+
@mock.patch.object(hardware, 'safety_check_block_device', autospec=True)
@mock.patch.object(hardware, 'ThreadPool', autospec=True)
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
@@ -4369,6 +4492,294 @@ class TestGenericHardwareManager(base.IronicAgentTest):
self.hardware.create_configuration,
self.node, [])
+ @mock.patch.object(raid_utils, 'get_volume_name_of_raid_device',
+ autospec=True)
+ @mock.patch.object(raid_utils, '_get_actual_component_devices',
+ autospec=True)
+ @mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
+ @mock.patch.object(disk_utils, 'list_partitions', autospec=True)
+ @mock.patch.object(il_utils, 'execute', autospec=True)
+ def test_create_configuration_with_skip_list(
+ self, mocked_execute, mock_list_parts, mocked_list_all_devices,
+ mocked_actual_comp, mocked_get_volume_name):
+ node = self.node
+
+ raid_config = {
+ "logical_disks": [
+ {
+ "size_gb": "10",
+ "raid_level": "1",
+ "controller": "software",
+ "volume_name": "small"
+ },
+ {
+ "size_gb": "MAX",
+ "raid_level": "0",
+ "controller": "software",
+ "volume_name": "large"
+ },
+ ]
+ }
+ node['target_raid_config'] = raid_config
+ node['properties'] = {'skip_block_devices': [{'volume_name': 'large'}]}
+ device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
+ device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
+ raid_device1 = hardware.BlockDevice('/dev/md0', 'RAID-1',
+ 107374182400, True)
+ self.hardware.list_block_devices = mock.Mock()
+ self.hardware.list_block_devices.return_value = [device1, device2]
+ hardware.list_all_block_devices.side_effect = [
+ [raid_device1], # block_type raid
+ [] # block type md
+ ]
+ mocked_get_volume_name.return_value = "large"
+
+ mocked_execute.side_effect = [
+ None, # examine md0
+ None, # mklabel sda
+ ('42', None), # sgdisk -F sda
+ None, # mklabel sda
+ ('42', None), # sgdisk -F sdb
+ None, None, None, # parted + partx + udevadm_settle sda
+ None, None, None, # parted + partx + udevadm_settle sdb
+ None, None, None, # parted + partx + udevadm_settle sda
+ None, None, None, # parted + partx + udevadm_settle sdb
+ None, None # mdadms
+ ]
+
+ mocked_actual_comp.side_effect = [
+ ('/dev/sda1', '/dev/sdb1'),
+ ('/dev/sda2', '/dev/sdb2'),
+ ]
+
+ result = self.hardware.create_configuration(node, [])
+ mocked_execute.assert_has_calls([
+ mock.call('mdadm', '--examine', '/dev/md0',
+ use_standard_locale=True),
+ mock.call('parted', '/dev/sda', '-s', '--', 'mklabel', 'msdos'),
+ mock.call('sgdisk', '-F', '/dev/sda'),
+ mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'msdos'),
+ mock.call('sgdisk', '-F', '/dev/sdb'),
+ mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
+ 'mkpart', 'primary', '42s', '10GiB'),
+ mock.call('partx', '-av', '/dev/sda', attempts=3,
+ delay_on_retry=True),
+ mock.call('udevadm', 'settle'),
+ mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
+ 'mkpart', 'primary', '42s', '10GiB'),
+ mock.call('partx', '-av', '/dev/sdb', attempts=3,
+ delay_on_retry=True),
+ mock.call('udevadm', 'settle'),
+ mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
+ '--metadata=1', '--level', '1', '--name', 'small',
+ '--raid-devices', 2, '/dev/sda1', '/dev/sdb1')])
+ self.assertEqual(raid_config, result)
+
+ self.assertEqual(0, mock_list_parts.call_count)
+
+ @mock.patch.object(raid_utils, 'get_volume_name_of_raid_device',
+ autospec=True)
+ @mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
+ @mock.patch.object(il_utils, 'execute', autospec=True)
+ def test_create_configuration_skip_list_existing_device_does_not_match(
+ self, mocked_execute, mocked_list_all_devices,
+ mocked_get_volume_name):
+ node = self.node
+
+ raid_config = {
+ "logical_disks": [
+ {
+ "size_gb": "10",
+ "raid_level": "1",
+ "controller": "software",
+ "volume_name": "small"
+ },
+ {
+ "size_gb": "MAX",
+ "raid_level": "0",
+ "controller": "software",
+ "volume_name": "large"
+ },
+ ]
+ }
+ node['target_raid_config'] = raid_config
+ node['properties'] = {'skip_block_devices': [{'volume_name': 'large'}]}
+ device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
+ device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
+ raid_device1 = hardware.BlockDevice('/dev/md0', 'RAID-1',
+ 107374182400, True)
+ self.hardware.list_block_devices = mock.Mock()
+ self.hardware.list_block_devices.return_value = [device1, device2]
+ hardware.list_all_block_devices.side_effect = [
+ [raid_device1], # block_type raid
+ [] # block type md
+ ]
+ mocked_get_volume_name.return_value = "small"
+
+ error_regex = "Existing Software RAID device detected that should not"
+ mocked_execute.side_effect = [
+ processutils.ProcessExecutionError]
+ self.assertRaisesRegex(errors.SoftwareRAIDError, error_regex,
+ self.hardware.create_configuration,
+ self.node, [])
+
+ mocked_execute.assert_called_once_with(
+ 'mdadm', '--examine', '/dev/md0', use_standard_locale=True)
+
+ @mock.patch.object(raid_utils, '_get_actual_component_devices',
+ autospec=True)
+ @mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
+ @mock.patch.object(disk_utils, 'list_partitions', autospec=True)
+ @mock.patch.object(il_utils, 'execute', autospec=True)
+ def test_create_configuration_with_skip_list_no_existing_device(
+ self, mocked_execute, mock_list_parts,
+ mocked_list_all_devices, mocked_actual_comp):
+ node = self.node
+
+ raid_config = {
+ "logical_disks": [
+ {
+ "size_gb": "10",
+ "raid_level": "1",
+ "controller": "software",
+ "volume_name": "small"
+ },
+ {
+ "size_gb": "MAX",
+ "raid_level": "0",
+ "controller": "software",
+ "volume_name": "large"
+ },
+ ]
+ }
+ node['target_raid_config'] = raid_config
+ node['properties'] = {'skip_block_devices': [{'volume_name': 'large'}]}
+ device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
+ device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
+ self.hardware.list_block_devices = mock.Mock()
+ self.hardware.list_block_devices.return_value = [device1, device2]
+ mock_list_parts.side_effect = [
+ [],
+ processutils.ProcessExecutionError
+ ]
+ hardware.list_all_block_devices.side_effect = [
+ [], # block_type raid
+ [] # block type md
+ ]
+
+ mocked_execute.side_effect = [
+ None, # mklabel sda
+ ('42', None), # sgdisk -F sda
+ None, # mklabel sda
+ ('42', None), # sgdisk -F sdb
+ None, None, None, # parted + partx + udevadm_settle sda
+ None, None, None, # parted + partx + udevadm_settle sdb
+ None, None, None, # parted + partx + udevadm_settle sda
+ None, None, None, # parted + partx + udevadm_settle sdb
+ None, None # mdadms
+ ]
+
+ mocked_actual_comp.side_effect = [
+ ('/dev/sda1', '/dev/sdb1'),
+ ('/dev/sda2', '/dev/sdb2'),
+ ]
+
+ result = self.hardware.create_configuration(node, [])
+ mocked_execute.assert_has_calls([
+ mock.call('parted', '/dev/sda', '-s', '--', 'mklabel', 'msdos'),
+ mock.call('sgdisk', '-F', '/dev/sda'),
+ mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'msdos'),
+ mock.call('sgdisk', '-F', '/dev/sdb'),
+ mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
+ 'mkpart', 'primary', '42s', '10GiB'),
+ mock.call('partx', '-av', '/dev/sda', attempts=3,
+ delay_on_retry=True),
+ mock.call('udevadm', 'settle'),
+ mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
+ 'mkpart', 'primary', '42s', '10GiB'),
+ mock.call('partx', '-av', '/dev/sdb', attempts=3,
+ delay_on_retry=True),
+ mock.call('udevadm', 'settle'),
+ mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
+ 'mkpart', 'primary', '10GiB', '-1'),
+ mock.call('partx', '-av', '/dev/sda', attempts=3,
+ delay_on_retry=True),
+ mock.call('udevadm', 'settle'),
+ mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
+ 'mkpart', 'primary', '10GiB', '-1'),
+ mock.call('partx', '-av', '/dev/sdb', attempts=3,
+ delay_on_retry=True),
+ mock.call('udevadm', 'settle'),
+ mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
+ '--metadata=1', '--level', '1', '--name', 'small',
+ '--raid-devices', 2, '/dev/sda1', '/dev/sdb1'),
+ mock.call('mdadm', '--create', '/dev/md1', '--force', '--run',
+ '--metadata=1', '--level', '0', '--name', 'large',
+ '--raid-devices', 2, '/dev/sda2', '/dev/sdb2')])
+
+ self.assertEqual(raid_config, result)
+
+ self.assertEqual(2, mock_list_parts.call_count)
+ mock_list_parts.assert_has_calls([
+ mock.call(x) for x in ['/dev/sda', '/dev/sdb']
+ ])
+
+ @mock.patch.object(raid_utils, 'get_volume_name_of_raid_device',
+ autospec=True)
+ @mock.patch.object(raid_utils, '_get_actual_component_devices',
+ autospec=True)
+ @mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
+ @mock.patch.object(il_utils, 'execute', autospec=True)
+ def test_create_configuration_with_complete_skip_list(
+ self, mocked_execute, mocked_ls_all_devs,
+ mocked_actual_comp, mocked_get_volume_name):
+ node = self.node
+
+ raid_config = {
+ "logical_disks": [
+ {
+ "size_gb": "10",
+ "raid_level": "1",
+ "controller": "software",
+ "volume_name": "small"
+ },
+ {
+ "size_gb": "MAX",
+ "raid_level": "0",
+ "controller": "software",
+ "volume_name": "large"
+ },
+ ]
+ }
+ node['target_raid_config'] = raid_config
+ node['properties'] = {'skip_block_devices': [{'volume_name': 'small'},
+ {'volume_name': 'large'}]}
+ raid_device0 = hardware.BlockDevice('/dev/md0', 'RAID-1',
+ 2147483648, True)
+ raid_device1 = hardware.BlockDevice('/dev/md1', 'RAID-0',
+ 107374182400, True)
+ device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
+ device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
+ hardware.list_all_block_devices.side_effect = [
+ [raid_device0, raid_device1], # block_type raid
+ [] # block type md
+ ]
+ self.hardware.list_block_devices = mock.Mock()
+ self.hardware.list_block_devices.return_value = [device1, device2]
+ mocked_get_volume_name.side_effect = [
+ "small",
+ "large",
+ ]
+
+ self.hardware.create_configuration(node, [])
+ mocked_execute.assert_has_calls([
+ mock.call('mdadm', '--examine', '/dev/md0',
+ use_standard_locale=True),
+ mock.call('mdadm', '--examine', '/dev/md1',
+ use_standard_locale=True),
+ ])
+ self.assertEqual(2, mocked_execute.call_count)
+
@mock.patch.object(il_utils, 'execute', autospec=True)
def test__get_md_uuid(self, mocked_execute):
mocked_execute.side_effect = [(hws.MDADM_DETAIL_OUTPUT, '')]
@@ -4462,12 +4873,15 @@ class TestGenericHardwareManager(base.IronicAgentTest):
holder_disks = hardware.get_holder_disks('/dev/md0')
self.assertEqual(['/dev/vda', '/dev/vdb'], holder_disks)
+ @mock.patch.object(raid_utils, 'get_volume_name_of_raid_device',
+ autospec=True)
@mock.patch.object(hardware, 'get_holder_disks', autospec=True)
@mock.patch.object(hardware, 'get_component_devices', autospec=True)
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
@mock.patch.object(il_utils, 'execute', autospec=True)
def test_delete_configuration(self, mocked_execute, mocked_list,
- mocked_get_component, mocked_get_holder):
+ mocked_get_component, mocked_get_holder,
+ mocked_get_volume_name):
raid_device1 = hardware.BlockDevice('/dev/md0', 'RAID-1',
107374182400, True)
raid_device2 = hardware.BlockDevice('/dev/md1', 'RAID-0',
@@ -4490,6 +4904,9 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mocked_get_holder.side_effect = [
["/dev/sda", "/dev/sdb"],
["/dev/sda", "/dev/sdb"]]
+ mocked_get_volume_name.side_effect = [
+ "/dev/md0", "/dev/md1"
+ ]
mocked_execute.side_effect = [
None, # mdadm --assemble --scan
None, # wipefs md0
@@ -4551,11 +4968,14 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
])
+ @mock.patch.object(raid_utils, 'get_volume_name_of_raid_device',
+ autospec=True)
@mock.patch.object(hardware, 'get_component_devices', autospec=True)
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
@mock.patch.object(il_utils, 'execute', autospec=True)
def test_delete_configuration_partition(self, mocked_execute, mocked_list,
- mocked_get_component):
+ mocked_get_component,
+ mocked_get_volume_name):
# This test checks that if no components are returned for a given
# raid device, then it must be a nested partition and so it gets
# skipped
@@ -4569,6 +4989,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
[], # list_all_block_devices raid
[], # list_all_block_devices raid (md)
]
+ mocked_get_volume_name.return_value = None
mocked_get_component.return_value = []
self.assertIsNone(self.hardware.delete_configuration(self.node, []))
mocked_execute.assert_has_calls([
@@ -4576,11 +4997,14 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
])
+ @mock.patch.object(raid_utils, 'get_volume_name_of_raid_device',
+ autospec=True)
@mock.patch.object(hardware, 'get_component_devices', autospec=True)
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
@mock.patch.object(il_utils, 'execute', autospec=True)
def test_delete_configuration_failure_blocks_remaining(
- self, mocked_execute, mocked_list, mocked_get_component):
+ self, mocked_execute, mocked_list, mocked_get_component,
+ mocked_get_volume_name):
# This test checks that, if after two raid clean passes there still
# remain softraid hints on drives, then the delete_configuration call
@@ -4601,6 +5025,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
[], # list_all_block_devices raid (type md)
]
mocked_get_component.return_value = []
+ mocked_get_volume_name.return_value = "/dev/md0"
self.assertRaisesRegex(
errors.SoftwareRAIDError,
@@ -4614,6 +5039,79 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
])
+ @mock.patch.object(raid_utils, 'get_volume_name_of_raid_device',
+ autospec=True)
+ @mock.patch.object(hardware.GenericHardwareManager,
+ 'get_skip_list_from_node', autospec=True)
+ @mock.patch.object(hardware, 'get_holder_disks', autospec=True)
+ @mock.patch.object(hardware, 'get_component_devices', autospec=True)
+ @mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
+ @mock.patch.object(il_utils, 'execute', autospec=True)
+ def test_delete_configuration_skip_list(self, mocked_execute, mocked_list,
+ mocked_get_component,
+ mocked_get_holder,
+ mocked_get_skip_list,
+ mocked_get_volume_name):
+ raid_device1 = hardware.BlockDevice('/dev/md0', 'RAID-1',
+ 107374182400, True)
+ raid_device2 = hardware.BlockDevice('/dev/md1', 'RAID-0',
+ 2147483648, True)
+ sda = hardware.BlockDevice('/dev/sda', 'model12', 21, True)
+ sdb = hardware.BlockDevice('/dev/sdb', 'model12', 21, True)
+ sdc = hardware.BlockDevice('/dev/sdc', 'model12', 21, True)
+
+ partitions = [
+ hardware.BlockDevice('/dev/sdb1', 'raid-member', 32767, False),
+ hardware.BlockDevice('/dev/sdb2', 'raid-member', 32767, False),
+ hardware.BlockDevice('/dev/sda1', 'raid_member', 32767, False),
+ hardware.BlockDevice('/dev/sda2', 'raid-member', 32767, False),
+ ]
+
+ hardware.list_all_block_devices.side_effect = [
+ [raid_device1, raid_device2], # list_all_block_devices raid
+ [], # list_all_block_devices raid (md)
+ [sda, sdb, sdc], # list_all_block_devices disks
+ partitions, # list_all_block_devices parts
+ [], # list_all_block_devices raid
+ [], # list_all_block_devices raid (md)
+ ]
+ mocked_get_component.side_effect = [
+ ["/dev/sda1", "/dev/sdb1"],
+ ["/dev/sda2", "/dev/sdb2"]]
+ mocked_get_holder.side_effect = [
+ ["/dev/sda", "/dev/sdb"],
+ ["/dev/sda", "/dev/sdb"]]
+ mocked_get_volume_name.side_effect = [
+ "/dev/md0", "small"
+ ]
+ mocked_get_skip_list.return_value = ["small"]
+
+ self.hardware.delete_configuration(self.node, [])
+
+ mocked_execute.assert_has_calls([
+ mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
+ mock.call('wipefs', '-af', '/dev/md0'),
+ mock.call('mdadm', '--stop', '/dev/md0'),
+ mock.call('mdadm', '--examine', '/dev/sda1',
+ use_standard_locale=True),
+ mock.call('mdadm', '--zero-superblock', '/dev/sda1'),
+ mock.call('mdadm', '--examine', '/dev/sdb1',
+ use_standard_locale=True),
+ mock.call('mdadm', '--zero-superblock', '/dev/sdb1'),
+ mock.call('mdadm', '--examine', '/dev/sda1',
+ use_standard_locale=True),
+ mock.call('mdadm', '--zero-superblock', '/dev/sda1'),
+ mock.call('mdadm', '--examine', '/dev/sdb1',
+ use_standard_locale=True),
+ mock.call('mdadm', '--zero-superblock', '/dev/sdb1'),
+ mock.call('mdadm', '--examine', '/dev/sdc',
+ use_standard_locale=True),
+ mock.call('mdadm', '--zero-superblock', '/dev/sdc'),
+ mock.call('parted', '/dev/sda', 'rm', '1'),
+ mock.call('parted', '/dev/sdb', 'rm', '1'),
+ mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
+ ])
+
@mock.patch.object(il_utils, 'execute', autospec=True)
def test_validate_configuration_valid_raid1(self, mocked_execute):
raid_config = {
@@ -5034,7 +5532,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',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
mock.call('multipath', '-c', '/dev/sda'),
mock.call('multipath', '-c', '/dev/sdb')
@@ -5081,7 +5579,7 @@ class TestModuleFunctions(base.IronicAgentTest):
]
expected_calls = [
mock.call('lsblk', '-bia', '--json',
- '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
mock.call('multipath', '-c', '/dev/sda'),
mock.call('multipath', '-c', '/dev/sda1'),
@@ -5120,7 +5618,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',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
mock.call('multipath', '-c', '/dev/sda'),
mock.call('multipath', '-c', '/dev/sda1'),
@@ -5143,7 +5641,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',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0])
self.assertEqual([], result)
mocked_udev.assert_called_once_with()
@@ -5157,7 +5655,7 @@ class TestModuleFunctions(base.IronicAgentTest):
mocked_mpath.return_value = False
expected_calls = [
mock.call('lsblk', '-bia', '--json',
- '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID',
+ '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID,SERIAL',
check_exit_code=[0]),
]
mocked_execute.return_value = (
@@ -5165,7 +5663,7 @@ class TestModuleFunctions(base.IronicAgentTest):
self.assertRaisesRegex(
errors.BlockDeviceError,
r'^Block device caused unknown error: kname, partuuid, rota, '
- r'size, uuid must be returned by lsblk.$',
+ r'serial, size, uuid 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/tests/unit/test_raid_utils.py b/ironic_python_agent/tests/unit/test_raid_utils.py
index 6624304b..a82027c8 100644
--- a/ironic_python_agent/tests/unit/test_raid_utils.py
+++ b/ironic_python_agent/tests/unit/test_raid_utils.py
@@ -139,6 +139,20 @@ class TestRaidUtils(base.IronicAgentTest):
raid_utils.create_raid_device, 0,
logical_disk)
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test_get_volume_name_of_raid_device(self, mock_execute):
+ mock_execute.side_effect = [(hws.MDADM_DETAIL_OUTPUT_VOLUME_NAME, '')]
+ volume_name = raid_utils.get_volume_name_of_raid_device('/dev/md0')
+ self.assertEqual("this_name", volume_name)
+
+ @mock.patch.object(utils, 'execute', autospec=True)
+ def test_get_volume_name_of_raid_device_invalid(self, mock_execute):
+ mock_execute.side_effect = [(
+ hws.MDADM_DETAIL_OUTPUT_VOLUME_NAME_INVALID, ''
+ )]
+ volume_name = raid_utils.get_volume_name_of_raid_device('/dev/md0')
+ self.assertIsNone(volume_name)
+
@mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True)
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
return_value='/dev/md42')
diff --git a/ironic_python_agent/utils.py b/ironic_python_agent/utils.py
index 09d726cf..41daaf97 100644
--- a/ironic_python_agent/utils.py
+++ b/ironic_python_agent/utils.py
@@ -59,7 +59,8 @@ CONF = cfg.CONF
AGENT_PARAMS_CACHED = dict()
-LSBLK_COLUMNS = ['KNAME', 'MODEL', 'SIZE', 'ROTA', 'TYPE', 'UUID', 'PARTUUID']
+LSBLK_COLUMNS = ['KNAME', 'MODEL', 'SIZE', 'ROTA',
+ 'TYPE', 'UUID', 'PARTUUID', 'SERIAL']
COLLECT_LOGS_COMMANDS = {
@@ -651,6 +652,22 @@ def extract_device(part):
return (m.group(1) or m.group(2))
+def split_device_and_partition_number(part):
+ """Extract the partition number from a partition name or path.
+
+ :param part: the partition
+ :return: device and partition number if success, None otherwise
+ """
+
+ device = extract_device(part)
+ if not device:
+ return None
+ partition_number = part.replace(device, '')
+ if 'nvme' in device and partition_number[0] == 'p':
+ partition_number = partition_number[1:]
+ return (device, partition_number)
+
+
# See ironic.drivers.utils.get_node_capability
def _parse_capabilities_str(cap_str):
"""Extract capabilities from string.
diff --git a/releasenotes/notes/enable-skipping-raids-40263cc3a19cfd27.yaml b/releasenotes/notes/enable-skipping-raids-40263cc3a19cfd27.yaml
new file mode 100644
index 00000000..999437cb
--- /dev/null
+++ b/releasenotes/notes/enable-skipping-raids-40263cc3a19cfd27.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - The node property ``skip_block_devices`` supports
+ specifying volume names of software RAID devices.
+ These devices are not cleaned during cleaning and
+ are not created provided they already exist.
diff --git a/releasenotes/notes/prioritize-lsblk-device-serials-8cae406ca5164a01.yaml b/releasenotes/notes/prioritize-lsblk-device-serials-8cae406ca5164a01.yaml
new file mode 100644
index 00000000..3c0b26b1
--- /dev/null
+++ b/releasenotes/notes/prioritize-lsblk-device-serials-8cae406ca5164a01.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ When detecting a serial number of a block device, the agent now tries
+ to use lsblk first and only falls back to udev if lsblk does not return
+ a serial number. Based on experience it looks like lsblk might be a better
+ source of truth than udev in regerard to serial number information.
+
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index d3d198cb..21b2de88 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@ Ironic Python Agent Release Notes
:maxdepth: 1
unreleased
+ zed
yoga
xena
wallaby
diff --git a/releasenotes/source/yoga.rst b/releasenotes/source/yoga.rst
index 7cd5e908..9b0242d5 100644
--- a/releasenotes/source/yoga.rst
+++ b/releasenotes/source/yoga.rst
@@ -1,6 +1,6 @@
-=========================
-Yoga Series Release Notes
-=========================
+=========================================
+Yoga Series (8.3.0 - 8.5.x) Release Notes
+=========================================
.. release-notes::
:branch: stable/yoga
diff --git a/releasenotes/source/zed.rst b/releasenotes/source/zed.rst
new file mode 100644
index 00000000..9608c05e
--- /dev/null
+++ b/releasenotes/source/zed.rst
@@ -0,0 +1,6 @@
+========================
+Zed Series Release Notes
+========================
+
+.. release-notes::
+ :branch: stable/zed
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 0c4e16fb..ceff4cfb 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -2,7 +2,8 @@
templates:
- check-requirements
- openstack-cover-jobs
- - openstack-python3-zed-jobs
+ - openstack-python3-jobs
+ - openstack-python3-jobs-arm64
- publish-openstack-docs-pti
- release-notes-jobs-python3
check: