mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
WDC Redfish support for chassis indicator LED toggling. (#5059)
* WDC Redfish support for chassis indicator LED toggling. * Added changelog fragment. * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
496bf27b5c
commit
6062ae8fae
4 changed files with 245 additions and 18 deletions
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- wdc_redfish_command - add ``IndicatorLedOn`` and ``IndicatorLedOff`` commands for ``Chassis`` category (https://github.com/ansible-collections/community.general/pull/5059).
|
|
@ -405,3 +405,50 @@ class WdcRedfishUtils(RedfishUtils):
|
|||
return iom_b_firmware_version
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_led_locate_uri(data):
|
||||
"""Get the LED locate URI given a resource body."""
|
||||
if "Actions" not in data:
|
||||
return None
|
||||
if "Oem" not in data["Actions"]:
|
||||
return None
|
||||
if "WDC" not in data["Actions"]["Oem"]:
|
||||
return None
|
||||
if "#Chassis.Locate" not in data["Actions"]["Oem"]["WDC"]:
|
||||
return None
|
||||
if "target" not in data["Actions"]["Oem"]["WDC"]["#Chassis.Locate"]:
|
||||
return None
|
||||
return data["Actions"]["Oem"]["WDC"]["#Chassis.Locate"]["target"]
|
||||
|
||||
def manage_indicator_led(self, command, resource_uri):
|
||||
key = 'IndicatorLED'
|
||||
|
||||
payloads = {'IndicatorLedOn': 'On', 'IndicatorLedOff': 'Off'}
|
||||
current_led_status_map = {'IndicatorLedOn': 'Blinking', 'IndicatorLedOff': 'Off'}
|
||||
|
||||
result = {}
|
||||
response = self.get_request(self.root_uri + resource_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
result['ret'] = True
|
||||
data = response['data']
|
||||
if key not in data:
|
||||
return {'ret': False, 'msg': "Key %s not found" % key}
|
||||
current_led_status = data[key]
|
||||
if current_led_status == current_led_status_map[command]:
|
||||
return {'ret': True, 'changed': False}
|
||||
|
||||
led_locate_uri = self._get_led_locate_uri(data)
|
||||
if led_locate_uri is None:
|
||||
return {'ret': False, 'msg': 'LED locate URI not found.'}
|
||||
|
||||
if command in payloads.keys():
|
||||
payload = {'LocateState': payloads[command]}
|
||||
response = self.post_request(self.root_uri + led_locate_uri, payload)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
else:
|
||||
return {'ret': False, 'msg': 'Invalid command'}
|
||||
|
||||
return result
|
||||
|
|
|
@ -55,6 +55,12 @@ options:
|
|||
- Timeout in seconds for URL requests to OOB controller.
|
||||
default: 10
|
||||
type: int
|
||||
resource_id:
|
||||
required: false
|
||||
description:
|
||||
- ID of the component to modify, such as C(Enclosure), C(IOModuleAFRU), C(PowerSupplyBFRU), C(FanExternalFRU3), or C(FanInternalFRU).
|
||||
type: str
|
||||
version_added: 5.4.0
|
||||
update_image_uri:
|
||||
required: false
|
||||
description:
|
||||
|
@ -76,8 +82,6 @@ options:
|
|||
description:
|
||||
- The password for retrieving the update image.
|
||||
type: str
|
||||
requirements:
|
||||
- dnspython (2.1.0 for Python 3, 1.16.0 for Python 2)
|
||||
notes:
|
||||
- In the inventory, you can specify baseuri or ioms. See the EXAMPLES section.
|
||||
- ioms is a list of FQDNs for the enclosure's IOMs.
|
||||
|
@ -125,6 +129,47 @@ EXAMPLES = '''
|
|||
update_creds:
|
||||
username: operator
|
||||
password: supersecretpwd
|
||||
|
||||
- name: Turn on enclosure indicator LED
|
||||
community.general.wdc_redfish_command:
|
||||
category: Chassis
|
||||
resource_id: Enclosure
|
||||
command: IndicatorLedOn
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
|
||||
- name: Turn off IOM A indicator LED
|
||||
community.general.wdc_redfish_command:
|
||||
category: Chassis
|
||||
resource_id: IOModuleAFRU
|
||||
command: IndicatorLedOff
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
|
||||
- name: Turn on Power Supply B indicator LED
|
||||
community.general.wdc_redfish_command:
|
||||
category: Chassis
|
||||
resource_id: PowerSupplyBFRU
|
||||
command: IndicatorLedOn
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
|
||||
- name: Turn on External Fan 3 indicator LED
|
||||
community.general.wdc_redfish_command:
|
||||
category: Chassis
|
||||
resource_id: FanExternalFRU3
|
||||
command: IndicatorLedOn
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
|
||||
- name: Turn on Internal Fan indicator LED
|
||||
community.general.wdc_redfish_command:
|
||||
category: Chassis
|
||||
resource_id: FanInternalFRU
|
||||
command: IndicatorLedOn
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -143,6 +188,10 @@ CATEGORY_COMMANDS_ALL = {
|
|||
"Update": [
|
||||
"FWActivate",
|
||||
"UpdateAndActivate"
|
||||
],
|
||||
"Chassis": [
|
||||
"IndicatorLedOn",
|
||||
"IndicatorLedOff"
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -164,6 +213,7 @@ def main():
|
|||
password=dict(no_log=True)
|
||||
)
|
||||
),
|
||||
resource_id=dict(),
|
||||
update_image_uri=dict(),
|
||||
timeout=dict(type='int', default=10)
|
||||
),
|
||||
|
@ -191,6 +241,9 @@ def main():
|
|||
# timeout
|
||||
timeout = module.params['timeout']
|
||||
|
||||
# Resource to modify
|
||||
resource_id = module.params['resource_id']
|
||||
|
||||
# Check that Category is valid
|
||||
if category not in CATEGORY_COMMANDS_ALL:
|
||||
module.fail_json(msg=to_native("Invalid Category '%s'. Valid Categories = %s" % (category, sorted(CATEGORY_COMMANDS_ALL.keys()))))
|
||||
|
@ -209,7 +262,7 @@ def main():
|
|||
"https://" + iom for iom in module.params['ioms']
|
||||
]
|
||||
rf_utils = WdcRedfishUtils(creds, root_uris, timeout, module,
|
||||
resource_id=None, data_modification=True)
|
||||
resource_id=resource_id, data_modification=True)
|
||||
|
||||
# Organize by Categories / Commands
|
||||
|
||||
|
@ -236,6 +289,22 @@ def main():
|
|||
update_opts["update_image_uri"] = module.params['update_image_uri']
|
||||
result = rf_utils.update_and_activate(update_opts)
|
||||
|
||||
elif category == "Chassis":
|
||||
result = rf_utils._find_chassis_resource()
|
||||
if result['ret'] is False:
|
||||
module.fail_json(msg=to_native(result['msg']))
|
||||
|
||||
led_commands = ["IndicatorLedOn", "IndicatorLedOff"]
|
||||
|
||||
# Check if more than one led_command is present
|
||||
num_led_commands = sum([command in led_commands for command in command_list])
|
||||
if num_led_commands > 1:
|
||||
result = {'ret': False, 'msg': "Only one IndicatorLed command should be sent at a time."}
|
||||
else:
|
||||
for command in command_list:
|
||||
if command.startswith("IndicatorLed"):
|
||||
result = rf_utils.manage_chassis_indicator_led(command)
|
||||
|
||||
if result['ret'] is False:
|
||||
module.fail_json(msg=to_native(result['msg']))
|
||||
else:
|
||||
|
|
|
@ -49,6 +49,37 @@ MOCK_SUCCESSFUL_RESPONSE_WITH_UPDATE_SERVICE_RESOURCE = {
|
|||
"data": {
|
||||
"UpdateService": {
|
||||
"@odata.id": "/UpdateService"
|
||||
},
|
||||
"Chassis": {
|
||||
"@odata.id": "/Chassis"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_SUCCESSFUL_RESPONSE_CHASSIS = {
|
||||
"ret": True,
|
||||
"data": {
|
||||
"Members": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Chassis/Enclosure"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_SUCCESSFUL_RESPONSE_CHASSIS_ENCLOSURE = {
|
||||
"ret": True,
|
||||
"data": {
|
||||
"Id": "Enclosure",
|
||||
"IndicatorLED": "Off",
|
||||
"Actions": {
|
||||
"Oem": {
|
||||
"WDC": {
|
||||
"#Chassis.Locate": {
|
||||
"target": "/Chassis.Locate"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,14 +236,30 @@ 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."""
|
||||
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"):
|
||||
return MOCK_SUCCESSFUL_RESPONSE_CHASSIS
|
||||
elif args[1].endswith("Chassis/Enclosure"):
|
||||
return MOCK_SUCCESSFUL_RESPONSE_CHASSIS_ENCLOSURE
|
||||
else:
|
||||
raise RuntimeError("Illegal call to get_request in test: " + args[1])
|
||||
|
||||
|
||||
def mock_post_request(*args, **kwargs):
|
||||
"""Mock post_request with successful response."""
|
||||
if args[1].endswith("/UpdateService.FWActivate"):
|
||||
valid_endpoints = [
|
||||
"/UpdateService.FWActivate",
|
||||
"/Chassis.Locate"
|
||||
]
|
||||
for endpoint in valid_endpoints:
|
||||
if args[1].endswith(endpoint):
|
||||
return {
|
||||
"ret": True,
|
||||
"data": ACTION_WAS_SUCCESSFUL_MESSAGE
|
||||
}
|
||||
else:
|
||||
raise RuntimeError("Illegal POST call to: " + args[1])
|
||||
|
||||
|
||||
|
@ -277,6 +324,68 @@ class TestWdcRedfishCommand(unittest.TestCase):
|
|||
})
|
||||
module.main()
|
||||
|
||||
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 = {
|
||||
'category': 'Chassis',
|
||||
'command': 'IndicatorLedOn',
|
||||
'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_led_indicator,
|
||||
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_invalid_resource_led_indicator_on(self):
|
||||
"""Test turning LED on for an invalid resource id."""
|
||||
module_args = {
|
||||
'category': 'Chassis',
|
||||
'command': 'IndicatorLedOn',
|
||||
'username': 'USERID',
|
||||
'password': 'PASSW0RD=21',
|
||||
"resource_id": "Disk99",
|
||||
"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_led_indicator,
|
||||
post_request=mock_post_request):
|
||||
with self.assertRaises(AnsibleFailJson) as ansible_fail_json:
|
||||
module.main()
|
||||
expected_error_message = "Chassis resource Disk99 not found"
|
||||
self.assertEqual(expected_error_message,
|
||||
get_exception_message(ansible_fail_json))
|
||||
|
||||
def test_module_enclosure_led_off_already_off(self):
|
||||
"""Test turning LED indicator off when it's already off. Confirm changed is False and no POST occurs."""
|
||||
module_args = {
|
||||
'category': 'Chassis',
|
||||
'command': 'IndicatorLedOff',
|
||||
'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_led_indicator):
|
||||
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_fw_activate_first_iom_unavailable(self):
|
||||
"""Test that if the first IOM is not available, the 2nd one is used."""
|
||||
ioms = [
|
||||
|
|
Loading…
Reference in a new issue