Repository: ansible-collections/community.general

Issue:
Support same reset actions on Managers as on Systems
<!--- Verify first that your feature was not already discussed on GitHub -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Describe the new feature/improvement briefly below -->
The redfish_command.py module supports commands for performing power reset operations (power on, off, reboot, etc.) on a Redfish System. Redfish Managers have the same model for power reset operations as Systems. But the redfish_command.py module currently only supports a single reset option for Managers - GracefulRestart.

This feature request is to support the same power reset actions on Managers as on Systems. 
 

##### ISSUE TYPE
- Feature Idea

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
redfish_command.py

##### ADDITIONAL INFORMATION
<!--- Describe how the feature would be used, why it is needed and what it would solve -->
Currently, there is no way to issue reset actions like On, ForceOff, GracefulShutdown, and ForceRestart on Redfish Managers. These are actions that BMC operators may need to perform.

<!--- Paste example playbooks or commands between quotes below -->
Example of plays that can be performed after this feature has been implemented:
```yaml
  - name: Turn manager power off
    community.general.redfish_command:
      category: Manager
      command: PowerForceOff
      resource_id: BMC
```

```yaml
  - name: Turn manager power on
    community.general.redfish_command:
      category: Manager
      command: PowerOn
      resource_id: BMC
```

<!--- HINT: You can also paste gist.github.com links for larger files -->



Discussion:
Files identified in the description:
* [`plugins/modules/remote_management/redfish/redfish_command.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/remote_management/redfish/redfish_command.py)
* [`plugins/modules/remote_management/redfish/redfish_config.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/remote_management/redfish/redfish_config.py)
* [`plugins/modules/remote_management/redfish/redfish_info.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/remote_management/redfish/redfish_info.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @mraineri @tomasg2012
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Files identified in the description:
* [`plugins/modules/remote_management/redfish/redfish_command.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/remote_management/redfish/redfish_command.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->

Resolving patch:
diff --git a/plugins/module_utils/redfish_utils.py b/plugins/module_utils/redfish_utils.py
--- a/plugins/module_utils/redfish_utils.py
+++ b/plugins/module_utils/redfish_utils.py
@@ -710,24 +710,6 @@ def get_volume_inventory(self, systems_uri):
     def get_multi_volume_inventory(self):
         return self.aggregate_systems(self.get_volume_inventory)
 
-    def restart_manager_gracefully(self):
-        result = {}
-        key = "Actions"
-
-        # Search for 'key' entry and extract URI from it
-        response = self.get_request(self.root_uri + self.manager_uri)
-        if response['ret'] is False:
-            return response
-        result['ret'] = True
-        data = response['data']
-        action_uri = data[key]["#Manager.Reset"]["target"]
-
-        payload = {'ResetType': 'GracefulRestart'}
-        response = self.post_request(self.root_uri + action_uri, payload)
-        if response['ret'] is False:
-            return response
-        return {'ret': True}
-
     def manage_indicator_led(self, command):
         result = {}
         key = 'IndicatorLED'
@@ -773,6 +755,14 @@ def _map_reset_type(self, reset_type, allowable_values):
         return reset_type
 
     def manage_system_power(self, command):
+        return self.manage_power(command, self.systems_uri,
+                                 '#ComputerSystem.Reset')
+
+    def manage_manager_power(self, command):
+        return self.manage_power(command, self.manager_uri,
+                                 '#Manager.Reset')
+
+    def manage_power(self, command, resource_uri, action_name):
         key = "Actions"
         reset_type_values = ['On', 'ForceOff', 'GracefulShutdown',
                              'GracefulRestart', 'ForceRestart', 'Nmi',
@@ -790,8 +780,8 @@ def manage_system_power(self, command):
         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_uri)
+        # read the resource and get the current power state
+        response = self.get_request(self.root_uri + resource_uri)
         if response['ret'] is False:
             return response
         data = response['data']
@@ -803,13 +793,13 @@ def manage_system_power(self, command):
         if power_state == "Off" and reset_type in ['GracefulShutdown', 'ForceOff']:
             return {'ret': True, 'changed': False}
 
-        # 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']
+        # get the reset Action and target URI
+        if key not in data or action_name not in data[key]:
+            return {'ret': False, 'msg': 'Action %s not found' % action_name}
+        reset_action = data[key][action_name]
         if 'target' not in reset_action:
             return {'ret': False,
-                    'msg': 'target URI missing from Action #ComputerSystem.Reset'}
+                    'msg': 'target URI missing from Action %s' % action_name}
         action_uri = reset_action['target']
 
         # get AllowableValues
