Repository: ansible/ansible
Pull request: Add GetChassisPower command to Chassis category of redfish_facts
##### SUMMARY
This feature request adds a GetChassisPower command for the Chassis category of redfish_facts. The get_chassis_power() function added to redfish_utils implements the retrieval of various wattage-related data for each chassis in the system. 

<!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->
Fixes #54230 

##### ISSUE TYPE
- Feature Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
redfish_facts
redfish_utils

##### ADDITIONAL INFORMATION
<!--- Include additional information to help people understand the change here -->
<!--- A step-by-step reproduction of the problem is helpful if there is no related issue -->





```yaml
- name: Test Redfish modules
  hosts: localhost
  gather_facts: false
  vars:
    baseuri: 10.240.50.78
    user: USERID
    password: PASSW0RD

  tasks:

    - name: Get Chassis Power
      redfish_facts:
        category: Chassis
        command: GetChassisPower
        baseuri: "{{ baseuri }}"
        username: "{{ user }}"
        password: "{{ password }}"
```


<!--- Paste verbatim command output below, e.g. before and after your change -->
```paste below
ansible-playbook 2.8.0.dev0
  config file = None
  configured module search path = [u'/Users/xandermadsen/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/xandermadsen/ansible/lib/ansible
  executable location = /Users/xandermadsen/ansible/bin/ansible-playbook
  python version = 2.7.15 (default, Feb 12 2019, 11:00:12) [GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)]
No config file found; using defaults
host_list declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
script declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
yaml declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
ini declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
toml declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
 [WARNING]: No inventory was parsed, only implicit localhost is available

 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAYBOOK: test_redfish_facts.yml ***************************************************************************************************
1 plays in /Users/xandermadsen/test_redfish_facts.yml

PLAY [Test Redfish modules] ********************************************************************************************************
META: ran handlers

TASK [Get Chassis Power] ***********************************************************************************************************
task path: /Users/xandermadsen/test_redfish_facts.yml:11
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: xandermadsen
<127.0.0.1> EXEC /bin/sh -c 'echo ~xandermadsen && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /Users/xandermadsen/.ansible/tmp/ansible-tmp-1553478912.99-149907692690693 `" && echo ansible-tmp-1553478912.99-149907692690693="` echo /Users/xandermadsen/.ansible/tmp/ansible-tmp-1553478912.99-149907692690693 `" ) && sleep 0'
Using module file /Users/xandermadsen/ansible/lib/ansible/modules/remote_management/redfish/redfish_facts.py
<127.0.0.1> PUT /Users/xandermadsen/.ansible/tmp/ansible-local-645745cjm_F/tmpchwgFC TO /Users/xandermadsen/.ansible/tmp/ansible-tmp-1553478912.99-149907692690693/AnsiballZ_redfish_facts.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /Users/xandermadsen/.ansible/tmp/ansible-tmp-1553478912.99-149907692690693/ /Users/xandermadsen/.ansible/tmp/ansible-tmp-1553478912.99-149907692690693/AnsiballZ_redfish_facts.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/local/opt/python@2/bin/python2.7 /Users/xandermadsen/.ansible/tmp/ansible-tmp-1553478912.99-149907692690693/AnsiballZ_redfish_facts.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /Users/xandermadsen/.ansible/tmp/ansible-tmp-1553478912.99-149907692690693/ > /dev/null 2>&1 && sleep 0'
ok: [localhost] => {
    "ansible_facts": {
        "redfish_facts": {
            "power": {
                "chassis_power": [
                    {
                        "MemberId": "0", 
                        "Name": "Server Power Control", 
                        "PowerAllocatedWatts": 1920, 
                        "PowerAvailableWatts": 0, 
                        "PowerCapacityWatts": 1920, 
                        "PowerConsumedWatts": 234, 
                        "PowerMetrics": {
                            "AverageConsumedWatts": 193.25, 
                            "IntervalInMin": 60, 
                            "MaxConsumedWatts": 242, 
                            "MinConsumedWatts": 184
                        }, 
                        "PowerRequestedWatts": 701, 
                        "Status": {
                            "HealthRollup": "OK", 
                            "State": "Enabled"
                        }
                    }
                ], 
                "ret": true
            }
        }
    }, 
    "changed": false, 
    "invocation": {
        "module_args": {
            "baseuri": "10.240.50.78", 
            "category": [
                "Chassis"
            ], 
            "command": [
                "GetChassisPower"
            ], 
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "username": "USERID"
        }
    }
}
META: ran handlers
META: ran handlers

PLAY RECAP *************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

