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 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):
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue