Redfish: Adds 'delete_raid_configuration' API to clear raid

The commit adds functionality to delete logical drives in a
SmartStorageConfig redfish systems.

Co-Authored-By: Paresh Sao <paresh.sao@hpe.com>
Change-Id: Id3a07ab0ca1c0ad8199a28ef2efb22aadc5edfd5
This commit is contained in:
Anshul Jain 2017-09-28 14:42:55 +05:30 committed by paresh-sao
parent 21549e746d
commit 2cf3a473c9
16 changed files with 440 additions and 6 deletions

View File

@ -80,6 +80,16 @@ class IloCommandNotSupportedInBiosError(IloCommandNotSupportedError):
super(IloCommandNotSupportedInBiosError, self).__init__(message)
class IloLogicalDriveNotFoundError(IloError):
"""Logical drive not found error.
This exception is raised when iLO client library unable to find
any logical drive on storage controller
"""
def __init__(self, message, errorcode=None):
super(IloLogicalDriveNotFoundError, self).__init__(message)
class IloLoginFailError(IloError):
"""iLO Login Failed.

View File

@ -1,4 +1,4 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
# Copyright 2018 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -25,6 +25,7 @@ from proliantutils.redfish import redfish
SUPPORTED_RIS_METHODS = [
'activate_license',
'clear_secure_boot_keys',
'delete_raid_configuration',
'eject_virtual_media',
'get_current_bios_settings',
'get_current_boot_mode',
@ -68,6 +69,7 @@ SUPPORTED_RIS_METHODS = [
]
SUPPORTED_REDFISH_METHODS = [
'delete_raid_configuration',
'get_product_name',
'get_host_post_state',
'get_host_power_status',
@ -637,6 +639,15 @@ class IloClient(operations.IloOperations):
"""
return self._call_method('activate_license', key)
def delete_raid_configuration(self):
"""Deletes the logical drives from the system
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is not supported
on the server.
"""
return self._call_method('delete_raid_configuration')
def update_firmware(self, firmware_url, component_type):
"""Updates the given firmware on the server

View File

@ -445,3 +445,12 @@ class IloOperations(object):
on the server.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)
def delete_raid_configuration(self):
"""Deletes the logical drives from the system
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is
not supported on the server.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)

View File

@ -1,4 +1,4 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
# Copyright 2018 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -1209,6 +1209,17 @@ class RIBCLOperations(operations.IloOperations):
"""
self._raise_command_not_supported("get_host_post_state")
def delete_raid_configuration(self):
"""Delete the raid configuration on the hardware.
Loops through each SmartStorageConfig controller and clears the
raid configuration.
:raises: IloError, on an error from iLO
:raises: IloCommandNotSupportedError
"""
self._raise_command_not_supported("delete_raid_configuration")
# The below block of code is there only for backward-compatibility
# reasons (before commit 47608b6 for ris-support).

View File

@ -1,4 +1,4 @@
# Copyright 2017 Hewlett Packard Enterprise Development Company, L.P.
# Copyright 2018 Hewlett Packard Enterprise Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -1974,3 +1974,20 @@ class RISOperations(rest.RestConnectorBase, operations.IloOperations):
return utils.apply_bios_properties_filter(
default_settings, constants.SUPPORTED_BIOS_PROPERTIES)
return default_settings
def _raise_command_not_supported(self, method):
platform = self.get_product_name()
msg = ("`%(method)s` is not supported on %(platform)s" %
{'method': method, 'platform': platform})
raise (exception.IloCommandNotSupportedError(msg))
def delete_raid_configuration(self):
"""Delete the raid configuration on the hardware.
Loops through each SmartStorageConfig controller and clears the
raid configuration.
:raises: IloError, on an error from iLO
:raises: IloCommandNotSupportedError
"""
self._raise_command_not_supported("delete_raid_configuration")

View File

@ -1,4 +1,4 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -1050,3 +1050,8 @@ class RedfishOperations(operations.IloOperations):
"""
sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
return GET_POST_STATE_MAP.get(sushy_system.post_state)
def delete_raid_configuration(self):
"""Delete the raid configuration on the hardware."""
sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
sushy_system.delete_raid()

