#!/usr/bin/python # -*- coding: utf-8 -*- # # Dell EMC OpenManage Ansible Modules # Version 3.0.0 # Copyright (C) 2020-2021 Dell Inc. or its subsidiaries. All Rights Reserved. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = r''' --- module: redfish_powerstate short_description: Manage device power state version_added: "2.1.0" description: - This module allows to manage the different power states of the specified device. extends_documentation_fragment: - dellemc.openmanage.redfish_auth_options options: resource_id: description: - The unique identifier of the device being managed. For example- U(https:///redfish/v1/Systems/). - This option is mandatory for I(base_uri) with multiple devices. - To get the device details, use the API U(https:///redfish/v1/Systems). required: False type: str reset_type: description: - This option resets the device. - If C(ForceOff), Turns off the device immediately. - If C(ForceOn), Turns on the device immediately. - If C(ForceRestart), Turns off the device immediately, and then restarts the device. - If C(GracefulRestart), Performs graceful shutdown of the device, and then restarts the device. - If C(GracefulShutdown), Performs a graceful shutdown of the device, and the turns off the device. - If C(Nmi), Sends a diagnostic interrupt to the device. This is usually a non-maskable interrupt (NMI) on x86 device. - If C(On), Turns on the device. - If C(PowerCycle), Performs power cycle on the device. - If C(PushPowerButton), Simulates the pressing of a physical power button on the device. - When a power control operation is performed, which is not supported on the device, an error message is displayed with the list of operations that can be performed. required: True type: str choices: ["ForceOff", "ForceOn", "ForceRestart", "GracefulRestart", "GracefulShutdown", "Nmi", "On", "PowerCycle", "PushPowerButton"] requirements: - "python >= 2.7.5" author: - "Sajna Shetty(@Sajna-Shetty)" notes: - Run this module from a system that has direct access to Redfish APIs. - This module supports C(check_mode). ''' EXAMPLES = r''' --- - name: Manage power state of the first device dellemc.openmanage.redfish_powerstate: baseuri: "192.168.0.1" username: "username" password: "password" reset_type: "On" - name: Manage power state of a specified device dellemc.openmanage.redfish_powerstate: baseuri: "192.168.0.1" username: "username" password: "password" reset_type: "ForceOff" resource_id: "System.Embedded.1" ''' RETURN = r''' --- msg: description: Overall status of the reset operation. returned: always type: str sample: "Successfully performed the reset type operation 'On'." error_info: type: dict description: Details of the HTTP error. returned: on http error sample: { "error": { "@Message.ExtendedInfo": [ { "Message": "Unable to complete the operation because the resource /redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset entered in not found.", "MessageArgs": [ "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset" ], "MessageArgs@odata.count": 1, "MessageId": "IDRAC.2.1.SYS403", "RelatedProperties": [], "RelatedProperties@odata.count": 0, "Resolution": "Enter the correct resource and retry the operation. For information about valid resource, see the Redfish Users Guide available on the support site.", "Severity": "Critical" }, ], "code": "Base.1.5.GeneralError", "message": "A general error has occurred. See ExtendedInfo for more information" } } ''' import json import re from ssl import SSLError from ansible_collections.dellemc.openmanage.plugins.module_utils.redfish import Redfish from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError from ansible.module_utils.urls import ConnectionError, SSLValidationError powerstate_map = {} def fetch_power_uri_resource(module, session_obj): try: resource_id = module.params.get("resource_id") static_resource_id_resource = None if resource_id: static_resource_id_resource = "{0}{1}{2}".format(session_obj.root_uri, "Systems/", resource_id) error_message1 = "The target device does not support the system reset feature" \ " using Redfish API." system_uri = "{0}{1}".format(session_obj.root_uri, "Systems") system_resp = session_obj.invoke_request("GET", system_uri) system_members = system_resp.json_data.get("Members") if len(system_members) > 1 and static_resource_id_resource is None: module.fail_json(msg="Multiple devices exists in the system, but option 'resource_id' is not specified.") if system_members: resource_id_list = [system_id["@odata.id"] for system_id in system_members if "@odata.id" in system_id] system_id_res = static_resource_id_resource or resource_id_list[0] if system_id_res in resource_id_list: system_id_res_resp = session_obj.invoke_request("GET", system_id_res) system_id_res_data = system_id_res_resp.json_data action_id_res = system_id_res_data.get("Actions") if action_id_res: current_state = system_id_res_data["PowerState"] power_uri = action_id_res['#ComputerSystem.Reset']['target'] allowable_enums = action_id_res['#ComputerSystem.Reset']['ResetType@Redfish.AllowableValues'] powerstate_map.update( {'power_uri': power_uri, 'allowable_enums': allowable_enums, 'current_state': current_state}) else: module.fail_json(msg=error_message1) else: error_message2 = "Invalid device Id '{0}' is provided".format(resource_id) module.fail_json(msg=error_message2) else: module.fail_json(msg=error_message1) except HTTPError as err: if err.code in [404, 405]: module.fail_json(msg=error_message1, error_info=json.load(err)) raise err def is_change_applicable_for_power_state(current_power_state, apply_power_state): """ checks if changes are applicable or not for current system state :param current_power_state: Current power state :type current_power_state: str :param apply_power_state: Required power state :type apply_power_state: str :return: boolean True if changes is applicable """ on_states = ["On", "PoweringOn"] off_states = ["Off", "PoweringOff"] reset_map_apply = { ("On", "ForceOn",): off_states, ("PushPowerButton",): on_states + off_states, ("ForceOff", "ForceRestart", "GracefulRestart", "GracefulShutdown", "Nmi", "PowerCycle",): on_states } is_reset_applicable = False for apply_states, applicable_states in reset_map_apply.items(): if apply_power_state in apply_states: if current_power_state in applicable_states: is_reset_applicable = True break break return is_reset_applicable def is_valid_reset_type(reset_type, allowable_enum, module): if reset_type not in allowable_enum: res_list = re.findall('[A-Z][^A-Z]*', reset_type) lw_reset_type = " ".join([word.lower() for word in res_list]) error_msg = "The target device does not support a" \ " {0} operation.The acceptable values for device reset types" \ " are {1}.".format(lw_reset_type, ", ".join(allowable_enum)) module.fail_json(msg=error_msg) def run_change_power_state(redfish_session_obj, module): """ Apply reset type to system Keyword arguments: redfish_session_obj -- session handle module -- Ansible module obj """ apply_reset_type = module.params["reset_type"] fetch_power_uri_resource(module, redfish_session_obj) is_valid_reset_type(apply_reset_type, powerstate_map["allowable_enums"], module) current_power_state = powerstate_map["current_state"] reset_flag = is_change_applicable_for_power_state(current_power_state, apply_reset_type) if module.check_mode is True: if reset_flag is True: module.exit_json(msg="Changes found to be applied.", changed=True) else: module.exit_json(msg="No Changes found to be applied.", changed=False) if reset_flag is True: payload = {"ResetType": apply_reset_type} power_uri = powerstate_map["power_uri"] reset_resp = redfish_session_obj.invoke_request("POST", power_uri, data=payload) if reset_resp.success: module.exit_json(msg="Successfully performed the reset type operation" " '{0}'.".format(apply_reset_type), changed=True) else: module.exit_json(msg="Unable to perform the reset type operation '{0}'.".format(apply_reset_type), changed=False) else: module.exit_json(msg="The device is already powered {0}.".format(current_power_state.lower()), changed=False) def main(): module = AnsibleModule( argument_spec={ "baseuri": {"required": True, "type": "str"}, "username": {"required": True, "type": "str"}, "password": {"required": True, "type": "str", "no_log": True}, "resource_id": {"required": False, "type": "str"}, "reset_type": {"required": True, "type": "str", "choices": ['ForceOff', 'ForceOn', 'ForceRestart', 'GracefulRestart', 'GracefulShutdown', 'Nmi', 'On', 'PowerCycle', 'PushPowerButton']}, }, supports_check_mode=True) try: with Redfish(module.params) as redfish_obj: run_change_power_state(redfish_obj, module) except HTTPError as err: module.fail_json(msg=str(err), error_info=json.load(err)) except URLError as err: module.exit_json(msg=str(err), unreachable=True) except (IOError, ValueError, SSLError, TypeError, ConnectionError) as err: module.fail_json(msg=str(err)) except Exception as err: module.fail_json(msg=str(err)) if __name__ == '__main__': main()