diff --git a/rsd_lib/main.py b/rsd_lib/main.py index 716dfca..b7451f3 100644 --- a/rsd_lib/main.py +++ b/rsd_lib/main.py @@ -17,6 +17,7 @@ import sushy from sushy.resources import base from rsd_lib.resources.node import node +from rsd_lib.resources.storage_service import storage_service class RSDLib(sushy.Sushy): @@ -24,6 +25,10 @@ class RSDLib(sushy.Sushy): _nodes_path = base.Field(['Nodes', '@odata.id'], required=True) """NodeCollection path""" + _storage_service_path = base.Field(['Services', + '@odata.id'], required=True) + """StorageServiceCollection path""" + def get_node_collection(self): """Get the NodeCollection object @@ -42,3 +47,24 @@ class RSDLib(sushy.Sushy): """ return node.Node(self._conn, identity, redfish_version=self.redfish_version) + + def get_storage_service_collection(self): + """Get the StorageServiceCollection object + + :raises: MissingAttributeError, if the collection attribute is + not found + :returns: a StorageServiceCollection object + """ + return storage_service.StorageServiceCollection( + self._conn, self._storage_service_path, + redfish_version=self.redfish_version) + + def get_storage_service(self, identity): + """Given the identity return a StorageService object + + :param identity: The identity of the StorageService resource + :returns: The StorageService object + """ + return storage_service.StorageService( + self._conn, identity, + redfish_version=self.redfish_version) diff --git a/rsd_lib/resources/storage_service/__init__.py b/rsd_lib/resources/storage_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rsd_lib/resources/storage_service/logical_drive.py b/rsd_lib/resources/storage_service/logical_drive.py new file mode 100644 index 0000000..1bff530 --- /dev/null +++ b/rsd_lib/resources/storage_service/logical_drive.py @@ -0,0 +1,76 @@ +# Copyright 2017 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. + +import logging + +from sushy.resources import base + +LOG = logging.getLogger(__name__) + + +class LogicalDrive(base.ResourceBase): + + identity = base.Field('Id', required=True) + """The logical drive identity string""" + + drive_type = base.Field('Type') + """Type of logical drive""" + + mode = base.Field('Mode') + """Drive mode - for Type=='LVM' only supported mode is 'LV'""" + + protected = base.Field('Protected') + """If logical drive is protected""" + + capacity_gib = base.Field('CapacityGiB') + """Logical drive capacity in GiB""" + + image = base.Field('Image') + """Any name that identifies the content of image copied to this LV""" + + bootable = base.Field('Bootable') + """If the LV contains a bootable operating system""" + + snapshot = base.Field('Snapshot') + """Type of drive replication. Yes - copy on write, No - disc clone""" + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a LogicalDrive + + :param connector: A Connector instance + :param identity: The identity of the LogicalDrive resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(LogicalDrive, self).__init__(connector, identity, + redfish_version) + + +class LogicalDriveCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return LogicalDrive + + def __init__(self, connector, path, redfish_version=None): + """A class representing a LogicalDriveCollection + + :param connector: A Connector instance + :param path: The canonical path to the LogicalDrive collection resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(LogicalDriveCollection, self).__init__(connector, path, + redfish_version) diff --git a/rsd_lib/resources/storage_service/physical_drive.py b/rsd_lib/resources/storage_service/physical_drive.py new file mode 100644 index 0000000..aa051a2 --- /dev/null +++ b/rsd_lib/resources/storage_service/physical_drive.py @@ -0,0 +1,77 @@ +# Copyright 2017 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. + +import logging + +from sushy.resources import base + +LOG = logging.getLogger(__name__) + + +class PhysicalDrive(base.ResourceBase): + + identity = base.Field('Id', required=True) + """The physical drive identity string""" + + interface = base.Field('Interface') + """The interface for the physical drive""" + + capacity_gib = base.Field('CapacityGiB') + """Physical drive capacity in GiB""" + + drive_type = base.Field('Type') + """Type of physical drive""" + + rpm = base.Field('RPM') + """The RPM of this physical drive""" + + manufacturer = base.Field('Manufacture') + """The manufacturer of the physical drive""" + + model = base.Field('Model') + """The model of this physical drive""" + + serial_number = base.Field('SerialNumber') + """The serial number for the physical drive""" + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a PhysicalDrive + + :param connector: A Connector instance + :param identity: The identity of the PhysicalDrive resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(PhysicalDrive, self).__init__(connector, identity, + redfish_version) + + +class PhysicalDriveCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return PhysicalDrive + + def __init__(self, connector, path, redfish_version=None): + """A class representing a PhysicalDriveCollection + + :param connector: A Connector instance + :param path: The canonical path to the PhysicalDrive collection + resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(PhysicalDriveCollection, self).__init__(connector, path, + redfish_version) diff --git a/rsd_lib/resources/storage_service/remote_target.py b/rsd_lib/resources/storage_service/remote_target.py new file mode 100644 index 0000000..f8bfbea --- /dev/null +++ b/rsd_lib/resources/storage_service/remote_target.py @@ -0,0 +1,62 @@ +# Copyright 2017 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. + +import logging + +from sushy.resources import base + +LOG = logging.getLogger(__name__) + + +class RemoteTarget(base.ResourceBase): + + identity = base.Field('Id', required=True) + """The target identity string""" + + target_type = base.Field('Type') + """Type of target""" + + addresses = base.Field('Addresses') + + initiator = base.Field('Initiator') + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a RemoteTarget + + :param connector: A Connector instance + :param identity: The identity of the RemoteTarget resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(RemoteTarget, self).__init__(connector, identity, + redfish_version) + + +class RemoteTargetCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return RemoteTarget + + def __init__(self, connector, path, redfish_version=None): + """A class representing a RemoteTargetCollection + + :param connector: A Connector instance + :param path: The canonical path to the RemoteTarget collection resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(RemoteTargetCollection, self).__init__(connector, path, + redfish_version) diff --git a/rsd_lib/resources/storage_service/storage_service.py b/rsd_lib/resources/storage_service/storage_service.py new file mode 100644 index 0000000..d00052b --- /dev/null +++ b/rsd_lib/resources/storage_service/storage_service.py @@ -0,0 +1,145 @@ +# Copyright 2017 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. + +import logging + +from sushy import exceptions +from sushy.resources import base + +from rsd_lib.resources.storage_service import logical_drive +from rsd_lib.resources.storage_service import physical_drive +from rsd_lib.resources.storage_service import remote_target + +LOG = logging.getLogger(__name__) + + +class StorageService(base.ResourceBase): + + description = base.Field('Description') + """The storage service description""" + + identity = base.Field('Id', required=True) + """The storage service identity string""" + + name = base.Field('Name') + """The storage service name""" + + _logical_drives = None # ref to LogicalDriveCollection instance + + _physical_drives = None # ref to PhysicalDrivesCollection instance + + _remote_targets = None # ref to RemoteTargetCollection instance + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a StorageService + + :param connector: A Connector instance + :param identity: The identity of the StorageService resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(StorageService, self).__init__(connector, identity, + redfish_version) + + def _get_logical_drive_collection_path(self): + """Helper function to find the LogicalDriveCollection path""" + logical_drive_col = self.json.get('LogicalDrives') + if not logical_drive_col: + raise exceptions.MissingAttributeError(attribute='LogicalDrives', + resource=self._path) + return logical_drive_col.get('@odata.id') + + @property + def logical_drives(self): + """Property to provide reference to `LogicalDriveCollection` instance + + It is calculated once when it is queried for the first time. On + refresh, this property is reset. + """ + if self._logical_drives is None: + self._logical_drives = logical_drive.LogicalDriveCollection( + self._conn, self._get_logical_drive_collection_path(), + redfish_version=self.redfish_version) + + return self._logical_drives + + def _get_physical_drive_collection_path(self): + """Helper function to find the PhysicalDriveCollection path""" + physical_drive_col = self.json.get('Drives') + if not physical_drive_col: + raise exceptions.MissingAttributeError(attribute='PhysicalDrives', + resource=self._path) + return physical_drive_col.get('@odata.id') + + @property + def physical_drives(self): + """Property to provide reference to `PhysicalDriveCollection` instance + + It is calculated once when it is queried for the first time. On + refresh, this property is reset. + """ + if self._physical_drives is None: + self._physical_drives = physical_drive.PhysicalDriveCollection( + self._conn, self._get_physical_drive_collection_path(), + redfish_version=self.redfish_version) + + return self._physical_drives + + def _get_remote_target_collection_path(self): + """Helper function to find the RemoteTargetCollection path""" + remote_target_col = self.json.get('RemoteTargets') + if not remote_target_col: + raise exceptions.MissingAttributeError(attribute='RemoteTargets', + resource=self._path) + return remote_target_col.get('@odata.id') + + @property + def remote_targets(self): + """Property to provide reference to `RemoteTargetCollection` instance + + It is calculated once when it is queried for the first time. On + refresh, this property is reset. + """ + if self._remote_targets is None: + self._remote_targets = remote_target.RemoteTargetCollection( + self._conn, self._get_remote_target_collection_path(), + redfish_version=self.redfish_version) + + return self._remote_targets + + def refresh(self): + super(StorageService, self).refresh() + self._logical_drives = None + self._physical_drives = None + self._remote_targets = None + + +class StorageServiceCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return StorageService + + def __init__(self, connector, path, redfish_version=None): + """A class representing a StorageServiceCollection + + :param connector: A Connector instance + :param path: The canonical path to the StorageService collection + resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(StorageServiceCollection, self).__init__(connector, path, + redfish_version) diff --git a/rsd_lib/tests/unit/json_samples/logical_drive.json b/rsd_lib/tests/unit/json_samples/logical_drive.json new file mode 100644 index 0000000..079d142 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/logical_drive.json @@ -0,0 +1,43 @@ +{ + "@odata.context": "/redfish/v1/$metadata#LogicalDrives/Links/Members/$entity", + "@odata.id": "/redfish/v1/Services/1/LogicalDrives/1", + "@odata.type": "#LogicalDrive.LogicalDrive", + "Id": "1", + "Name": "Logical Drive", + "Description": "Logical Drive", + "Status": { + "State": "Enabled", + "Health": "OK", + }, + "Type": "LVM", + "Mode": "RAID0", + "Protected": false, + "CapacityGiB": 8096, + "Image": "Ubuntu 12.04.4LTS / Linux 3.11 / 2014.1", + "Bootable": true, + "Snapshot": false, + "Oem": {}, + "Links": { + "LogicalDrives": [ + ], + "PhysicalDrives": [ + { + "@odata.id": "/redfish/v1/Services/1/Drives/2" + } + ], + "MasterDrive": { + "@odata.id": "/redfish/v1/Services/1/LogicalDrives/12" + }, + "UsedBy": [ + { + "@odata.id": "/redfish/v1/Services/1/LogicalDrives/14" + } + ], + "Targets": [ + { + "@odata.id": "/redfish/v1/Services/1/Targets/2" + } + ], + "Oem": {} + } +} diff --git a/rsd_lib/tests/unit/json_samples/logical_drive_collection.json b/rsd_lib/tests/unit/json_samples/logical_drive_collection.json new file mode 100644 index 0000000..9eab601 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/logical_drive_collection.json @@ -0,0 +1,12 @@ +{ + "@odata.context": "/redfish/v1/$metadata#LogicalDrives", + "@odata.id": "/redfish/v1/Services/1/LogicalDrives", + "@odata.type": "#LogicalDriveCollection.LogicalDriveCollection", + "Name": "Logical Drives Collection", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Services/1/LogicalDrives/1" + } + ] +} diff --git a/rsd_lib/tests/unit/json_samples/physical_drive.json b/rsd_lib/tests/unit/json_samples/physical_drive.json new file mode 100644 index 0000000..aab8026 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/physical_drive.json @@ -0,0 +1,28 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Drive/Links/Members/$entity", + "@odata.id": "/redfish/v1/Services/1/Drives/1", + "@odata.type": "#PhysicalDrive.v1_0_0.PhysicalDrive", + "Id": "1","Name": "Simple drive" + "Description": "Physical drive" + "Interface": < { "PCIe", "SAS", "SATA" } > + "CapacityGiB": 500, + "Type": < { "HDD", "SSD" } >, + "RPM": 0, + "Manufacturer": "Intel", + "Model": "S3710", + "SerialNumber": "XYZ123456789", + "Status": { + "State": < { "Enabled", "Disabled", "Offline", "InTest", "Starting", "Absent" } >, + "Health": < {"OK", "Warning", "Critical" } >, + "HealthRollup": < {"OK", "Warning", "Critical" } > + }, + "Oem": {}, + "Links": { + "UsedBy": [ + { + "@odata.id": "/redfish/v1/Services/1/LogicalDrives/1" + } + ], + "Oem": {} + } +} diff --git a/rsd_lib/tests/unit/json_samples/physical_drive_collection.json b/rsd_lib/tests/unit/json_samples/physical_drive_collection.json new file mode 100644 index 0000000..a0582c1 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/physical_drive_collection.json @@ -0,0 +1,12 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Drives", + "@odata.id": "/redfish/v1/Services/1/Drives", + "@odata.type": "#PhysicalDriveCollection.PhysicalDriveCollection", + "Name": "Physical Drives Collection", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Services/1/Drives/1" + } + ] +} diff --git a/rsd_lib/tests/unit/json_samples/remote_target.json b/rsd_lib/tests/unit/json_samples/remote_target.json new file mode 100644 index 0000000..6534ff2 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/remote_target.json @@ -0,0 +1,46 @@ +{ + "@odata.context": "/redfish/v1/$metadata#RemoteTargets/Links/Members/$entity", + "@odata.id": "/redfish/v1/Services/1/Targets/1", + "@odata.type": "#RemoteTarget.v1_1_0.RemoteTarget", + "Id": "1", + "Name": "Remote Target", + "Description": "Remote Target", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "Type": "Network Storage", + "Addresses": [ + { + "iSCSI": + { + "TargetLUN": [ + { + "LUN" : 1, + "LogicalDrive": "/redfish/v1/Services/1/LogicalDrives/1" + } + ], + "TargetIQN": "iqn.2015-01.com.example:ceph-ubuntu14", + "TargetPortalIP": "10.102.44.54", + "TargetPortalPort": 3260, + "CHAP": { + "Type": "Mutual", + "Username": "valid_user", + "Secret": null, + "MutualUsername": "user2", + "MutualSecret": null + } + } + } + ], + "Initiator": [ + { + "iSCSI": + { + "InitiatorIQN": "iqn.2015-01.com.example:fedora21" + } + } + ], + "Oem": {}, + "Links": {} +} diff --git a/rsd_lib/tests/unit/json_samples/remote_target_collection.json b/rsd_lib/tests/unit/json_samples/remote_target_collection.json new file mode 100644 index 0000000..40951b1 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/remote_target_collection.json @@ -0,0 +1,12 @@ +{ + "@odata.context": "/redfish/v1/$metadata#RemoteTargets", + "@odata.id": "/redfish/v1/Services/1/Targets", + "@odata.type": "#RemoteTargetCollection.RemoteTargetCollection", + "Name": "Remote Targets Collection", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Services/1/Targets/1" + } + ] +} diff --git a/rsd_lib/tests/unit/json_samples/root.json b/rsd_lib/tests/unit/json_samples/root.json index 060efb5..ad14936 100644 --- a/rsd_lib/tests/unit/json_samples/root.json +++ b/rsd_lib/tests/unit/json_samples/root.json @@ -7,6 +7,9 @@ "Systems": { "@odata.id": "/redfish/v1/Systems" }, + "Services": { + "@odata.id": "/redfish/v1/Services" + }, "Chassis": { "@odata.id": "/redfish/v1/Chassis" }, diff --git a/rsd_lib/tests/unit/json_samples/storage_service.json b/rsd_lib/tests/unit/json_samples/storage_service.json new file mode 100644 index 0000000..f0240b1 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/storage_service.json @@ -0,0 +1,30 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Services/Members/1/$entity", + "@odata.id": "/redfish/v1/Services/RSS1", + "@odata.type": "#StorageService.v1 0 0.StorageService", + "Id": "RSS1", + "Name": "Storage Service", + "Description": "Storage Service", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "RemoteTargets": { + "@odata.id": "/redfish/v1/Services/RSS1/Targets" + }, + "LogicalDrives": { + "@odata.id": "/redfish/v1/Services/RSS1/LogicalDrives" + }, + "Drives": { + "@odata.id": "/redfish/v1/Services/RSS1/Drives" + }, + "Oem": {}, + "Links": { + "ManagedBy": [ + { + "@odata.id": "/redfish/v1/Managers/RSS" + } + ], + "Oem": {} + } +} diff --git a/rsd_lib/tests/unit/json_samples/storage_service_collection.json b/rsd_lib/tests/unit/json_samples/storage_service_collection.json new file mode 100644 index 0000000..f1ee242 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/storage_service_collection.json @@ -0,0 +1,13 @@ +{ + "@odata.context": "/redfish/v1/$metadata#StorageServices", + "@odata.id": "/redfish/v1/Services", + "@odata.type": "#StorageServiceCollection.StorageServiceCollection", + "Name": "Storage Services Collection", + "Description": "Collection of RSD Storage Services", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Services/RSS1" + } + ] +} diff --git a/rsd_lib/tests/unit/resources/storage_service/__init__.py b/rsd_lib/tests/unit/resources/storage_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rsd_lib/tests/unit/resources/storage_service/test_storage_service.py b/rsd_lib/tests/unit/resources/storage_service/test_storage_service.py new file mode 100644 index 0000000..81f59a2 --- /dev/null +++ b/rsd_lib/tests/unit/resources/storage_service/test_storage_service.py @@ -0,0 +1,251 @@ +# Copyright 2017 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. + +import json + +import mock +from sushy import exceptions +import testtools + +from rsd_lib.resources.storage_service import logical_drive +from rsd_lib.resources.storage_service import physical_drive +from rsd_lib.resources.storage_service import remote_target +from rsd_lib.resources.storage_service import storage_service + + +class StorageServiceTestCase(testtools.TestCase): + + def setUp(self): + super(StorageServiceTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/storage_service.json', + 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.storage_service_inst = storage_service.StorageService( + self.conn, '/redfish/v1/Nodes/RSS1', + redfish_version='1.0.2') + + def test__parse_attributes(self): + self.storage_service_inst._parse_attributes() + self.assertEqual('1.0.2', self.storage_service_inst.redfish_version) + self.assertEqual('Storage Service', + self.storage_service_inst.description) + self.assertEqual('RSS1', self.storage_service_inst.identity) + self.assertEqual('Storage Service', self.storage_service_inst.name) + self.assertIsNone(self.storage_service_inst._logical_drives) + self.assertIsNone(self.storage_service_inst._physical_drives) + self.assertIsNone(self.storage_service_inst._remote_targets) + + def test__get_logical_drive_collection_path_missing_processors_attr(self): + self.storage_service_inst._json.pop('LogicalDrives') + self.assertRaisesRegex( + exceptions.MissingAttributeError, 'attribute LogicalDrives', + self.storage_service_inst._get_logical_drive_collection_path) + + def test_logical_drives(self): + # check for the underneath variable value + self.assertIsNone(self.storage_service_inst._logical_drives) + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('rsd_lib/tests/unit/json_samples/' + 'logical_drive_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN | + actual_logical_drives = self.storage_service_inst.logical_drives + # | THEN | + self.assertIsInstance(actual_logical_drives, + logical_drive.LogicalDriveCollection) + self.conn.get.return_value.json.assert_called_once_with() + + # reset mock + self.conn.get.return_value.json.reset_mock() + # | WHEN & THEN | + # tests for same object on invoking subsequently + self.assertIs(actual_logical_drives, + self.storage_service_inst.logical_drives) + self.conn.get.return_value.json.assert_not_called() + + def test_logical_drives_on_refresh(self): + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/' + 'logical_drive_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.storage_service_inst.logical_drives, + logical_drive.LogicalDriveCollection) + + # On refreshing the storage service instance... + with open('rsd_lib/tests/unit/json_samples/' + 'storage_service.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.storage_service_inst.refresh() + + # | WHEN & THEN | + self.assertIsNone(self.storage_service_inst._logical_drives) + + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/' + 'logical_drive_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.storage_service_inst.logical_drives, + logical_drive.LogicalDriveCollection) + + def test__get_physical_drive_collection_path_missing_processors_attr(self): + self.storage_service_inst._json.pop('Drives') + self.assertRaisesRegex( + exceptions.MissingAttributeError, 'attribute PhysicalDrives', + self.storage_service_inst._get_physical_drive_collection_path) + + def test_physical_drives(self): + # check for the underneath variable value + self.assertIsNone(self.storage_service_inst._physical_drives) + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('rsd_lib/tests/unit/json_samples/' + 'physical_drive_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN | + actual_physical_drives = self.storage_service_inst.physical_drives + # | THEN | + self.assertIsInstance(actual_physical_drives, + physical_drive.PhysicalDriveCollection) + self.conn.get.return_value.json.assert_called_once_with() + + # reset mock + self.conn.get.return_value.json.reset_mock() + # | WHEN & THEN | + # tests for same object on invoking subsequently + self.assertIs(actual_physical_drives, + self.storage_service_inst.physical_drives) + self.conn.get.return_value.json.assert_not_called() + + def test_physical_drives_on_refresh(self): + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/' + 'physical_drive_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.storage_service_inst.physical_drives, + physical_drive.PhysicalDriveCollection) + + # On refreshing the storage service instance... + with open('rsd_lib/tests/unit/json_samples/' + 'storage_service.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.storage_service_inst.refresh() + + # | WHEN & THEN | + self.assertIsNone(self.storage_service_inst._physical_drives) + + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/' + 'physical_drive_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.storage_service_inst.physical_drives, + physical_drive.PhysicalDriveCollection) + + def test__get_remote_target_collection_path_missing_processors_attr(self): + self.storage_service_inst._json.pop('RemoteTargets') + self.assertRaisesRegex( + exceptions.MissingAttributeError, 'attribute RemoteTargets', + self.storage_service_inst._get_remote_target_collection_path) + + def test_remote_targets(self): + # check for the underneath variable value + self.assertIsNone(self.storage_service_inst._remote_targets) + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('rsd_lib/tests/unit/json_samples/' + 'remote_target_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN | + actual_remote_targets = self.storage_service_inst.remote_targets + # | THEN | + self.assertIsInstance(actual_remote_targets, + remote_target.RemoteTargetCollection) + self.conn.get.return_value.json.assert_called_once_with() + + # reset mock + self.conn.get.return_value.json.reset_mock() + # | WHEN & THEN | + # tests for same object on invoking subsequently + self.assertIs(actual_remote_targets, + self.storage_service_inst.remote_targets) + self.conn.get.return_value.json.assert_not_called() + + def test_remote_targets_on_refresh(self): + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/' + 'remote_target_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.storage_service_inst.remote_targets, + remote_target.RemoteTargetCollection) + + # On refreshing the storage service instance... + with open('rsd_lib/tests/unit/json_samples/' + 'storage_service.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.storage_service_inst.refresh() + + # | WHEN & THEN | + self.assertIsNone(self.storage_service_inst._remote_targets) + + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/' + 'remote_target_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.storage_service_inst.remote_targets, + remote_target.RemoteTargetCollection) + + +class StorageServiceCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(StorageServiceCollectionTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/' + 'storage_service_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.storage_service_col = storage_service.StorageServiceCollection( + self.conn, '/redfish/v1/Services', redfish_version='1.0.2') + + def test__parse_attributes(self): + self.storage_service_col._parse_attributes() + self.assertEqual('1.0.2', self.storage_service_col.redfish_version) + self.assertEqual('Storage Services Collection', + self.storage_service_col.name) + self.assertEqual(('/redfish/v1/Services/RSS1',), + self.storage_service_col.members_identities) + + @mock.patch.object(storage_service, 'StorageService', autospec=True) + def test_get_member(self, mock_storage_service): + self.storage_service_col.get_member('/redfish/v1/Services/RSS1') + mock_storage_service.assert_called_once_with( + self.storage_service_col._conn, '/redfish/v1/Services/RSS1', + redfish_version=self.storage_service_col.redfish_version) + + @mock.patch.object(storage_service, 'StorageService', autospec=True) + def test_get_members(self, mock_storage_service): + members = self.storage_service_col.get_members() + mock_storage_service.assert_called_once_with( + self.storage_service_col._conn, '/redfish/v1/Services/RSS1', + redfish_version=self.storage_service_col.redfish_version) + self.assertIsInstance(members, list) + self.assertEqual(1, len(members))