Skip to content

Commit

Permalink
WDC Redfish support for setting the power mode. (#5145)
Browse files Browse the repository at this point in the history
* WDC Redfish support for setting the power mode.

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

* Add change fragment.

* Add extension to changelog fragment.

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 2a449eb)
  • Loading branch information
mikemoerk authored and patchback[bot] committed Sep 3, 2022
1 parent 73ee970 commit 3599173
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- wdc_redfish_command - add ``PowerModeLow`` and ``PowerModeNormal`` commands for ``Chassis`` category (https://github.com/ansible-collections/community.general/pull/5145).
78 changes: 72 additions & 6 deletions plugins/module_utils/wdc_redfish_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ class WdcRedfishUtils(RedfishUtils):
UPDATE_STATUS_MESSAGE_FW_UPDATE_COMPLETED_WAITING_FOR_ACTIVATION = "FW update completed. Waiting for activation."
UPDATE_STATUS_MESSAGE_FW_UPDATE_FAILED = "FW update failed."

# Dict keys for resource bodies
# Standard keys
ACTIONS = "Actions"
OEM = "Oem"
WDC = "WDC"
TARGET = "target"

# Keys for specific operations
CHASSIS_LOCATE = "#Chassis.Locate"
CHASSIS_POWER_MODE = "#Chassis.PowerMode"

def __init__(self,
creds,
root_uris,
Expand Down Expand Up @@ -409,17 +420,32 @@ def _get_installed_firmware_version_of_multi_tenant_system(self,
@staticmethod
def _get_led_locate_uri(data):
"""Get the LED locate URI given a resource body."""
if "Actions" not in data:
if WdcRedfishUtils.ACTIONS not in data:
return None
if "Oem" not in data["Actions"]:
if WdcRedfishUtils.OEM not in data[WdcRedfishUtils.ACTIONS]:
return None
if "WDC" not in data["Actions"]["Oem"]:
if WdcRedfishUtils.WDC not in data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM]:
return None
if "#Chassis.Locate" not in data["Actions"]["Oem"]["WDC"]:
if WdcRedfishUtils.CHASSIS_LOCATE not in data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM][WdcRedfishUtils.WDC]:
return None
if "target" not in data["Actions"]["Oem"]["WDC"]["#Chassis.Locate"]:
if WdcRedfishUtils.TARGET not in data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM][WdcRedfishUtils.WDC][WdcRedfishUtils.CHASSIS_LOCATE]:
return None
return data["Actions"]["Oem"]["WDC"]["#Chassis.Locate"]["target"]
return data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM][WdcRedfishUtils.WDC][WdcRedfishUtils.CHASSIS_LOCATE][WdcRedfishUtils.TARGET]

@staticmethod
def _get_power_mode_uri(data):
"""Get the Power Mode URI given a resource body."""
if WdcRedfishUtils.ACTIONS not in data:
return None
if WdcRedfishUtils.OEM not in data[WdcRedfishUtils.ACTIONS]:
return None
if WdcRedfishUtils.WDC not in data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM]:
return None
if WdcRedfishUtils.CHASSIS_POWER_MODE not in data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM][WdcRedfishUtils.WDC]:
return None
if WdcRedfishUtils.TARGET not in data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM][WdcRedfishUtils.WDC][WdcRedfishUtils.CHASSIS_POWER_MODE]:
return None
return data[WdcRedfishUtils.ACTIONS][WdcRedfishUtils.OEM][WdcRedfishUtils.WDC][WdcRedfishUtils.CHASSIS_POWER_MODE][WdcRedfishUtils.TARGET]

def manage_indicator_led(self, command, resource_uri):
key = 'IndicatorLED'
Expand Down Expand Up @@ -452,3 +478,43 @@ def manage_indicator_led(self, command, resource_uri):
return {'ret': False, 'msg': 'Invalid command'}

return result

def manage_chassis_power_mode(self, command):
return self.manage_power_mode(command, self.chassis_uri)

def manage_power_mode(self, command, resource_uri=None):
if resource_uri is None:
resource_uri = self.chassis_uri

payloads = {'PowerModeNormal': 'Normal', 'PowerModeLow': 'Low'}
requested_power_mode = payloads[command]

result = {}
response = self.get_request(self.root_uri + resource_uri)
if response['ret'] is False:
return response
result['ret'] = True
data = response['data']

# Make sure the response includes Oem.WDC.PowerMode, and get current power mode
power_mode = 'PowerMode'
if WdcRedfishUtils.OEM not in data or WdcRedfishUtils.WDC not in data[WdcRedfishUtils.OEM] or\
power_mode not in data[WdcRedfishUtils.OEM][WdcRedfishUtils.WDC]:
return {'ret': False, 'msg': 'Resource does not support Oem.WDC.PowerMode'}
current_power_mode = data[WdcRedfishUtils.OEM][WdcRedfishUtils.WDC][power_mode]
if current_power_mode == requested_power_mode:
return {'ret': True, 'changed': False}

power_mode_uri = self._get_power_mode_uri(data)
if power_mode_uri is None:
return {'ret': False, 'msg': 'Power Mode URI not found.'}

if command in payloads.keys():
payload = {'PowerMode': payloads[command]}
response = self.post_request(self.root_uri + power_mode_uri, payload)
if response['ret'] is False:
return response
else:
return {'ret': False, 'msg': 'Invalid command'}