```


Problem statement:
redfish_facts - Chassis - GetChassisPower
<!--- 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 -->
This feature would implement a GetChassisPower command for the Chassis category of redfish_facts, and would retrieve properties associated with the power usage in Watts, gathered from the Chassis/PowerControl field.

##### 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_facts 

##### ADDITIONAL INFORMATION
<!--- Describe how the feature would be used, why it is needed and what it would solve -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- HINT: You can also paste gist.github.com links for larger files -->

Hints from issue discussion:
@samerhaj
I will code a PR for this.
cc @billdodd @jose-delarosa @mraineri @tomasg2012
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Commit message: Update lib/ansible/module_utils/redfish_utils.py

Co-Authored-By: xmadsen <xander.madsen@gmail.com>

Patch to review:
diff --git a/lib/ansible/module_utils/redfish_utils.py b/lib/ansible/module_utils/redfish_utils.py
index 083b4c3c4c1881..11432f928f7cc4 100644
--- a/lib/ansible/module_utils/redfish_utils.py
+++ b/lib/ansible/module_utils/redfish_utils.py
@@ -920,6 +920,44 @@ def get_fan_inventory(self):
         result["entries"] = fan_results
         return result
 
+    def get_chassis_power(self):
+        result = {}
+        key = "Power"
+
+        # Get these entries, but does not fail if not found
+        properties = ['Name', 'PowerAllocatedWatts',
+                      'PowerAvailableWatts', 'PowerCapacityWatts',
+                      'PowerConsumedWatts', 'PowerMetrics',
+                      'PowerRequestedWatts', 'RelatedItem', 'Status']
+
+        chassis_power_results = []
+        # Go through list
+        for chassis_uri in self.chassis_uri_list:
+            chassis_power_result = {}
+            response = self.get_request(self.root_uri + chassis_uri)
+            if response['ret'] is False:
+                return response
+            result['ret'] = True
+            data = response['data']
+            if key in data:
+                response = self.get_request(self.root_uri + chassis_uri +
+                                            "/" + key)
+                data = response['data']
+                if 'PowerControl' in data:
+                    if len(data['PowerControl']) > 0:
+                        data = data['PowerControl'][0]
+                        for property in properties:
+                            if property in data:
+                                chassis_power_result[property] = data[property]
+                else:
+                    return {'ret': False, 'msg': 'Key PowerControl not found.'}
+                chassis_power_results.append(chassis_power_result)
+            else:
+                return {'ret': False, 'msg': 'Key Power not found.'}
+
+        result['chassis_power'] = chassis_power_results
+        return result
+
     def get_chassis_thermals(self):
         result = {}
         sensors = []
diff --git a/lib/ansible/modules/remote_management/redfish/redfish_facts.py b/lib/ansible/modules/remote_management/redfish/redfish_facts.py
index 8a8ae59346f74e..b0e540ee8f9a90 100644
--- a/lib/ansible/modules/remote_management/redfish/redfish_facts.py
+++ b/lib/ansible/modules/remote_management/redfish/redfish_facts.py
@@ -170,7 +170,7 @@
                 "GetMemoryInventory", "GetNicInventory",
                 "GetStorageControllerInventory", "GetDiskInventory",
                 "GetBiosAttributes", "GetBootOrder"],
-    "Chassis": ["GetFanInventory", "GetPsuInventory", "GetChassisThermals"],
+    "Chassis": ["GetFanInventory", "GetPsuInventory", "GetChassisPower", "GetChassisThermals"],
     "Accounts": ["ListUsers"],
     "Update": ["GetFirmwareInventory", "GetFirmwareUpdateCapabilities"],
     "Manager": ["GetManagerNicInventory", "GetLogs"],
@@ -281,6 +281,8 @@ def main():
                     result["psu"] = rf_utils.get_psu_inventory()
                 elif command == "GetChassisThermals":
                     result["thermals"] = rf_utils.get_chassis_thermals()
+                elif command == "GetChassisPower":
+                    result["power"] = rf_utils.get_chassis_power()
 
         elif category == "Accounts":
             # execute only if we find an Account service resource


Code review comments:
diff_hunk: @@ -920,6 +920,44 @@ def get_fan_inventory(self):
         result["entries"] = fan_results
         return result
 
+    def get_chassis_power(self):
+        result = {}
+        key = "Power"
+
+        # Get these entries, but does not fail if not found
+        properties = ['Name', 'PowerAllocatedWatts',
+                      'PowerAvailableWatts', 'PowerCapacityWatts',
+                      'PowerConsumedWatts', 'PowerMetrics',
+                      'PowerRequestedWatts', 'RelatedItem', 'Status']
+
+        chassis_power_results = []
+        # Go through list
+        for chassis_uri in self.chassis_uri_list:
+            chassis_power_result = {}
+            response = self.get_request(self.root_uri + chassis_uri)
+            if response['ret'] is False:
+                return response
+            result['ret'] = True
+            data = response['data']
+            if key in data:
+                response = self.get_request(self.root_uri + chassis_uri +
+                                            "/" + key)
+                data = response['data']
+                if 'PowerControl' in data:
+                    if len(data['PowerControl']) > 0:
+                        data = data['PowerControl'][0]
+                        for property in properties:
+                            if property in data:
+                                chassis_power_result[property] = data[property]
+                else:
+                    return {'ret': False, 'msg': 'Key PowerControl not found.'}
+                chassis_power_results.append(chassis_power_result)
+            else:
+                return {'ret': False, 'msg': 'Key Power not found.'}
+
+        result['chassis_power'] = chassis_power_results
path: lib/ansible/module_utils/redfish_utils.py
text: @user1:
```suggestion
        result['entries'] = chassis_power_results
```

diff_hunk: @@ -281,6 +281,8 @@ def main():
                     result["psu"] = rf_utils.get_psu_inventory()
                 elif command == "GetChassisThermals":
                     result["thermals"] = rf_utils.get_chassis_thermals()
+                elif command == "GetChassisPower":
+                    result["power"] = rf_utils.get_chassis_power()
path: lib/ansible/modules/remote_management/redfish/redfish_facts.py
text: @user1:
```suggestion
                    result["'chassis_power"] = rf_utils.get_chassis_power()
```