diff --git a/proliantutils/redfish/redfish.py b/proliantutils/redfish/redfish.py index 0bffd67..49c62a3 100644 --- a/proliantutils/redfish/redfish.py +++ b/proliantutils/redfish/redfish.py @@ -593,3 +593,21 @@ class RedfishOperations(operations.IloOperations): {'username': self._username, 'error': str(e)}) LOG.debug(msg) raise exception.IloError(msg) + + def get_server_capabilities(self): + """Returns the server capabilities + + :raises: IloError if any Sushy error is encountered. + """ + capabilities = {} + sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) + try: + count = len(sushy_system.pci_devices.gpu_devices) + capabilities.update({'pci_gpu_devices': count}) + except sushy.exceptions.SushyError as e: + msg = (self._("The Redfish controller is unable to get " + "resource or its members. Error" + "%(error)s)") % {'error': str(e)}) + LOG.debug(msg) + raise exception.IloError(msg) + return capabilities diff --git a/proliantutils/redfish/resources/system/pci_device.py b/proliantutils/redfish/resources/system/pci_device.py new file mode 100644 index 0000000..653bc0c --- /dev/null +++ b/proliantutils/redfish/resources/system/pci_device.py @@ -0,0 +1,56 @@ +# Copyright 2017 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. + +import logging + +from sushy.resources import base + +LOG = logging.getLogger(__name__) + +CLASSCODE_FOR_GPU_DEVICES = [3] +SUBCLASSCODE_FOR_GPU_DEVICES = [0, 1, 2, 128] + + +class PCIDevice(base.ResourceBase): + + identity = base.Field('Id', required=True) + + name = base.Field('Name') + + class_code = base.Field('ClassCode') + + sub_class_code = base.Field('SubclassCode') + + +class PCIDeviceCollection(base.ResourceCollectionBase): + + _gpu_devices = None + + @property + def _resource_type(self): + return PCIDevice + + @property + def gpu_devices(self): + if self._gpu_devices is None: + self._gpu_devices = [] + for member in self.get_members(): + if member.class_code in CLASSCODE_FOR_GPU_DEVICES: + if member.sub_class_code in SUBCLASSCODE_FOR_GPU_DEVICES: + self._gpu_devices.append(member) + return self._gpu_devices + + def refresh(self): + super(PCIDeviceCollection, self).refresh() + self._gpu_devices = None diff --git a/proliantutils/redfish/resources/system/system.py b/proliantutils/redfish/resources/system/system.py index 44321aa..cfeee0f 100644 --- a/proliantutils/redfish/resources/system/system.py +++ b/proliantutils/redfish/resources/system/system.py @@ -22,6 +22,7 @@ from proliantutils import exception from proliantutils import log from proliantutils.redfish.resources.system import bios from proliantutils.redfish.resources.system import mappings +from proliantutils.redfish.resources.system import pci_device from proliantutils.redfish import utils LOG = log.get_logger(__name__) @@ -59,6 +60,8 @@ class HPESystem(system.System): _bios_settings = None + _pci_devices = None + def _get_hpe_push_power_button_action_element(self): push_action = self._hpe_actions.computer_system_ext_powerbutton if not push_action: @@ -141,3 +144,20 @@ class HPESystem(system.System): tenure = (sushy.BOOT_SOURCE_ENABLED_CONTINUOUS if persistent else sushy.BOOT_SOURCE_ENABLED_ONCE) self.set_system_boot_source(device, enabled=tenure) + + @property + def pci_devices(self): + """Provides the collection of PCI devices + + It is calculated once when the first time it is queried. On refresh, + this property gets reset. + """ + if self._pci_devices is None: + self._pci_devices = pci_device.PCIDeviceCollection( + self._conn, utils.get_subresource_path_by( + self, ['Oem', 'Hpe', 'Links', 'PCIDevices'])) + return self._pci_devices + + def refresh(self): + super(HPESystem, self).refresh() + self._pci_devices = None diff --git a/proliantutils/tests/redfish/json_samples/pci_device.json b/proliantutils/tests/redfish/json_samples/pci_device.json new file mode 100644 index 0000000..71ab557 --- /dev/null +++ b/proliantutils/tests/redfish/json_samples/pci_device.json @@ -0,0 +1,25 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Systems/Members/1/PCIDevices/Members/$entity", + "@odata.etag": "W/\"33150E20\"", + "@odata.id": "/redfish/v1/Systems/1/PCIDevices/1/", + "@odata.type": "#HpeServerPciDevice.v2_0_0.HpeServerPciDevice", + "BusNumber": 2, + "ClassCode": 2, + "DeviceID": 5719, + "DeviceInstance": 1, + "DeviceLocation": "Embedded", + "DeviceNumber": 0, + "DeviceSubInstance": 1, + "DeviceType": "Embedded LOM", + "FunctionNumber": 0, + "Id": "1", + "LocationString": "Embedded LOM 1", + "Name": "Network Controller", + "SegmentNumber": 0, + "StructuredName": "NIC.LOM.1.1", + "SubclassCode": 0, + "SubsystemDeviceID": 8894, + "SubsystemVendorID": 4156, + "UEFIDevicePath": "PciRoot(0x0)/Pci(0x1C,0x0)/Pci(0x0,0x0)", + "VendorID": 5348 +} diff --git a/proliantutils/tests/redfish/json_samples/pci_device1.json b/proliantutils/tests/redfish/json_samples/pci_device1.json new file mode 100644 index 0000000..f2e32aa --- /dev/null +++ b/proliantutils/tests/redfish/json_samples/pci_device1.json @@ -0,0 +1,25 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Systems/Members/1/PCIDevices/Members/$entity", + "@odata.etag": "W/\"5ABCB1B9\"", + "@odata.id": "/redfish/v1/Systems/1/PCIDevices/6/", + "@odata.type": "#HpeServerPciDevice.v2_0_0.HpeServerPciDevice", + "BusNumber": 1, + "ClassCode": 3, + "DeviceID": 1336, + "DeviceInstance": 1, + "DeviceLocation": "Embedded", + "DeviceNumber": 0, + "DeviceSubInstance": 1, + "DeviceType": "Video", + "FunctionNumber": 1, + "Id": "6", + "LocationString": "Embedded Video Controller", + "Name": "Embedded Video Controller", + "SegmentNumber": 0, + "StructuredName": "PCI.Emb.1.1", + "SubclassCode": 0, + "SubsystemDeviceID": 228, + "SubsystemVendorID": 5520, + "UEFIDevicePath": "PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x1)", + "VendorID": 4139 +} diff --git a/proliantutils/tests/redfish/json_samples/pci_device_collection.json b/proliantutils/tests/redfish/json_samples/pci_device_collection.json new file mode 100644 index 0000000..e31a20f --- /dev/null +++ b/proliantutils/tests/redfish/json_samples/pci_device_collection.json @@ -0,0 +1,17 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Systems/Members/1/PCIDevices", + "@odata.etag": "W/\"9975C252\"", + "@odata.id": "/redfish/v1/Systems/1/PCIDevices/", + "@odata.type": "#HpeServerPciDeviceCollection.HpeServerPciDeviceCollection", + "Description": " PciDevices view", + "Members": [ + { + "@odata.id": "/redfish/v1/Systems/1/PCIDevices/1/" + }, + { + "@odata.id": "/redfish/v1/Systems/1/PCIDevices/6/" + } + ], + "Members@odata.count": 2, + "Name": "PciDevices" +} diff --git a/proliantutils/tests/redfish/resources/system/test_pci_device.py b/proliantutils/tests/redfish/resources/system/test_pci_device.py new file mode 100644 index 0000000..fc8749f --- /dev/null +++ b/proliantutils/tests/redfish/resources/system/test_pci_device.py @@ -0,0 +1,102 @@ +# 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.redfish.resources.system import pci_device + + +class PCIDeviceTestCase(testtools.TestCase): + + def setUp(self): + super(PCIDeviceTestCase, self).setUp() + self.conn = mock.Mock() + pci_file = 'proliantutils/tests/redfish/json_samples/pci_device.json' + with open(pci_file, 'r') as f: + self.conn.get.return_value.json.return_value = ( + json.loads(f.read())) + + pci_path = "/redfish/v1/Systems/1/PCIDevices/1" + self.sys_pci = pci_device.PCIDevice( + self.conn, pci_path, redfish_version='1.0.2') + + def test__parse_attributes(self): + self.sys_pci._parse_attributes() + self.assertEqual('1.0.2', self.sys_pci.redfish_version) + self.assertEqual('1', self.sys_pci.identity) + + +class PCIDeviceCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(PCIDeviceCollectionTestCase, self).setUp() + self.conn = mock.Mock() + with open('proliantutils/tests/redfish/json_samples/' + 'pci_device_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.sys_pci_col = pci_device.PCIDeviceCollection( + self.conn, '/redfish/v1/Systems/1/PCIDevices', + redfish_version='1.0.2') + + def test__parse_attributes(self): + self.sys_pci_col._parse_attributes() + self.assertEqual('1.0.2', self.sys_pci_col.redfish_version) + self.assertEqual('PciDevices', self.sys_pci_col.name) + pci_path = ('/redfish/v1/Systems/1/PCIDevices/1', + '/redfish/v1/Systems/1/PCIDevices/6') + self.assertEqual(pci_path, self.sys_pci_col.members_identities) + + @mock.patch.object(pci_device, 'PCIDevice', autospec=True) + def test_get_member(self, mock_pci): + self.sys_pci_col.get_member( + '/redfish/v1/Systems/1/PCIDevices/1') + mock_pci.assert_called_once_with( + self.sys_pci_col._conn, + ('/redfish/v1/Systems/1/PCIDevices/1'), + redfish_version=self.sys_pci_col.redfish_version) + + @mock.patch.object(pci_device, 'PCIDevice', autospec=True) + def test_get_members(self, mock_pci): + members = self.sys_pci_col.get_members() + path_list = ["/redfish/v1/Systems/1/PCIDevices/1", + "/redfish/v1/Systems/1/PCIDevices/6"] + calls = [ + mock.call(self.sys_pci_col._conn, path_list[0], + redfish_version=self.sys_pci_col.redfish_version), + mock.call(self.sys_pci_col._conn, path_list[1], + redfish_version=self.sys_pci_col.redfish_version) + ] + mock_pci.assert_has_calls(calls) + self.assertIsInstance(members, list) + self.assertEqual(2, len(members)) + + def test_gpu_devices(self): + self.assertIsNone(self.sys_pci_col._gpu_devices) + self.conn.get.return_value.json.reset_mock() + val = [] + path = ('proliantutils/tests/redfish/json_samples/' + 'pci_device.json') + with open(path, 'r') as f: + val.append(json.loads(f.read())) + path = ('proliantutils/tests/redfish/json_samples/' + 'pci_device1.json') + with open(path, 'r') as f: + val.append(json.loads(f.read())) + self.conn.get.return_value.json.side_effect = val + self.sys_pci_col.gpu_devices + self.assertEqual(1, len(self.sys_pci_col._gpu_devices)) diff --git a/proliantutils/tests/redfish/resources/system/test_system.py b/proliantutils/tests/redfish/resources/system/test_system.py index 3384d3e..7365c02 100644 --- a/proliantutils/tests/redfish/resources/system/test_system.py +++ b/proliantutils/tests/redfish/resources/system/test_system.py @@ -145,3 +145,27 @@ class HPESystemTestCase(testtools.TestCase): exception.IloError, 'The BIOS Boot Settings was not found.', self.sys_inst.update_persistent_boot, ['ISCSI'], True, '12345678') + + def test_pci_devices(self): + pci_dev_return_value = None + pci_dev1_return_value = None + pci_coll_return_value = None + self.assertIsNone(self.sys_inst._pci_devices) + self.conn.get.return_value.json.reset_mock() + with open('proliantutils/tests/redfish/' + 'json_samples/pci_device_collection.json') as f: + pci_coll_return_value = json.loads(f.read()) + with open('proliantutils/tests/redfish/' + 'json_samples/pci_device.json') as f: + pci_dev_return_value = json.loads(f.read()) + with open('proliantutils/tests/redfish/' + 'json_samples/pci_device1.json') as f: + pci_dev1_return_value = json.loads(f.read()) + self.conn.get.return_value.json.side_effect = ( + [pci_coll_return_value, pci_dev_return_value, + pci_dev1_return_value]) + actual_pci = self.sys_inst.pci_devices + self.conn.get.return_value.json.reset_mock() + self.assertIs(actual_pci, + self.sys_inst.pci_devices) + self.conn.get.return_value.json.assert_not_called() diff --git a/proliantutils/tests/redfish/test_redfish.py b/proliantutils/tests/redfish/test_redfish.py index 7f23492..2853afa 100644 --- a/proliantutils/tests/redfish/test_redfish.py +++ b/proliantutils/tests/redfish/test_redfish.py @@ -645,3 +645,25 @@ class RedfishOperationsTestCase(testtools.TestCase): exception.IloError, 'No account found with username: foo', self.rf_client.reset_ilo_credential, 'fake-password') + + @mock.patch.object(redfish.RedfishOperations, '_get_sushy_system') + def test_get_server_capabilities(self, get_system_mock): + val = [] + path = ('proliantutils/tests/redfish/json_samples/' + 'pci_device.json') + with open(path, 'r') as f: + val.append(json.loads(f.read())) + gpu_mock = mock.PropertyMock(return_value=val) + type(get_system_mock.return_value.pci_devices).gpu_devices = ( + gpu_mock) + actual = self.rf_client.get_server_capabilities() + expected = {'pci_gpu_devices': 1} + self.assertEqual(expected, actual) + + @mock.patch.object(redfish.RedfishOperations, '_get_sushy_system') + def test_get_server_capabilities_gpu_fail(self, get_system_mock): + gpu_mock = mock.PropertyMock(side_effect=sushy.exceptions.SushyError) + type(get_system_mock.return_value.pci_devices).gpu_devices = ( + gpu_mock) + self.assertRaises(exception.IloError, + self.rf_client.get_server_capabilities)