return result
18 changes: 17 additions & 1 deletion plugins/modules/remote_management/redfish/wdc_redfish_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@
username: "{{ username }}"
password: "{{ password }}"
- name: Set chassis to Low Power Mode
community.general.wdc_redfish_command:
category: Chassis
resource_id: Enclosure
command: PowerModeLow
- name: Set chassis to Normal Power Mode
community.general.wdc_redfish_command:
category: Chassis
resource_id: Enclosure
command: PowerModeNormal
'''

RETURN = '''
Expand All @@ -191,7 +203,9 @@
],
"Chassis": [
"IndicatorLedOn",
"IndicatorLedOff"
"IndicatorLedOff",
"PowerModeLow",
"PowerModeNormal",
]
}

Expand Down Expand Up @@ -304,6 +318,8 @@ def main():
for command in command_list:
if command.startswith("IndicatorLed"):
result = rf_utils.manage_chassis_indicator_led(command)
elif command.startswith("PowerMode"):
result = rf_utils.manage_chassis_power_mode(command)

if result['ret'] is False:
module.fail_json(msg=to_native(result['msg']))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,17 @@
"WDC": {
"#Chassis.Locate": {
"target": "/Chassis.Locate"
},
"#Chassis.PowerMode": {
"target": "/redfish/v1/Chassis/Enclosure/Actions/Chassis.PowerMode",
}
}
}
},
"Oem": {
"WDC": {
"PowerMode": "Normal"
}
}
}
}
Expand Down Expand Up @@ -237,8 +245,8 @@ def mock_get_request_enclosure_multi_tenant(*args, **kwargs):
raise RuntimeError("Illegal call to get_request in test: " + args[1])


def mock_get_request_led_indicator(*args, **kwargs):
"""Mock for get_request for LED indicator tests."""
def mock_get_request(*args, **kwargs):
"""Mock for get_request for simple resource tests."""
if args[1].endswith("/redfish/v1") or args[1].endswith("/redfish/v1/"):
return MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE
elif args[1].endswith("/Chassis"):
Expand All @@ -253,7 +261,8 @@ def mock_post_request(*args, **kwargs):
"""Mock post_request with successful response."""
valid_endpoints = [
"/UpdateService.FWActivate",
"/Chassis.Locate"
"/Chassis.Locate",
"/Chassis.PowerMode",
]
for endpoint in valid_endpoints:
if args[1].endswith(endpoint):
Expand Down Expand Up @@ -325,6 +334,64 @@ def test_module_fail_when_unknown_command(self):
})
module.main()

def test_module_chassis_power_mode_low(self):
"""Test setting chassis power mode to low (happy path)."""
module_args = {
'category': 'Chassis',
'command': 'PowerModeLow',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_id': 'Enclosure',
'baseuri': 'example.com'
}
set_module_args(module_args)
with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils",
get_request=mock_get_request,
post_request=mock_post_request):
with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
module.main()
self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE,
get_exception_message(ansible_exit_json))
self.assertTrue(is_changed(ansible_exit_json))

def test_module_chassis_power_mode_normal_when_already_normal(self):
"""Test setting chassis power mode to normal when it already is. Verify we get changed=False."""
module_args = {
'category': 'Chassis',
'command': 'PowerModeNormal',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_id': 'Enclosure',
'baseuri': 'example.com'
}
set_module_args(module_args)
with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils",
get_request=mock_get_request):
with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
module.main()
self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE,
get_exception_message(ansible_exit_json))
self.assertFalse(is_changed(ansible_exit_json))

def test_module_chassis_power_mode_invalid_command(self):
"""Test that we get an error when issuing an invalid PowerMode command."""
module_args = {
'category': 'Chassis',
'command': 'PowerModeExtraHigh',
'username': 'USERID',
'password': 'PASSW0RD=21',
'resource_id': 'Enclosure',
'baseuri': 'example.com'
}
set_module_args(module_args)
with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils",
get_request=mock_get_request):
with self.assertRaises(AnsibleFailJson) as ansible_fail_json:
module.main()
expected_error_message = "Invalid Command 'PowerModeExtraHigh'"
self.assertIn(expected_error_message,
get_exception_message(ansible_fail_json))

def test_module_enclosure_led_indicator_on(self):
"""Test turning on a valid LED indicator (in this case we use the Enclosure resource)."""
module_args = {
Expand All @@ -338,7 +405,7 @@ def test_module_enclosure_led_indicator_on(self):
set_module_args(module_args)

with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils",
get_request=mock_get_request_led_indicator,
get_request=mock_get_request,
post_request=mock_post_request):
with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
module.main()
Expand All @@ -359,7 +426,7 @@ def test_module_invalid_resource_led_indicator_on(self):
set_module_args(module_args)

with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils",
get_request=mock_get_request_led_indicator,
get_request=mock_get_request,
post_request=mock_post_request):
with self.assertRaises(AnsibleFailJson) as ansible_fail_json:
module.main()
Expand All @@ -380,7 +447,7 @@ def test_module_enclosure_led_off_already_off(self):
set_module_args(module_args)

with patch.multiple("ansible_collections.community.general.plugins.module_utils.wdc_redfish_utils.WdcRedfishUtils",
get_request=mock_get_request_led_indicator):
get_request=mock_get_request):
with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
module.main()
self.assertEqual(ACTION_WAS_SUCCESSFUL_MESSAGE,
Expand Down

0 comments on commit 3599173

Please sign in to comment.