Add support to walk through /redfish/v1 using plugin
Changes:
- Add check for plugin response if dictionary
- Add new redfish plugin module
- Add entry in the YAML
Tested:
- With plugin
- Generic existing testing
Change-Id: I71291e337d0a064bbe024fbef193380ec3cfb8ad
Signed-off-by: George Keishing <gkeishin@in.ibm.com>
diff --git a/ffdc/ffdc_collector.py b/ffdc/ffdc_collector.py
index 95d4fa7..e33fb84 100644
--- a/ffdc/ffdc_collector.py
+++ b/ffdc/ffdc_collector.py
@@ -470,7 +470,10 @@
# Creates a new file
with open(targ_file_with_path, 'w') as fp:
- fp.write(result)
+ if isinstance(result, dict):
+ fp.write(json.dumps(result))
+ else:
+ fp.write(result)
fp.close
executed_files_saved.append(targ_file)
diff --git a/ffdc/ffdc_config.yaml b/ffdc/ffdc_config.yaml
index 71bf9c6..d297c07 100644
--- a/ffdc/ffdc_config.yaml
+++ b/ffdc/ffdc_config.yaml
@@ -111,6 +111,14 @@
- redfishtool -u ${username} -p ${password} -r ${hostname} -S Always raw GET /redfish/v1/Managers/bmc/LogServices/Dump/Entries
- redfishtool -u ${username} -p ${password} -r ${hostname} -S Always raw GET /redfish/v1/Systems/system/LogServices/Dump/Entries
- redfishtool -u ${username} -p ${password} -r ${hostname} -S Always raw GET /redfish/v1/Systems/system/LogServices/EventLog/Entries
+ - plugin:
+ - plugin_name: plugin.redfish.enumerate_request
+ - plugin_args:
+ - ${hostname}
+ - ${username}
+ - ${password}
+ - /redfish/v1/
+ - json
FILES:
- 'REDFISH_software.json'
- 'REDFISH_bmc_state.json'
@@ -119,6 +127,7 @@
- 'REDFISH_bmc_dump_entries.json'
- 'REDFISH_system_dumps_entries.json'
- 'REDFISH_event_log_entries.json'
+ - 'REDFISH_enumerate_v1.json'
PROTOCOL:
- 'REDFISH'
diff --git a/ffdc/plugins/redfish.py b/ffdc/plugins/redfish.py
new file mode 100644
index 0000000..8815555
--- /dev/null
+++ b/ffdc/plugins/redfish.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+
+r"""
+This module contains functions having to do with redfish path walking.
+"""
+
+import os
+import subprocess
+import json
+
+ERROR_RESPONSE = {
+ "404": 'Response Error: status_code: 404 -- Not Found',
+ "500": 'Response Error: status_code: 500 -- Internal Server Error',
+}
+
+# Variable to hold enumerated data.
+result = {}
+
+# Variable to hold the pending list of resources for which enumeration.
+# is yet to be obtained.
+pending_enumeration = set()
+
+
+def execute_redfish_cmd(parms, json_type="json"):
+ r"""
+ Run CLI standard redfish tool.
+
+ Description of variable:
+ parms_string Command to execute from the current SHELL.
+ quiet do not print tool error message if True
+ """
+ resp = subprocess.run([parms],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=True,
+ universal_newlines=True)
+
+ if resp.stderr:
+ print('\n\t\tERROR with %s ' % parms)
+ print('\t\t' + resp.stderr)
+ return resp.stderr
+ elif json_type == "json":
+ json_data = json.loads(resp.stdout)
+ return json_data
+ else:
+ return resp.stdout
+
+
+def enumerate_request(hostname, username, password, url, return_json="json"):
+ r"""
+ Perform a GET enumerate request and return available resource paths.
+
+ Description of argument(s):
+ url URI resource absolute path (e.g.
+ "/redfish/v1/SessionService/Sessions").
+ return_json Indicates whether the result should be
+ returned as a json string or as a
+ dictionary.
+ """
+ parms = 'redfishtool -u ' + username + ' -p ' + password + ' -r ' + \
+ hostname + ' -S Always raw GET '
+
+ pending_enumeration.add(url)
+
+ # Variable having resources for which enumeration is completed.
+ enumerated_resources = set()
+
+ resources_to_be_enumerated = (url,)
+
+ while resources_to_be_enumerated:
+ for resource in resources_to_be_enumerated:
+ # JsonSchemas, SessionService or URLs containing # are not
+ # required in enumeration.
+ # Example: '/redfish/v1/JsonSchemas/' and sub resources.
+ # '/redfish/v1/SessionService'
+ # '/redfish/v1/Managers/bmc#/Oem'
+ if ('JsonSchemas' in resource) or ('SessionService' in resource)\
+ or ('PostCodes' in resource) or ('Registries' in resource)\
+ or ('#' in resource):
+ continue
+
+ response = execute_redfish_cmd(parms + resource)
+ # Enumeration is done for available resources ignoring the
+ # ones for which response is not obtained.
+ if 'Response Error' in response:
+ continue
+
+ walk_nested_dict(response, url=resource)
+
+ enumerated_resources.update(set(resources_to_be_enumerated))
+ resources_to_be_enumerated = \
+ tuple(pending_enumeration - enumerated_resources)
+
+ if return_json == "json":
+ return json.dumps(result, sort_keys=True,
+ indent=4, separators=(',', ': '))
+ else:
+ return result
+
+
+def walk_nested_dict(data, url=''):
+ r"""
+ Parse through the nested dictionary and get the resource id paths.
+
+ Description of argument(s):
+ data Nested dictionary data from response message.
+ url Resource for which the response is obtained in data.
+ """
+ url = url.rstrip('/')
+
+ for key, value in data.items():
+
+ # Recursion if nested dictionary found.
+ if isinstance(value, dict):
+ walk_nested_dict(value)
+ else:
+ # Value contains a list of dictionaries having member data.
+ if 'Members' == key:
+ if isinstance(value, list):
+ for memberDict in value:
+ if isinstance(memberDict, str):
+ pending_enumeration.add(memberDict)
+ else:
+ pending_enumeration.add(memberDict['@odata.id'])
+
+ if '@odata.id' == key:
+ value = value.rstrip('/')
+ # Data for the given url.
+ if value == url:
+ result[url] = data
+ # Data still needs to be looked up,
+ else:
+ pending_enumeration.add(value)
+
+
+def get_key_value_nested_dict(data, key):
+ r"""
+ Parse through the nested dictionary and get the searched key value.
+
+ Description of argument(s):
+ data Nested dictionary data from response message.
+ key Search dictionary key element.
+ """
+
+ for k, v in data.items():
+ if isinstance(v, dict):
+ get_key_value_nested_dict(v, key)
+
+ if k == key:
+ target_list.append(v)