Redfish: Adds manager support for redfish API's.

This commit enables the support for all redfish API's to make
use of Manager features.
This patch also includes a temporary hack for manager as
'manager_mock.py' file which needs to be removed once new Sushy
version is released with manager feature. Added the 'utils' file
containing the utility function, ``get_subresource_path_by``.

Co-Authored-By: Aparna Vikraman <aparnavtce@gmail.com>
Co-Authored-By: Debayan Ray <debayan.ray@gmail.com>

Partial-Bug: 1691955

Change-Id: Iffec7e2e459455dba3b5bac817faa89341b4b9d3
This commit is contained in:
Anshul Jain 2017-06-13 07:21:29 +00:00 committed by Debayan Ray
parent e39fd72626
commit 5dab85a2a4
13 changed files with 493 additions and 25 deletions

View File

@ -14,9 +14,12 @@
__author__ = 'HPE' __author__ = 'HPE'
from proliantutils.redfish.resources.system import system
import sushy import sushy
from proliantutils.redfish.resources.manager import manager
from proliantutils.redfish.resources.system import system
from proliantutils.redfish import utils
class HPESushy(sushy.Sushy): class HPESushy(sushy.Sushy):
"""Class that extends base Sushy class """Class that extends base Sushy class
@ -25,6 +28,9 @@ class HPESushy(sushy.Sushy):
required to customize the functionality of different resources required to customize the functionality of different resources
""" """
def get_system_collection_path(self):
return utils.get_subresource_path_by(self, 'Systems')
def get_system(self, identity): def get_system(self, identity):
"""Given the identity return a HPESystem object """Given the identity return a HPESystem object
@ -33,3 +39,15 @@ class HPESushy(sushy.Sushy):
""" """
return system.HPESystem(self._conn, identity, return system.HPESystem(self._conn, identity,
redfish_version=self.redfish_version) redfish_version=self.redfish_version)
def get_manager_collection_path(self):
return utils.get_subresource_path_by(self, 'Managers')
def get_manager(self, identity):
"""Given the identity return a HPEManager object
:param identity: The identity of the Manager resource
:returns: The Manager object
"""
return manager.HPEManager(self._conn, identity,
redfish_version=self.redfish_version)

View File

@ -103,14 +103,6 @@ class RedfishOperations(operations.IloOperations):
LOG.debug(msg) LOG.debug(msg)
raise exception.IloConnectionError(msg) raise exception.IloConnectionError(msg)
def _get_system_collection_path(self):
"""Helper function to find the SystemCollection path"""
systems_col = self._sushy.json.get('Systems')
if not systems_col:
raise exception.MissingAttributeError(attribute='Systems',
resource=self._root_prefix)
return systems_col.get('@odata.id')
def _get_sushy_system(self, system_id): def _get_sushy_system(self, system_id):
"""Get the sushy system for system_id """Get the sushy system for system_id
@ -118,7 +110,7 @@ class RedfishOperations(operations.IloOperations):
:returns: the Sushy system instance :returns: the Sushy system instance
:raises: IloError :raises: IloError
""" """
system_url = parse.urljoin(self._get_system_collection_path(), system_url = parse.urljoin(self._sushy.get_system_collection_path(),
system_id) system_id)
try: try:
return self._sushy.get_system(system_url) return self._sushy.get_system(system_url)
@ -129,6 +121,24 @@ class RedfishOperations(operations.IloOperations):
LOG.debug(msg) LOG.debug(msg)
raise exception.IloError(msg) raise exception.IloError(msg)
def _get_sushy_manager(self, manager_id):
"""Get the sushy Manager for manager_id
:param manager_id: The identity of the Manager resource
:returns: the Sushy Manager instance
:raises: IloError
"""
manager_url = parse.urljoin(self._sushy.get_manager_collection_path(),
manager_id)
try:
return self._sushy.get_manager(manager_url)
except sushy.exceptions.SushyError as e:
msg = (self._('The Redfish Manager "%(manager)s" was not found. '
'Error %(error)s') %
{'manager': manager_id, 'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
def get_product_name(self): def get_product_name(self):
"""Gets the product name of the server. """Gets the product name of the server.

View File

@ -0,0 +1,25 @@
# 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.
__author__ = 'HPE'
from sushy.resources.manager import manager
class HPEManager(manager.Manager):
"""Class that extends the functionality of Manager resource class
This class extends the functionality of Manager resource class
from sushy
"""

View File

@ -0,0 +1,51 @@
# 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.
__author__ = 'HPE'
import six
from proliantutils import exception
def get_subresource_path_by(resource, subresource_path):
"""Helper function to find the resource path
:param resource: ResourceBase instance from which the path is loaded.
:param subresource_path: JSON field to fetch the value from.
Either a string, or a list of strings in case of a nested field.
It should also include the '@odata.id'
:raises: MissingAttributeError, if required path is missing.
:raises: ValueError, if path is empty.
:raises: AttributeError, if json attr not found in resource
"""
if isinstance(subresource_path, six.string_types):
subresource_path = [subresource_path]
elif not subresource_path:
raise ValueError('"subresource_path" cannot be empty')
body = resource.json
for path_item in subresource_path:
body = body.get(path_item, {})
if not body:
raise exception.MissingAttributeError(
attribute='/'.join(subresource_path), resource=resource.path)
if '@odata.id' not in body:
raise exception.MissingAttributeError(
attribute='/'.join(subresource_path)+'/@odata.id',
resource=resource.path)
return body['@odata.id']

View File

@ -0,0 +1,17 @@
# 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.
# TODO(deray): Need to remove this hack sooner
from proliantutils.tests.redfish import manager_mock # noqa

View File

@ -0,0 +1,196 @@
{
"@odata.context": "/redfish/v1/$metadata#Managers/Members/$entity",
"@odata.etag": "W/\"FD28A1E2\"",
"@odata.id": "/redfish/v1/Managers/1/",
"@odata.type": "#Manager.v1_1_0.Manager",
"Actions": {
"#Manager.Reset": {
"target": "/redfish/v1/Managers/1/Actions/Manager.Reset/"
}
},
"CommandShell": {
"ConnectTypesSupported": [
"SSH",
"Oem"
],
"MaxConcurrentSessions": 9,
"ServiceEnabled": true
},
"EthernetInterfaces": {
"@odata.id": "/redfish/v1/Managers/1/EthernetInterfaces/"
},
"FirmwareVersion": "iLO 5 v1.15",
"GraphicalConsole": {
"ConnectTypesSupported": [
"KVMIP"
],
"MaxConcurrentSessions": 10,
"ServiceEnabled": true
},
"Id": "1",
"Links": {
"ManagerForChassis": [
{
"@odata.id": "/redfish/v1/Chassis/1/"
}
],
"ManagerForServers": [
{
"@odata.id": "/redfish/v1/Systems/1/"
}
],
"ManagerInChassis": {
"@odata.id": "/redfish/v1/Chassis/1/"
}
},
"LogServices": {
"@odata.id": "/redfish/v1/Managers/1/LogServices/"
},
"ManagerType": "BMC",
"Name": "Manager",
"NetworkProtocol": {
"@odata.id": "/redfish/v1/Managers/1/NetworkService/"
},
"Oem": {
"Hpe": {
"@odata.type": "#HpeiLO.v2_1_0.HpeiLO",
"Actions": {
"#HpeiLO.ClearRestApiState": {
"target": "/redfish/v1/Managers/1/Actions/Oem/Hpe/HpeiLO.ClearRestApiState/"
},
"#HpeiLO.DisableiLOFunctionality": {
"target": "/redfish/v1/Managers/1/Actions/Oem/Hpe/HpeiLO.DisableiLOFunctionality/"
},
"#HpeiLO.ResetToFactoryDefaults": {
"ResetType@Redfish.AllowableValues": [
"Default"
],
"target": "/redfish/v1/Managers/1/Actions/Oem/Hpe/HpeiLO.ResetToFactoryDefaults/"
}
},
"ClearRestApiStatus": "DataPresent",
"ConfigurationSettings": "Current",
"FederationConfig": {
"IPv6MulticastScope": "Site",
"MulticastAnnouncementInterval": 600,
"MulticastDiscovery": "Enabled",
"MulticastTimeToLive": 5,
"iLOFederationManagement": "Enabled"
},
"Firmware": {
"Current": {
"Date": "Jun 05 2017",
"DebugBuild": false,
"MajorVersion": 1,
"MinorVersion": 15,
"VersionString": "iLO 5 v1.15"
}
},
"FrontPanelUSB": {
"State": "Ready"
},
"IdleConnectionTimeoutMinutes": 30,
"License": {
"LicenseKey": "XXXXX-XXXXX-XXXXX-XXXXX-456N6",
"LicenseString": "iLO Advanced limited-distribution test",
"LicenseType": "Internal"
},
"Links": {
"ActiveHealthSystem": {
"@odata.id": "/redfish/v1/Managers/1/ActiveHealthSystem/"
},
"DateTimeService": {
"@odata.id": "/redfish/v1/Managers/1/DateTime/"
},
"EmbeddedMediaService": {
"@odata.id": "/redfish/v1/Managers/1/EmbeddedMedia/"
},
"FederationDispatch": {
"extref": "/dispatch"
},
"FederationGroups": {
"@odata.id": "/redfish/v1/Managers/1/FederationGroups/"
},
"FederationPeers": {
"@odata.id": "/redfish/v1/Managers/1/FederationPeers/"
},
"LicenseService": {
"@odata.id": "/redfish/v1/Managers/1/LicenseService/"
},
"SecurityService": {
"@odata.id": "/redfish/v1/Managers/1/SecurityService/"
},
"Thumbnail": {
"extref": "/images/thumbnail.bmp"
},
"VSPLogLocation": {
"extref": "/sol.log.gz"
}
},
"PersistentMouseKeyboardEnabled": false,
"RIBCLEnabled": true,
"RequiredLoginForiLORBSU": false,
"SerialCLISpeed": 9600,
"SerialCLIStatus": "EnabledAuthReq",
"VSPDlLoggingEnabled": false,
"VSPLogDownloadEnabled": false,
"WebGuiEnabled": true,
"iLOFunctionalityRequired": false,
"iLORBSUEnabled": true,
"iLOSelfTestResults": [
{
"Notes": "",
"SelfTestName": "NVRAMData",
"Status": "OK"
},
{
"Notes": "Controller firmware revision 2.10.00 ",
"SelfTestName": "EmbeddedFlash",
"Status": "OK"
},
{
"Notes": "",
"SelfTestName": "HostRom",
"Status": "OK"
},
{
"Notes": "",
"SelfTestName": "SupportedHost",
"Status": "OK"
},
{
"Notes": "Version 1.0.2",
"SelfTestName": "PowerManagementController",
"Status": "Informational"
},
{
"Notes": "ProLiant DL180 Gen10 System Programmable Logic Device 0x07",
"SelfTestName": "CPLDPAL0",
"Status": "Informational"
}
],
"iLOServicePort": {
"MassStorageAuthenticationRequired": false,
"USBEthernetAdaptersEnabled": true,
"USBFlashDriveEnabled": true,
"iLOServicePortEnabled": true
}
}
},
"SerialConsole": {
"ConnectTypesSupported": [
"SSH",
"IPMI",
"Oem"
],
"MaxConcurrentSessions": 13,
"ServiceEnabled": true
},
"Status": {
"State": "Enabled"
},
"UUID": null,
"VirtualMedia": {
"@odata.id": "/redfish/v1/Managers/1/VirtualMedia/"
}
}

View File

@ -1,5 +1,5 @@
{ {
"Default": { "default": {
"@odata.context": "/redfish/v1/$metadata#Systems/Members/$entity", "@odata.context": "/redfish/v1/$metadata#Systems/Members/$entity",
"@odata.etag": "W/\"0E79655D\"", "@odata.etag": "W/\"0E79655D\"",
"@odata.id": "/redfish/v1/Systems/1/", "@odata.id": "/redfish/v1/Systems/1/",

View File

@ -0,0 +1,39 @@
# 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.
# TODO(deray): Need to remove this hack sooner
import sys
import mock
from oslo_utils import importutils
import six
SUSHY_MANAGER_PACKAGE_SPEC = ('manager',)
sushy = importutils.try_import('sushy')
if sushy:
sushy_resources_manager = mock.MagicMock(
spec_set=SUSHY_MANAGER_PACKAGE_SPEC)
sys.modules['sushy.resources.manager'] = sushy_resources_manager
sushy.resources.common = mock.MagicMock()
sushy_resources_manager.manager.Manager = type(
'Manager', (sushy.resources.base.ResourceBase,), {})
sushy.resources.common.ResetActionField = type(
'ResetActionField', (sushy.resources.base.CompositeField,),
{"target_uri": sushy.resources.base.Field('target', required=True)})
if 'proliantutils.redfish' in sys.modules:
six.moves.reload_module(sys.modules['proliantutils.redfish'])

View File

@ -31,7 +31,7 @@ class HPESystemTestCase(testtools.TestCase):
with open('proliantutils/tests/redfish/' with open('proliantutils/tests/redfish/'
'json_samples/system.json', 'r') as f: 'json_samples/system.json', 'r') as f:
system_json = json.loads(f.read()) system_json = json.loads(f.read())
self.conn.get.return_value.json.return_value = system_json['Default'] self.conn.get.return_value.json.return_value = system_json['default']
self.sys_inst = system.HPESystem( self.sys_inst = system.HPESystem(
self.conn, '/redfish/v1/Systems/1', self.conn, '/redfish/v1/Systems/1',

View File

@ -13,11 +13,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import mock import mock
from sushy import connector from sushy import connector
import testtools import testtools
from proliantutils import exception
from proliantutils.redfish import main from proliantutils.redfish import main
from proliantutils.redfish.resources.manager import manager
from proliantutils.redfish.resources.system import system from proliantutils.redfish.resources.system import system
@ -26,10 +30,26 @@ class HPESushyTestCase(testtools.TestCase):
@mock.patch.object(connector, 'Connector', autospec=True) @mock.patch.object(connector, 'Connector', autospec=True)
def setUp(self, connector_mock): def setUp(self, connector_mock):
super(HPESushyTestCase, self).setUp() super(HPESushyTestCase, self).setUp()
with open('proliantutils/tests/redfish/'
'json_samples/root.json', 'r') as f:
root_json = json.loads(f.read())
connector_mock.return_value.get.return_value.json.return_value = (
root_json)
self.hpe_sushy = main.HPESushy('https://1.2.3.4', self.hpe_sushy = main.HPESushy('https://1.2.3.4',
username='foo', username='foo',
password='bar') password='bar')
def test_get_system_collection_path(self):
self.assertEqual('/redfish/v1/Systems/',
self.hpe_sushy.get_system_collection_path())
def test_get_system_collection_path_missing_systems_attr(self):
self.hpe_sushy.json.pop('Systems')
self.assertRaisesRegex(
exception.MissingAttributeError,
'The attribute Systems is missing',
self.hpe_sushy.get_system_collection_path)
@mock.patch.object(system, 'HPESystem', autospec=True) @mock.patch.object(system, 'HPESystem', autospec=True)
def test_get_system(self, mock_system): def test_get_system(self, mock_system):
sys_inst = self.hpe_sushy.get_system('1234') sys_inst = self.hpe_sushy.get_system('1234')
@ -38,3 +58,23 @@ class HPESushyTestCase(testtools.TestCase):
mock_system.assert_called_once_with(self.hpe_sushy._conn, mock_system.assert_called_once_with(self.hpe_sushy._conn,
'1234', '1234',
self.hpe_sushy.redfish_version) self.hpe_sushy.redfish_version)
def test_get_manager_collection_path(self):
self.assertEqual('/redfish/v1/Managers/',
self.hpe_sushy.get_manager_collection_path())
def test_get_manager_collection_path_missing_systems_attr(self):
self.hpe_sushy.json.pop('Managers')
self.assertRaisesRegex(
exception.MissingAttributeError,
'The attribute Managers is missing',
self.hpe_sushy.get_manager_collection_path)
@mock.patch.object(manager, 'HPEManager', autospec=True)
def test_get_manager(self, mock_manager):
sys_inst = self.hpe_sushy.get_manager('1234')
self.assertTrue(isinstance(sys_inst,
manager.HPEManager.__class__))
mock_manager.assert_called_once_with(self.hpe_sushy._conn,
'1234',
self.hpe_sushy.redfish_version)

View File

@ -32,6 +32,10 @@ class RedfishOperationsTestCase(testtools.TestCase):
def setUp(self, sushy_mock): def setUp(self, sushy_mock):
super(RedfishOperationsTestCase, self).setUp() super(RedfishOperationsTestCase, self).setUp()
self.sushy = mock.MagicMock() self.sushy = mock.MagicMock()
self.sushy.get_system_collection_path.return_value = (
'/redfish/v1/Systems')
self.sushy.get_manager_collection_path.return_value = (
'/redfish/v1/Managers')
sushy_mock.return_value = self.sushy sushy_mock.return_value = self.sushy
with open('proliantutils/tests/redfish/' with open('proliantutils/tests/redfish/'
'json_samples/root.json', 'r') as f: 'json_samples/root.json', 'r') as f:
@ -51,17 +55,6 @@ class RedfishOperationsTestCase(testtools.TestCase):
redfish.RedfishOperations, redfish.RedfishOperations,
'1.2.3.4', username='foo', password='bar') '1.2.3.4', username='foo', password='bar')
def test__get_system_collection_path(self):
self.assertEqual('/redfish/v1/Systems/',
self.rf_client._get_system_collection_path())
def test__get_system_collection_path_missing_systems_attr(self):
self.rf_client._sushy.json.pop('Systems')
self.assertRaisesRegex(
exception.MissingAttributeError,
'The attribute Systems is missing',
self.rf_client._get_system_collection_path)
def test__get_sushy_system_fail(self): def test__get_sushy_system_fail(self):
self.rf_client._sushy.get_system.side_effect = ( self.rf_client._sushy.get_system.side_effect = (
sushy.exceptions.SushyError) sushy.exceptions.SushyError)
@ -70,11 +63,19 @@ class RedfishOperationsTestCase(testtools.TestCase):
'The Redfish System "apple" was not found.', 'The Redfish System "apple" was not found.',
self.rf_client._get_sushy_system, 'apple') self.rf_client._get_sushy_system, 'apple')
def test__get_sushy_manager_fail(self):
self.rf_client._sushy.get_manager.side_effect = (
sushy.exceptions.SushyError)
self.assertRaisesRegex(
exception.IloError,
'The Redfish Manager "banana" was not found.',
self.rf_client._get_sushy_manager, 'banana')
def test_get_product_name(self): def test_get_product_name(self):
with open('proliantutils/tests/redfish/' with open('proliantutils/tests/redfish/'
'json_samples/system.json', 'r') as f: 'json_samples/system.json', 'r') as f:
system_json = json.loads(f.read()) system_json = json.loads(f.read())
self.sushy.get_system().json = system_json['Default'] self.sushy.get_system().json = system_json['default']
product_name = self.rf_client.get_product_name() product_name = self.rf_client.get_product_name()
self.assertEqual('ProLiant DL180 Gen10', product_name) self.assertEqual('ProLiant DL180 Gen10', product_name)
@ -156,7 +157,7 @@ class RedfishOperationsTestCase(testtools.TestCase):
with open('proliantutils/tests/redfish/' with open('proliantutils/tests/redfish/'
'json_samples/system.json', 'r') as f: 'json_samples/system.json', 'r') as f:
system_json = json.loads(f.read()) system_json = json.loads(f.read())
self.sushy.get_system().json = system_json['Default'] self.sushy.get_system().json = system_json['default']
boot = self.rf_client.get_one_time_boot() boot = self.rf_client.get_one_time_boot()
self.assertEqual('Normal', boot) self.assertEqual('Normal', boot)

View File

@ -0,0 +1,71 @@
# Copyright 2016 Hewlett Packard Enterprise Company, L.P.
# 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.
"""Test class for Utils Module."""
import json
import ddt
import mock
import testtools
from proliantutils import exception
from proliantutils.redfish.resources.system import system
from proliantutils.redfish import utils
@ddt.ddt
class UtilsTestCase(testtools.TestCase):
def setUp(self):
super(UtilsTestCase, self).setUp()
self.conn = mock.MagicMock()
with open('proliantutils/tests/redfish/'
'json_samples/system.json', 'r') as f:
system_json = json.loads(f.read())
self.conn.get.return_value.json.return_value = system_json['default']
self.sys_inst = system.HPESystem(self.conn, '/redfish/v1/Systems/1',
redfish_version='1.0.2')
@ddt.data(('SecureBoot', '/redfish/v1/Systems/1/SecureBoot/'),
(['Oem', 'Hpe', 'Links', 'NetworkAdapters'],
'/redfish/v1/Systems/1/NetworkAdapters/'),
)
@ddt.unpack
def test_get_subresource_path_by(self, subresource_path, expected_result):
value = utils.get_subresource_path_by(self.sys_inst,
subresource_path)
self.assertEqual(expected_result, value)
@ddt.data(('NonSecureBoot', 'attribute NonSecureBoot is missing'),
(['Links', 'Chassis'],
'attribute Links/Chassis/@odata.id is missing'),
)
@ddt.unpack
def test_get_subresource_path_by_when_fails(
self, subresource_path, expected_exception_string_subset):
self.assertRaisesRegex(
exception.MissingAttributeError,
expected_exception_string_subset,
utils.get_subresource_path_by,
self.sys_inst, subresource_path)
def test_get_subresource_path_by_fails_with_empty_path(self):
self.assertRaisesRegex(
ValueError,
'"subresource_path" cannot be empty',
utils.get_subresource_path_by,
self.sys_inst, [])