1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Redfish: Add options to check the availability of the service (#8434)

* Redfish: Add options to check the availability of the service

Signed-off-by: Mike Raineri <michael.raineri@dell.com>

* Updates based on review feedback

Signed-off-by: Mike Raineri <michael.raineri@dell.com>

* Updated comment to reflect changed behavior

Signed-off-by: Mike Raineri <michael.raineri@dell.com>

* Added changelog fragments

Signed-off-by: Mike Raineri <michael.raineri@dell.com>

* Update changelogs/fragments/8051-Redfish-Wait-For-Service.yml

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

* Update plugins/modules/redfish_command.py

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

* Update plugins/modules/redfish_command.py

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

* Update plugins/modules/redfish_command.py

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

* Update plugins/modules/redfish_command.py

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

---------

Signed-off-by: Mike Raineri <michael.raineri@dell.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Mike Raineri 2024-06-03 00:49:40 -04:00 committed by GitHub
parent d46e12e280
commit 961767e2dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 9 deletions

View file

@ -0,0 +1,3 @@
minor_changes:
- redfish_info - add command ``CheckAvailability`` to check if a service is accessible (https://github.com/ansible-collections/community.general/issues/8051, https://github.com/ansible-collections/community.general/pull/8434).
- redfish_command - add ``wait`` and ``wait_timeout`` options to allow a user to block a command until a service is accessible after performing the requested command (https://github.com/ansible-collections/community.general/issues/8051, https://github.com/ansible-collections/community.general/pull/8434).

View file

@ -11,6 +11,7 @@ import os
import random import random
import string import string
import gzip import gzip
import time
from io import BytesIO from io import BytesIO
from ansible.module_utils.urls import open_url from ansible.module_utils.urls import open_url
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
@ -132,11 +133,13 @@ class RedfishUtils(object):
return resp return resp
# The following functions are to send GET/POST/PATCH/DELETE requests # The following functions are to send GET/POST/PATCH/DELETE requests
def get_request(self, uri, override_headers=None, allow_no_resp=False): def get_request(self, uri, override_headers=None, allow_no_resp=False, timeout=None):
req_headers = dict(GET_HEADERS) req_headers = dict(GET_HEADERS)
if override_headers: if override_headers:
req_headers.update(override_headers) req_headers.update(override_headers)
username, password, basic_auth = self._auth_params(req_headers) username, password, basic_auth = self._auth_params(req_headers)
if timeout is None:
timeout = self.timeout
try: try:
# Service root is an unauthenticated resource; remove credentials # Service root is an unauthenticated resource; remove credentials
# in case the caller will be using sessions later. # in case the caller will be using sessions later.
@ -146,7 +149,7 @@ class RedfishUtils(object):
url_username=username, url_password=password, url_username=username, url_password=password,
force_basic_auth=basic_auth, validate_certs=False, force_basic_auth=basic_auth, validate_certs=False,
follow_redirects='all', follow_redirects='all',
use_proxy=True, timeout=self.timeout) use_proxy=True, timeout=timeout)
headers = dict((k.lower(), v) for (k, v) in resp.info().items()) headers = dict((k.lower(), v) for (k, v) in resp.info().items())
try: try:
if headers.get('content-encoding') == 'gzip' and LooseVersion(ansible_version) < LooseVersion('2.14'): if headers.get('content-encoding') == 'gzip' and LooseVersion(ansible_version) < LooseVersion('2.14'):
@ -624,6 +627,24 @@ class RedfishUtils(object):
allowable_values = default_values allowable_values = default_values
return allowable_values return allowable_values
def check_service_availability(self):
"""
Checks if the service is accessible.
:return: dict containing the status of the service
"""
# Get the service root
# Override the timeout since the service root is expected to be readily
# available.
service_root = self.get_request(self.root_uri + self.service_root, timeout=10)
if service_root['ret'] is False:
# Failed, either due to a timeout or HTTP error; not available
return {'ret': True, 'available': False}
# Successfully accessed the service root; available
return {'ret': True, 'available': True}
def get_logs(self): def get_logs(self):
log_svcs_uri_list = [] log_svcs_uri_list = []
list_of_logs = [] list_of_logs = []
@ -1083,11 +1104,12 @@ class RedfishUtils(object):
return self.manage_power(command, self.systems_uri, return self.manage_power(command, self.systems_uri,
'#ComputerSystem.Reset') '#ComputerSystem.Reset')
def manage_manager_power(self, command): def manage_manager_power(self, command, wait=False, wait_timeout=120):
return self.manage_power(command, self.manager_uri, return self.manage_power(command, self.manager_uri,
'#Manager.Reset') '#Manager.Reset', wait, wait_timeout)
def manage_power(self, command, resource_uri, action_name): def manage_power(self, command, resource_uri, action_name, wait=False,
wait_timeout=120):
key = "Actions" key = "Actions"
reset_type_values = ['On', 'ForceOff', 'GracefulShutdown', reset_type_values = ['On', 'ForceOff', 'GracefulShutdown',
'GracefulRestart', 'ForceRestart', 'Nmi', 'GracefulRestart', 'ForceRestart', 'Nmi',
@ -1147,6 +1169,30 @@ class RedfishUtils(object):
response = self.post_request(self.root_uri + action_uri, payload) response = self.post_request(self.root_uri + action_uri, payload)
if response['ret'] is False: if response['ret'] is False:
return response return response
# If requested to wait for the service to be available again, block
# until it's ready
if wait:
elapsed_time = 0
start_time = time.time()
# Start with a large enough sleep. Some services will process new
# requests while in the middle of shutting down, thus breaking out
# early.
time.sleep(30)
# Periodically check for the service's availability.
while elapsed_time <= wait_timeout:
status = self.check_service_availability()
if status['available']:
# It's available; we're done
break
time.sleep(5)
elapsed_time = time.time() - start_time
if elapsed_time > wait_timeout:
# Exhausted the wait timer; error
return {'ret': False, 'changed': True,
'msg': 'The service did not become available after %d seconds' % wait_timeout}
return {'ret': True, 'changed': True} return {'ret': True, 'changed': True}
def manager_reset_to_defaults(self, command): def manager_reset_to_defaults(self, command):

View file

@ -288,6 +288,20 @@ options:
type: str type: str
choices: [ ResetAll, PreserveNetworkAndUsers, PreserveNetwork ] choices: [ ResetAll, PreserveNetworkAndUsers, PreserveNetwork ]
version_added: 8.6.0 version_added: 8.6.0
wait:
required: false
description:
- Block until the service is ready again.
type: bool
default: false
version_added: 9.1.0
wait_timeout:
required: false
description:
- How long to block until the service is ready again before giving up.
type: int
default: 120
version_added: 9.1.0
author: author:
- "Jose Delarosa (@jose-delarosa)" - "Jose Delarosa (@jose-delarosa)"
@ -685,6 +699,16 @@ EXAMPLES = '''
username: "{{ username }}" username: "{{ username }}"
password: "{{ password }}" password: "{{ password }}"
- name: Restart manager power gracefully and wait for it to be available
community.general.redfish_command:
category: Manager
command: GracefulRestart
resource_id: BMC
baseuri: "{{ baseuri }}"
username: "{{ username }}"
password: "{{ password }}"
wait: True
- name: Restart manager power gracefully - name: Restart manager power gracefully
community.general.redfish_command: community.general.redfish_command:
category: Manager category: Manager
@ -841,7 +865,9 @@ def main():
), ),
strip_etag_quotes=dict(type='bool', default=False), strip_etag_quotes=dict(type='bool', default=False),
reset_to_defaults_mode=dict(choices=['ResetAll', 'PreserveNetworkAndUsers', 'PreserveNetwork']), reset_to_defaults_mode=dict(choices=['ResetAll', 'PreserveNetworkAndUsers', 'PreserveNetwork']),
bios_attributes=dict(type="dict") bios_attributes=dict(type="dict"),
wait=dict(type='bool', default=False),
wait_timeout=dict(type='int', default=120),
), ),
required_together=[ required_together=[
('username', 'password'), ('username', 'password'),
@ -1016,7 +1042,7 @@ def main():
command = 'PowerGracefulRestart' command = 'PowerGracefulRestart'
if command.startswith('Power'): if command.startswith('Power'):
result = rf_utils.manage_manager_power(command) result = rf_utils.manage_manager_power(command, module.params['wait'], module.params['wait_timeout'])
elif command == 'ClearLogs': elif command == 'ClearLogs':
result = rf_utils.clear_logs() result = rf_utils.clear_logs()
elif command == 'VirtualMediaInsert': elif command == 'VirtualMediaInsert':

View file

@ -359,6 +359,16 @@ EXAMPLES = '''
baseuri: "{{ baseuri }}" baseuri: "{{ baseuri }}"
username: "{{ username }}" username: "{{ username }}"
password: "{{ password }}" password: "{{ password }}"
- name: Check the availability of the service with a timeout of 5 seconds
community.general.redfish_info:
category: Service
command: CheckAvailability
baseuri: "{{ baseuri }}"
username: "{{ username }}"
password: "{{ password }}"
timeout: 5
register: result
''' '''
RETURN = ''' RETURN = '''
@ -385,6 +395,7 @@ CATEGORY_COMMANDS_ALL = {
"GetUpdateStatus"], "GetUpdateStatus"],
"Manager": ["GetManagerNicInventory", "GetVirtualMedia", "GetLogs", "GetNetworkProtocols", "Manager": ["GetManagerNicInventory", "GetVirtualMedia", "GetLogs", "GetNetworkProtocols",
"GetHealthReport", "GetHostInterfaces", "GetManagerInventory", "GetServiceIdentification"], "GetHealthReport", "GetHostInterfaces", "GetManagerInventory", "GetServiceIdentification"],
"Service": ["CheckAvailability"],
} }
CATEGORY_COMMANDS_DEFAULT = { CATEGORY_COMMANDS_DEFAULT = {
@ -393,7 +404,8 @@ CATEGORY_COMMANDS_DEFAULT = {
"Accounts": "ListUsers", "Accounts": "ListUsers",
"Update": "GetFirmwareInventory", "Update": "GetFirmwareInventory",
"Sessions": "GetSessions", "Sessions": "GetSessions",
"Manager": "GetManagerNicInventory" "Manager": "GetManagerNicInventory",
"Service": "CheckAvailability",
} }
@ -473,7 +485,13 @@ def main():
module.fail_json(msg="Invalid Category: %s" % category) module.fail_json(msg="Invalid Category: %s" % category)
# Organize by Categories / Commands # Organize by Categories / Commands
if category == "Systems": if category == "Service":
# service-level commands are always available
for command in command_list:
if command == "CheckAvailability":
result["service"] = rf_utils.check_service_availability()
elif category == "Systems":
# execute only if we find a Systems resource # execute only if we find a Systems resource
resource = rf_utils._find_systems_resource() resource = rf_utils._find_systems_resource()
if resource['ret'] is False: if resource['ret'] is False: