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'
from proliantutils.redfish.resources.system import system
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 that extends base Sushy class
@ -25,6 +28,9 @@ class HPESushy(sushy.Sushy):
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):
"""Given the identity return a HPESystem object
@ -33,3 +39,15 @@ class HPESushy(sushy.Sushy):
"""
return system.HPESystem(self._conn, identity,
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)
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):
"""Get the sushy system for system_id
@ -118,7 +110,7 @@ class RedfishOperations(operations.IloOperations):
:returns: the Sushy system instance
:raises: IloError
"""
system_url = parse.urljoin(self._get_system_collection_path(),
system_url = parse.urljoin(self._sushy.get_system_collection_path(),
system_id)
try:
return self._sushy.get_system(system_url)
@ -129,6 +121,24 @@ class RedfishOperations(operations.IloOperations):
LOG.debug(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):
"""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.etag": "W/\"0E79655D\"",
"@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/'
'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.conn.get.return_value.json.return_value = system_json['default']
self.sys_inst = system.HPESystem(
self.conn, '/redfish/v1/Systems/1',

View File

@ -13,11 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
from sushy import connector
import testtools
from proliantutils import exception
from proliantutils.redfish import main
from proliantutils.redfish.resources.manager import manager
from proliantutils.redfish.resources.system import system
@ -26,10 +30,26 @@ class HPESushyTestCase(testtools.TestCase):
@mock.patch.object(connector, 'Connector', autospec=True)
def setUp(self, connector_mock):
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',
username='foo',
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)
def test_get_system(self, mock_system):
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,
'1234',
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):
super(RedfishOperationsTestCase, self).setUp()
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
with open('proliantutils/tests/redfish/'
'json_samples/root.json', 'r') as f:
@ -51,17 +55,6 @@ class RedfishOperationsTestCase(testtools.TestCase):
redfish.RedfishOperations,
'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):
self.rf_client._sushy.get_system.side_effect = (
sushy.exceptions.SushyError)
@ -70,11 +63,19 @@ class RedfishOperationsTestCase(testtools.TestCase):
'The Redfish System "apple" was not found.',
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):
with open('proliantutils/tests/redfish/'
'json_samples/system.json', 'r') as f:
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()
self.assertEqual('ProLiant DL180 Gen10', product_name)
@ -156,7 +157,7 @@ class RedfishOperationsTestCase(testtools.TestCase):
with open('proliantutils/tests/redfish/'
'json_samples/system.json', 'r') as f:
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()
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, [])