View File

@ -0,0 +1,55 @@
# Copyright 2018 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from proliantutils import exception
from proliantutils import log
from sushy.resources import base
LOG = log.get_logger(__name__)
class LogicalDriveListField(base.ListField):
volume_unique_identifier = base.Field('VolumeUniqueIdentifier',
required=True)
class HPESmartStorageConfig(base.ResourceBase):
"""Class that defines the functionality for SmartSorageConfig Resources."""
controller_id = base.Field("Id")
logical_drives = LogicalDriveListField("LogicalDrives", default=[])
settings_uri = base.Field(["@Redfish.Settings",
"SettingsObject", "@odata.id"])
def delete_raid(self):
"""Clears the RAID configuration from the system.
"""
if not self.logical_drives:
msg = ('No logical drives found on the controller '
'%(controller)s' % {'controller': str(self.controller_id)})
LOG.debug(msg)
raise exception.IloLogicalDriveNotFoundError(msg)
lds = [{
'Actions': [{"Action": "LogicalDriveDelete"}],
'VolumeUniqueIdentifier':
logical_drive.volume_unique_identifier}
for logical_drive in self.logical_drives]
data = {'LogicalDrives': lds, 'DataGuard': 'Permissive'}
self._conn.put(self.settings_uri, data=data)

View File

@ -1,4 +1,4 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -17,6 +17,7 @@ __author__ = 'HPE'
import sushy
from sushy.resources import base
from sushy.resources.system import system
from sushy import utils as sushy_utils
from proliantutils import exception
from proliantutils import log
@ -27,6 +28,7 @@ from proliantutils.redfish.resources.system import mappings
from proliantutils.redfish.resources.system import memory
from proliantutils.redfish.resources.system import pci_device
from proliantutils.redfish.resources.system import secure_boot
from proliantutils.redfish.resources.system import smart_storage_config
from proliantutils.redfish.resources.system.storage import simple_storage
from proliantutils.redfish.resources.system.storage import \
smart_storage as hpe_smart_storage
@ -71,6 +73,11 @@ class HPESystem(system.System):
'Boot',
'UefiTargetBootSourceOverride@Redfish.AllowableValues'],
adapter=list))
smart_storage_config_identities = base.Field(
['Oem', 'Hpe', 'SmartStorageConfig'],
adapter=sushy_utils.get_members_identities)
supported_boot_mode = base.MappedField(
['Oem', 'Hpe', 'Bios', 'UefiClass'], mappings.SUPPORTED_BOOT_MODE,
default=constants.SUPPORTED_LEGACY_BIOS_ONLY)
@ -323,3 +330,51 @@ class HPESystem(system.System):
self._memory.refresh(force=False)
return self._memory
def get_smart_storage_config(self, smart_storage_config_url):
"""Returns a SmartStorageConfig Instance for each controller."""
return (smart_storage_config.
HPESmartStorageConfig(self._conn, smart_storage_config_url,
redfish_version=self.redfish_version))
def check_smart_storage_config_ids(self):
"""Check SmartStorageConfig controllers is there in hardware.
:raises: IloError, on an error from iLO.
"""
if self.smart_storage_config_identities is None:
msg = ('The Redfish controller failed to get the '
'SmartStorageConfig controller configurations.')
LOG.debug(msg)
raise exception.IloError(msg)
def delete_raid(self):
"""Delete the raid configuration on the hardware.
Loops through each SmartStorageConfig controller and clears the
raid configuration.
:raises: IloError, on an error from iLO.
"""
self.check_smart_storage_config_ids()
any_exceptions = []
ld_exc_count = 0
for config_id in self.smart_storage_config_identities:
try:
ssc_obj = self.get_smart_storage_config(config_id)
ssc_obj.delete_raid()
except exception.IloLogicalDriveNotFoundError as e:
ld_exc_count += 1
except sushy.exceptions.SushyError as e:
any_exceptions.append((config_id, str(e)))
if any_exceptions:
msg = ('The Redfish controller failed to delete the '
'raid configuration in one or more controllers with '
'Error: %(error)s' % {'error': str(any_exceptions)})
raise exception.IloError(msg)
if ld_exc_count == len(self.smart_storage_config_identities):
msg = ('No logical drives are found in any controllers. Nothing '
'to delete.')
raise exception.IloLogicalDriveNotFoundError(msg)

