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'}]
|
||||
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -863,6 +863,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 +1402,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 +1425,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 +2366,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 +2410,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 +2562,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 +2577,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 +2604,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)
|
||||
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',
|
||||
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:
|
||||
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 +2695,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 +2724,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)
|
||||
|
@ -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):
|
||||
|
@ -1031,6 +1031,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
|
||||
|
@ -1542,6 +1542,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,
|
||||
@ -4369,6 +4417,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 +4798,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 +4829,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 +4893,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 +4914,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 +4922,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 +4950,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 +4964,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 = {
|
||||
|
@ -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')
|
||||
|
@ -651,6 +651,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.
|
||||
|
@ -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…
Reference in New Issue
Block a user