Support to perform erase on all the disks(volumes and drives)

This commit adds change to perform generic IPA `erase_devices`
on success of Sanitize disk erase. Also, performs disk erase
by overwriting the disks with zero when Sanitize disk erase is
not supported.

Change-Id: I40e502a24dd2ae5f0ce4e24c4b239ce6b743f0af
Closes-Bug: #1666518
This commit is contained in:
Aparna 2017-03-09 09:07:12 +00:00
parent 7baf09af8d
commit 69f21a99aa
7 changed files with 179 additions and 80 deletions

View File

@ -371,18 +371,11 @@ def erase_devices():
"""
server = objects.Server()
select_controllers = lambda x: (x.properties.get(
'Sanitize Erase Supported',
False) == 'True')
_select_controllers_by(server, select_controllers,
'Sanitize Erase Supported')
for controller in server.controllers:
drives = [x for x in controller.unassigned_physical_drives
if (x.get_physical_drive_dict().get('erase_status', '')
== 'OK')]
if drives:
drives = ','.join(x.id for x in drives)
controller.erase_devices(drives)
common.wait_for_operation_to_complete(
@ -397,6 +390,16 @@ def erase_devices():
for controller in server.controllers:
drive_status = {x.id: x.erase_status
for x in controller.unassigned_physical_drives}
sanitize_supported = controller.properties.get(
'Sanitize Erase Supported', 'False')
if sanitize_supported == 'False':
msg = ("Drives overwritten with zeros because sanitize erase "
"is not supported on the controller.")
else:
msg = ("Sanitize Erase performed on the disks attached to "
"the controller.")
drive_status.update({'Summary': msg})
status[controller.id] = drive_status
return status

View File

@ -418,14 +418,52 @@ class Controller(object):
"""
self.execute_cmd("logicaldrive", "all", "delete", "forced")
def erase_devices(self, drive):
def _get_erase_command(self, drive, pattern):
"""Return the command arguments based on the pattern.
Erase command examples:
1) Sanitize: "ssacli ctrl slot=0 pd 1I:1:1 modify erase
erasepattern=overwrite unrestricted=off forced"
2) Zeros: "ssacli ctrl slot=0 pd 1I:1:1 modify erase
erasepattern=zero forced"
:param drive: A string with comma separated list of drives.
:param pattern: A string which defines the type of erase.
:returns: A list of ssacli command arguments.
"""
cmd_args = []
cmd_args.append("pd %s" % drive)
cmd_args.extend(['modify', 'erase',
'erasepattern=overwrite',
'unrestricted=off',
'forced'])
self.execute_cmd(*cmd_args)
cmd_args.extend(['modify', 'erase', pattern])
if pattern != 'erasepattern=zero':
cmd_args.append('unrestricted=off')
cmd_args.append('forced')
return cmd_args
def erase_devices(self, drives):
"""Perform Erase on all the drives in the controller.
This method erases all the drives in the controller by overwriting
the drives with the pattern. The drives will be unavailable until
after successful completion or failure. The possible erase pattern
available for sanitize erase are 'overwrite' and 'block' to perform
erase on HDD and SSD respectively.
If the sanitize erase is not supported on the controller it performs
the disk erase by overwritting with zeros.
:param drives: A list of drive objects in the controller.
"""
# TODO(aparnav): Add Sanitize erase support for SSD
drive = ','.join([x.id for x in drives])
cmd_args = self._get_erase_command(drive, 'erasepattern=overwrite')
stdout = self.execute_cmd(*cmd_args)
if "not supported" in str(stdout):
cmd_args = self._get_erase_command(drive,
'erasepattern=zero')
self.execute_cmd(*cmd_args)
class RaidArray(object):

View File

@ -14,7 +14,6 @@
from ironic_python_agent import hardware
from proliantutils import exception
from proliantutils.hpssa import manager as hpssa_manager
from proliantutils.hpsum import hpsum_controller
@ -92,19 +91,22 @@ class ProliantHardwareManager(hardware.GenericHardwareManager):
def erase_devices(self, node, port):
"""Erase the drives on the bare metal.
This method erase all the drives which supports sanitize on
bare metal. If fails, it falls back to the generic erase method.
This method erase all the drives which supports sanitize and the drives
which are not part of any logical volume on the bare metal. It calls
generic erase method after the success of Sanitize disk erase.
:param node: A dictionary of the node object.
:param port: A list of dictionaries containing information of ports
for the node.
:raises exception.HPSSAOperationError, if there is a failure on the
erase operation on the controllers.
:returns: The dictionary of controllers with the drives and erase
status for each drive.
"""
try:
result = {}
result['Sanitize Erase'] = hpssa_manager.erase_devices()
except exception.HPSSAOperationError:
result.update(super(ProliantHardwareManager,
self).erase_devices(node, port))
result = {}
result['Disk Erase Status'] = hpssa_manager.erase_devices()
result.update(super(ProliantHardwareManager,
self).erase_devices(node, port))
return result
def update_firmware(self, node, port):

