diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index 06ad50cf..2711d687 100644 --- a/proliantutils/ilo/client.py +++ b/proliantutils/ilo/client.py @@ -62,6 +62,8 @@ SUPPORTED_REDFISH_METHODS = [ 'get_host_power_status', 'set_host_power', 'reset_server', + 'press_pwr_btn', + 'hold_pwr_btn', ] LOG = log.get_logger(__name__) diff --git a/proliantutils/redfish/redfish.py b/proliantutils/redfish/redfish.py index 90a05e1b..fd965f20 100644 --- a/proliantutils/redfish/redfish.py +++ b/proliantutils/redfish/redfish.py @@ -21,7 +21,7 @@ from proliantutils import exception from proliantutils.ilo import operations from proliantutils import log from proliantutils.redfish import main - +from proliantutils.redfish.resources.system import constants as sys_cons """ Class specific for Redfish APIs. @@ -39,6 +39,8 @@ POWER_RESET_MAP = { 'OFF': sushy.RESET_FORCE_OFF, } +# Assuming only one sushy_system present as part of collection, +# as we are dealing with iLO's here. PROLIANT_SYSTEM_ID = '1' LOG = log.get_logger(__name__) @@ -47,17 +49,11 @@ LOG = log.get_logger(__name__) class RedfishOperations(operations.IloOperations): """Operations supported on redfish based hardware. - This is the list of APIs which are currently supported via Redfish mode + This class holds APIs which are currently supported via Redfish mode of operation. This is a growing list which needs to be updated as and when the existing API/s (of its cousin RIS and RIBCL interfaces) are migrated. - - --- START --- - - * get_product_name(self) - * get_host_power_status(self) - - --- END --- - + For operations currently supported on the client object, please refer: + *proliantutils.ilo.client.SUPPORTED_REDFISH_METHODS* """ def __init__(self, redfish_controller_ip, username, password, @@ -129,8 +125,6 @@ class RedfishOperations(operations.IloOperations): :returns: server model name. :raises: IloError, on an error from iLO. """ - # Assuming only one system present as part of collection, - # as we are dealing with iLO's here. sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) return sushy_system.json.get('Model') @@ -140,8 +134,6 @@ class RedfishOperations(operations.IloOperations): :returns: Power State of the server, 'ON' or 'OFF' :raises: IloError, on an error from iLO. """ - # Assuming only one sushy_system present as part of collection, - # as we are dealing with iLO's here. sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) return GET_POWER_STATE_MAP.get(sushy_system.power_state) @@ -150,8 +142,6 @@ class RedfishOperations(operations.IloOperations): :raises: IloError, on an error from iLO. """ - # Assuming only one sushy_system present as part of collection, - # as we are dealing with iLO's here. sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) try: sushy_system.reset_system(sushy.RESET_FORCE_RESTART) @@ -185,8 +175,6 @@ class RedfishOperations(operations.IloOperations): "state."), {'target_value': target_value}) return - # Assuming only one system present as part of collection, - # as we are dealing with iLO's here. sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) try: sushy_system.reset_system(POWER_RESET_MAP[target_value]) @@ -196,3 +184,34 @@ class RedfishOperations(operations.IloOperations): {'target_value': target_value, 'error': str(e)}) LOG.debug(msg) raise exception.IloError(msg) + + def press_pwr_btn(self): + """Simulates a physical press of the server power button. + + :raises: IloError, on an error from iLO. + """ + sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) + try: + sushy_system.push_power_button(sys_cons.PUSH_POWER_BUTTON_PRESS) + except sushy.exceptions.SushyError as e: + msg = (self._('The Redfish controller failed to press power button' + ' of server. Error %(error)s') % + {'error': str(e)}) + LOG.debug(msg) + raise exception.IloError(msg) + + def hold_pwr_btn(self): + """Simulate a physical press and hold of the server power button. + + :raises: IloError, on an error from iLO. + """ + sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID) + try: + sushy_system.push_power_button( + sys_cons.PUSH_POWER_BUTTON_PRESS_AND_HOLD) + except sushy.exceptions.SushyError as e: + msg = (self._('The Redfish controller failed to press and hold ' + 'power button of server. Error %(error)s') % + {'error': str(e)}) + LOG.debug(msg) + raise exception.IloError(msg) diff --git a/proliantutils/redfish/resources/system/constants.py b/proliantutils/redfish/resources/system/constants.py new file mode 100644 index 00000000..c1e9c6e3 --- /dev/null +++ b/proliantutils/redfish/resources/system/constants.py @@ -0,0 +1,20 @@ +# 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. + +# Push power button action constants + +PUSH_POWER_BUTTON_PRESS = 'press' +PUSH_POWER_BUTTON_PRESS_AND_HOLD = 'press and hold' diff --git a/proliantutils/redfish/resources/system/mappings.py b/proliantutils/redfish/resources/system/mappings.py new file mode 100644 index 00000000..95630193 --- /dev/null +++ b/proliantutils/redfish/resources/system/mappings.py @@ -0,0 +1,27 @@ +# 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. + +from sushy import utils + +from proliantutils.redfish.resources.system import constants + +PUSH_POWER_BUTTON_VALUE_MAP = { + 'Press': constants.PUSH_POWER_BUTTON_PRESS, + 'PressAndHold': constants.PUSH_POWER_BUTTON_PRESS_AND_HOLD, +} + +PUSH_POWER_BUTTON_VALUE_MAP_REV = ( + utils.revert_dictionary(PUSH_POWER_BUTTON_VALUE_MAP)) diff --git a/proliantutils/redfish/resources/system/system.py b/proliantutils/redfish/resources/system/system.py index c477dd1b..dedde84a 100644 --- a/proliantutils/redfish/resources/system/system.py +++ b/proliantutils/redfish/resources/system/system.py @@ -14,8 +14,27 @@ __author__ = 'HPE' +from sushy.resources import base from sushy.resources.system import system +from proliantutils import exception +from proliantutils import log +from proliantutils.redfish.resources.system import mappings + +LOG = log.get_logger(__name__) + + +class PowerButtonActionField(base.CompositeField): + allowed_values = base.Field('PushType@Redfish.AllowableValues', + adapter=list) + + target_uri = base.Field('target', required=True) + + +class HpeActionsField(base.CompositeField): + computer_system_ext_powerbutton = ( + PowerButtonActionField('#HpeComputerSystemExt.PowerButton')) + class HPESystem(system.System): """Class that extends the functionality of System resource class @@ -23,3 +42,37 @@ class HPESystem(system.System): This class extends the functionality of System resource class from sushy """ + + _hpe_actions = HpeActionsField(['Oem', 'Hpe', 'Actions'], required=True) + """Oem specific system extensibility actions""" + + def _get_hpe_push_power_button_action_element(self): + push_action = self._hpe_actions.computer_system_ext_powerbutton + if not push_action: + raise exception.MissingAttributeError( + attribute='Oem/Hpe/Actions/#HpeComputerSystemExt.PowerButton', + resource=self.path) + + return push_action + + def push_power_button(self, target_value): + """Reset the system in hpe exclusive manner. + + :param target_value: The target value to be set. + :raises: InvalidInputError, if the target value is not + allowed. + :raises: SushyError, on an error from iLO. + """ + if target_value not in mappings.PUSH_POWER_BUTTON_VALUE_MAP_REV: + msg = ('The parameter "%(parameter)s" value "%(target_value)s" is ' + 'invalid. Valid values are: %(valid_power_values)s' % + {'parameter': 'target_value', 'target_value': target_value, + 'valid_power_values': ( + mappings.PUSH_POWER_BUTTON_VALUE_MAP_REV.keys())}) + raise exception.InvalidInputError(msg) + + value = mappings.PUSH_POWER_BUTTON_VALUE_MAP_REV[target_value] + target_uri = ( + self._get_hpe_push_power_button_action_element().target_uri) + + self._conn.post(target_uri, data={'PushType': value}) diff --git a/proliantutils/tests/redfish/resources/__init__.py b/proliantutils/tests/redfish/resources/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/proliantutils/tests/redfish/resources/test_system.py b/proliantutils/tests/redfish/resources/test_system.py new file mode 100644 index 00000000..2bb518d3 --- /dev/null +++ b/proliantutils/tests/redfish/resources/test_system.py @@ -0,0 +1,63 @@ +# 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 import exception +from proliantutils.redfish.resources.system import constants as sys_cons +from proliantutils.redfish.resources.system import system + + +class HPESystemTestCase(testtools.TestCase): + + def setUp(self): + super(HPESystemTestCase, self).setUp() + self.conn = mock.MagicMock() + with open('proliantutils/tests/redfish/' + 'json_samples/system.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.sys_inst = system.HPESystem( + self.conn, '/redfish/v1/Systems/1', + redfish_version='1.0.2') + + def test__get_hpe_push_power_button_action_element(self): + value = self.sys_inst._get_hpe_push_power_button_action_element() + self.assertEqual("/redfish/v1/Systems/1/Actions/Oem/Hpe/" + "HpeComputerSystemExt.PowerButton/", + value.target_uri) + self.assertEqual(["Press", "PressAndHold"], value.allowed_values) + + def test__get_hpe_push_power_button_action_element_missing_action(self): + self.sys_inst._hpe_actions.computer_system_ext_powerbutton = None + self.assertRaisesRegex( + exception.MissingAttributeError, + 'Oem/Hpe/Actions/#HpeComputerSystemExt.PowerButton is missing', + self.sys_inst._get_hpe_push_power_button_action_element) + + def test_push_power_button(self): + self.sys_inst.push_power_button( + sys_cons.PUSH_POWER_BUTTON_PRESS) + self.sys_inst._conn.post.assert_called_once_with( + '/redfish/v1/Systems/1/Actions/Oem/Hpe/' + 'HpeComputerSystemExt.PowerButton/', + data={'PushType': 'Press'}) + + def test_push_power_button_invalid_value(self): + self.assertRaises(exception.InvalidInputError, + self.sys_inst.push_power_button, 'invalid-value') diff --git a/proliantutils/tests/redfish/test_redfish.py b/proliantutils/tests/redfish/test_redfish.py index 7fb58843..dbf3b1b0 100644 --- a/proliantutils/tests/redfish/test_redfish.py +++ b/proliantutils/tests/redfish/test_redfish.py @@ -22,6 +22,7 @@ import testtools from proliantutils import exception from proliantutils.redfish import main from proliantutils.redfish import redfish +from proliantutils.redfish.resources.system import constants as sys_cons class RedfishOperationsTestCase(testtools.TestCase): @@ -122,3 +123,29 @@ class RedfishOperationsTestCase(testtools.TestCase): self.rf_client.set_host_power('ON') self.sushy.get_system().reset_system.assert_called_once_with( sushy.RESET_ON) + + def test_press_pwr_btn(self): + self.rf_client.press_pwr_btn() + self.sushy.get_system().push_power_button.assert_called_once_with( + sys_cons.PUSH_POWER_BUTTON_PRESS) + + def test_press_pwr_btn_fail(self): + self.sushy.get_system().push_power_button.side_effect = ( + sushy.exceptions.SushyError) + self.assertRaisesRegex( + exception.IloError, + 'The Redfish controller failed to press power button', + self.rf_client.press_pwr_btn) + + def test_hold_pwr_btn(self): + self.rf_client.hold_pwr_btn() + self.sushy.get_system().push_power_button.assert_called_once_with( + sys_cons.PUSH_POWER_BUTTON_PRESS_AND_HOLD) + + def test_hold_pwr_btn_fail(self): + self.sushy.get_system().push_power_button.side_effect = ( + sushy.exceptions.SushyError) + self.assertRaisesRegex( + exception.IloError, + 'The Redfish controller failed to press and hold power button', + self.rf_client.hold_pwr_btn)