Aligning redfish calls to new framework

This patch proposes a new framework to communicate on redfish
protocol in proliantutils.

Change-Id: I719ee8da74ba4109ac1c9dfffdf54baed67cd95c
Closes-Bug: 1646685
This commit is contained in:
Debayan Ray 2017-05-19 03:53:37 -04:00
parent 2932ddfe90
commit 100c058a09
3 changed files with 73 additions and 50 deletions

View File

@ -15,17 +15,26 @@
__author__ = 'HPE' __author__ = 'HPE'
from six.moves.urllib import parse from six.moves.urllib import parse
import sushy
from proliantutils import exception from proliantutils import exception
from proliantutils.ilo import operations from proliantutils.ilo import operations
from proliantutils import log from proliantutils import log
from proliantutils import rest
""" """
Class specific for Redfish APIs. Class specific for Redfish APIs.
""" """
GET_POWER_STATE_MAP = {
sushy.SYSTEM_POWER_STATE_ON: 'ON',
sushy.SYSTEM_POWER_STATE_POWERING_ON: 'ON',
sushy.SYSTEM_POWER_STATE_OFF: 'OFF',
sushy.SYSTEM_POWER_STATE_POWERING_OFF: 'OFF'
}
PROLIANT_SYSTEM_ID = '1'
LOG = log.get_logger(__name__) LOG = log.get_logger(__name__)
@ -63,62 +72,69 @@ class RedfishOperations(operations.IloOperations):
the root service and version. Defaults to /redfish/v1 the root service and version. Defaults to /redfish/v1
""" """
super(RedfishOperations, self).__init__() super(RedfishOperations, self).__init__()
self._conn = rest.RestConnectorBase(redfish_controller_ip, username, address = ('https://' + redfish_controller_ip)
password, bios_password, cacert) LOG.debug('Redfish address: %s', address)
verify = False if cacert is None else cacert
# for error reporting purpose
self.host = redfish_controller_ip
self._root_prefix = root_prefix self._root_prefix = root_prefix
# Fetch the ServiceRoot response
self._fetch_root_resources()
def _fetch_root_resources(self): try:
"""Fetches the service root resources self._sushy = sushy.Sushy(
address, username=username, password=password,
Retrieves/fetches the resources at ServiceRoot root_prefix=root_prefix, verify=verify)
:raises: IloConnectionError except sushy.exceptions.SushyError as e:
""" msg = (self._('The Redfish controller at "%(controller)s" has '
status, headers, service_root_resp = ( 'thrown error. Error %(error)s') %
self._conn._rest_get(self._root_prefix)) {'controller': address, 'error': str(e)})
self._root_resp = service_root_resp LOG.debug(msg)
raise exception.IloConnectionError(msg)
def _get_system_collection_path(self): def _get_system_collection_path(self):
"""Helper function to find the SystemCollection path""" """Helper function to find the SystemCollection path"""
systems_col = self._root_resp.get('Systems') systems_col = self._sushy.json.get('Systems')
if not systems_col: if not systems_col:
raise exception.MissingAttributeError(attribute='Systems', raise exception.MissingAttributeError(attribute='Systems',
resource=self._root_prefix) resource=self._root_prefix)
return systems_col.get('@odata.id') return systems_col.get('@odata.id')
def _get_system_details(self, system_id): def _get_sushy_system(self, system_id):
"""Get the system details. """Get the sushy system for system_id
:param system_id: The identity of the System resource :param system_id: The identity of the System resource
:returns: the Sushy system instance
:raises: IloError :raises: IloError
:raises: MissingAttributeError
""" """
system_url = parse.urljoin(self._get_system_collection_path(), system_url = parse.urljoin(self._get_system_collection_path(),
system_id) system_id)
status, headers, system = self._conn._rest_get(system_url) try:
return system return self._sushy.get_system(system_url)
except sushy.exceptions.SushyError as e:
msg = (self._('The Redfish System "%(system)s" was not found. '
'Error %(error)s') %
{'system': system_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.
:returns: server model name. :returns: server model name.
:raises: IloError, on an error from iLO. :raises: IloError, on an error from iLO.
:raises: MissingAttributeError
""" """
# Assuming only one system present as part of collection, # Assuming only one system present as part of collection,
# as we are dealing with iLO's here. # as we are dealing with iLO's here.
system = self._get_system_details('1') sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
return system['Model'] return sushy_system.json.get('Model')
def get_host_power_status(self): def get_host_power_status(self):
"""Request the power state of the server. """Request the power state of the server.
:returns: Power State of the server, 'ON' or 'OFF' :returns: Power State of the server, 'ON' or 'OFF'
:raises: IloError, on an error from iLO. :raises: IloError, on an error from iLO.
:raises: MissingAttributeError
""" """
# Assuming only one system present as part of collection, # Assuming only one sushy_system present as part of collection,
# as we are dealing with iLO's here. # as we are dealing with iLO's here.
system = self._get_system_details('1') sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
return system['PowerState'].upper() return GET_POWER_STATE_MAP.get(sushy_system.power_state)

View File

@ -16,61 +16,65 @@
import json import json
import mock import mock
import sushy
import testtools import testtools
from proliantutils import exception from proliantutils import exception
from proliantutils.redfish import redfish from proliantutils.redfish import redfish
from proliantutils import rest
class RedfishOperationsTestCase(testtools.TestCase): class RedfishOperationsTestCase(testtools.TestCase):
@mock.patch.object(rest, 'RestConnectorBase', autospec=True) @mock.patch.object(sushy, 'Sushy', autospec=True)
def setUp(self, rest_connector_mock): def setUp(self, sushy_mock):
super(RedfishOperationsTestCase, self).setUp() super(RedfishOperationsTestCase, self).setUp()
self.conn = mock.MagicMock() self.sushy = mock.MagicMock()
rest_connector_mock.return_value = self.conn 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:
self.conn._rest_get.return_value = 200, None, json.loads(f.read()) self.sushy.json = json.loads(f.read())
self.rf_client = redfish.RedfishOperations( self.rf_client = redfish.RedfishOperations(
'1.2.3.4', username='foo', password='bar') '1.2.3.4', username='foo', password='bar')
rest_connector_mock.assert_called_once_with( sushy_mock.assert_called_once_with(
'1.2.3.4', 'foo', 'bar', None, None) 'https://1.2.3.4', 'foo', 'bar', '/redfish/v1/', False)
def test__fetch_root_resources(self): @mock.patch.object(sushy, 'Sushy', autospec=True)
rf_client = self.rf_client def test_sushy_init_fail(self, sushy_mock):
rf_client._fetch_root_resources() sushy_mock.side_effect = sushy.exceptions.SushyError
self.assertEqual('HPE RESTful Root Service', self.assertRaisesRegex(
rf_client._root_resp.get('Name')) exception.IloConnectionError,
self.assertEqual('1.0.0', rf_client._root_resp.get('RedfishVersion')) 'The Redfish controller at "https://1.2.3.4" has thrown error',
self.assertEqual('7704b47b-2fbe-5920-99a5-b766dd84cc28', redfish.RedfishOperations,
rf_client._root_resp.get('UUID')) '1.2.3.4', username='foo', password='bar')
for resource in ['Systems', 'Managers', 'Chassis']:
self.assertTrue(resource in rf_client._root_resp)
def test__get_system_collection_path(self): def test__get_system_collection_path(self):
self.assertEqual('/redfish/v1/Systems/', self.assertEqual('/redfish/v1/Systems/',
self.rf_client._get_system_collection_path()) self.rf_client._get_system_collection_path())
def test__get_system_collection_path_missing_systems_attr(self): def test__get_system_collection_path_missing_systems_attr(self):
self.rf_client._root_resp.pop('Systems') self.rf_client._sushy.json.pop('Systems')
self.assertRaisesRegex( self.assertRaisesRegex(
exception.MissingAttributeError, exception.MissingAttributeError,
'The attribute Systems is missing', 'The attribute Systems is missing',
self.rf_client._get_system_collection_path) 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)
self.assertRaisesRegex(
exception.IloError,
'The Redfish System "apple" was not found.',
self.rf_client._get_sushy_system, 'apple')
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:
self.conn._rest_get.return_value = 200, None, json.loads(f.read()) self.sushy.get_system().json = json.loads(f.read())
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)
def test_get_host_power_status(self): def test_get_host_power_status(self):
with open('proliantutils/tests/redfish/' self.sushy.get_system().power_state = sushy.SYSTEM_POWER_STATE_ON
'json_samples/system.json', 'r') as f:
self.conn._rest_get.return_value = 200, None, json.loads(f.read())
power_state = self.rf_client.get_host_power_status() power_state = self.rf_client.get_host_power_status()
self.assertEqual('ON', power_state) self.assertEqual('ON', power_state)

View File

@ -7,3 +7,6 @@ jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0
retrying!=1.3.0,>=1.2.3 # Apache-2.0 retrying!=1.3.0,>=1.2.3 # Apache-2.0
pysnmp>=4.2.3,<5.0.0 # BSD pysnmp>=4.2.3,<5.0.0 # BSD
# Redfish communication uses the Sushy library
sushy