SoftwareRAID: Enable skipping RAIDS
Extend the ability to skip disks to RAID devices This allows users to specify the volume name of a logical device in the skip list which is then not cleaned or created again during the create/apply configuration phase The volume name can be specified in target raid config provided the change https://review.opendev.org/c/openstack/ironic-python-agent/+/853182/ passes Story: 2010233 Change-Id: Ib9290a97519bc48e585e1bafb0b60cc14e621e0f
This commit is contained in:
parent
ed6a8d28b7
commit
a99bf274e4
@ -121,6 +121,15 @@ containing hints to identify the drives. For example::
|
|||||||
'skip_block_devices': [{'name': '/dev/vda', 'vendor': '0x1af4'}]
|
'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
|
Shared Disk Cluster Filesystems
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -863,6 +863,17 @@ class HardwareManager(object, metaclass=abc.ABCMeta):
|
|||||||
"""
|
"""
|
||||||
raise errors.IncompatibleHardwareMethodError
|
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,
|
def list_block_devices_check_skip_list(self, node,
|
||||||
include_partitions=False):
|
include_partitions=False):
|
||||||
"""List physical block devices without the ones listed in
|
"""List physical block devices without the ones listed in
|
||||||
@ -1391,17 +1402,22 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
)
|
)
|
||||||
return block_devices
|
return block_devices
|
||||||
|
|
||||||
def list_block_devices_check_skip_list(self, node,
|
def get_skip_list_from_node(self, node,
|
||||||
include_partitions=False):
|
block_devices=None, just_raids=False):
|
||||||
block_devices = self.list_block_devices(
|
|
||||||
include_partitions=include_partitions)
|
|
||||||
properties = node.get('properties', {})
|
properties = node.get('properties', {})
|
||||||
skip_list_hints = properties.get("skip_block_devices", [])
|
skip_list_hints = properties.get("skip_block_devices", [])
|
||||||
if not skip_list_hints:
|
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()
|
skip_list = set()
|
||||||
serialized_devs = [dev.serialize() for dev in block_devices]
|
serialized_devs = [dev.serialize() for dev in block_devices]
|
||||||
for hint in skip_list_hints:
|
for hint in skip_list_hints:
|
||||||
|
if 'volume_name' in hint:
|
||||||
|
continue
|
||||||
found_devs = il_utils.find_devices_by_hints(serialized_devs, hint)
|
found_devs = il_utils.find_devices_by_hints(serialized_devs, hint)
|
||||||
excluded_devs = {dev['name'] for dev in found_devs}
|
excluded_devs = {dev['name'] for dev in found_devs}
|
||||||
skipped_devices = excluded_devs.difference(skip_list)
|
skipped_devices = excluded_devs.difference(skip_list)
|
||||||
@ -1409,8 +1425,17 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
if skipped_devices:
|
if skipped_devices:
|
||||||
LOG.warning("Using hint %(hint)s skipping devices: %(devs)s",
|
LOG.warning("Using hint %(hint)s skipping devices: %(devs)s",
|
||||||
{'hint': hint, 'devs': ','.join(skipped_devices)})
|
{'hint': hint, 'devs': ','.join(skipped_devices)})
|
||||||
block_devices = [d for d in block_devices
|
return skip_list
|
||||||
if d.name not in 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
|
return block_devices
|
||||||
|
|
||||||
def get_os_install_device(self, permit_refresh=False):
|
def get_os_install_device(self, permit_refresh=False):
|
||||||
@ -2341,15 +2366,41 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
return self._do_create_configuration(node, ports, raid_config)
|
return self._do_create_configuration(node, ports, raid_config)
|
||||||
|
|
||||||
def _do_create_configuration(self, 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
|
# No 'software' controller: do nothing. If 'controller' is
|
||||||
# set to 'software' on only one of the drives, the validation
|
# set to 'software' on only one of the drives, the validation
|
||||||
# code will catch it.
|
# code will catch it.
|
||||||
software_raid = False
|
software_raid = False
|
||||||
logical_disks = raid_config.get('logical_disks')
|
logical_disks = raid_config.get('logical_disks')
|
||||||
|
software_raid_disks = []
|
||||||
for logical_disk in logical_disks:
|
for logical_disk in logical_disks:
|
||||||
if logical_disk.get('controller') == 'software':
|
if logical_disk.get('controller') == 'software':
|
||||||
software_raid = True
|
software_raid = True
|
||||||
break
|
software_raid_disks.append(logical_disk)
|
||||||
if not software_raid:
|
if not software_raid:
|
||||||
LOG.debug("No Software RAID config found")
|
LOG.debug("No Software RAID config found")
|
||||||
return {}
|
return {}
|
||||||
@ -2359,24 +2410,51 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# Check if the config is compliant with current limitations.
|
# Check if the config is compliant with current limitations.
|
||||||
self.validate_configuration(raid_config, node)
|
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 the validated target_raid_configuration.
|
||||||
LOG.debug("Target Software RAID configuration: %s", raid_config)
|
LOG.debug("Target Software RAID configuration: %s", raid_config)
|
||||||
|
|
||||||
block_devices, logical_disks = raid_utils.get_block_devices_for_raid(
|
block_devices, logical_disks = raid_utils.get_block_devices_for_raid(
|
||||||
self.list_block_devices(), logical_disks)
|
self.list_block_devices(), logical_disks)
|
||||||
# Make sure there are no partitions yet (or left behind).
|
if not rm_from_list:
|
||||||
with_parts = []
|
# Make sure there are no partitions yet (or left behind).
|
||||||
for dev_name in block_devices:
|
with_parts = []
|
||||||
try:
|
for dev_name in block_devices:
|
||||||
if disk_utils.list_partitions(dev_name):
|
try:
|
||||||
with_parts.append(dev_name)
|
if disk_utils.list_partitions(dev_name):
|
||||||
except processutils.ProcessExecutionError:
|
with_parts.append(dev_name)
|
||||||
# Presumably no partitions (or no partition table)
|
except processutils.ProcessExecutionError:
|
||||||
continue
|
# Presumably no partitions (or no partition table)
|
||||||
if with_parts:
|
continue
|
||||||
msg = ("Partitions detected on devices %s during RAID config" %
|
if with_parts:
|
||||||
', '.join(with_parts))
|
msg = ("Partitions detected on devices %s during RAID config" %
|
||||||
raise errors.SoftwareRAIDError(msg)
|
', '.join(with_parts))
|
||||||
|
raise errors.SoftwareRAIDError(msg)
|
||||||
|
|
||||||
partition_table_type = utils.get_partition_table_type_from_specs(node)
|
partition_table_type = utils.get_partition_table_type_from_specs(node)
|
||||||
target_boot_mode = utils.get_node_boot_mode(node)
|
target_boot_mode = utils.get_node_boot_mode(node)
|
||||||
@ -2484,10 +2562,12 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
return raid_devices
|
return raid_devices
|
||||||
|
|
||||||
raid_devices = _scan_raids()
|
raid_devices = _scan_raids()
|
||||||
|
skip_list = self.get_skip_list_from_node(
|
||||||
|
node, just_raids=True)
|
||||||
attempts = 0
|
attempts = 0
|
||||||
while attempts < 2:
|
while attempts < 2:
|
||||||
attempts += 1
|
attempts += 1
|
||||||
self._delete_config_pass(raid_devices)
|
self._delete_config_pass(raid_devices, skip_list)
|
||||||
raid_devices = _scan_raids()
|
raid_devices = _scan_raids()
|
||||||
if not raid_devices:
|
if not raid_devices:
|
||||||
break
|
break
|
||||||
@ -2497,9 +2577,22 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise errors.SoftwareRAIDError(msg)
|
raise errors.SoftwareRAIDError(msg)
|
||||||
|
|
||||||
def _delete_config_pass(self, raid_devices):
|
def _delete_config_pass(self, raid_devices, skip_list):
|
||||||
all_holder_disks = []
|
all_holder_disks = []
|
||||||
|
do_not_delete_devices = set()
|
||||||
|
delete_partitions = {}
|
||||||
for raid_device in raid_devices:
|
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)
|
component_devices = get_component_devices(raid_device.name)
|
||||||
if not component_devices:
|
if not component_devices:
|
||||||
# A "Software RAID device" without components is usually
|
# A "Software RAID device" without components is usually
|
||||||
@ -2511,52 +2604,73 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
continue
|
continue
|
||||||
holder_disks = get_holder_disks(raid_device.name)
|
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 component devices %s', component_devices)
|
||||||
LOG.debug('Found holder disks %s', holder_disks)
|
LOG.debug('Found holder disks %s', holder_disks)
|
||||||
|
|
||||||
# Remove md devices.
|
if not do_not_delete:
|
||||||
try:
|
# Remove md devices.
|
||||||
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:
|
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--examine', component_device,
|
il_utils.execute('wipefs', '-af', raid_device.name)
|
||||||
use_standard_locale=True)
|
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
if "No md superblock detected" in str(e):
|
LOG.warning('Failed to wipefs %(device)s: %(err)s',
|
||||||
# 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})
|
{'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:
|
||||||
|
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
|
# NOTE(arne_wiebalck): We cannot delete the partitions right
|
||||||
# away since there may be other partitions on the same disks
|
# away since there may be other partitions on the same disks
|
||||||
# which are members of other RAID devices. So we remember them
|
# which are members of other RAID devices. So we remember them
|
||||||
# for later.
|
# for later.
|
||||||
all_holder_disks.extend(holder_disks)
|
all_holder_disks.extend(holder_disks)
|
||||||
|
if do_not_delete:
|
||||||
LOG.info('Deleted Software RAID device %s', raid_device.name)
|
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
|
# Remove all remaining raid traces from any drives, in case some
|
||||||
# drives or partitions have been member of some raid once
|
# drives or partitions have been member of some raid once
|
||||||
@ -2581,7 +2695,13 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# mdadm: Couldn't open /dev/block for write - not zeroing
|
# mdadm: Couldn't open /dev/block for write - not zeroing
|
||||||
# mdadm -E /dev/block1: still shows superblocks
|
# mdadm -E /dev/block1: still shows superblocks
|
||||||
all_blks = reversed(self.list_block_devices(include_partitions=True))
|
all_blks = reversed(self.list_block_devices(include_partitions=True))
|
||||||
|
do_not_delete_disks = set()
|
||||||
for blk in all_blks:
|
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:
|
try:
|
||||||
il_utils.execute('mdadm', '--examine', blk.name,
|
il_utils.execute('mdadm', '--examine', blk.name,
|
||||||
use_standard_locale=True)
|
use_standard_locale=True)
|
||||||
@ -2604,6 +2724,20 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
all_holder_disks_uniq = list(
|
all_holder_disks_uniq = list(
|
||||||
collections.OrderedDict.fromkeys(all_holder_disks))
|
collections.OrderedDict.fromkeys(all_holder_disks))
|
||||||
for holder_disk in all_holder_disks_uniq:
|
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)
|
LOG.info('Removing partitions on holder disk %s', holder_disk)
|
||||||
try:
|
try:
|
||||||
il_utils.execute('wipefs', '-af', holder_disk)
|
il_utils.execute('wipefs', '-af', holder_disk)
|
||||||
|
@ -267,10 +267,44 @@ def get_next_free_raid_device():
|
|||||||
name = f'/dev/md{idx}'
|
name = f'/dev/md{idx}'
|
||||||
if name not in names:
|
if name not in names:
|
||||||
return name
|
return name
|
||||||
|
|
||||||
raise errors.SoftwareRAIDError("No free md (RAID) devices are left")
|
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
|
# TODO(rg): handle PreP boot parts relocation as well
|
||||||
def prepare_boot_partitions_for_softraid(device, holders, efi_part,
|
def prepare_boot_partitions_for_softraid(device, holders, efi_part,
|
||||||
target_boot_mode):
|
target_boot_mode):
|
||||||
|
@ -1031,6 +1031,61 @@ Working Devices : 2
|
|||||||
1 259 3 1 active sync /dev/nvme1n1p1
|
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:
|
MDADM_DETAIL_OUTPUT_BROKEN_RAID0 = ("""/dev/md126:
|
||||||
Version : 1.2
|
Version : 1.2
|
||||||
Raid Level : raid0
|
Raid Level : raid0
|
||||||
|
@ -1542,6 +1542,54 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
ignore_raid=True)],
|
ignore_raid=True)],
|
||||||
list_mock.call_args_list)
|
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,
|
@mock.patch.object(hardware.GenericHardwareManager,
|
||||||
'list_block_devices', autospec=True)
|
'list_block_devices', autospec=True)
|
||||||
def test_list_block_devices_check_skip_list_with_skip_list(self,
|
def test_list_block_devices_check_skip_list_with_skip_list(self,
|
||||||
@ -4369,6 +4417,294 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
self.hardware.create_configuration,
|
self.hardware.create_configuration,
|
||||||
self.node, [])
|
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)
|
@mock.patch.object(il_utils, 'execute', autospec=True)
|
||||||
def test__get_md_uuid(self, mocked_execute):
|
def test__get_md_uuid(self, mocked_execute):
|
||||||
mocked_execute.side_effect = [(hws.MDADM_DETAIL_OUTPUT, '')]
|
mocked_execute.side_effect = [(hws.MDADM_DETAIL_OUTPUT, '')]
|
||||||
@ -4462,12 +4798,15 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
holder_disks = hardware.get_holder_disks('/dev/md0')
|
holder_disks = hardware.get_holder_disks('/dev/md0')
|
||||||
self.assertEqual(['/dev/vda', '/dev/vdb'], holder_disks)
|
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_holder_disks', autospec=True)
|
||||||
@mock.patch.object(hardware, 'get_component_devices', autospec=True)
|
@mock.patch.object(hardware, 'get_component_devices', autospec=True)
|
||||||
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
|
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
|
||||||
@mock.patch.object(il_utils, 'execute', autospec=True)
|
@mock.patch.object(il_utils, 'execute', autospec=True)
|
||||||
def test_delete_configuration(self, mocked_execute, mocked_list,
|
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',
|
raid_device1 = hardware.BlockDevice('/dev/md0', 'RAID-1',
|
||||||
107374182400, True)
|
107374182400, True)
|
||||||
raid_device2 = hardware.BlockDevice('/dev/md1', 'RAID-0',
|
raid_device2 = hardware.BlockDevice('/dev/md1', 'RAID-0',
|
||||||
@ -4490,6 +4829,9 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
mocked_get_holder.side_effect = [
|
mocked_get_holder.side_effect = [
|
||||||
["/dev/sda", "/dev/sdb"],
|
["/dev/sda", "/dev/sdb"],
|
||||||
["/dev/sda", "/dev/sdb"]]
|
["/dev/sda", "/dev/sdb"]]
|
||||||
|
mocked_get_volume_name.side_effect = [
|
||||||
|
"/dev/md0", "/dev/md1"
|
||||||
|
]
|
||||||
mocked_execute.side_effect = [
|
mocked_execute.side_effect = [
|
||||||
None, # mdadm --assemble --scan
|
None, # mdadm --assemble --scan
|
||||||
None, # wipefs md0
|
None, # wipefs md0
|
||||||
@ -4551,11 +4893,14 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
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, 'get_component_devices', autospec=True)
|
||||||
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
|
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
|
||||||
@mock.patch.object(il_utils, 'execute', autospec=True)
|
@mock.patch.object(il_utils, 'execute', autospec=True)
|
||||||
def test_delete_configuration_partition(self, mocked_execute, mocked_list,
|
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
|
# 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
|
# raid device, then it must be a nested partition and so it gets
|
||||||
# skipped
|
# skipped
|
||||||
@ -4569,6 +4914,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
[], # list_all_block_devices raid
|
[], # list_all_block_devices raid
|
||||||
[], # list_all_block_devices raid (md)
|
[], # list_all_block_devices raid (md)
|
||||||
]
|
]
|
||||||
|
mocked_get_volume_name.return_value = None
|
||||||
mocked_get_component.return_value = []
|
mocked_get_component.return_value = []
|
||||||
self.assertIsNone(self.hardware.delete_configuration(self.node, []))
|
self.assertIsNone(self.hardware.delete_configuration(self.node, []))
|
||||||
mocked_execute.assert_has_calls([
|
mocked_execute.assert_has_calls([
|
||||||
@ -4576,11 +4922,14 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
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, 'get_component_devices', autospec=True)
|
||||||
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
|
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
|
||||||
@mock.patch.object(il_utils, 'execute', autospec=True)
|
@mock.patch.object(il_utils, 'execute', autospec=True)
|
||||||
def test_delete_configuration_failure_blocks_remaining(
|
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
|
# This test checks that, if after two raid clean passes there still
|
||||||
# remain softraid hints on drives, then the delete_configuration call
|
# remain softraid hints on drives, then the delete_configuration call
|
||||||
@ -4601,6 +4950,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
[], # list_all_block_devices raid (type md)
|
[], # list_all_block_devices raid (type md)
|
||||||
]
|
]
|
||||||
mocked_get_component.return_value = []
|
mocked_get_component.return_value = []
|
||||||
|
mocked_get_volume_name.return_value = "/dev/md0"
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
errors.SoftwareRAIDError,
|
errors.SoftwareRAIDError,
|
||||||
@ -4614,6 +4964,79 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
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)
|
@mock.patch.object(il_utils, 'execute', autospec=True)
|
||||||
def test_validate_configuration_valid_raid1(self, mocked_execute):
|
def test_validate_configuration_valid_raid1(self, mocked_execute):
|
||||||
raid_config = {
|
raid_config = {
|
||||||
|
@ -139,6 +139,20 @@ class TestRaidUtils(base.IronicAgentTest):
|
|||||||
raid_utils.create_raid_device, 0,
|
raid_utils.create_raid_device, 0,
|
||||||
logical_disk)
|
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(disk_utils, 'trigger_device_rescan', autospec=True)
|
||||||
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
||||||
return_value='/dev/md42')
|
return_value='/dev/md42')
|
||||||
|
@ -651,6 +651,22 @@ def extract_device(part):
|
|||||||
return (m.group(1) or m.group(2))
|
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
|
# See ironic.drivers.utils.get_node_capability
|
||||||
def _parse_capabilities_str(cap_str):
|
def _parse_capabilities_str(cap_str):
|
||||||
"""Extract capabilities from string.
|
"""Extract capabilities from string.
|
||||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user