Delete_configuration, consider removed raid members as well
Change-Id: Ie4f62d8855d3f30a55f7032918dfed1f1d8b5b31 Story: #2006535 Task: #36591
This commit is contained in:
parent
b9a2ac8914
commit
37385dd9dd
@ -1629,8 +1629,28 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
of ports for the node
|
of ports for the node
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _scan_raids():
|
||||||
|
utils.execute('mdadm', '--assemble', '--scan',
|
||||||
|
check_exit_code=False)
|
||||||
raid_devices = list_all_block_devices(block_type='raid',
|
raid_devices = list_all_block_devices(block_type='raid',
|
||||||
ignore_raid=False)
|
ignore_raid=False)
|
||||||
|
return raid_devices
|
||||||
|
|
||||||
|
raid_devices = _scan_raids()
|
||||||
|
attempts = 0
|
||||||
|
while attempts < 2:
|
||||||
|
attempts += 1
|
||||||
|
self._delete_config_pass(raid_devices)
|
||||||
|
raid_devices = _scan_raids()
|
||||||
|
if not raid_devices:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
msg = "Unable to clean all softraid correctly. Remaining {}".\
|
||||||
|
format([dev.name for dev in raid_devices])
|
||||||
|
LOG.error(msg)
|
||||||
|
raise errors.SoftwareRAIDError(msg)
|
||||||
|
|
||||||
|
def _delete_config_pass(self, raid_devices):
|
||||||
for raid_device in raid_devices:
|
for raid_device in raid_devices:
|
||||||
component_devices = _get_component_devices(raid_device.name)
|
component_devices = _get_component_devices(raid_device.name)
|
||||||
if not component_devices:
|
if not component_devices:
|
||||||
@ -1694,6 +1714,48 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
|
|
||||||
LOG.info('Deleted Software RAID device %s', raid_device.name)
|
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
|
||||||
|
|
||||||
|
# TBD: should we consider all block devices by default, but still
|
||||||
|
# provide some 'control' through the node information
|
||||||
|
# (for example target_raid_config at the time of calling this). This
|
||||||
|
# may make sense if you do not want the delete_config to touch some
|
||||||
|
# drives, like cinder volumes locally attached, for example, or any
|
||||||
|
# kind of 'non-ephemeral' drive that you do not want to consider during
|
||||||
|
# deployment (= specify which drives to consider just like create
|
||||||
|
# configuration might consider the physical_disks parameter in a near
|
||||||
|
# future)
|
||||||
|
|
||||||
|
# Consider partitions first, before underlying disks, never hurts and
|
||||||
|
# can even avoid some failures. Example to reproduce:
|
||||||
|
# mdadm --stop /dev/md0
|
||||||
|
# mdadm --zero-superblock /dev/block
|
||||||
|
# mdadm: Unrecognised md component device - /dev/block
|
||||||
|
# (mdadm -E /dev/block still returns 0 so won't be skipped for zeroing)
|
||||||
|
# mdadm --zero-superblock /dev/block1
|
||||||
|
# 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))
|
||||||
|
for blk in all_blks:
|
||||||
|
try:
|
||||||
|
utils.execute('mdadm', '--examine', blk.name,
|
||||||
|
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(
|
||||||
|
blk.name, e)
|
||||||
|
LOG.warning(msg)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
utils.execute('mdadm', '--zero-superblock', blk.name)
|
||||||
|
except processutils.ProcessExecutionError as e:
|
||||||
|
LOG.warning('Failed to remove superblock from %s: %s',
|
||||||
|
raid_device.name, e)
|
||||||
|
|
||||||
LOG.debug("Finished deleting Software RAID(s)")
|
LOG.debug("Finished deleting Software RAID(s)")
|
||||||
|
|
||||||
def validate_configuration(self, raid_config, node):
|
def validate_configuration(self, raid_config, node):
|
||||||
|
@ -3251,62 +3251,141 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||||||
107374182400, True)
|
107374182400, True)
|
||||||
raid_device2 = hardware.BlockDevice('/dev/md1', 'RAID-0',
|
raid_device2 = hardware.BlockDevice('/dev/md1', 'RAID-0',
|
||||||
2147483648, True)
|
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)
|
||||||
|
|
||||||
hardware.list_all_block_devices.side_effect = [
|
hardware.list_all_block_devices.side_effect = [
|
||||||
[raid_device1, raid_device2]]
|
[raid_device1, raid_device2], # list_all_block_devices raid
|
||||||
|
[sda, sdb, sdc], # list_all_block_devices disks
|
||||||
|
[], # list_all_block_devices parts
|
||||||
|
[], # list_all_block_devices raid
|
||||||
|
]
|
||||||
mocked_get_component.side_effect = [
|
mocked_get_component.side_effect = [
|
||||||
["/dev/sda1", "/dev/sda2"],
|
["/dev/sda1", "/dev/sdb1"],
|
||||||
["/dev/sdb1", "/dev/sdb2"]]
|
["/dev/sda2", "/dev/sdb2"]]
|
||||||
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_execute.side_effect = [
|
mocked_execute.side_effect = [
|
||||||
None, None, None,
|
None, # mdadm --assemble --scan
|
||||||
|
None, # wipefs md0
|
||||||
|
None, # mdadm --stop md0
|
||||||
['_', 'mdadm --examine output for sda1'],
|
['_', 'mdadm --examine output for sda1'],
|
||||||
None,
|
None, # mdadm zero-superblock sda1
|
||||||
['_', 'mdadm --examine output for sdb1'],
|
['_', 'mdadm --examine output for sdb1'],
|
||||||
None, None, None,
|
None, # mdadm zero-superblock sdb1
|
||||||
None, None, None,
|
None, # wipefs sda
|
||||||
|
None, # wipefs sdb
|
||||||
|
None, # wipfs md1
|
||||||
|
None, # mdadm --stop md1
|
||||||
['_', 'mdadm --examine output for sda2'],
|
['_', 'mdadm --examine output for sda2'],
|
||||||
None,
|
None, # mdadm zero-superblock sda2
|
||||||
['_', 'mdadm --examine output for sdb2'],
|
['_', 'mdadm --examine output for sdb2'],
|
||||||
None, None, None]
|
None, # mdadm zero-superblock sdb2
|
||||||
|
None, # wipefs sda
|
||||||
|
None, # wipefs sda
|
||||||
|
['_', 'mdadm --examine output for sdc'],
|
||||||
|
None, # mdadm zero-superblock sdc
|
||||||
|
# examine sdb
|
||||||
|
processutils.ProcessExecutionError('No md superblock detected'),
|
||||||
|
# examine sda
|
||||||
|
processutils.ProcessExecutionError('No md superblock detected'),
|
||||||
|
None, # mdadm --assemble --scan
|
||||||
|
]
|
||||||
|
|
||||||
self.hardware.delete_configuration(self.node, [])
|
self.hardware.delete_configuration(self.node, [])
|
||||||
|
|
||||||
mocked_execute.assert_has_calls([
|
mocked_execute.assert_has_calls([
|
||||||
|
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
||||||
mock.call('wipefs', '-af', '/dev/md0'),
|
mock.call('wipefs', '-af', '/dev/md0'),
|
||||||
mock.call('mdadm', '--stop', '/dev/md0'),
|
mock.call('mdadm', '--stop', '/dev/md0'),
|
||||||
mock.call('mdadm', '--examine', '/dev/sda1',
|
mock.call('mdadm', '--examine', '/dev/sda1',
|
||||||
use_standard_locale=True),
|
use_standard_locale=True),
|
||||||
mock.call('mdadm', '--zero-superblock', '/dev/sda1'),
|
mock.call('mdadm', '--zero-superblock', '/dev/sda1'),
|
||||||
mock.call('mdadm', '--examine', '/dev/sda2',
|
mock.call('mdadm', '--examine', '/dev/sdb1',
|
||||||
use_standard_locale=True),
|
use_standard_locale=True),
|
||||||
mock.call('mdadm', '--zero-superblock', '/dev/sda2'),
|
mock.call('mdadm', '--zero-superblock', '/dev/sdb1'),
|
||||||
mock.call('wipefs', '-af', '/dev/sda'),
|
mock.call('wipefs', '-af', '/dev/sda'),
|
||||||
mock.call('wipefs', '-af', '/dev/sdb'),
|
mock.call('wipefs', '-af', '/dev/sdb'),
|
||||||
mock.call('wipefs', '-af', '/dev/md1'),
|
mock.call('wipefs', '-af', '/dev/md1'),
|
||||||
mock.call('mdadm', '--stop', '/dev/md1'),
|
mock.call('mdadm', '--stop', '/dev/md1'),
|
||||||
mock.call('mdadm', '--examine', '/dev/sdb1',
|
mock.call('mdadm', '--examine', '/dev/sda2',
|
||||||
use_standard_locale=True),
|
use_standard_locale=True),
|
||||||
mock.call('mdadm', '--zero-superblock', '/dev/sdb1'),
|
mock.call('mdadm', '--zero-superblock', '/dev/sda2'),
|
||||||
mock.call('mdadm', '--examine', '/dev/sdb2',
|
mock.call('mdadm', '--examine', '/dev/sdb2',
|
||||||
use_standard_locale=True),
|
use_standard_locale=True),
|
||||||
mock.call('mdadm', '--zero-superblock', '/dev/sdb2'),
|
mock.call('mdadm', '--zero-superblock', '/dev/sdb2'),
|
||||||
mock.call('wipefs', '-af', '/dev/sda'),
|
mock.call('wipefs', '-af', '/dev/sda'),
|
||||||
mock.call('wipefs', '-af', '/dev/sdb')])
|
mock.call('wipefs', '-af', '/dev/sdb'),
|
||||||
|
mock.call('mdadm', '--examine', '/dev/sdc',
|
||||||
|
use_standard_locale=True),
|
||||||
|
mock.call('mdadm', '--zero-superblock', '/dev/sdc'),
|
||||||
|
mock.call('mdadm', '--examine', '/dev/sdb',
|
||||||
|
use_standard_locale=True),
|
||||||
|
mock.call('mdadm', '--examine', '/dev/sda',
|
||||||
|
use_standard_locale=True),
|
||||||
|
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
||||||
|
])
|
||||||
|
|
||||||
@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(utils, 'execute', autospec=True)
|
@mock.patch.object(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):
|
||||||
|
# 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
|
||||||
raid_device1_part1 = hardware.BlockDevice('/dev/md0p1', 'RAID-1',
|
raid_device1_part1 = hardware.BlockDevice('/dev/md0p1', 'RAID-1',
|
||||||
1073741824, True)
|
1073741824, True)
|
||||||
hardware.list_all_block_devices.return_value = [raid_device1_part1]
|
hardware.list_all_block_devices.side_effect = [
|
||||||
|
[raid_device1_part1], # list_all_block_devices raid
|
||||||
|
[], # list_all_block_devices disks
|
||||||
|
[], # list_all_block_devices parts
|
||||||
|
[], # list_all_block_devices raid
|
||||||
|
]
|
||||||
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([
|
||||||
|
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
||||||
|
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
||||||
|
])
|
||||||
|
|
||||||
|
@mock.patch.object(hardware, '_get_component_devices', autospec=True)
|
||||||
|
@mock.patch.object(hardware, 'list_all_block_devices', autospec=True)
|
||||||
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
|
def test_delete_configuration_failure_blocks_remaining(
|
||||||
|
self, mocked_execute, mocked_list, mocked_get_component):
|
||||||
|
|
||||||
|
# This test checks that, if after two raid clean passes there still
|
||||||
|
# remain softraid hints on drives, then the delete_configuration call
|
||||||
|
# raises an error
|
||||||
|
raid_device1 = hardware.BlockDevice('/dev/md0', 'RAID-1',
|
||||||
|
107374182400, True)
|
||||||
|
|
||||||
|
hardware.list_all_block_devices.side_effect = [
|
||||||
|
[raid_device1], # list_all_block_devices raid
|
||||||
|
[], # list_all_block_devices disks
|
||||||
|
[], # list_all_block_devices parts
|
||||||
|
[raid_device1], # list_all_block_devices raid
|
||||||
|
[], # list_all_block_devices disks
|
||||||
|
[], # list_all_block_devices parts
|
||||||
|
[raid_device1], # list_all_block_devices raid
|
||||||
|
]
|
||||||
|
mocked_get_component.return_value = []
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
errors.SoftwareRAIDError,
|
||||||
|
r"^Software RAID caused unknown error: Unable to clean all "
|
||||||
|
r"softraid correctly. Remaining \['/dev/md0'\]$",
|
||||||
|
self.hardware.delete_configuration, self.node, [])
|
||||||
|
|
||||||
|
mocked_execute.assert_has_calls([
|
||||||
|
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
||||||
|
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
||||||
|
mock.call('mdadm', '--assemble', '--scan', check_exit_code=False),
|
||||||
|
])
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
def test_validate_configuration_valid_raid1(self, mocked_execute):
|
def test_validate_configuration_valid_raid1(self, mocked_execute):
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Zap superblocks from all block devices, as an attempt to erase any
|
||||||
|
softraid hint from devices when calling delete_configuration, including
|
||||||
|
from drives that are no more members of any raid.
|
Loading…
Reference in New Issue
Block a user