diff --git a/plugins/modules/remote_management/redfish/redfish_command.py b/plugins/modules/remote_management/redfish/redfish_command.py
--- a/plugins/modules/remote_management/redfish/redfish_command.py
+++ b/plugins/modules/remote_management/redfish/redfish_command.py
@@ -206,6 +206,36 @@
       username: "{{ username }}"
       password: "{{ password }}"
 
+  - name: Turn system power off
+    community.general.redfish_command:
+      category: Systems
+      command: PowerForceOff
+      resource_id: 437XR1138R2
+
+  - name: Restart system power forcefully
+    community.general.redfish_command:
+      category: Systems
+      command: PowerForceRestart
+      resource_id: 437XR1138R2
+
+  - name: Shutdown system power gracefully
+    community.general.redfish_command:
+      category: Systems
+      command: PowerGracefulShutdown
+      resource_id: 437XR1138R2
+
+  - name: Turn system power on
+    community.general.redfish_command:
+      category: Systems
+      command: PowerOn
+      resource_id: 437XR1138R2
+
+  - name: Reboot system power
+    community.general.redfish_command:
+      category: Systems
+      command: PowerReboot
+      resource_id: 437XR1138R2
+
   - name: Set one-time boot device to {{ bootdevice }}
     community.general.redfish_command:
       category: Systems
@@ -439,6 +469,51 @@
       virtual_media:
         image_url: 'http://example.com/images/SomeLinux-current.iso'
       resource_id: BMC
+
+  - name: Restart manager power gracefully
+    community.general.redfish_command:
+      category: Manager
+      command: GracefulRestart
+      resource_id: BMC
+      baseuri: "{{ baseuri }}"
+      username: "{{ username }}"
+      password: "{{ password }}"
+
+  - name: Restart manager power gracefully
+    community.general.redfish_command:
+      category: Manager
+      command: PowerGracefulRestart
+      resource_id: BMC
+
+  - name: Turn manager power off
+    community.general.redfish_command:
+      category: Manager
+      command: PowerForceOff
+      resource_id: BMC
+
+  - name: Restart manager power forcefully
+    community.general.redfish_command:
+      category: Manager
+      command: PowerForceRestart
+      resource_id: BMC
+
+  - name: Shutdown manager power gracefully
+    community.general.redfish_command:
+      category: Manager
+      command: PowerGracefulShutdown
+      resource_id: BMC
+
+  - name: Turn manager power on
+    community.general.redfish_command:
+      category: Manager
+      command: PowerOn
+      resource_id: BMC
+
+  - name: Reboot manager power
+    community.general.redfish_command:
+      category: Manager
+      command: PowerReboot
+      resource_id: BMC
 '''
 
 RETURN = '''
@@ -464,7 +539,8 @@
                  "UpdateAccountServiceProperties"],
     "Sessions": ["ClearSessions"],
     "Manager": ["GracefulRestart", "ClearLogs", "VirtualMediaInsert",
-                "VirtualMediaEject"],
+                "VirtualMediaEject", "PowerOn", "PowerForceOff", "PowerForceRestart",
+                "PowerGracefulRestart", "PowerGracefulShutdown", "PowerReboot"],
     "Update": ["SimpleUpdate"]
 }
 
@@ -598,7 +674,7 @@ def main():
             module.fail_json(msg=to_native(result['msg']))
 
         for command in command_list:
-            if "Power" in command:
+            if command.startswith('Power'):
                 result = rf_utils.manage_system_power(command)
             elif command == "SetOneTimeBoot":
                 boot_opts['override_enabled'] = 'Once'
@@ -643,8 +719,13 @@ def main():
             module.fail_json(msg=to_native(result['msg']))
 
         for command in command_list:
+            # standardize on the Power* commands, but allow the the legacy
+            # GracefulRestart command
             if command == 'GracefulRestart':
-                result = rf_utils.restart_manager_gracefully()
+                command = 'PowerGracefulRestart'
+
+            if command.startswith('Power'):
+                result = rf_utils.manage_manager_power(command)
             elif command == 'ClearLogs':
                 result = rf_utils.clear_logs()
             elif command == 'VirtualMediaInsert':


Test patch: