diff --git a/requirements.txt b/requirements.txt index d09b88e..be8ab4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 python-dateutil>=2.7.0 # BSD -sushy>=3.7.0 # Apache-2.0 +sushy>=3.11.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 93a594d..57cc8f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,8 @@ packages = [entry_points] sushy.resources.manager.oems = dell = sushy_oem_idrac.resources.manager.manager:get_extension - +sushy.resources.system.oems = + dell = sushy_oem_idrac.resources.system.system:get_extension [build_sphinx] source-dir = doc/source diff --git a/sushy_oem_idrac/resources/__init__.py b/sushy_oem_idrac/resources/__init__.py index e69de29..e290842 100644 --- a/sushy_oem_idrac/resources/__init__.py +++ b/sushy_oem_idrac/resources/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) 2021 Dell Inc. or its subsidiaries. +# +# 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 sushy_oem_idrac.resources.system.constants import * # noqa diff --git a/sushy_oem_idrac/resources/system/constants.py b/sushy_oem_idrac/resources/system/constants.py new file mode 100644 index 0000000..730c9b6 --- /dev/null +++ b/sushy_oem_idrac/resources/system/constants.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021 Dell Inc. or its subsidiaries. +# +# 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. + +PHYSICAL_DISK_STATE_MODE_RAID = 'RAID' +"""RAID mode""" + +PHYSICAL_DISK_STATE_MODE_NONRAID = 'Non-RAID' +"""Non-RAID mode""" diff --git a/sushy_oem_idrac/resources/system/raid_service.py b/sushy_oem_idrac/resources/system/raid_service.py new file mode 100644 index 0000000..e94acdf --- /dev/null +++ b/sushy_oem_idrac/resources/system/raid_service.py @@ -0,0 +1,108 @@ +# Copyright (c) 2021 Dell Inc. or its subsidiaries. +# +# 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 logging + +from sushy import exceptions +from sushy.resources import base +from sushy.resources import common +from sushy import taskmonitor + +LOG = logging.getLogger(__name__) + + +class ActionsField(base.CompositeField): + convert_to_raid = common.ActionField("#DellRaidService.ConvertToRAID") + convert_to_nonraid = common.ActionField( + "#DellRaidService.ConvertToNonRAID") + + +class DellRaidService(base.ResourceBase): + identity = base.Field('Id', required=True) + _actions = ActionsField('Actions') + + def __init__(self, connector, identity, redfish_version=None, + registries=None, root=None): + """A class representing a DellRaidService. + + :param connector: A Connector instance + :param identity: The identity of the DellRaidService resource + :param redfish_version: The version of Redfish. Used to + construct the object according to schema of the given + version. + :param registries: Dict of Redfish Message Registry objects to + be used in any resource that needs registries to parse + messages. + :param root: Sushy root object. Empty for Sushy root itself. + """ + super(DellRaidService, self).__init__( + connector, identity, redfish_version=redfish_version, + registries=registries, root=root) + + def convert_to_raid(self, physical_disk_fqdds): + """Converts physical disks to a state usable for RAID + + :param physical_disk_fqdds: An array of FQDDs where each + identifies a physical drive. + :returns: Sushy's TaskMonitor instance for TaskService task + """ + target_uri = self._actions.convert_to_raid.target_uri + payload = {'PDArray': physical_disk_fqdds} + response = self._conn.post(target_uri, data=payload) + task_monitor = self._get_task_monitor_from_dell_job(response) + LOG.info('Converting to RAID mode: %s', physical_disk_fqdds) + return task_monitor + + def convert_to_nonraid(self, physical_disk_fqdds): + """Converts physical disks to non-RAID state. + + :param physical_disk_fqdds: An array of FQDDs where each + identifies a physical drive. + :returns: Sushy's TaskMonitor instance for TaskService task + """ + target_uri = self._actions.convert_to_nonraid.target_uri + payload = {'PDArray': physical_disk_fqdds} + response = self._conn.post(target_uri, data=payload) + task_monitor = self._get_task_monitor_from_dell_job(response) + LOG.info('Converting to non-RAID mode: %s', physical_disk_fqdds) + return task_monitor + + def _get_task_monitor_from_dell_job(self, response): + """From OEM job response returns generic Task monitor + + :param response: Response from OEM job + :returns: Sushy's TaskMonitor instance for TaskService task + """ + location = response.headers.get('Location') + if not location: + raise exceptions.ExtensionError( + error='Response %s does not include Location in header' + % (response.url)) + + task_id = location.split('/')[-1] + task = None + + for t in self.root.get_task_service().tasks.get_members(): + if t.identity == task_id: + task = t + break + + if not task: + raise exceptions.ExtensionError( + error="Did not find task by id %s" % task_id) + + return taskmonitor.TaskMonitor( + self._conn, task.path, + redfish_version=self.redfish_version, + registries=self.registries) diff --git a/sushy_oem_idrac/resources/system/system.py b/sushy_oem_idrac/resources/system/system.py new file mode 100644 index 0000000..4458c83 --- /dev/null +++ b/sushy_oem_idrac/resources/system/system.py @@ -0,0 +1,127 @@ +# Copyright (c) 2021 Dell Inc. or its subsidiaries. +# +# 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 sushy +from sushy import exceptions +from sushy.resources.oem import base as oem_base +from sushy import utils as sushy_utils + +from sushy_oem_idrac.resources.system import constants as sys_cons +from sushy_oem_idrac.resources.system import raid_service + + +def _filter_disks_not_in_mode(controller_to_disks, mode): + """Filters disks that are not in requested mode + + :param controller_to_disks: dictionary of controllers and their drives + :param mode: constants.PHYSICAL_DISK_STATE_MODE_RAID or + constants.PHYSICAL_DISK_STATE_MODE_NONRAID + :returns: dictionary of controllers and their drives that need mode changed + """ + sushy_raw_device = sushy.VOLUME_TYPE_RAW_DEVICE + for controller, drives in controller_to_disks.items(): + toprocess_drives = [] + for drive in drives: + is_raw_device = False + volumes = None + try: + volumes = drive.volumes + except exceptions.MissingAttributeError: + pass + + if (volumes + and (volumes[0].volume_type == sushy_raw_device + or volumes[0].raid_type is None)): + is_raw_device = True + + if (mode == sys_cons.PHYSICAL_DISK_STATE_MODE_RAID + and is_raw_device + or mode == sys_cons.PHYSICAL_DISK_STATE_MODE_NONRAID + and not is_raw_device): + toprocess_drives.append(drive) + controller_to_disks[controller] = toprocess_drives + return controller_to_disks + + +class DellSystemExtension(oem_base.OEMResourceBase): + + @property + @sushy_utils.cache_it + def raid_service(self): + """`DellRaidService` of the system""" + + path = sushy_utils.get_sub_resource_path_by( + self, ["Links", "Oem", "Dell", "DellRaidService"], + is_collection=False) + + return raid_service.DellRaidService( + self._conn, path, redfish_version=self.redfish_version, + registries=self.registries, root=self.root) + + def change_physical_disk_state(self, mode, controller_to_disks=None): + """Converts physical disks RAID status + + Converts only those disks that are not already in requested mode. + + :param mode: constants.PHYSICAL_DISK_STATE_MODE_RAID or + constants.PHYSICAL_DISK_STATE_MODE_NONRAID + :param controller_to_disks: dictionary of controllers and their drives. + Optional, if not provided, processes all RAID, except BOSS, + controller drives. + :returns: List of task monitors for each controller's disks if any + drives need changes + """ + if not controller_to_disks: + controller_to_disks = self._get_controller_to_disks() + + # Do not process BOSS controllers as can't convert their disks + boss_controllers = [c for c in controller_to_disks + if 'BOSS' in c.name.upper()] + for c in boss_controllers: + controller_to_disks.pop(c) + + controller_to_disks = _filter_disks_not_in_mode( + controller_to_disks, mode) + + # Convert by each controller that have eligible disks + task_monitors = [] + for controller, drives in controller_to_disks.items(): + if drives: + drive_fqdds = [d.identity for d in drives] + if mode == sys_cons.PHYSICAL_DISK_STATE_MODE_RAID: + task_monitors.append( + self.raid_service.convert_to_raid(drive_fqdds)) + elif mode == sys_cons.PHYSICAL_DISK_STATE_MODE_NONRAID: + task_monitors.append( + self.raid_service.convert_to_nonraid(drive_fqdds)) + + return task_monitors + + def _get_controller_to_disks(self): + """Gets all RAID controllers and their disks on system + + :returns: dictionary of RAID controllers and their disks + """ + controller_to_disks = {} + for storage in self._parent_resource.storage.get_members(): + controller = (storage.storage_controllers[0] + if storage.storage_controllers else None) + if not controller or controller and not controller.raid_types: + continue + controller_to_disks[controller] = storage.drives + return controller_to_disks + + +def get_extension(*args, **kwargs): + return DellSystemExtension diff --git a/sushy_oem_idrac/tests/unit/json_samples/raid_service.json b/sushy_oem_idrac/tests/unit/json_samples/raid_service.json new file mode 100644 index 0000000..3279bde --- /dev/null +++ b/sushy_oem_idrac/tests/unit/json_samples/raid_service.json @@ -0,0 +1,213 @@ +{ + "@odata.context": "/redfish/v1/$metadata#DellRaidService.DellRaidService", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService", + "@odata.type": "#DellRaidService.v1_3_0.DellRaidService", + "Actions": { + "#DellRaidService.AssignSpare": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.AssignSpare" + }, + "#DellRaidService.BlinkTarget": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.BlinkTarget" + }, + "#DellRaidService.CancelBackgroundInitialization": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.CancelBackgroundInitialization" + }, + "#DellRaidService.CancelCheckConsistency": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.CancelCheckConsistency" + }, + "#DellRaidService.CancelRebuildPhysicalDisk": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.CancelRebuildPhysicalDisk" + }, + "#DellRaidService.ChangePDState": { + "State@Redfish.AllowableValues": [ + "Offline", + "Online" + ], + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ChangePDState" + }, + "#DellRaidService.CheckVDValues": { + "VDPropNameArrayIn@Redfish.AllowableValues": [ + "RAIDLevel", + "Size", + "SpanDepth", + "SpanLength", + "StartingLBA", + "T10PIStatus" + ], + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.CheckVDValues" + }, + "#DellRaidService.ClearControllerPreservedCache": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ClearControllerPreservedCache" + }, + "#DellRaidService.ClearForeignConfig": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ClearForeignConfig" + }, + "#DellRaidService.ConvertToNonRAID": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ConvertToNonRAID" + }, + "#DellRaidService.ConvertToRAID": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ConvertToRAID" + }, + "#DellRaidService.EnableControllerEncryption": { + "Mode@Redfish.AllowableValues": [ + "DKM", + "LKM", + "SEKM" + ], + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.EnableControllerEncryption" + }, + "#DellRaidService.GetAvailableDisks": { + "BlockSizeInBytes@Redfish.AllowableValues": [ + "4096", + "512", + "All" + ], + "DiskEncrypt@Redfish.AllowableValues": [ + "All", + "FDE", + "NonFDE" + ], + "DiskType@Redfish.AllowableValues": [ + "All", + "HDD", + "SSD" + ], + "Diskprotocol@Redfish.AllowableValues": [ + "AllProtocols", + "NVMe", + "SAS", + "SATA" + ], + "FormFactor@Redfish.AllowableValues": [ + "All", + "M.2" + ], + "RaidLevel@Redfish.AllowableValues": [ + "NoRAID", + "RAID0", + "RAID1", + "RAID10", + "RAID5", + "RAID50", + "RAID6", + "RAID60" + ], + "T10PIStatus@Redfish.AllowableValues": [ + "All", + "T10PICapable", + "T10PIIncapable" + ], + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.GetAvailableDisks" + }, + "#DellRaidService.GetDHSDisks": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.GetDHSDisks" + }, + "#DellRaidService.GetRAIDLevels": { + "BlockSizeInBytes@Redfish.AllowableValues": [ + "4096", + "512", + "All" + ], + "DiskEncrypt@Redfish.AllowableValues": [ + "All", + "FDE", + "NonFDE" + ], + "DiskType@Redfish.AllowableValues": [ + "All", + "HDD", + "SSD" + ], + "Diskprotocol@Redfish.AllowableValues": [ + "AllProtocols", + "NVMe", + "SAS", + "SATA" + ], + "FormFactor@Redfish.AllowableValues": [ + "All", + "M.2" + ], + "T10PIStatus@Redfish.AllowableValues": [ + "All", + "T10PICapable", + "T10PIIncapable" + ], + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.GetRAIDLevels" + }, + "#DellRaidService.ImportForeignConfig": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ImportForeignConfig" + }, + "#DellRaidService.LockVirtualDisk": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.LockVirtualDisk" + }, + "#DellRaidService.OnlineCapacityExpansion": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.OnlineCapacityExpansion" + }, + "#DellRaidService.PrepareToRemove": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.PrepareToRemove" + }, + "#DellRaidService.RAIDLevelMigration": { + "NewRaidLevel@Redfish.AllowableValues": [ + "NoRAID", + "RAID0", + "RAID1", + "RAID10", + "RAID5", + "RAID50", + "RAID6", + "RAID60" + ], + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.RAIDLevelMigration" + }, + "#DellRaidService.ReKey": { + "Mode@Redfish.AllowableValues": [ + "LKM", + "SEKM" + ], + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ReKey" + }, + "#DellRaidService.RebuildPhysicalDisk": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.RebuildPhysicalDisk" + }, + "#DellRaidService.RemoveControllerKey": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.RemoveControllerKey" + }, + "#DellRaidService.RenameVD": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.RenameVD" + }, + "#DellRaidService.ReplacePhysicalDisk": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ReplacePhysicalDisk" + }, + "#DellRaidService.ResetConfig": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.ResetConfig" + }, + "#DellRaidService.SetAssetName": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.SetAssetName" + }, + "#DellRaidService.SetBootVD": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.SetBootVD" + }, + "#DellRaidService.SetControllerKey": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.SetControllerKey" + }, + "#DellRaidService.StartPatrolRead": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.StartPatrolRead" + }, + "#DellRaidService.StopPatrolRead": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.StopPatrolRead" + }, + "#DellRaidService.UnBlinkTarget": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.UnBlinkTarget" + }, + "#DellRaidService.UnLockSecureForeignConfig": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.UnLockSecureForeignConfig" + }, + "#DellRaidService.UnassignSpare": { + "target": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/Actions/DellRaidService.UnassignSpare" + } + }, + "Description": "The DellRaidService resource provides some actions to support RAID functionality.", + "Id": "DellRaidService", + "Name": "DellRaidService" +} \ No newline at end of file diff --git a/sushy_oem_idrac/tests/unit/json_samples/system.json b/sushy_oem_idrac/tests/unit/json_samples/system.json new file mode 100644 index 0000000..f14400f --- /dev/null +++ b/sushy_oem_idrac/tests/unit/json_samples/system.json @@ -0,0 +1,398 @@ +{ + "@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1", + "@odata.type": "#ComputerSystem.v1_10_0.ComputerSystem", + "Actions": { + "#ComputerSystem.Reset": { + "target": "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset", + "ResetType@Redfish.AllowableValues": [ + "On", + "ForceOff", + "ForceRestart", + "GracefulRestart", + "GracefulShutdown", + "PushPowerButton", + "Nmi", + "PowerCycle" + ] + } + }, + "AssetTag": "", + "Bios": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Bios" + }, + "BiosVersion": "2.10.2", + "Boot": { + "BootOptions": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/BootOptions" + }, + "Certificates": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Boot/Certificates" + }, + "BootOrder": [ + "Boot0006", + "Boot0009", + "Boot0007", + "Boot0005", + "Boot0003", + "Boot0004", + "Boot0008", + "Boot0002" + ], + "BootOrder@odata.count": 8, + "BootSourceOverrideEnabled": "Disabled", + "BootSourceOverrideMode": "UEFI", + "BootSourceOverrideTarget": "None", + "UefiTargetBootSourceOverride": null, + "BootSourceOverrideTarget@Redfish.AllowableValues": [ + "None", + "Pxe", + "Floppy", + "Cd", + "Hdd", + "BiosSetup", + "Utilities", + "UefiTarget", + "SDCard", + "UefiHttp" + ] + }, + "Description": "Computer System which represents a machine (physical or virtual) and the local resources such as memory, cpu and other devices that can be accessed from that machine.", + "EthernetInterfaces": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces" + }, + "HostName": "", + "HostWatchdogTimer": { + "FunctionEnabled": false, + "Status": { + "State": "Disabled" + }, + "TimeoutAction": "None" + }, + "HostingRoles": [], + "HostingRoles@odata.count": 0, + "Id": "System.Embedded.1", + "IndicatorLED": "Lit", + "Links": { + "Chassis": [ + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1" + } + ], + "Chassis@odata.count": 1, + "CooledBy": [ + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/0" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/1" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/2" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/3" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/4" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/5" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/6" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/7" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/8" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/9" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/10" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/11" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/12" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/13" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/14" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/15" + } + ], + "CooledBy@odata.count": 16, + "ManagedBy": [ + { + "@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1" + } + ], + "ManagedBy@odata.count": 1, + "Oem": { + "Dell": { + "@odata.type": "#DellOem.v1_1_0.DellOemLinks", + "BootOrder": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources" + }, + "DellBootSources": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources" + }, + "DellSoftwareInstallationService": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSoftwareInstallationService" + }, + "DellVideoCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideo" + }, + "DellChassisCollection": { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Oem/Dell/DellChassis" + }, + "DellPresenceAndStatusSensorCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPresenceAndStatusSensors" + }, + "DellSensorCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSensors" + }, + "DellRollupStatusCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRollupStatus" + }, + "DellPSNumericSensorCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPSNumericSensors" + }, + "DellVideoNetworkCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideoNetwork" + }, + "DellOSDeploymentService": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellOSDeploymentService" + }, + "DellMetricService": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellMetricService" + }, + "DellGPUSensorCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellGPUSensors" + }, + "DellRaidService": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService" + }, + "DellNumericSensorCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellNumericSensors" + }, + "DellBIOSService": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBIOSService" + }, + "DellSlotCollection": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSlots" + } + } + }, + "PoweredBy": [ + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/0" + }, + { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/1" + } + ], + "PoweredBy@odata.count": 2 + }, + "Manufacturer": "Dell Inc.", + "Memory": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Memory" + }, + "MemorySummary": { + "MemoryMirroring": "System", + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + }, + "TotalSystemMemoryGiB": 192 + }, + "Model": "PowerEdge R640", + "Name": "System", + "NetworkInterfaces": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/NetworkInterfaces" + }, + "Oem": { + "Dell": { + "@odata.type": "#DellOem.v1_1_0.DellOemResources", + "DellSystem": { + "BIOSReleaseDate": "02/24/2021", + "BaseBoardChassisSlot": "NA", + "BatteryRollupStatus": "OK", + "BladeGeometry": "NotApplicable", + "CMCIP": null, + "CPURollupStatus": "OK", + "ChassisModel": "", + "ChassisName": "Main System Chassis", + "ChassisServiceTag": "8CYCZ23", + "ChassisSystemHeightUnit": 1, + "CurrentRollupStatus": "OK", + "EstimatedExhaustTemperatureCelsius": 46, + "EstimatedSystemAirflowCFM": 26, + "ExpressServiceCode": "18197565051", + "FanRollupStatus": "OK", + "Id": "System.Embedded.1", + "IDSDMRollupStatus": null, + "IntrusionRollupStatus": "OK", + "IsOEMBranded": "False", + "LastSystemInventoryTime": "2021-06-16T09:46:25+00:00", + "LastUpdateTime": "2021-05-31T15:39:10+00:00", + "LicensingRollupStatus": "OK", + "MaxCPUSockets": 2, + "MaxDIMMSlots": 24, + "MaxPCIeSlots": 3, + "MemoryOperationMode": "OptimizerMode", + "Name": "DellSystem", + "NodeID": "8CYCZ23", + "PSRollupStatus": "OK", + "PlatformGUID": "33325a4f-c0b8-4380-5910-00434c4c4544", + "PopulatedDIMMSlots": 12, + "PopulatedPCIeSlots": 1, + "PowerCapEnabledState": "Disabled", + "SDCardRollupStatus": null, + "SELRollupStatus": "OK", + "ServerAllocationWatts": null, + "StorageRollupStatus": "OK", + "SysMemErrorMethodology": "Multi-bitECC", + "SysMemFailOverState": "NotInUse", + "SysMemLocation": "SystemBoardOrMotherboard", + "SysMemPrimaryStatus": "OK", + "SystemGeneration": "14G Monolithic", + "SystemID": 1814, + "SystemRevision": "I", + "TempRollupStatus": "OK", + "TempStatisticsRollupStatus": "OK", + "UUID": "4c4c4544-0043-5910-8043-b8c04f5a3233", + "VoltRollupStatus": "OK", + "smbiosGUID": "44454c4c-4300-1059-8043-b8c04f5a3233", + "@odata.context": "/redfish/v1/$metadata#DellSystem.DellSystem", + "@odata.type": "#DellSystem.v1_2_0.DellSystem", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSystem/System.Embedded.1" + } + } + }, + "PCIeDevices": [ + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/25-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/24-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-28" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-23" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-31" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/3-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-17" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/59-0" + } + ], + "PCIeDevices@odata.count": 9, + "PCIeFunctions": [ + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/25-0/PCIeFunctions/25-0-1" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/24-0/PCIeFunctions/24-0-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/25-0/PCIeFunctions/25-0-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-28/PCIeFunctions/0-28-4" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-23/PCIeFunctions/0-23-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-31/PCIeFunctions/0-31-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-28/PCIeFunctions/0-28-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/3-0/PCIeFunctions/3-0-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-0/PCIeFunctions/0-0-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-17/PCIeFunctions/0-17-5" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/59-0/PCIeFunctions/59-0-1" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/59-0/PCIeFunctions/59-0-0" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-31/PCIeFunctions/0-31-4" + } + ], + "PCIeFunctions@odata.count": 13, + "PartNumber": "06NR82A02", + "PowerState": "On", + "ProcessorSummary": { + "Count": 2, + "LogicalProcessorCount": 80, + "Model": "Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz", + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + } + }, + "Processors": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Processors" + }, + "SKU": "8CYCZ23", + "SecureBoot": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/SecureBoot" + }, + "SerialNumber": "CNIVC000180873", + "SimpleStorage": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/SimpleStorage" + }, + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + }, + "Storage": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Storage" + }, + "SystemType": "Physical", + "TrustedModules": [ + { + "FirmwareVersion": "Unknown", + "InterfaceType": null, + "Status": { + "State": "Disabled" + } + } + ], + "TrustedModules@odata.count": 1, + "UUID": "4c4c4544-0043-5910-8043-b8c04f5a3233" +} \ No newline at end of file diff --git a/sushy_oem_idrac/tests/unit/test_raid_service.py b/sushy_oem_idrac/tests/unit/test_raid_service.py new file mode 100644 index 0000000..b793d65 --- /dev/null +++ b/sushy_oem_idrac/tests/unit/test_raid_service.py @@ -0,0 +1,122 @@ +# Copyright (c) 2021 Dell Inc. or its subsidiaries. +# +# 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 +from unittest import mock + +from oslotest.base import BaseTestCase +from sushy import exceptions +from sushy import taskmonitor + +from sushy_oem_idrac.resources.system import raid_service + + +class DellRaidService(BaseTestCase): + + def setUp(self): + super(DellRaidService, self).setUp() + self.conn = mock.Mock() + with open('sushy_oem_idrac/tests/unit/json_samples/' + 'raid_service.json') as f: + mock_response = self.conn.get.return_value + mock_response.json.return_value = json.load(f) + mock_response.status_code = 200 + + self.mock_response = mock.Mock() + self.mock_response.status_code = 202 + self.mock_response.headers = { + 'Location': '/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/' + 'JID_999888777666' + } + + self.root = mock.Mock() + + self.raid_service = raid_service.DellRaidService( + self.conn, + '/redfish/v1/Systems/System.Embedded.1/Oem/Dell/' + 'DellRaidService', root=self.root + ) + + @mock.patch.object(raid_service.DellRaidService, + '_get_task_monitor_from_dell_job', autospec=True) + def test_convert_to_raid(self, mock_get_task_mon): + mock_task_mon = mock.Mock() + mock_get_task_mon.return_value = mock_task_mon + fqdds = ["Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1", + "Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1"] + + task_mon = self.raid_service.convert_to_raid(fqdds) + + self.conn.post.assert_called_once_with( + '/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/' + 'Actions/DellRaidService.ConvertToRAID', + data={'PDArray': fqdds}) + self.assertEqual(mock_task_mon, task_mon) + + @mock.patch.object(raid_service.DellRaidService, + '_get_task_monitor_from_dell_job', autospec=True) + def test_convert_to_nonraid(self, mock_get_task_mon): + mock_task_mon = mock.Mock() + mock_get_task_mon.return_value = mock_task_mon + fqdds = ["Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1", + "Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1"] + + task_mon = self.raid_service.convert_to_nonraid(fqdds) + + self.conn.post.assert_called_once_with( + '/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService/' + 'Actions/DellRaidService.ConvertToNonRAID', + data={'PDArray': fqdds}) + self.assertEqual(mock_task_mon, task_mon) + + def test__get_task_monitor_from_dell_job(self): + mock_task1 = mock.Mock(identity='JID_111222333444', + path='/TaskService/Task/JID_111222333444') + mock_task2 = mock.Mock(identity='JID_999888777666', + path='/TaskService/Task/JID_999888777666') + mock_tasks = mock.Mock() + mock_tasks.get_members.return_value = [mock_task1, mock_task2] + self.root.get_task_service.return_value.tasks = mock_tasks + + task_mon = self.raid_service._get_task_monitor_from_dell_job( + self.mock_response) + + self.assertIsInstance(task_mon, taskmonitor.TaskMonitor) + self.assertEqual('/TaskService/Task/JID_999888777666', + task_mon.task_monitor_uri) + + def test__get_task_monitor_from_dell_job_location_missing(self): + mock_response = mock.Mock() + mock_response.status_code = 202 + mock_response.headers = { + 'Connection': 'Keep-Alive' + } + + self.assertRaisesRegex( + exceptions.ExtensionError, + 'does not include Location', + self.raid_service._get_task_monitor_from_dell_job, mock_response) + + def test__get_task_monitor_from_dell_job_task_not_found(self): + mock_task1 = mock.Mock(identity='JID_000000000000', + path='/TaskService/Task/JID_000000000000') + mock_tasks = mock.Mock() + mock_tasks.get_members.return_value = [mock_task1] + self.root.get_task_service.return_value.tasks = mock_tasks + + self.assertRaisesRegex( + exceptions.ExtensionError, + 'not find task by id', + self.raid_service._get_task_monitor_from_dell_job, + self.mock_response) diff --git a/sushy_oem_idrac/tests/unit/test_system.py b/sushy_oem_idrac/tests/unit/test_system.py new file mode 100644 index 0000000..13b4868 --- /dev/null +++ b/sushy_oem_idrac/tests/unit/test_system.py @@ -0,0 +1,112 @@ +# Copyright (c) 2021 Dell Inc. or its subsidiaries. +# +# 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 +from unittest import mock + +from oslotest.base import BaseTestCase +from sushy import exceptions + +from sushy_oem_idrac.resources.system import constants as sys_cons +from sushy_oem_idrac.resources.system import raid_service +from sushy_oem_idrac.resources.system import system as oem_system + + +class SystemTestCase(BaseTestCase): + + def setUp(self): + super(SystemTestCase, self).setUp() + self.conn = mock.Mock() + + with open('sushy_oem_idrac/tests/unit/json_samples/' + 'system.json') as f: + mock_response = self.conn.get.return_value + mock_response.json.return_value = json.load(f) + mock_response.status_code = 200 + + self.oem_system = oem_system.DellSystemExtension( + self.conn, '/redfish/v1/Systems/System.Embedded.1') + + mock_perc_raid = mock.Mock( + identity='Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1') + type(mock_perc_raid).volumes = mock.PropertyMock( + side_effect=exceptions.MissingAttributeError) + mock_perc_nonraid = mock.Mock( + identity='Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1', + volumes=[mock.Mock(volume_type='rawdevice', raid_type=None)]) + + mock_boss_controller = mock.MagicMock(raid_types=['RAID1']) + mock_boss_controller.name = 'BOSS-S1' + mock_boss = mock.Mock(storage_controllers=[mock_boss_controller], + drives=mock.Mock()) + mock_perc_controller = mock.MagicMock(raid_types=['RAID1']) + mock_perc_controller.name = 'PERC' + mock_perc = mock.Mock(storage_controllers=[mock_perc_controller], + drives=[mock_perc_raid, mock_perc_nonraid]) + + mock_system = mock.Mock() + mock_storage_nocontroller = mock.Mock(storage_controllers=[]) + mock_storage_nonraid = mock.Mock( + storage_controllers=[mock.Mock(raid_types=[])]) + mock_storage_boss = mock_boss + mock_storage_raid = mock_perc + mock_system.storage.get_members.return_value = [ + mock_storage_nocontroller, mock_storage_nonraid, mock_storage_boss, + mock_storage_raid] + self.oem_system._parent_resource = mock_system + + def test_raid_service(self): + with open('sushy_oem_idrac/tests/unit/json_samples/' + 'raid_service.json') as f: + mock_response = self.conn.get.return_value + mock_response.json.return_value = json.load(f) + mock_response.status_code = 200 + result = self.oem_system.raid_service + self.assertEqual( + '/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService', + result.path) + self.assertIsInstance(result, + raid_service.DellRaidService) + + def test_change_physical_disk_state_raid(self): + mock_taskmon = mock.Mock() + mock_raid = mock.Mock() + mock_raid.return_value = mock_taskmon + mock_nonraid = mock.Mock() + self.oem_system.raid_service.convert_to_raid = mock_raid + self.oem_system.raid_service.convert_to_nonraid = mock_nonraid + + task_mons = self.oem_system.change_physical_disk_state( + sys_cons.PHYSICAL_DISK_STATE_MODE_RAID) + + self.assertEqual([mock_taskmon], task_mons) + mock_raid.assert_called_once_with( + ['Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1']) + mock_nonraid.assert_not_called() + + def test_change_physical_disk_state_nonraid(self): + mock_taskmon = mock.Mock() + mock_raid = mock.Mock() + mock_nonraid = mock.Mock() + mock_nonraid.return_value = mock_taskmon + self.oem_system.raid_service.convert_to_raid = mock_raid + self.oem_system.raid_service.convert_to_nonraid = mock_nonraid + + task_mons = self.oem_system.change_physical_disk_state( + sys_cons.PHYSICAL_DISK_STATE_MODE_NONRAID) + + self.assertEqual([mock_taskmon], task_mons) + mock_raid.assert_not_called() + mock_nonraid.assert_called_once_with( + ['Disk.Bay.0:Enclosure.Internal.0-1:RAID.Integrated.1-1'])