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)