View File

@ -756,6 +756,29 @@ class IloClientTestCase(testtools.TestCase):
self.client.activate_license('fake-key')
call_mock.assert_called_once_with('activate_license', 'fake-key')
@mock.patch.object(client.IloClient, '_call_method')
def test_delete_raid_configuration(self, call_mock):
self.client.delete_raid_configuration()
call_mock.assert_called_once_with('delete_raid_configuration')
@mock.patch.object(ris.RISOperations, 'get_product_name')
def test_delete_raid_configuration_gen9(self, get_product_mock):
self.client.model = 'Gen9'
get_product_mock.return_value = 'ProLiant BL460c Gen9'
self.assertRaisesRegexp(exception.IloCommandNotSupportedError,
'`delete_raid_configuration` is not supported '
'on ProLiant BL460c Gen9',
self.client.delete_raid_configuration)
@mock.patch.object(ribcl.RIBCLOperations, 'get_product_name')
def test_delete_raid_configuration_gen8(self, get_product_mock):
self.client.model = 'Gen8'
get_product_mock.return_value = 'ProLiant DL380 G8'
self.assertRaisesRegexp(exception.IloCommandNotSupportedError,
'`delete_raid_configuration` is not supported '
'on ProLiant DL380 G8',
self.client.delete_raid_configuration)
@mock.patch.object(ris.RISOperations, 'eject_virtual_media')
def test_eject_virtual_media_gen9(self, eject_virtual_media_mock):
self.client.model = 'Gen9'

View File

@ -1055,5 +1055,13 @@ class IloRibclTestCaseBeforeRisSupport(unittest.TestCase):
'ProLiant DL380 G7',
self.ilo.get_host_post_state)
@mock.patch.object(ribcl.RIBCLOperations, 'get_product_name')
def test_delete_raid_configuration(self, product_name_mock):
product_name_mock.return_value = constants.GET_PRODUCT_NAME
self.assertRaisesRegexp(exception.IloCommandNotSupportedError,
'ProLiant DL380 G7',
self.ilo.delete_raid_configuration)
if __name__ == '__main__':
unittest.main()

View File

@ -2551,3 +2551,10 @@ class TestRISOperationsPrivateMethods(testtools.TestCase):
ret = self.client._is_raid_supported()
self.assertEqual(ret, expt_ret)
get_array_mock.assert_called_once_with()
@mock.patch.object(ris.RISOperations, 'get_product_name')
def test_delete_raid_configuration(self, product_name_mock):
product_name_mock.return_value = 'ProLiant BL460c Gen9'
self.assertRaisesRegexp(exception.IloCommandNotSupportedError,
'ProLiant BL460c Gen9',
self.client.delete_raid_configuration)

View File

