diff --git a/proliantutils/hpssa/manager.py b/proliantutils/hpssa/manager.py index bad4314..6682927 100644 --- a/proliantutils/hpssa/manager.py +++ b/proliantutils/hpssa/manager.py @@ -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 diff --git a/proliantutils/hpssa/objects.py b/proliantutils/hpssa/objects.py index 51384c8..61bfd69 100644 --- a/proliantutils/hpssa/objects.py +++ b/proliantutils/hpssa/objects.py @@ -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): diff --git a/proliantutils/ipa_hw_manager/hardware_manager.py b/proliantutils/ipa_hw_manager/hardware_manager.py index 7c2df88..41b0011 100644 --- a/proliantutils/ipa_hw_manager/hardware_manager.py +++ b/proliantutils/ipa_hw_manager/hardware_manager.py @@ -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): diff --git a/proliantutils/tests/hpssa/raid_constants.py b/proliantutils/tests/hpssa/raid_constants.py index ebd22fa..69aa9e4 100644 --- a/proliantutils/tests/hpssa/raid_constants.py +++ b/proliantutils/tests/hpssa/raid_constants.py @@ -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 ''' diff --git a/proliantutils/tests/hpssa/test_manager.py b/proliantutils/tests/hpssa/test_manager.py index 41da928..f5ed340 100644 --- a/proliantutils/tests/hpssa/test_manager.py +++ b/proliantutils/tests/hpssa/test_manager.py @@ -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): diff --git a/proliantutils/tests/hpssa/test_objects.py b/proliantutils/tests/hpssa/test_objects.py index cfdcde7..b06df28 100644 --- a/proliantutils/tests/hpssa/test_objects.py +++ b/proliantutils/tests/hpssa/test_objects.py @@ -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): diff --git a/proliantutils/tests/ipa_hw_manager/test_hardware_manager.py b/proliantutils/tests/ipa_hw_manager/test_hardware_manager.py index 3d94493..4f9d47b 100644 --- a/proliantutils/tests/ipa_hw_manager/test_hardware_manager.py +++ b/proliantutils/tests/ipa_hw_manager/test_hardware_manager.py @@ -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))