diff --git a/proliantutils/redfish/redfish.py b/proliantutils/redfish/redfish.py index 17077a8a..540b97b0 100644 --- a/proliantutils/redfish/redfish.py +++ b/proliantutils/redfish/redfish.py @@ -15,17 +15,26 @@ __author__ = 'HPE' from six.moves.urllib import parse +import sushy from proliantutils import exception from proliantutils.ilo import operations from proliantutils import log -from proliantutils import rest """ 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__) @@ -63,62 +72,69 @@ class RedfishOperations(operations.IloOperations): the root service and version. Defaults to /redfish/v1 """ super(RedfishOperations, self).__init__() - self._conn = rest.RestConnectorBase(redfish_controller_ip, username, - password, bios_password, cacert) + address = ('https://' + redfish_controller_ip) + 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 - # Fetch the ServiceRoot response - self._fetch_root_resources() - def _fetch_root_resources(self): - """Fetches the service root resources - - Retrieves/fetches the resources at ServiceRoot - :raises: IloConnectionError - """ - status, headers, service_root_resp = ( - self._conn._rest_get(self._root_prefix)) - self._root_resp = service_root_resp + try: + self._sushy = sushy.Sushy( + address, username=username, password=password, + root_prefix=root_prefix, verify=verify) + except sushy.exceptions.SushyError as e: + msg = (self._('The Redfish controller at "%(controller)s" has ' + 'thrown error. Error %(error)s') % + {'controller': address, 'error': str(e)}) + LOG.debug(msg) + raise exception.IloConnectionError(msg) def _get_system_collection_path(self): """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: raise exception.MissingAttributeError(attribute='Systems', resource=self._root_prefix) return systems_col.get('@odata.id') - def _get_system_details(self, system_id): - """Get the system details. + def _get_sushy_system(self, system_id): + """Get the sushy system for system_id :param system_id: The identity of the System resource + :returns: the Sushy system instance :raises: IloError - :raises: MissingAttributeError """ system_url = parse.urljoin(self._get_system_collection_path(), system_id) - status, headers, system = self._conn._rest_get(system_url) - return system + try: + 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): """Gets the product name of the server. :returns: server model name. :raises: IloError, on an error from iLO. - :raises: MissingAttributeError """ # Assuming only one system present as part of collection, # as we are dealing with iLO's here. - system = self._get_system_details('1') - return system['Model'] + sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) + return sushy_system.json.get('Model') def get_host_power_status(self): """Request the power state of the server. :returns: Power State of the server, 'ON' or 'OFF' :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. - system = self._get_system_details('1') - return system['PowerState'].upper() + sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) + return GET_POWER_STATE_MAP.get(sushy_system.power_state) diff --git a/proliantutils/tests/redfish/test_redfish.py b/proliantutils/tests/redfish/test_redfish.py index df265729..0df709b0 100644 --- a/proliantutils/tests/redfish/test_redfish.py +++ b/proliantutils/tests/redfish/test_redfish.py @@ -16,61 +16,65 @@ import json import mock +import sushy import testtools from proliantutils import exception from proliantutils.redfish import redfish -from proliantutils import rest class RedfishOperationsTestCase(testtools.TestCase): - @mock.patch.object(rest, 'RestConnectorBase', autospec=True) - def setUp(self, rest_connector_mock): + @mock.patch.object(sushy, 'Sushy', autospec=True) + def setUp(self, sushy_mock): super(RedfishOperationsTestCase, self).setUp() - self.conn = mock.MagicMock() - rest_connector_mock.return_value = self.conn + self.sushy = mock.MagicMock() + sushy_mock.return_value = self.sushy with open('proliantutils/tests/redfish/' '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( '1.2.3.4', username='foo', password='bar') - rest_connector_mock.assert_called_once_with( - '1.2.3.4', 'foo', 'bar', None, None) + sushy_mock.assert_called_once_with( + 'https://1.2.3.4', 'foo', 'bar', '/redfish/v1/', False) - def test__fetch_root_resources(self): - rf_client = self.rf_client - rf_client._fetch_root_resources() - self.assertEqual('HPE RESTful Root Service', - rf_client._root_resp.get('Name')) - self.assertEqual('1.0.0', rf_client._root_resp.get('RedfishVersion')) - self.assertEqual('7704b47b-2fbe-5920-99a5-b766dd84cc28', - rf_client._root_resp.get('UUID')) - for resource in ['Systems', 'Managers', 'Chassis']: - self.assertTrue(resource in rf_client._root_resp) + @mock.patch.object(sushy, 'Sushy', autospec=True) + def test_sushy_init_fail(self, sushy_mock): + sushy_mock.side_effect = sushy.exceptions.SushyError + self.assertRaisesRegex( + exception.IloConnectionError, + 'The Redfish controller at "https://1.2.3.4" has thrown error', + 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._root_resp.pop('Systems') + 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) + self.assertRaisesRegex( + exception.IloError, + 'The Redfish System "apple" was not found.', + self.rf_client._get_sushy_system, 'apple') + def test_get_product_name(self): with open('proliantutils/tests/redfish/' '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() self.assertEqual('ProLiant DL180 Gen10', product_name) def test_get_host_power_status(self): - with open('proliantutils/tests/redfish/' - 'json_samples/system.json', 'r') as f: - self.conn._rest_get.return_value = 200, None, json.loads(f.read()) + self.sushy.get_system().power_state = sushy.SYSTEM_POWER_STATE_ON power_state = self.rf_client.get_host_power_status() self.assertEqual('ON', power_state) diff --git a/requirements.txt b/requirements.txt index 25420ab0..1ce97522 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 retrying!=1.3.0,>=1.2.3 # Apache-2.0 pysnmp>=4.2.3,<5.0.0 # BSD + +# Redfish communication uses the Sushy library +sushy