From 2a932ad7cfe27663d47cbd43de124fc756632361 Mon Sep 17 00:00:00 2001 From: Bill Dodd Date: Thu, 5 Sep 2019 04:39:21 -0500 Subject: [PATCH] Fix power command ResetType mapping logic (#59927) * fix power command ResetType mapping logic * add changelog fragment --- ...-fix-redfish-power-reset-type-mapping.yaml | 3 + lib/ansible/module_utils/redfish_utils.py | 103 +++++++++++++----- 2 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 changelogs/fragments/59927-fix-redfish-power-reset-type-mapping.yaml diff --git a/changelogs/fragments/59927-fix-redfish-power-reset-type-mapping.yaml b/changelogs/fragments/59927-fix-redfish-power-reset-type-mapping.yaml new file mode 100644 index 00000000000..dd43eb341c1 --- /dev/null +++ b/changelogs/fragments/59927-fix-redfish-power-reset-type-mapping.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: +- redfish_command - fix power ResetType mapping logic (https://github.com/ansible/ansible/issues/59804) diff --git a/lib/ansible/module_utils/redfish_utils.py b/lib/ansible/module_utils/redfish_utils.py index b79f1df8769..35eda9aa2d5 100644 --- a/lib/ansible/module_utils/redfish_utils.py +++ b/lib/ansible/module_utils/redfish_utils.py @@ -628,48 +628,91 @@ class RedfishUtils(object): return result + def _map_reset_type(self, reset_type, allowable_values): + equiv_types = { + 'On': 'ForceOn', + 'ForceOn': 'On', + 'ForceOff': 'GracefulShutdown', + 'GracefulShutdown': 'ForceOff', + 'GracefulRestart': 'ForceRestart', + 'ForceRestart': 'GracefulRestart' + } + + if reset_type in allowable_values: + return reset_type + if reset_type not in equiv_types: + return reset_type + mapped_type = equiv_types[reset_type] + if mapped_type in allowable_values: + return mapped_type + return reset_type + def manage_system_power(self, command): key = "Actions" + reset_type_values = ['On', 'ForceOff', 'GracefulShutdown', + 'GracefulRestart', 'ForceRestart', 'Nmi', + 'ForceOn', 'PushPowerButton', 'PowerCycle'] - # Search for 'key' entry and extract URI from it + # command should be PowerOn, PowerForceOff, etc. + if not command.startswith('Power'): + return {'ret': False, 'msg': 'Invalid Command (%s)' % command} + reset_type = command[5:] + + # map Reboot to a ResetType that does a reboot + if reset_type == 'Reboot': + reset_type = 'GracefulRestart' + + if reset_type not in reset_type_values: + return {'ret': False, 'msg': 'Invalid Command (%s)' % command} + + # read the system resource and get the current power state response = self.get_request(self.root_uri + self.systems_uris[0]) if response['ret'] is False: return response data = response['data'] - power_state = data["PowerState"] + power_state = data.get('PowerState') - if power_state == "On" and command == 'PowerOn': + # if power is already in target state, nothing to do + if power_state == "On" and reset_type in ['On', 'ForceOn']: return {'ret': True, 'changed': False} - - if power_state == "Off" and command in ['PowerGracefulShutdown', 'PowerForceOff']: + if power_state == "Off" and reset_type in ['GracefulShutdown', 'ForceOff']: return {'ret': True, 'changed': False} - reset_action = data[key]["#ComputerSystem.Reset"] - action_uri = reset_action["target"] - allowable_vals = reset_action.get("ResetType@Redfish.AllowableValues", []) - restart_cmd = "GracefulRestart" - if "ForceRestart" in allowable_vals and "GracefulRestart" not in allowable_vals: - restart_cmd = "ForceRestart" - - # Define payload accordingly - if command == "PowerOn": - payload = {'ResetType': 'On'} - elif command == "PowerForceOff": - payload = {'ResetType': 'ForceOff'} - elif command == "PowerForceRestart": - payload = {'ResetType': "ForceRestart"} - elif command == "PowerGracefulRestart": - payload = {'ResetType': 'GracefulRestart'} - elif command == "PowerGracefulShutdown": - payload = {'ResetType': 'GracefulShutdown'} - elif command == "PowerReboot": - if power_state == "On": - payload = {'ResetType': restart_cmd} - else: - payload = {'ResetType': "On"} - else: - return {'ret': False, 'msg': 'Invalid Command'} + # get the #ComputerSystem.Reset Action and target URI + if key not in data or '#ComputerSystem.Reset' not in data[key]: + return {'ret': False, 'msg': 'Action #ComputerSystem.Reset not found'} + reset_action = data[key]['#ComputerSystem.Reset'] + if 'target' not in reset_action: + return {'ret': False, + 'msg': 'target URI missing from Action #ComputerSystem.Reset'} + action_uri = reset_action['target'] + + # get AllowableValues from ActionInfo + allowable_values = None + if '@Redfish.ActionInfo' in reset_action: + action_info_uri = reset_action.get('@Redfish.ActionInfo') + response = self.get_request(self.root_uri + action_info_uri) + if response['ret'] is True: + data = response['data'] + if 'Parameters' in data: + params = data['Parameters'] + for param in params: + if param.get('Name') == 'ResetType': + allowable_values = param.get('AllowableValues') + break + + # fallback to @Redfish.AllowableValues annotation + if allowable_values is None: + allowable_values = reset_action.get('ResetType@Redfish.AllowableValues', []) + + # map ResetType to an allowable value if needed + if reset_type not in allowable_values: + reset_type = self._map_reset_type(reset_type, allowable_values) + + # define payload + payload = {'ResetType': reset_type} + # POST to Action URI response = self.post_request(self.root_uri + action_uri, payload) if response['ret'] is False: return response