View File

@ -2360,33 +2360,9 @@ Smart Array P440 in Slot 2
Port Location: Internal
Managed Cable Connected: False
Internal Drive Cage at Port 1I, Box 0, OK
Power Supply Status: Not Redundant
Drive Bays: 4
Port: 1I
Box: 0
Location: Internal
Physical Drives
None attached
Internal Drive Cage at Port 1I, Box 2, OK
Power Supply Status: Not Redundant
Drive Bays: 4
Port: 1I
Box: 2
Location: Internal
Physical Drives
physicaldrive 1I:2:1 (port 1I:box 2:bay 1, SAS HDD, 300 GB, OK)
unassigned
physicaldrive 1I:2:1
@ -2425,11 +2401,6 @@ Smart Array P440 in Slot 2
Primary Boot Volume: None
Secondary Boot Volume: None
Physical Drives
physicaldrive 1I:2:1 (port 1I:box 2:bay 1, SAS HDD, 300 GB, OK)
unassigned
physicaldrive 1I:2:1
@ -2451,11 +2422,6 @@ Smart Array P440 in Slot 2
Primary Boot Volume: None
Secondary Boot Volume: None
Physical Drives
physicaldrive 1I:2:1 (port 1I:box 2:bay 1, SAS HDD, 300 GB, OK)
unassigned
physicaldrive 1I:2:1
@ -2482,4 +2448,41 @@ Smart Array P440 in Slot 2
Primary Boot Volume: None
Secondary Boot Volume: None
unassigned
physicaldrive 1I:2:1
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 300 GB
Status: OK
Drive Type: Unassigned Drive
Sanitize Erase Supported: False
Sanitize Estimated Max Erase Time: 0 hour(s)36 minute(s)
Unrestricted Sanitize Supported: False
'''
SSA_ERASE_COMPLETE_NOT_SUPPORTED = '''
Smart Array P440 in Slot 2
Controller Status: OK
Firmware Version: 4.52-0
Spare Activation Mode: Activate on physical drive failure (default)
Controller Mode: RAID
Pending Controller Mode: RAID
Controller Mode Reboot: Not Required
Sanitize Erase Supported: False
Primary Boot Volume: None
Secondary Boot Volume: None
unassigned
physicaldrive 1I:2:1
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 300 GB
Status: Erase Complete. Reenable Before Using.
Drive Type: Unassigned Drive
Sanitize Erase Supported: False
Sanitize Estimated Max Erase Time: 0 hour(s)36 minute(s)
Unrestricted Sanitize Supported: False
'''

View File

@ -493,7 +493,9 @@ class ManagerTestCases(testtools.TestCase):
'forced'])
expt_ret = {
'Smart Array P440 in Slot 2': {
'1I:2:1': 'Erase Complete. Reenable Before Using.'}}
'1I:2:1': 'Erase Complete. Reenable Before Using.',
'Summary': ('Sanitize Erase performed on the disks attached to'
' the controller.')}}
get_all_details_mock.side_effect = [erase_drive, erase_complete,
erase_complete]
@ -502,17 +504,6 @@ class ManagerTestCases(testtools.TestCase):
controller_exec_cmd_mock.assert_any_call(*cmd_args)
self.assertEqual(expt_ret, ret)
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_erase_devices_no_drives(self, controller_exec_cmd_mock,
get_all_details_mock):
erase_no_drives = raid_constants.SSA_ERASE_NOT_SUPPORTED
get_all_details_mock.side_effect = [erase_no_drives]
ex = self.assertRaises(exception.HPSSAOperationError,
manager.erase_devices)
expt_ret = ("None of the available SSA controllers Smart "
"Array P440 in Slot 2 have Sanitize Erase Supported.")
self.assertIn(expt_ret, str(ex))
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_erase_devices_in_progress(self, controller_exec_cmd_mock,
get_all_details_mock):
@ -521,7 +512,9 @@ class ManagerTestCases(testtools.TestCase):
expt_ret = {
'Smart Array P440 in Slot 2': {
'1I:2:1': 'Erase Complete. Reenable Before Using.'}}
'1I:2:1': 'Erase Complete. Reenable Before Using.',
'Summary': ('Sanitize Erase performed on the disks attached to'
' the controller.')}}
get_all_details_mock.side_effect = [erase_progress, erase_complete,
erase_complete]
@ -529,6 +522,27 @@ class ManagerTestCases(testtools.TestCase):
self.assertFalse(controller_exec_cmd_mock.called)
self.assertEqual(expt_ret, ret)
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_erase_devices_not_supported(self, controller_exec_cmd_mock,
get_all_details_mock):
erase_not_supported = raid_constants.SSA_ERASE_NOT_SUPPORTED
erase_complete = raid_constants.SSA_ERASE_COMPLETE_NOT_SUPPORTED
get_all_details_mock.side_effect = [erase_not_supported,
erase_complete, erase_complete]
value = ("Drive 1I:2:1: This operation is not supported in this "
"physical drive")
controller_exec_cmd_mock.return_value = value
expt_ret = {
'Smart Array P440 in Slot 2': {
'1I:2:1': 'Erase Complete. Reenable Before Using.',
'Summary': ('Drives overwritten with zeros because '
'sanitize erase is not supported on the '
'controller.')
}}
ret = manager.erase_devices()
self.assertEqual(expt_ret, ret)
class RaidConfigValidationTestCases(testtools.TestCase):

View File

@ -344,13 +344,49 @@ class ControllerTest(testtools.TestCase):
get_all_details_mock):
get_all_details_mock.return_value = raid_constants.SSA_ERASE_DRIVE
server = objects.Server()
d = [x for x in server.controllers[0].unassigned_physical_drives]
controller = server.controllers[0]
controller.erase_devices('1I:2:1')
controller.erase_devices(d)
execute_mock.assert_called_once_with('pd 1I:2:1', 'modify', 'erase',
'erasepattern=overwrite',
'unrestricted=off',
'forced')
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_erase_devices_sanitize_not_supported(self, execute_mock,
get_all_details_mock):
erase_not_supported = raid_constants.SSA_ERASE_NOT_SUPPORTED
get_all_details_mock.return_value = erase_not_supported
server = objects.Server()
d = [x for x in server.controllers[0].unassigned_physical_drives]
controller = server.controllers[0]
value = ("Drive 1I:2:1: This operation is not supported in this "
"physical drive")
execute_mock.return_value = value
controller.erase_devices(d)
calls = [mock.call('pd 1I:2:1', 'modify', 'erase',
'erasepattern=overwrite', 'unrestricted=off',
'forced'),
mock.call('pd 1I:2:1', 'modify', 'erase',
'erasepattern=zero', 'forced')]
execute_mock.assert_has_calls(calls)
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_erase_devices_exception(self, execute_mock,
get_all_details_mock):
get_all_details_mock.return_value = raid_constants.SSA_ERASE_DRIVE
server = objects.Server()
d = [x for x in server.controllers[0].unassigned_physical_drives]
controller = server.controllers[0]
value = 'Some Exception'
execute_mock.side_effect = [exception.HPSSAOperationError(
reason=value), None]
ex = self.assertRaises(exception.HPSSAOperationError,
controller.erase_devices, d)
self.assertIn(value, str(ex))
@mock.patch.object(objects.Server, '_get_all_details')
class LogicalDriveTest(testtools.TestCase):

View File

@ -61,14 +61,19 @@ class ProliantHardwareManagerTestCase(testtools.TestCase):
delete_mock.assert_called_once_with()
self.assertEqual('current-config', ret)
@mock.patch.object(ironic_python_agent.hardware.GenericHardwareManager,
'erase_devices')
@mock.patch.object(hpssa_manager, 'erase_devices')
def test_erase_devices(self, erase_mock):
def test_erase_devices(self, erase_mock, generic_erase_mock):
node = {}
port = {}
erase_mock.return_value = 'erase_status'
generic_erase_mock.return_value = {'foo': 'bar'}
ret = self.hardware_manager.erase_devices(node, port)
erase_mock.assert_called_once_with()
self.assertEqual({'Sanitize Erase': 'erase_status'}, ret)
generic_erase_mock.assert_called_once_with(node, port)
self.assertEqual({'Disk Erase Status': 'erase_status', 'foo': 'bar'},
ret)
@mock.patch.object(ironic_python_agent.hardware.GenericHardwareManager,
'erase_devices')
@ -78,13 +83,11 @@ class ProliantHardwareManagerTestCase(testtools.TestCase):
port = {}
value = ("Sanitize erase not supported in the "
"available controllers")
e = exception.HPSSAOperationError(value)
e = exception.HPSSAOperationError(reason=value)
erase_mock.side_effect = e
generic_erase_mock.return_value = {'foo': 'bar'}
expt_return = {
'Sanitize Erase': erase_mock.side_effect}
expt_return.update(generic_erase_mock.return_value)
self.hardware_manager.erase_devices(node, port)
exc = self.assertRaises(exception.HPSSAOperationError,
self.hardware_manager.erase_devices,
node, port)
generic_erase_mock.assert_called_once_with(node, port)
self.assertIn(value, str(exc))