From 642827b727ba55d629a60f3fda5d95dceff36f40 Mon Sep 17 00:00:00 2001 From: leizhang Date: Fri, 31 May 2019 19:44:53 +0800 Subject: [PATCH] Add new get_resource method for rsd-lib 2.1 Add a get_resource method to return a resource instance from a uri, currently there is no way for users to get rsd resource from a uri. This is helpful for cases where users get the path from links and want to access the resource using given path. Change-Id: I51d4ead04d829631d37cb3bad8dd8bcf1194db3f --- rsd_lib/exceptions.py | 30 ++++ rsd_lib/resources/v2_1/__init__.py | 34 ++++ rsd_lib/resources/v2_1/types.py | 163 ++++++++++++++++++ .../unit/resources/v2_1/test_rsdlib_v2_1.py | 58 +++++++ 4 files changed, 285 insertions(+) create mode 100644 rsd_lib/exceptions.py create mode 100644 rsd_lib/resources/v2_1/types.py diff --git a/rsd_lib/exceptions.py b/rsd_lib/exceptions.py new file mode 100644 index 0000000..e696976 --- /dev/null +++ b/rsd_lib/exceptions.py @@ -0,0 +1,30 @@ +# Copyright 2019 Intel, Inc. +# 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. + + +class RsdlibError(Exception): + """Basic exception for errors raised by rsd-lib""" + + message = None + + def __init__(self, **kwargs): + if self.message and kwargs: + self.message = self.message % kwargs + + super(RsdlibError, self).__init__(self.message) + + +class NoMatchingResourceError(RsdlibError): + message = "No matching resource for the uri %(uri)s" diff --git a/rsd_lib/resources/v2_1/__init__.py b/rsd_lib/resources/v2_1/__init__.py index 7fa5d9f..f176115 100644 --- a/rsd_lib/resources/v2_1/__init__.py +++ b/rsd_lib/resources/v2_1/__init__.py @@ -13,8 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +from sushy import exceptions from sushy.resources import base +from rsd_lib import exceptions as rsd_lib_exceptions from rsd_lib.resources.v2_1.chassis import chassis from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch from rsd_lib.resources.v2_1.event_service import event_service @@ -25,6 +27,7 @@ from rsd_lib.resources.v2_1.registries import message_registry_file from rsd_lib.resources.v2_1.storage_service import storage_service from rsd_lib.resources.v2_1.system import system from rsd_lib.resources.v2_1.task import task_service +from rsd_lib.resources.v2_1 import types class RSDLibV2_1(base.ResourceBase): @@ -279,3 +282,34 @@ class RSDLibV2_1(base.ResourceBase): self._event_service_path, redfish_version=self.redfish_version, ) + + def _get_resource_class_from_path(self, path): + """Get resource class from a given path + + :param path: Path of any rsd resource + :returns: Corresponding resource class + """ + body = self._conn.get(path=path).json() + if not body.get("@odata.type"): + raise exceptions.MissingAttributeError( + attribute="@odata.type", resource=path + ) + + # Normally the format of resource_type is '#{namespace}.{entity_type}' + # Here we use entity_type to find the corresponding resource class + entity_type = body["@odata.type"].split(".")[-1] + + return types.RESOURCE_CLASS.get(entity_type) + + def get_resource(self, path): + """Return corresponding resource object from path + + :param path: The path of a resource or resource collection + :returns: corresponding resource or resource collection object + """ + resource_class = self._get_resource_class_from_path(path) + if not resource_class: + raise rsd_lib_exceptions.NoMatchingResourceError(uri=path) + return resource_class( + self._conn, path, redfish_version=self.redfish_version + ) diff --git a/rsd_lib/resources/v2_1/types.py b/rsd_lib/resources/v2_1/types.py new file mode 100644 index 0000000..88da0c5 --- /dev/null +++ b/rsd_lib/resources/v2_1/types.py @@ -0,0 +1,163 @@ +# Copyright 2019 Intel, Inc. +# 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. + +from rsd_lib.resources.v2_1.chassis import chassis +from rsd_lib.resources.v2_1.chassis import log_entry +from rsd_lib.resources.v2_1.chassis import log_service +from rsd_lib.resources.v2_1.chassis import power +from rsd_lib.resources.v2_1.chassis import power_zone +from rsd_lib.resources.v2_1.chassis import thermal +from rsd_lib.resources.v2_1.chassis import thermal_zone + +from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch +from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch_acl +from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch_acl_rule +from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch_port +from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch_static_mac +from rsd_lib.resources.v2_1.ethernet_switch import vlan_network_interface + +from rsd_lib.resources.v2_1.event_service import event_destination +from rsd_lib.resources.v2_1.event_service import event_service + +from rsd_lib.resources.v2_1.fabric import endpoint +from rsd_lib.resources.v2_1.fabric import fabric +from rsd_lib.resources.v2_1.fabric import port +from rsd_lib.resources.v2_1.fabric import switch +from rsd_lib.resources.v2_1.fabric import zone + +from rsd_lib.resources.v2_1.manager import manager +from rsd_lib.resources.v2_1.manager import manager_network_protocol +from rsd_lib.resources.v2_1.manager import serial_interface +from rsd_lib.resources.v2_1.manager import virtual_media + +from rsd_lib.resources.v2_1.node import node + +from rsd_lib.resources.v2_1.registries import message_registry_file + +from rsd_lib.resources.v2_1.storage_service import logical_drive +from rsd_lib.resources.v2_1.storage_service import physical_drive +from rsd_lib.resources.v2_1.storage_service import remote_target +from rsd_lib.resources.v2_1.storage_service import storage_service + +from rsd_lib.resources.v2_1.system import drive +from rsd_lib.resources.v2_1.system import ethernet_interface +from rsd_lib.resources.v2_1.system import memory +from rsd_lib.resources.v2_1.system import network_device_function +from rsd_lib.resources.v2_1.system import network_interface +from rsd_lib.resources.v2_1.system import pcie_device +from rsd_lib.resources.v2_1.system import pcie_function +from rsd_lib.resources.v2_1.system import processor +from rsd_lib.resources.v2_1.system import simple_storage +from rsd_lib.resources.v2_1.system import storage +from rsd_lib.resources.v2_1.system import system +from rsd_lib.resources.v2_1.system import volume + +from rsd_lib.resources.v2_1.task import task +from rsd_lib.resources.v2_1.task import task_service + +RESOURCE_CLASS = { + 'Chassis': chassis.Chassis, + 'ChassisCollection': chassis.ChassisCollection, + '.ComposedNode': node.Node, + 'ComposedNodeCollection': node.NodeCollection, + 'ComputerSystem': system.System, + 'ComputerSystemCollection': system.SystemCollection, + 'Drive': drive.Drive, + 'Endpoint': endpoint.Endpoint, + 'EndpointCollection': endpoint.EndpointCollection, + 'EthernetInterface': ethernet_interface.EthernetInterface, + 'EthernetInterfaceCollection': + ethernet_interface.EthernetInterfaceCollection, + 'EthernetSwitch': ethernet_switch.EthernetSwitch, + 'EthernetSwitchACL': ethernet_switch_acl.EthernetSwitchACL, + 'EthernetSwitchACLCollection': + ethernet_switch_acl.EthernetSwitchACLCollection, + 'EthernetSwitchACLRule': ethernet_switch_acl_rule.EthernetSwitchACLRule, + 'EthernetSwitchACLRuleCollection': + ethernet_switch_acl_rule.EthernetSwitchACLRuleCollection, + 'EthernetSwitchCollection': ethernet_switch.EthernetSwitchCollection, + 'EthernetSwitchPort': ethernet_switch_port.EthernetSwitchPort, + 'EthernetSwitchPortCollection': + ethernet_switch_port.EthernetSwitchPortCollection, + 'EthernetSwitchStaticMAC': + ethernet_switch_static_mac.EthernetSwitchStaticMAC, + 'EthernetSwitchStaticMACCollection': + ethernet_switch_static_mac.EthernetSwitchStaticMACCollection, + 'EventDestination': event_destination.EventDestination, + 'EventDestinationCollection': event_destination.EventDestinationCollection, + 'EventService': event_service.EventService, + 'Fabric': fabric.Fabric, + 'FabricCollection': fabric.FabricCollection, + 'LogEntry': log_entry.LogEntry, + 'LogEntryCollection': log_entry.LogEntryCollection, + 'LogService': log_service.LogService, + 'LogServiceCollection': log_service.LogServiceCollection, + 'LogicalDrive': logical_drive.LogicalDrive, + 'LogicalDriveCollection': logical_drive.LogicalDriveCollection, + 'Manager': manager.Manager, + 'ManagerCollection': manager.ManagerCollection, + 'ManagerNetworkProtocol': manager_network_protocol.ManagerNetworkProtocol, + 'Memory': memory.Memory, + 'MemoryCollection': memory.MemoryCollection, + 'MessageRegistryFile': message_registry_file.MessageRegistryFile, + 'MessageRegistryFileCollection': + message_registry_file.MessageRegistryFileCollection, + 'NetworkDeviceFunction': network_device_function.NetworkDeviceFunction, + 'NetworkDeviceFunctionCollection': + network_device_function.NetworkDeviceFunctionCollection, + 'NetworkInterface': network_interface.NetworkInterface, + 'NetworkInterfaceCollection': network_interface.NetworkInterfaceCollection, + 'PCIeDevice': pcie_device.PCIeDevice, + 'PCIeFunction': pcie_function.PCIeFunction, + 'PhysicalDrive': physical_drive.PhysicalDrive, + 'PhysicalDriveCollection': physical_drive.PhysicalDriveCollection, + 'Port': port.Port, + 'PortCollection': port.PortCollection, + 'Power': power.Power, + 'PowerZone': power_zone.PowerZone, + 'PowerZoneCollection': power_zone.PowerZoneCollection, + 'Processor': processor.Processor, + 'ProcessorCollection': processor.ProcessorCollection, + 'RemoteTarget': remote_target.RemoteTarget, + 'RemoteTargetCollection': + remote_target.RemoteTargetCollection, + 'SerialInterface': serial_interface.SerialInterface, + 'SerialInterfaceCollection': serial_interface.SerialInterfaceCollection, + 'SimpleStorage': simple_storage.SimpleStorage, + 'SimpleStorageCollection': simple_storage.SimpleStorageCollection, + 'Storage': storage.Storage, + 'StorageCollection': storage.StorageCollection, + 'StorageService': storage_service.StorageService, + 'StorageServiceCollection': storage_service.StorageServiceCollection, + 'Switch': switch.Switch, + 'SwitchCollection': switch.SwitchCollection, + 'Task': task.Task, + 'TaskCollection': task.TaskCollection, + 'TaskService': task_service.TaskService, + 'Thermal': thermal.Thermal, + 'ThermalZone': thermal_zone.ThermalZone, + 'ThermalZoneCollection': + thermal_zone.ThermalZoneCollection, + 'VLanNetworkInterface': + vlan_network_interface.VLanNetworkInterface, + 'VLanNetworkInterfaceCollection': + vlan_network_interface.VLanNetworkInterfaceCollection, + 'VirtualMedia': virtual_media.VirtualMedia, + 'VirtualMediaCollection': virtual_media.VirtualMediaCollection, + 'Volume': volume.Volume, + 'VolumeCollection': volume.VolumeCollection, + 'Zone': zone.Zone, + 'ZoneCollection': zone.ZoneCollection +} diff --git a/rsd_lib/tests/unit/resources/v2_1/test_rsdlib_v2_1.py b/rsd_lib/tests/unit/resources/v2_1/test_rsdlib_v2_1.py index 475d8ec..da7a71a 100644 --- a/rsd_lib/tests/unit/resources/v2_1/test_rsdlib_v2_1.py +++ b/rsd_lib/tests/unit/resources/v2_1/test_rsdlib_v2_1.py @@ -15,8 +15,10 @@ import json import mock +from sushy import exceptions import testtools +from rsd_lib.exceptions import NoMatchingResourceError from rsd_lib.resources import v2_1 from rsd_lib.resources.v2_1.chassis import chassis from rsd_lib.resources.v2_1.ethernet_switch import ethernet_switch @@ -234,3 +236,59 @@ class RSDLibV2_1TestCase(testtools.TestCase): "/redfish/v1/EventService", redfish_version=self.rsd.redfish_version, ) + + def test_get_resource_class_with_connection_error(self): + self.conn.get.side_effect = exceptions.ConnectionError() + self.assertRaises( + exceptions.ConnectionError, + self.rsd._get_resource_class_from_path, + "/redfish/v1/Chassis/1", + ) + self.conn.reset() + + def test_get_resource_class_from_path_with_invalid_response(self): + self.conn.get.return_value.json.return_value = {} + self.assertRaisesRegexp( + exceptions.MissingAttributeError, + "The attribute @odata.type is missing from the resource " + "/redfish/v1/Chassis/1", + self.rsd._get_resource_class_from_path, + "/redfish/v1/Chassis/1", + ) + self.conn.reset() + + def test_get_resource_class_from_path(self): + self.conn.get.return_value.json.return_value = { + "@odata.type": "#Chassis.v1_3_0.Chassis" + } + self.assertEqual( + chassis.Chassis, + self.rsd._get_resource_class_from_path("/redfish/v1/Chassis/1"), + ) + self.conn.reset() + + def test_get_resource(self): + with mock.patch.object( + self.rsd, + "_get_resource_class_from_path", + return_value=chassis.Chassis, + ): + with open( + "rsd_lib/tests/unit/json_samples/v2_1/chassis.json", "r" + ) as f: + self.conn.get.return_value.json.return_value = json.loads( + f.read() + ) + self.assertIsInstance( + self.rsd.get_resource("/redfish/v1/Chassis/1"), chassis.Chassis + ) + + def test_get_resource_with_no_class_match(self): + with mock.patch.object( + self.rsd, "_get_resource_class_from_path", return_value=None + ): + self.assertRaises( + NoMatchingResourceError, + self.rsd.get_resource, + "/redfish/v1/chassis/1", + )