@ -0,0 +1,87 @@
{
"@Redfish.Settings": {
"@odata.type": "#Settings.v1_0_0.Settings",
"ETag": "78F92741",
"Messages": [
{
"MessageId": "SmartStorageMessages.2.0.0.Success"
}
],
"SettingsObject": {
"@odata.id": "/redfish/v1/systems/1/smartstorageconfig/settings/"
},
"Time": "2017-10-31T12:12:35+00:00"
},
"@odata.context": "/redfish/v1/$metadata#SmartStorageConfig.SmartStorageConfig",
"@odata.etag": "W/\"90107B96DAE4060606D29991827C7EF7\"",
"@odata.id": "/redfish/v1/systems/1/smartstorageconfig/",
"@odata.type": "#SmartStorageConfig.v2_0_0.SmartStorageConfig",
"CurrentParallelSurfaceScanCount": null,
"DataGuard": "Strict",
"DegradedPerformanceOptimization": null,
"DriveWriteCache": null,
"ElevatorSort": null,
"EncryptionConfiguration": "None",
"EncryptionEULA": null,
"ExpandPriority": null,
"FlexibleLatencySchedulerSetting": null,
"Id": "smartstorageconfig",
"InconsistencyRepairPolicy": null,
"Location": "Slot 0",
"LocationFormat": "PCISlot",
"LogicalDrives": [
{
"Accelerator": "ControllerCache",
"BlockSizeBytes": 512,
"CapacityBlocks": 4688319664,
"CapacityGiB": 2235,
"DataDrives": [
"2I:1:2",
"2I:1:1"
],
"DriveLocationFormat": "ControllerPort:Box:Bay",
"LegacyBootPriority": "None",
"LogicalDriveName": "01A27294PFJHD0ARCA218H 63E0",
"LogicalDriveNumber": 1,
"ParityGroupCount": 0,
"Raid": "Raid0",
"SpareDrives": [],
"SpareRebuildMode": null,
"StripSizeBytes": 262144,
"StripeSizeBytes": 524288,
"VolumeUniqueIdentifier": "600508B1001C045A9BAAC9F4F49498AE"
}
],
"MonitorAndPerformanceAnalysisDelaySeconds": null,
"NoBatteryWriteCache": null,
"Oem": {
"Hpe": {
"@odata.type": "#HpeBiosExt.v2_0_0.HpeBiosExt",
"SettingsObject": {
"UnmodifiedETag": "W/\"1BBCED8C1E405050504A92724569294F\""
}
}
},
"PhysicalDrives": [
{
"LegacyBootPriority": "None",
"Location": "1I:1:2",
"LocationFormat": "ControllerPort:Box:Bay"
}
],
"Ports": [
{
"OperatingModeAfterReboot": "Mixed",
"PortIndex": 0
}
],
"PowerModeAfterReboot": "MaxPerformance",
"PredictiveSpareRebuild": null,
"QueueDepth": null,
"ReadCachePercent": null,
"RebuildPriority": null,
"SurfaceScanAnalysisDelaySeconds": null,
"SurfaceScanAnalysisPriority": null,
"SurvivalPowerMode": "Enabled",
"WriteCacheBypassThresholdKiB": null
}

View File

