Add a HardwareManager method to erase devices
Add erase_devices method to the HardwareManager class. By default this method iterates block devices, and calls a new abstract erase_block_device method for each device. This patch includes a simple implementation of erase_block_device on the GenericHardwareManager which attempts to issue an ATA secure erase on supported devices. Change-Id: I81da065395b8785f636f1b0a0d60c9f1c045441e
This commit is contained in:
parent
8fd76c877b
commit
dff46583d3
@ -10,7 +10,7 @@ ADD . /tmp/ironic-python-agent
|
||||
RUN apt-get update && \
|
||||
apt-get -y upgrade && \
|
||||
apt-get install -y --no-install-recommends python2.7 python2.7-dev \
|
||||
python-pip qemu-utils parted util-linux genisoimage git gcc && \
|
||||
python-pip qemu-utils parted hdparm util-linux genisoimage git gcc && \
|
||||
apt-get -y autoremove && \
|
||||
apt-get clean
|
||||
|
||||
|
@ -184,5 +184,15 @@ class SystemRebootError(RESTError):
|
||||
self.details = self.details.format(exit_code)
|
||||
|
||||
|
||||
class BlockDeviceEraseError(RESTError):
|
||||
"""Error raised when an error occurs erasing a block device."""
|
||||
|
||||
message = 'Error erasing block device'
|
||||
|
||||
def __init__(self, details):
|
||||
super(BlockDeviceEraseError, self).__init__(details)
|
||||
self.details = details
|
||||
|
||||
|
||||
class ExtensionError(Exception):
|
||||
pass
|
||||
|
@ -21,6 +21,7 @@ import six
|
||||
import stevedore
|
||||
|
||||
from ironic_python_agent import encoding
|
||||
from ironic_python_agent import errors
|
||||
from ironic_python_agent.openstack.common import log
|
||||
from ironic_python_agent import utils
|
||||
|
||||
@ -106,6 +107,42 @@ class HardwareManager(object):
|
||||
def get_os_install_device(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def erase_block_device(self, block_device):
|
||||
"""Attempt to erase a block device.
|
||||
|
||||
Implementations should detect the type of device and erase it in the
|
||||
most appropriate way possible. Generic implementations should support
|
||||
common erase mechanisms such as ATA secure erase, or multi-pass random
|
||||
writes. Operators with more specific needs should override this method
|
||||
in order to detect and handle "interesting" cases, or delegate to the
|
||||
parent class to handle generic cases.
|
||||
|
||||
For example: operators running ACME MagicStore (TM) cards alongside
|
||||
standard SSDs might check whether the device is a MagicStore and use a
|
||||
proprietary tool to erase that, otherwise call this method on their
|
||||
parent class. Upstream submissions of common functionality are
|
||||
encouraged.
|
||||
|
||||
:param block_device: a BlockDevice indicating a device to be erased.
|
||||
:raises: BlockDeviceEraseError when an error occurs erasing a block
|
||||
device, or if the block device is not supported.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def erase_devices(self):
|
||||
"""Erase any device that holds user data.
|
||||
|
||||
By default this will attempt to erase block devices. This method can be
|
||||
overridden in an implementation-specific hardware manager in order to
|
||||
erase additional hardware, although backwards-compatible upstream
|
||||
submissions are encouraged.
|
||||
"""
|
||||
block_devices = self.list_block_devices()
|
||||
for block_device in block_devices:
|
||||
self.erase_block_device(block_device)
|
||||
|
||||
def list_hardware_info(self):
|
||||
hardware_info = {}
|
||||
hardware_info['interfaces'] = self.list_network_interfaces()
|
||||
@ -187,6 +224,65 @@ class GenericHardwareManager(HardwareManager):
|
||||
if device.size >= (4 * pow(1024, 3)):
|
||||
return device.name
|
||||
|
||||
def erase_block_device(self, block_device):
|
||||
if self._ata_erase(block_device):
|
||||
return
|
||||
|
||||
# NOTE(russell_h): Support for additional generic erase methods should
|
||||
# be added above this raise, in order of precedence.
|
||||
raise errors.BlockDeviceEraseError(('Unable to erase block device '
|
||||
'{0}: device is unsupported.').format(block_device.name))
|
||||
|
||||
def _get_ata_security_lines(self, block_device):
|
||||
output = utils.execute('hdparm', '-I', block_device.name)[0]
|
||||
|
||||
if '\nSecurity: ' not in output:
|
||||
return []
|
||||
|
||||
# Get all lines after the 'Security: ' line
|
||||
security_and_beyond = output.split('\nSecurity: \n')[1]
|
||||
security_and_beyond_lines = security_and_beyond.split('\n')
|
||||
|
||||
security_lines = []
|
||||
for line in security_and_beyond_lines:
|
||||
if line.startswith('\t'):
|
||||
security_lines.append(line.strip().replace('\t', ' '))
|
||||
else:
|
||||
break
|
||||
|
||||
return security_lines
|
||||
|
||||
def _ata_erase(self, block_device):
|
||||
security_lines = self._get_ata_security_lines(block_device)
|
||||
|
||||
# If secure erase isn't supported return False so erase_block_device
|
||||
# can try another mechanism. Below here, if secure erase is supported
|
||||
# but fails in some way, error out (operators of hardware that supports
|
||||
# secure erase presumably expect this to work).
|
||||
if 'supported' not in security_lines:
|
||||
return False
|
||||
|
||||
if 'enabled' in security_lines:
|
||||
raise errors.BlockDeviceEraseError(('Block device {0} already has '
|
||||
'a security password set').format(block_device.name))
|
||||
|
||||
if 'not frozen' not in security_lines:
|
||||
raise errors.BlockDeviceEraseError(('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)
|
||||
utils.execute('hdparm', '--user-master', 'u', '--security-erase',
|
||||
'NULL', block_device.name)
|
||||
|
||||
# Verify that security is now 'not enabled'
|
||||
security_lines = self._get_ata_security_lines(block_device)
|
||||
if 'not enabled' not in security_lines:
|
||||
raise errors.BlockDeviceEraseError(('An unknown error occurred '
|
||||
'erasing block device {0}').format(block_device.name))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _compare_extensions(ext1, ext2):
|
||||
mgr1 = ext1.obj
|
||||
|
@ -16,6 +16,7 @@ import mock
|
||||
from oslotest import base as test_base
|
||||
import six
|
||||
|
||||
from ironic_python_agent import errors
|
||||
from ironic_python_agent import hardware
|
||||
from ironic_python_agent import utils
|
||||
|
||||
@ -25,6 +26,93 @@ else:
|
||||
OPEN_FUNCTION_NAME = 'builtins.open'
|
||||
|
||||
|
||||
HDPARM_INFO_TEMPLATE = (
|
||||
'/dev/sda:\n'
|
||||
'\n'
|
||||
'ATA device, with non-removable media\n'
|
||||
'\tModel Number: 7 PIN SATA FDM\n'
|
||||
'\tSerial Number: 20131210000000000023\n'
|
||||
'\tFirmware Revision: SVN406\n'
|
||||
'\tTransport: Serial, ATA8-AST, SATA 1.0a, SATA II Extensions, '
|
||||
'SATA Rev 2.5, SATA Rev 2.6, SATA Rev 3.0\n'
|
||||
'Standards: \n'
|
||||
'\tSupported: 9 8 7 6 5\n'
|
||||
'\tLikely used: 9\n'
|
||||
'Configuration: \n'
|
||||
'\tLogical\t\tmax\tcurrent\n'
|
||||
'\tcylinders\t16383\t16383\n'
|
||||
'\theads\t\t16\t16\n'
|
||||
'\tsectors/track\t63\t63\n'
|
||||
'\t--\n'
|
||||
'\tCHS current addressable sectors: 16514064\n'
|
||||
'\tLBA user addressable sectors: 60579792\n'
|
||||
'\tLBA48 user addressable sectors: 60579792\n'
|
||||
'\tLogical Sector size: 512 bytes\n'
|
||||
'\tPhysical Sector size: 512 bytes\n'
|
||||
'\tLogical Sector-0 offset: 0 bytes\n'
|
||||
'\tdevice size with M = 1024*1024: 29579 MBytes\n'
|
||||
'\tdevice size with M = 1000*1000: 31016 MBytes (31 GB)\n'
|
||||
'\tcache/buffer size = unknown\n'
|
||||
'\tForm Factor: 2.5 inch\n'
|
||||
'\tNominal Media Rotation Rate: Solid State Device\n'
|
||||
'Capabilities: \n'
|
||||
'\tLBA, IORDY(can be disabled)\n'
|
||||
'\tQueue depth: 32\n'
|
||||
'\tStandby timer values: spec\'d by Standard, no device specific '
|
||||
'minimum\n'
|
||||
'\tR/W multiple sector transfer: Max = 1\tCurrent = 1\n'
|
||||
'\tDMA: mdma0 mdma1 mdma2 udma0 udma1 udma2 udma3 udma4 *udma5\n'
|
||||
'\t Cycle time: min=120ns recommended=120ns\n'
|
||||
'\tPIO: pio0 pio1 pio2 pio3 pio4\n'
|
||||
'\t Cycle time: no flow control=120ns IORDY flow '
|
||||
'control=120ns\n'
|
||||
'Commands/features: \n'
|
||||
'\tEnabled\tSupported:\n'
|
||||
'\t *\tSMART feature set\n'
|
||||
'\t \tSecurity Mode feature set\n'
|
||||
'\t *\tPower Management feature set\n'
|
||||
'\t *\tWrite cache\n'
|
||||
'\t *\tLook-ahead\n'
|
||||
'\t *\tHost Protected Area feature set\n'
|
||||
'\t *\tWRITE_BUFFER command\n'
|
||||
'\t *\tREAD_BUFFER command\n'
|
||||
'\t *\tNOP cmd\n'
|
||||
'\t \tSET_MAX security extension\n'
|
||||
'\t *\t48-bit Address feature set\n'
|
||||
'\t *\tDevice Configuration Overlay feature set\n'
|
||||
'\t *\tMandatory FLUSH_CACHE\n'
|
||||
'\t *\tFLUSH_CACHE_EXT\n'
|
||||
'\t *\tWRITE_{DMA|MULTIPLE}_FUA_EXT\n'
|
||||
'\t *\tWRITE_UNCORRECTABLE_EXT command\n'
|
||||
'\t *\tGen1 signaling speed (1.5Gb/s)\n'
|
||||
'\t *\tGen2 signaling speed (3.0Gb/s)\n'
|
||||
'\t *\tGen3 signaling speed (6.0Gb/s)\n'
|
||||
'\t *\tNative Command Queueing (NCQ)\n'
|
||||
'\t *\tHost-initiated interface power management\n'
|
||||
'\t *\tPhy event counters\n'
|
||||
'\t *\tDMA Setup Auto-Activate optimization\n'
|
||||
'\t \tDevice-initiated interface power management\n'
|
||||
'\t *\tSoftware settings preservation\n'
|
||||
'\t \tunknown 78[8]\n'
|
||||
'\t *\tSMART Command Transport (SCT) feature set\n'
|
||||
'\t *\tSCT Error Recovery Control (AC3)\n'
|
||||
'\t *\tSCT Features Control (AC4)\n'
|
||||
'\t *\tSCT Data Tables (AC5)\n'
|
||||
'\t *\tData Set Management TRIM supported (limit 2 blocks)\n'
|
||||
'Security: \n'
|
||||
'\tMaster password revision code = 65534\n'
|
||||
'\t%(supported)s\n'
|
||||
'\t%(enabled)s\n'
|
||||
'\tnot\tlocked\n'
|
||||
'\t%(frozen)s\n'
|
||||
'\tnot\texpired: security count\n'
|
||||
'\t\tsupported: enhanced erase\n'
|
||||
'\t24min for SECURITY ERASE UNIT. 24min for ENHANCED SECURITY '
|
||||
'ERASE UNIT.\n'
|
||||
'Checksum: correct\n'
|
||||
)
|
||||
|
||||
|
||||
class TestGenericHardwareManager(test_base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestGenericHardwareManager, self).setUp()
|
||||
@ -160,3 +248,120 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
|
||||
self.hardware.list_block_devices())
|
||||
self.assertEqual(hardware_info['interfaces'],
|
||||
self.hardware.list_network_interfaces())
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_erase_block_device_ata_success(self, mocked_execute):
|
||||
hdparm_info_fields = {
|
||||
'supported': '\tsupported',
|
||||
'enabled': 'not\tenabled',
|
||||
'frozen': 'not\tfrozen',
|
||||
}
|
||||
mocked_execute.side_effect = [
|
||||
(HDPARM_INFO_TEMPLATE % hdparm_info_fields, ''),
|
||||
('', ''),
|
||||
('', ''),
|
||||
(HDPARM_INFO_TEMPLATE % hdparm_info_fields, ''),
|
||||
]
|
||||
|
||||
block_device = hardware.BlockDevice('/dev/sda', 1073741824)
|
||||
self.hardware.erase_block_device(block_device)
|
||||
mocked_execute.assert_has_calls([
|
||||
mock.call('hdparm', '-I', '/dev/sda'),
|
||||
mock.call('hdparm', '--user-master', 'u', '--security-set-pass',
|
||||
'NULL', '/dev/sda'),
|
||||
mock.call('hdparm', '--user-master', 'u', '--security-erase',
|
||||
'NULL', '/dev/sda'),
|
||||
mock.call('hdparm', '-I', '/dev/sda'),
|
||||
])
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_erase_block_device_ata_nosecurtiy(self, mocked_execute):
|
||||
hdparm_output = HDPARM_INFO_TEMPLATE.split('\nSecurity:')[0]
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
(hdparm_output, '')
|
||||
]
|
||||
|
||||
block_device = hardware.BlockDevice('/dev/sda', 1073741824)
|
||||
self.assertRaises(errors.BlockDeviceEraseError,
|
||||
self.hardware.erase_block_device,
|
||||
block_device)
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_erase_block_device_ata_not_supported(self, mocked_execute):
|
||||
hdparm_output = HDPARM_INFO_TEMPLATE % {
|
||||
'supported': 'not\tsupported',
|
||||
'enabled': 'not\tenabled',
|
||||
'frozen': 'not\tfrozen',
|
||||
}
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
(hdparm_output, '')
|
||||
]
|
||||
|
||||
block_device = hardware.BlockDevice('/dev/sda', 1073741824)
|
||||
self.assertRaises(errors.BlockDeviceEraseError,
|
||||
self.hardware.erase_block_device,
|
||||
block_device)
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_erase_block_device_ata_security_enabled(self, mocked_execute):
|
||||
hdparm_output = HDPARM_INFO_TEMPLATE % {
|
||||
'supported': '\tsupported',
|
||||
'enabled': '\tenabled',
|
||||
'frozen': 'not\tfrozen',
|
||||
}
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
(hdparm_output, '')
|
||||
]
|
||||
|
||||
block_device = hardware.BlockDevice('/dev/sda', 1073741824)
|
||||
self.assertRaises(errors.BlockDeviceEraseError,
|
||||
self.hardware.erase_block_device,
|
||||
block_device)
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_erase_block_device_ata_frozen(self, mocked_execute):
|
||||
hdparm_output = HDPARM_INFO_TEMPLATE % {
|
||||
'supported': '\tsupported',
|
||||
'enabled': 'not\tenabled',
|
||||
'frozen': '\tfrozen',
|
||||
}
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
(hdparm_output, '')
|
||||
]
|
||||
|
||||
block_device = hardware.BlockDevice('/dev/sda', 1073741824)
|
||||
self.assertRaises(errors.BlockDeviceEraseError,
|
||||
self.hardware.erase_block_device,
|
||||
block_device)
|
||||
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_erase_block_device_ata_failed(self, mocked_execute):
|
||||
hdparm_output_before = HDPARM_INFO_TEMPLATE % {
|
||||
'supported': '\tsupported',
|
||||
'enabled': 'not\tenabled',
|
||||
'frozen': 'not\tfrozen',
|
||||
}
|
||||
|
||||
# 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',
|
||||
}
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
(hdparm_output_before, ''),
|
||||
('', ''),
|
||||
('', ''),
|
||||
(hdparm_output_after, ''),
|
||||
]
|
||||
|
||||
block_device = hardware.BlockDevice('/dev/sda', 1073741824)
|
||||
self.assertRaises(errors.BlockDeviceEraseError,
|
||||
self.hardware.erase_block_device,
|
||||
block_device)
|
||||
|
Loading…
x
Reference in New Issue
Block a user