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:
parent
d46e12e280
commit
961767e2dd
4 changed files with 102 additions and 9 deletions
3
changelogs/fragments/8051-Redfish-Wait-For-Service.yml
Normal file
3
changelogs/fragments/8051-Redfish-Wait-For-Service.yml
Normal 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).
|
|
@ -11,6 +11,7 @@ import os
|
|||
import random
|
||||
import string
|
||||
import gzip
|
||||
import time
|
||||
from io import BytesIO
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
@ -132,11 +133,13 @@ class RedfishUtils(object):
|
|||
return resp
|
||||
|
||||
# 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)
|
||||
if override_headers:
|
||||
req_headers.update(override_headers)
|
||||
username, password, basic_auth = self._auth_params(req_headers)
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
try:
|
||||
# Service root is an unauthenticated resource; remove credentials
|
||||
# in case the caller will be using sessions later.
|
||||
|
@ -146,7 +149,7 @@ class RedfishUtils(object):
|
|||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
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())
|
||||
try:
|
||||
if headers.get('content-encoding') == 'gzip' and LooseVersion(ansible_version) < LooseVersion('2.14'):
|
||||
|
@ -624,6 +627,24 @@ class RedfishUtils(object):
|
|||
allowable_values = default_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):
|
||||
log_svcs_uri_list = []
|
||||
list_of_logs = []
|
||||
|
@ -1083,11 +1104,12 @@ class RedfishUtils(object):
|
|||
return self.manage_power(command, self.systems_uri,
|
||||
'#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,
|
||||
'#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"
|
||||
reset_type_values = ['On', 'ForceOff', 'GracefulShutdown',
|
||||
'GracefulRestart', 'ForceRestart', 'Nmi',
|
||||
|
@ -1147,6 +1169,30 @@ class RedfishUtils(object):
|
|||
response = self.post_request(self.root_uri + action_uri, payload)
|
||||
if response['ret'] is False:
|
||||
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}
|
||||
|
||||
def manager_reset_to_defaults(self, command):
|
||||
|
|
|
@ -288,6 +288,20 @@ options:
|
|||
type: str
|
||||
choices: [ ResetAll, PreserveNetworkAndUsers, PreserveNetwork ]
|
||||
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:
|
||||
- "Jose Delarosa (@jose-delarosa)"
|
||||
|
@ -685,6 +699,16 @@ EXAMPLES = '''
|
|||
username: "{{ username }}"
|
||||
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
|
||||
community.general.redfish_command:
|
||||
category: Manager
|
||||
|
@ -841,7 +865,9 @@ def main():
|
|||
),
|
||||
strip_etag_quotes=dict(type='bool', default=False),
|
||||
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=[
|
||||
('username', 'password'),
|
||||
|
@ -1016,7 +1042,7 @@ def main():
|
|||
command = 'PowerGracefulRestart'
|
||||
|
||||
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':
|
||||
result = rf_utils.clear_logs()
|
||||
elif command == 'VirtualMediaInsert':
|
||||
|
|
|
@ -359,6 +359,16 @@ EXAMPLES = '''
|
|||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
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 = '''
|
||||
|
@ -385,6 +395,7 @@ CATEGORY_COMMANDS_ALL = {
|
|||
"GetUpdateStatus"],
|
||||
"Manager": ["GetManagerNicInventory", "GetVirtualMedia", "GetLogs", "GetNetworkProtocols",
|
||||
"GetHealthReport", "GetHostInterfaces", "GetManagerInventory", "GetServiceIdentification"],
|
||||
"Service": ["CheckAvailability"],
|
||||
}
|
||||
|
||||
CATEGORY_COMMANDS_DEFAULT = {
|
||||
|
@ -393,7 +404,8 @@ CATEGORY_COMMANDS_DEFAULT = {
|
|||
"Accounts": "ListUsers",
|
||||
"Update": "GetFirmwareInventory",
|
||||
"Sessions": "GetSessions",
|
||||
"Manager": "GetManagerNicInventory"
|
||||
"Manager": "GetManagerNicInventory",
|
||||
"Service": "CheckAvailability",
|
||||
}
|
||||
|
||||
|
||||
|
@ -473,7 +485,13 @@ def main():
|
|||
module.fail_json(msg="Invalid Category: %s" % category)
|
||||
|
||||
# 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
|
||||
resource = rf_utils._find_systems_resource()
|
||||
if resource['ret'] is False:
|
||||
|
|
Loading…
Reference in a new issue