Merge "Provide fallback from ATA erase to shredding"

This commit is contained in:
Jenkins 2016-04-08 22:21:39 +00:00 committed by Gerrit Code Review
commit a630a921d3
3 changed files with 251 additions and 18 deletions

@ -582,8 +582,25 @@ class GenericHardwareManager(HardwareManager):
block_device.name)
return
if self._ata_erase(block_device):
return
# Note(TheJulia) Use try/except to capture and log the failure
# and then revert to attempting to shred the volume if enabled.
try:
if self._ata_erase(block_device):
return
except errors.BlockDeviceEraseError as e:
info = node.get('driver_internal_info', {})
execute_shred = info.get(
'agent_continue_if_ata_erase_failed', False)
if execute_shred:
LOG.warning('Failed to invoke ata_erase, '
'falling back to shred: %(err)s'
% {'err': e})
else:
msg = ('Failed to invoke ata_erase, '
'fallback to shred is not enabled: %(err)s'
% {'err': e})
LOG.error(msg)
raise errors.IncompatibleHardwareMethodError(msg)
if self._shred_block_device(node, block_device):
return
@ -657,6 +674,20 @@ class GenericHardwareManager(HardwareManager):
if 'supported' not in security_lines:
return False
if 'enabled' in security_lines:
# Attempt to unlock the drive in the event it has already been
# locked by a previous failed attempt.
try:
utils.execute('hdparm', '--user-master', 'u',
'--security-unlock', 'NULL', block_device.name)
security_lines = self._get_ata_security_lines(block_device)
except processutils.ProcessExecutionError as e:
raise errors.BlockDeviceEraseError('Security password set '
'failed for device '
'%(name)s: %(err)s' %
{'name': block_device.name,
'err': e})
if 'enabled' in security_lines:
raise errors.BlockDeviceEraseError(
('Block device {0} already has a security password set'
@ -667,16 +698,29 @@ class GenericHardwareManager(HardwareManager):
('Block device {0} is frozen and cannot be erased'
).format(block_device.name))
utils.execute('hdparm', '--user-master', 'u', '--security-set-pass',
'NULL', block_device.name)
try:
utils.execute('hdparm', '--user-master', 'u',
'--security-set-pass', 'NULL', block_device.name)
except processutils.ProcessExecutionError as e:
raise errors.BlockDeviceEraseError('Security password set '
'failed for device '
'%(name)s: %(err)s' %
{'name': block_device.name,
'err': e})
# Use the 'enhanced' security erase option if it's supported.
erase_option = '--security-erase'
if 'not supported: enhanced erase' not in security_lines:
erase_option += '-enhanced'
utils.execute('hdparm', '--user-master', 'u', erase_option,
'NULL', block_device.name)
try:
utils.execute('hdparm', '--user-master', 'u', erase_option,
'NULL', block_device.name)
except processutils.ProcessExecutionError as e:
raise errors.BlockDeviceEraseError('Erase failed for device '
'%(name)s: %(err)s' %
{'name': block_device.name,
'err': e})
# Verify that security is now 'not enabled'
security_lines = self._get_ata_security_lines(block_device)

@ -770,8 +770,10 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
'shred', '--force', '--zero', '--verbose', '--iterations', '1',
'/dev/sda')
@mock.patch.object(hardware.GenericHardwareManager, '_shred_block_device')
@mock.patch.object(utils, 'execute')
def test_erase_block_device_ata_security_enabled(self, mocked_execute):
def test_erase_block_device_ata_security_enabled(
self, mocked_execute, mock_shred):
hdparm_output = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': '\tenabled',
@ -780,17 +782,143 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
}
mocked_execute.side_effect = [
(hdparm_output, ''),
None,
(hdparm_output, '')
]
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.assertRaises(errors.BlockDeviceEraseError,
self.hardware.erase_block_device,
self.node, block_device)
self.assertRaises(
errors.IncompatibleHardwareMethodError,
self.hardware.erase_block_device,
self.node,
block_device)
self.assertFalse(mock_shred.called)
@mock.patch.object(hardware.GenericHardwareManager, '_shred_block_device')
@mock.patch.object(utils, 'execute')
def test_erase_block_device_ata_security_enabled_unlock_attempt(
self, mocked_execute, mock_shred):
hdparm_output = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': '\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
hdparm_output_not_enabled = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': 'not\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
mocked_execute.side_effect = [
(hdparm_output, ''),
'',
(hdparm_output_not_enabled, ''),
'',
'',
(hdparm_output_not_enabled, '')
]
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.hardware.erase_block_device(self.node, block_device)
self.assertFalse(mock_shred.called)
@mock.patch.object(utils, 'execute')
def test_erase_block_device_ata_frozen(self, mocked_execute):
def test__ata_erase_security_enabled_unlock_exception(
self, mocked_execute):
hdparm_output = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': '\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
mocked_execute.side_effect = [
(hdparm_output, ''),
processutils.ProcessExecutionError()
]
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.assertRaises(errors.BlockDeviceEraseError,
self.hardware._ata_erase,
block_device)
@mock.patch.object(utils, 'execute')
def test__ata_erase_security_enabled_set_password_exception(
self, mocked_execute):
hdparm_output = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': '\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
hdparm_output_not_enabled = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': 'not\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
mocked_execute.side_effect = [
(hdparm_output, ''),
'',
(hdparm_output_not_enabled, ''),
'',
processutils.ProcessExecutionError()
]
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.assertRaises(errors.BlockDeviceEraseError,
self.hardware._ata_erase,
block_device)
@mock.patch.object(utils, 'execute')
def test__ata_erase_security_erase_exec_exception(
self, mocked_execute):
hdparm_output = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': '\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
hdparm_output_not_enabled = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': 'not\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
mocked_execute.side_effect = [
(hdparm_output, '', '-1'),
'',
(hdparm_output_not_enabled, ''),
'',
processutils.ProcessExecutionError()
]
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.assertRaises(errors.BlockDeviceEraseError,
self.hardware._ata_erase,
block_device)
@mock.patch.object(hardware.GenericHardwareManager, '_shred_block_device')
@mock.patch.object(utils, 'execute')
def test_erase_block_device_ata_frozen(self, mocked_execute, mock_shred):
hdparm_output = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': 'not\tenabled',
@ -804,12 +932,16 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.assertRaises(errors.BlockDeviceEraseError,
self.hardware.erase_block_device,
self.node, block_device)
self.assertRaises(
errors.IncompatibleHardwareMethodError,
self.hardware.erase_block_device,
self.node,
block_device)
self.assertFalse(mock_shred.called)
@mock.patch.object(hardware.GenericHardwareManager, '_shred_block_device')
@mock.patch.object(utils, 'execute')
def test_erase_block_device_ata_failed(self, mocked_execute):
def test_erase_block_device_ata_failed(self, mocked_execute, mock_shred):
hdparm_output_before = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': 'not\tenabled',
@ -835,9 +967,52 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.assertRaises(errors.BlockDeviceEraseError,
self.hardware.erase_block_device,
self.node, block_device)
self.assertRaises(
errors.IncompatibleHardwareMethodError,
self.hardware.erase_block_device,
self.node,
block_device)
self.assertFalse(mock_shred.called)
@mock.patch.object(hardware.GenericHardwareManager, '_shred_block_device')
@mock.patch.object(utils, 'execute')
def test_erase_block_device_ata_failed_continued(
self,
mocked_execute,
mock_shred):
info = self.node.get('driver_internal_info')
info['agent_continue_if_ata_erase_failed'] = True
hdparm_output_before = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': 'not\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
# If security mode remains enabled after the erase, it is indiciative
# of a failed erase.
hdparm_output_after = HDPARM_INFO_TEMPLATE % {
'supported': '\tsupported',
'enabled': '\tenabled',
'frozen': 'not\tfrozen',
'enhanced_erase': 'not\tsupported: enhanced erase',
}
mocked_execute.side_effect = [
(hdparm_output_before, ''),
('', ''),
('', ''),
(hdparm_output_after, ''),
]
block_device = hardware.BlockDevice('/dev/sda', 'big', 1073741824,
True)
self.hardware.erase_block_device(self.node, block_device)
self.assertTrue(mock_shred.called)
def test_normal_vs_enhanced_security_erase(self):
@mock.patch.object(utils, 'execute')

@ -0,0 +1,14 @@
---
features:
- The driver_internal_info internal setting
``agent_continue_if_ata_erase_failed`` allows operators
to enable disk cleaning operations to fallback from a failed
ata_erase operation to disk shredding operations.
fixes:
- IPA will now attempt to unlock a security locked drive
with a 'NULL' password if it is found to be enabled,
however this will only work if the password was previously
set to a 'NULL' value, such as if a failure during a previous
ata_erase sequence.
- Potential command failures in the secure erase process are
now captured and raised as BlockDeviceEraseError exceptions.