@ -97,6 +97,11 @@
"target": "/redfish/v1/Systems/1/Actions/Oem/Hpe/HpeComputerSystemExt.SystemReset/"
}
},
"SmartStorageConfig": [
{
"@odata.id": "/redfish/v1/systems/1/smartstorageconfig/"
}
],
"AggregateHealthStatus": {
"AgentlessManagementService": "Unavailable",
"BiosOrHardwareHealth": {

View File

@ -0,0 +1,63 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
import testtools
from proliantutils import exception
from proliantutils.redfish.resources.system import smart_storage_config
class HPESmartStorageConfigTestCase(testtools.TestCase):
def setUp(self):
super(HPESmartStorageConfigTestCase, self).setUp()
self.conn = mock.MagicMock()
with open('proliantutils/tests/redfish/'
'json_samples/smart_storage_config.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.load(f))
self.ssc_inst = smart_storage_config.HPESmartStorageConfig(
self.conn, '/redfish/v1/Systems/1/smartstorageconfig',
redfish_version='1.0.2')
def test_attributes(self):
self.assertEqual('smartstorageconfig', self.ssc_inst.controller_id)
self.assertEqual(
'600508B1001C045A9BAAC9F4F49498AE',
self.ssc_inst.logical_drives[0].volume_unique_identifier)
self.assertEqual("/redfish/v1/systems/1/smartstorageconfig/settings/",
self.ssc_inst.settings_uri)
def test_delete_raid(self):
settings_uri = "/redfish/v1/systems/1/smartstorageconfig/settings/"
data = {
"LogicalDrives": [{
"Actions": [{"Action": "LogicalDriveDelete"}],
"VolumeUniqueIdentifier": "600508B1001C045A9BAAC9F4F49498AE"}],
"DataGuard": "Permissive",
}
self.ssc_inst.delete_raid()
self.ssc_inst._conn.put.assert_called_once_with(settings_uri,
data=data)
def test_delete_raid_logical_drive_not_found(self):
type(self.ssc_inst).logical_drives = mock.PropertyMock(
return_value=[])
self.assertRaises(exception.IloLogicalDriveNotFoundError,
self.ssc_inst.delete_raid)

View File

@ -1,4 +1,4 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
# Copyright 2018 Hewlett Packard Enterprise Development LP
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -25,6 +25,7 @@ from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import ethernet_interface
from proliantutils.redfish.resources.system import memory
from proliantutils.redfish.resources.system import secure_boot
from proliantutils.redfish.resources.system import smart_storage_config
from proliantutils.redfish.resources.system.storage import simple_storage
from proliantutils.redfish.resources.system.storage import smart_storage
from proliantutils.redfish.resources.system.storage import storage
@ -497,3 +498,65 @@ class HPESystemTestCase(testtools.TestCase):
def test_get_host_post_state(self):
expected = sys_cons.POST_STATE_FINISHEDPOST
self.assertEqual(expected, self.sys_inst.post_state)
@mock.patch.object(smart_storage_config, 'HPESmartStorageConfig',
autospec=True)
def test_get_smart_storage_config(self, mock_ssc):
ssc_element = '/redfish/v1/systems/1/smartstorageconfig/'
ssc_inst = self.sys_inst.get_smart_storage_config(ssc_element)
self.assertIsInstance(ssc_inst,
smart_storage_config.HPESmartStorageConfig.
__class__)
mock_ssc.assert_called_once_with(
self.conn, "/redfish/v1/systems/1/smartstorageconfig/",
redfish_version='1.0.2')
@mock.patch.object(system.HPESystem, 'get_smart_storage_config')
def test_delete_raid(self, get_smart_storage_config_mock):
config_id = ['/redfish/v1/systems/1/smartstorageconfig/']
type(self.sys_inst).smart_storage_config_identities = (
mock.PropertyMock(return_value=config_id))
self.sys_inst.delete_raid()
get_smart_storage_config_mock.assert_called_once_with(config_id[0])
(get_smart_storage_config_mock.return_value.
delete_raid.assert_called_once_with())
@mock.patch.object(system.HPESystem, 'get_smart_storage_config')
def test_delete_raid_controller_failed(self,
get_smart_storage_config_mock):
config_id = ['/redfish/v1/systems/1/smartstorageconfig/',
'/redfish/v1/systems/1/smartstorageconfig1/',
'/redfish/v1/systems/1/smartstorageconfig2/']
type(self.sys_inst).smart_storage_config_identities = (
mock.PropertyMock(return_value=config_id))
get_smart_storage_config_mock.return_value.delete_raid.side_effect = (
[None, sushy.exceptions.SushyError, None])
self.assertRaisesRegex(
exception.IloError,
"The Redfish controller failed to delete the "
"raid configuration in one or more controllers with",
self.sys_inst.delete_raid)
@mock.patch.object(system.HPESystem, 'get_smart_storage_config')
def test_delete_raid_logical_drive_not_found(
self, get_smart_storage_config_mock):
config_id = ['/redfish/v1/systems/1/smartstorageconfig/',
'/redfish/v1/systems/1/smartstorageconfig1/']
type(self.sys_inst).smart_storage_config_identities = (
mock.PropertyMock(return_value=config_id))
get_smart_storage_config_mock.return_value.delete_raid.side_effect = (
exception.IloLogicalDriveNotFoundError('No logical drive found'))
self.assertRaisesRegex(
exception.IloError,
"No logical drives are found in any controllers. "
"Nothing to delete.",
self.sys_inst.delete_raid)
def test_check_smart_storage_config_ids(self):
type(self.sys_inst).smart_storage_config_identities = (
mock.PropertyMock(return_value=None))
self.assertRaisesRegex(
exception.IloError,
"The Redfish controller failed to get the SmartStorageConfig "
"controller configurations",
self.sys_inst.check_smart_storage_config_ids)

View File

@ -1454,3 +1454,8 @@ class RedfishOperationsTestCase(testtools.TestCase):
type(get_system_mock.return_value).post_state = post_state
result = self.rf_client.get_host_post_state()
self.assertEqual('PowerOff', result)
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_delete_raid_configuration(self, get_system_mock):
self.rf_client.delete_raid_configuration()
get_system_mock.return_value.delete_raid.assert_called_once_with()