1e7e9171eSGeorge Keishing#!/usr/bin/env python3
2f2613b7aSGeorge Keishing
3f2613b7aSGeorge Keishingr"""
4f2613b7aSGeorge KeishingBMC redfish utility functions.
5f2613b7aSGeorge Keishing"""
6f2613b7aSGeorge Keishing
7f2613b7aSGeorge Keishingimport json
83a6f0738SGeorge Keishingimport re
920f38712SPatrick Williams
10e635ddc0SGeorge Keishingimport gen_print as gp
1120f38712SPatrick Williamsfrom robot.libraries.BuiltIn import BuiltIn
12f2613b7aSGeorge Keishing
1305aa70bcSTony LeeMTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
1405aa70bcSTony Lee
15f2613b7aSGeorge Keishing
16f2613b7aSGeorge Keishingclass bmc_redfish_utils(object):
1720f38712SPatrick Williams    ROBOT_LIBRARY_SCOPE = "TEST SUITE"
18eb1fe352SGeorge Keishing
19f2613b7aSGeorge Keishing    def __init__(self):
20f2613b7aSGeorge Keishing        r"""
21f2613b7aSGeorge Keishing        Initialize the bmc_redfish_utils object.
22f2613b7aSGeorge Keishing        """
23f2613b7aSGeorge Keishing        # Obtain a reference to the global redfish object.
24eb1fe352SGeorge Keishing        self.__inited__ = False
2520f38712SPatrick Williams        self._redfish_ = BuiltIn().get_library_instance("redfish")
26f2613b7aSGeorge Keishing
2720f38712SPatrick Williams        if MTLS_ENABLED == "True":
2805aa70bcSTony Lee            self.__inited__ = True
2905aa70bcSTony Lee        else:
30eb1fe352SGeorge Keishing            # There is a possibility that a given driver support both redfish and
31eb1fe352SGeorge Keishing            # legacy REST.
32eb1fe352SGeorge Keishing            self._redfish_.login()
3320f38712SPatrick Williams            self._rest_response_ = self._redfish_.get(
3420f38712SPatrick Williams                "/xyz/openbmc_project/", valid_status_codes=[200, 404]
3520f38712SPatrick Williams            )
36eb1fe352SGeorge Keishing
37eb1fe352SGeorge Keishing            # If REST URL /xyz/openbmc_project/ is supported.
38eb1fe352SGeorge Keishing            if self._rest_response_.status == 200:
39eb1fe352SGeorge Keishing                self.__inited__ = True
40eb1fe352SGeorge Keishing
4120f38712SPatrick Williams        BuiltIn().set_global_variable(
4220f38712SPatrick Williams            "${REDFISH_REST_SUPPORTED}", self.__inited__
4320f38712SPatrick Williams        )
44eb1fe352SGeorge Keishing
45374e684cSGeorge Keishing    def get_redfish_session_info(self):
46374e684cSGeorge Keishing        r"""
47374e684cSGeorge Keishing        Returns redfish sessions info dictionary.
48374e684cSGeorge Keishing
49374e684cSGeorge Keishing        {
50374e684cSGeorge Keishing            'key': 'yLXotJnrh5nDhXj5lLiH' ,
51374e684cSGeorge Keishing            'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
52374e684cSGeorge Keishing        }
53374e684cSGeorge Keishing        """
54374e684cSGeorge Keishing        session_dict = {
5597c93945SGeorge Keishing            "key": self._redfish_.get_session_key(),
5620f38712SPatrick Williams            "location": self._redfish_.get_session_location(),
57374e684cSGeorge Keishing        }
58374e684cSGeorge Keishing        return session_dict
59374e684cSGeorge Keishing
6037122b63SSandhya Somashekar    def get_attribute(self, resource_path, attribute, verify=None):
61f2613b7aSGeorge Keishing        r"""
62f2613b7aSGeorge Keishing        Get resource attribute.
63f2613b7aSGeorge Keishing
64f2613b7aSGeorge Keishing        Description of argument(s):
65c86a2f72SMichael Walsh        resource_path               URI resource absolute path (e.g.
66c86a2f72SMichael Walsh                                    "/redfish/v1/Systems/1").
67f2613b7aSGeorge Keishing        attribute                   Name of the attribute (e.g. 'PowerState').
68f2613b7aSGeorge Keishing        """
69f2613b7aSGeorge Keishing
70f2613b7aSGeorge Keishing        resp = self._redfish_.get(resource_path)
7137122b63SSandhya Somashekar
7237122b63SSandhya Somashekar        if verify:
7337122b63SSandhya Somashekar            if resp.dict[attribute] == verify:
7437122b63SSandhya Somashekar                return resp.dict[attribute]
7537122b63SSandhya Somashekar            else:
7637122b63SSandhya Somashekar                raise ValueError("Attribute value is not equal")
7737122b63SSandhya Somashekar        elif attribute in resp.dict:
78f2613b7aSGeorge Keishing            return resp.dict[attribute]
79f2613b7aSGeorge Keishing
80f2613b7aSGeorge Keishing        return None
81f2613b7aSGeorge Keishing
82c3c05c2bSGeorge Keishing    def get_properties(self, resource_path):
83c3c05c2bSGeorge Keishing        r"""
84c3c05c2bSGeorge Keishing        Returns dictionary of attributes for the resource.
85c3c05c2bSGeorge Keishing
86c3c05c2bSGeorge Keishing        Description of argument(s):
87c86a2f72SMichael Walsh        resource_path               URI resource absolute path (e.g.
8837122b63SSandhya Somashekar                                    /redfish/v1/Systems/1").
89c3c05c2bSGeorge Keishing        """
90c3c05c2bSGeorge Keishing
91c3c05c2bSGeorge Keishing        resp = self._redfish_.get(resource_path)
92c3c05c2bSGeorge Keishing        return resp.dict
93c3c05c2bSGeorge Keishing
94789c3b4cSGeorge Keishing    def get_members_uri(self, resource_path, attribute):
95789c3b4cSGeorge Keishing        r"""
96789c3b4cSGeorge Keishing        Returns the list of valid path which has a given attribute.
97789c3b4cSGeorge Keishing
98789c3b4cSGeorge Keishing        Description of argument(s):
99789c3b4cSGeorge Keishing        resource_path            URI resource base path (e.g.
100789c3b4cSGeorge Keishing                                 '/redfish/v1/Systems/',
101789c3b4cSGeorge Keishing                                 '/redfish/v1/Chassis/').
102789c3b4cSGeorge Keishing        attribute                Name of the attribute (e.g. 'PowerSupplies').
103789c3b4cSGeorge Keishing        """
104789c3b4cSGeorge Keishing
105d5f179e2SGeorge Keishing        # Set quiet variable to keep subordinate get() calls quiet.
106d5f179e2SGeorge Keishing        quiet = 1
107d5f179e2SGeorge Keishing
108789c3b4cSGeorge Keishing        # Get the member id list.
109789c3b4cSGeorge Keishing        # e.g. ['/redfish/v1/Chassis/foo', '/redfish/v1/Chassis/bar']
110789c3b4cSGeorge Keishing        resource_path_list = self.get_member_list(resource_path)
111789c3b4cSGeorge Keishing
112789c3b4cSGeorge Keishing        valid_path_list = []
113789c3b4cSGeorge Keishing
114789c3b4cSGeorge Keishing        for path_idx in resource_path_list:
115789c3b4cSGeorge Keishing            # Get all the child object path under the member id e.g.
116789c3b4cSGeorge Keishing            # ['/redfish/v1/Chassis/foo/Power','/redfish/v1/Chassis/bar/Power']
117789c3b4cSGeorge Keishing            child_path_list = self.list_request(path_idx)
118789c3b4cSGeorge Keishing
119789c3b4cSGeorge Keishing            # Iterate and check if path object has the attribute.
120789c3b4cSGeorge Keishing            for child_path_idx in child_path_list:
12120f38712SPatrick Williams                if (
12220f38712SPatrick Williams                    ("JsonSchemas" in child_path_idx)
12320f38712SPatrick Williams                    or ("SessionService" in child_path_idx)
12420f38712SPatrick Williams                    or ("#" in child_path_idx)
12520f38712SPatrick Williams                ):
1266396bc6dSGeorge Keishing                    continue
127789c3b4cSGeorge Keishing                if self.get_attribute(child_path_idx, attribute):
128789c3b4cSGeorge Keishing                    valid_path_list.append(child_path_idx)
129789c3b4cSGeorge Keishing
130d5f179e2SGeorge Keishing        BuiltIn().log_to_console(valid_path_list)
131789c3b4cSGeorge Keishing        return valid_path_list
132789c3b4cSGeorge Keishing
1333a6f0738SGeorge Keishing    def get_endpoint_path_list(self, resource_path, end_point_prefix):
1343a6f0738SGeorge Keishing        r"""
1353a6f0738SGeorge Keishing        Returns list with entries ending in "/endpoint".
1363a6f0738SGeorge Keishing
1373a6f0738SGeorge Keishing        Description of argument(s):
1383a6f0738SGeorge Keishing        resource_path      URI resource base path (e.g. "/redfish/v1/Chassis/").
139e68cbfb3SGeorge Keishing        end_point_prefix   Name of the endpoint (e.g. 'Power').
1403a6f0738SGeorge Keishing
1413a6f0738SGeorge Keishing        Find all list entries ending in "/endpoint" combination such as
1423a6f0738SGeorge Keishing        /redfish/v1/Chassis/<foo>/Power
1433a6f0738SGeorge Keishing        /redfish/v1/Chassis/<bar>/Power
1443a6f0738SGeorge Keishing        """
1453a6f0738SGeorge Keishing
1463a6f0738SGeorge Keishing        end_point_list = self.list_request(resource_path)
1473a6f0738SGeorge Keishing
1483a6f0738SGeorge Keishing        # Regex to match entries ending in "/prefix" with optional underscore.
149d2f210a0SGeorge Keishing        regex = ".*/" + end_point_prefix + "[_]?[0-9]*$"
1503a6f0738SGeorge Keishing        return [x for x in end_point_list if re.match(regex, x, re.IGNORECASE)]
1513a6f0738SGeorge Keishing
1527ec45937SGeorge Keishing    def get_target_actions(self, resource_path, target_attribute):
1537ec45937SGeorge Keishing        r"""
1547ec45937SGeorge Keishing        Returns resource target entry of the searched target attribute.
1557ec45937SGeorge Keishing
1567ec45937SGeorge Keishing        Description of argument(s):
1577ec45937SGeorge Keishing        resource_path      URI resource absolute path
1587ec45937SGeorge Keishing                           (e.g. "/redfish/v1/Systems/system").
1597ec45937SGeorge Keishing        target_attribute   Name of the attribute (e.g. 'ComputerSystem.Reset').
1607ec45937SGeorge Keishing
1617ec45937SGeorge Keishing        Example:
1627ec45937SGeorge Keishing        "Actions": {
1637ec45937SGeorge Keishing        "#ComputerSystem.Reset": {
1647ec45937SGeorge Keishing        "ResetType@Redfish.AllowableValues": [
1657ec45937SGeorge Keishing            "On",
1667ec45937SGeorge Keishing            "ForceOff",
1677ec45937SGeorge Keishing            "GracefulRestart",
1687ec45937SGeorge Keishing            "GracefulShutdown"
1697ec45937SGeorge Keishing        ],
1707ec45937SGeorge Keishing        "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
1717ec45937SGeorge Keishing        }
1727ec45937SGeorge Keishing        }
1737ec45937SGeorge Keishing        """
1747ec45937SGeorge Keishing
1757ec45937SGeorge Keishing        global target_list
1767ec45937SGeorge Keishing        target_list = []
1777ec45937SGeorge Keishing
1787ec45937SGeorge Keishing        resp_dict = self.get_attribute(resource_path, "Actions")
1797ec45937SGeorge Keishing        if resp_dict is None:
1807ec45937SGeorge Keishing            return None
1817ec45937SGeorge Keishing
1827ec45937SGeorge Keishing        # Recursively search the "target" key in the nested dictionary.
1837ec45937SGeorge Keishing        # Populate the target_list of target entries.
1847ec45937SGeorge Keishing        self.get_key_value_nested_dict(resp_dict, "target")
1857ec45937SGeorge Keishing        # Return the matching target URL entry.
1867ec45937SGeorge Keishing        for target in target_list:
1877ec45937SGeorge Keishing            # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
18820f38712SPatrick Williams            attribute_in_uri = target.rsplit("/", 1)[-1]
189adfdb602SAnusha Dathatri            # attribute_in_uri "ComputerSystem.Reset"
190adfdb602SAnusha Dathatri            if target_attribute == attribute_in_uri:
1917ec45937SGeorge Keishing                return target
1927ec45937SGeorge Keishing
1937ec45937SGeorge Keishing        return None
1947ec45937SGeorge Keishing
195dabf38f1SGeorge Keishing    def get_member_list(self, resource_path):
196dabf38f1SGeorge Keishing        r"""
197dabf38f1SGeorge Keishing        Perform a GET list request and return available members entries.
198dabf38f1SGeorge Keishing
199dabf38f1SGeorge Keishing        Description of argument(s):
200dabf38f1SGeorge Keishing        resource_path  URI resource absolute path
201dabf38f1SGeorge Keishing                       (e.g. "/redfish/v1/SessionService/Sessions").
202dabf38f1SGeorge Keishing
203dabf38f1SGeorge Keishing        "Members": [
204dabf38f1SGeorge Keishing            {
205dabf38f1SGeorge Keishing             "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
206dabf38f1SGeorge Keishing            }
207dabf38f1SGeorge Keishing            {
208dabf38f1SGeorge Keishing             "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
209dabf38f1SGeorge Keishing            }
210dabf38f1SGeorge Keishing        ],
211dabf38f1SGeorge Keishing        """
212dabf38f1SGeorge Keishing
213dabf38f1SGeorge Keishing        member_list = []
214dabf38f1SGeorge Keishing        resp_list_dict = self.get_attribute(resource_path, "Members")
215dabf38f1SGeorge Keishing        if resp_list_dict is None:
216dabf38f1SGeorge Keishing            return member_list
217dabf38f1SGeorge Keishing
218dabf38f1SGeorge Keishing        for member_id in range(0, len(resp_list_dict)):
219dabf38f1SGeorge Keishing            member_list.append(resp_list_dict[member_id]["@odata.id"])
220dabf38f1SGeorge Keishing
221dabf38f1SGeorge Keishing        return member_list
222dabf38f1SGeorge Keishing
223f2613b7aSGeorge Keishing    def list_request(self, resource_path):
224f2613b7aSGeorge Keishing        r"""
225f2613b7aSGeorge Keishing        Perform a GET list request and return available resource paths.
226f2613b7aSGeorge Keishing        Description of argument(s):
227f2613b7aSGeorge Keishing        resource_path  URI resource absolute path
228f2613b7aSGeorge Keishing                       (e.g. "/redfish/v1/SessionService/Sessions").
229f2613b7aSGeorge Keishing        """
230c86a2f72SMichael Walsh        gp.qprint_executing(style=gp.func_line_style_short)
231c86a2f72SMichael Walsh        # Set quiet variable to keep subordinate get() calls quiet.
232c86a2f72SMichael Walsh        quiet = 1
23362dfb864SAnusha Dathatri        self.__pending_enumeration = set()
23420f38712SPatrick Williams        self._rest_response_ = self._redfish_.get(
23520f38712SPatrick Williams            resource_path, valid_status_codes=[200, 404, 500]
23620f38712SPatrick Williams        )
237f2613b7aSGeorge Keishing
238f2613b7aSGeorge Keishing        # Return empty list.
239f2613b7aSGeorge Keishing        if self._rest_response_.status != 200:
24062dfb864SAnusha Dathatri            return self.__pending_enumeration
241f2613b7aSGeorge Keishing        self.walk_nested_dict(self._rest_response_.dict)
24262dfb864SAnusha Dathatri        if not self.__pending_enumeration:
24362dfb864SAnusha Dathatri            return resource_path
24462dfb864SAnusha Dathatri        for resource in self.__pending_enumeration.copy():
24520f38712SPatrick Williams            self._rest_response_ = self._redfish_.get(
24620f38712SPatrick Williams                resource, valid_status_codes=[200, 404, 500]
24720f38712SPatrick Williams            )
24862dfb864SAnusha Dathatri
249f2613b7aSGeorge Keishing            if self._rest_response_.status != 200:
250f2613b7aSGeorge Keishing                continue
251f2613b7aSGeorge Keishing            self.walk_nested_dict(self._rest_response_.dict)
25262dfb864SAnusha Dathatri        return list(sorted(self.__pending_enumeration))
253f2613b7aSGeorge Keishing
25420f38712SPatrick Williams    def enumerate_request(
25520f38712SPatrick Williams        self, resource_path, return_json=1, include_dead_resources=False
25620f38712SPatrick Williams    ):
257f2613b7aSGeorge Keishing        r"""
258f2613b7aSGeorge Keishing        Perform a GET enumerate request and return available resource paths.
259f2613b7aSGeorge Keishing
260f2613b7aSGeorge Keishing        Description of argument(s):
26137e028f3SMichael Walsh        resource_path               URI resource absolute path (e.g.
26237e028f3SMichael Walsh                                    "/redfish/v1/SessionService/Sessions").
26337e028f3SMichael Walsh        return_json                 Indicates whether the result should be
26437e028f3SMichael Walsh                                    returned as a json string or as a
26537e028f3SMichael Walsh                                    dictionary.
2663e7930dfSAnusha Dathatri        include_dead_resources      Check and return a list of dead/broken URI
2673e7930dfSAnusha Dathatri                                    resources.
268f2613b7aSGeorge Keishing        """
269f2613b7aSGeorge Keishing
270c86a2f72SMichael Walsh        gp.qprint_executing(style=gp.func_line_style_short)
271c86a2f72SMichael Walsh
27237e028f3SMichael Walsh        return_json = int(return_json)
27337e028f3SMichael Walsh
274c86a2f72SMichael Walsh        # Set quiet variable to keep subordinate get() calls quiet.
275c86a2f72SMichael Walsh        quiet = 1
276c86a2f72SMichael Walsh
27762dfb864SAnusha Dathatri        # Variable to hold enumerated data.
27862dfb864SAnusha Dathatri        self.__result = {}
279f2613b7aSGeorge Keishing
28062dfb864SAnusha Dathatri        # Variable to hold the pending list of resources for which enumeration.
28162dfb864SAnusha Dathatri        # is yet to be obtained.
28262dfb864SAnusha Dathatri        self.__pending_enumeration = set()
283f2613b7aSGeorge Keishing
28462dfb864SAnusha Dathatri        self.__pending_enumeration.add(resource_path)
285f2613b7aSGeorge Keishing
28662dfb864SAnusha Dathatri        # Variable having resources for which enumeration is completed.
28762dfb864SAnusha Dathatri        enumerated_resources = set()
28862dfb864SAnusha Dathatri
2893e7930dfSAnusha Dathatri        if include_dead_resources:
2903e7930dfSAnusha Dathatri            dead_resources = {}
2913e7930dfSAnusha Dathatri
29262dfb864SAnusha Dathatri        resources_to_be_enumerated = (resource_path,)
29362dfb864SAnusha Dathatri
29462dfb864SAnusha Dathatri        while resources_to_be_enumerated:
29562dfb864SAnusha Dathatri            for resource in resources_to_be_enumerated:
2966d2d42fcSAnusha Dathatri                # JsonSchemas, SessionService or URLs containing # are not
2976d2d42fcSAnusha Dathatri                # required in enumeration.
29848156e71SGeorge Keishing                # Example: '/redfish/v1/JsonSchemas/' and sub resources.
299cdb77dbaSAnusha Dathatri                #          '/redfish/v1/SessionService'
300*4d430283Sganesanb                #          '/redfish/v1/Managers/${MANAGER_ID}#/Oem'
30120f38712SPatrick Williams                if (
30220f38712SPatrick Williams                    ("JsonSchemas" in resource)
30320f38712SPatrick Williams                    or ("SessionService" in resource)
30420f38712SPatrick Williams                    or ("PostCodes" in resource)
30520f38712SPatrick Williams                    or ("Registries" in resource)
30620f38712SPatrick Williams                    or ("Journal" in resource)
30720f38712SPatrick Williams                    or ("#" in resource)
30820f38712SPatrick Williams                ):
30948156e71SGeorge Keishing                    continue
31062dfb864SAnusha Dathatri
31120f38712SPatrick Williams                self._rest_response_ = self._redfish_.get(
31220f38712SPatrick Williams                    resource, valid_status_codes=[200, 404, 405, 500]
31320f38712SPatrick Williams                )
31462dfb864SAnusha Dathatri                # Enumeration is done for available resources ignoring the
31562dfb864SAnusha Dathatri                # ones for which response is not obtained.
316f2613b7aSGeorge Keishing                if self._rest_response_.status != 200:
3173e7930dfSAnusha Dathatri                    if include_dead_resources:
3183e7930dfSAnusha Dathatri                        try:
3193e7930dfSAnusha Dathatri                            dead_resources[self._rest_response_.status].append(
32020f38712SPatrick Williams                                resource
32120f38712SPatrick Williams                            )
3223e7930dfSAnusha Dathatri                        except KeyError:
32320f38712SPatrick Williams                            dead_resources[self._rest_response_.status] = [
32420f38712SPatrick Williams                                resource
32520f38712SPatrick Williams                            ]
326f2613b7aSGeorge Keishing                    continue
327f2613b7aSGeorge Keishing
32862dfb864SAnusha Dathatri                self.walk_nested_dict(self._rest_response_.dict, url=resource)
32962dfb864SAnusha Dathatri
33062dfb864SAnusha Dathatri            enumerated_resources.update(set(resources_to_be_enumerated))
33120f38712SPatrick Williams            resources_to_be_enumerated = tuple(
33220f38712SPatrick Williams                self.__pending_enumeration - enumerated_resources
33320f38712SPatrick Williams            )
33462dfb864SAnusha Dathatri
33537e028f3SMichael Walsh        if return_json:
3363e7930dfSAnusha Dathatri            if include_dead_resources:
33720f38712SPatrick Williams                return (
33820f38712SPatrick Williams                    json.dumps(
33920f38712SPatrick Williams                        self.__result,
34020f38712SPatrick Williams                        sort_keys=True,
34120f38712SPatrick Williams                        indent=4,
34220f38712SPatrick Williams                        separators=(",", ": "),
34320f38712SPatrick Williams                    ),
34420f38712SPatrick Williams                    dead_resources,
34520f38712SPatrick Williams                )
3463e7930dfSAnusha Dathatri            else:
34720f38712SPatrick Williams                return json.dumps(
34820f38712SPatrick Williams                    self.__result,
34920f38712SPatrick Williams                    sort_keys=True,
35020f38712SPatrick Williams                    indent=4,
35120f38712SPatrick Williams                    separators=(",", ": "),
35220f38712SPatrick Williams                )
35337e028f3SMichael Walsh        else:
3543e7930dfSAnusha Dathatri            if include_dead_resources:
3553e7930dfSAnusha Dathatri                return self.__result, dead_resources
3563e7930dfSAnusha Dathatri            else:
35737e028f3SMichael Walsh                return self.__result
358f2613b7aSGeorge Keishing
35920f38712SPatrick Williams    def walk_nested_dict(self, data, url=""):
360f2613b7aSGeorge Keishing        r"""
361f2613b7aSGeorge Keishing        Parse through the nested dictionary and get the resource id paths.
362f2613b7aSGeorge Keishing        Description of argument(s):
363f2613b7aSGeorge Keishing        data    Nested dictionary data from response message.
36462dfb864SAnusha Dathatri        url     Resource for which the response is obtained in data.
365f2613b7aSGeorge Keishing        """
36620f38712SPatrick Williams        url = url.rstrip("/")
367f2613b7aSGeorge Keishing
368f2613b7aSGeorge Keishing        for key, value in data.items():
36962dfb864SAnusha Dathatri            # Recursion if nested dictionary found.
370f2613b7aSGeorge Keishing            if isinstance(value, dict):
371f2613b7aSGeorge Keishing                self.walk_nested_dict(value)
372f2613b7aSGeorge Keishing            else:
37362dfb864SAnusha Dathatri                # Value contains a list of dictionaries having member data.
37420f38712SPatrick Williams                if "Members" == key:
375f2613b7aSGeorge Keishing                    if isinstance(value, list):
37662dfb864SAnusha Dathatri                        for memberDict in value:
3770d305d3eSAnusha Dathatri                            if isinstance(memberDict, str):
3780d305d3eSAnusha Dathatri                                self.__pending_enumeration.add(memberDict)
3790d305d3eSAnusha Dathatri                            else:
38020f38712SPatrick Williams                                self.__pending_enumeration.add(
38120f38712SPatrick Williams                                    memberDict["@odata.id"]
38220f38712SPatrick Williams                                )
3830d305d3eSAnusha Dathatri
38420f38712SPatrick Williams                if "@odata.id" == key:
38520f38712SPatrick Williams                    value = value.rstrip("/")
38662dfb864SAnusha Dathatri                    # Data for the given url.
38762dfb864SAnusha Dathatri                    if value == url:
38862dfb864SAnusha Dathatri                        self.__result[url] = data
38962dfb864SAnusha Dathatri                    # Data still needs to be looked up,
39062dfb864SAnusha Dathatri                    else:
39162dfb864SAnusha Dathatri                        self.__pending_enumeration.add(value)
3927ec45937SGeorge Keishing
3937ec45937SGeorge Keishing    def get_key_value_nested_dict(self, data, key):
3947ec45937SGeorge Keishing        r"""
3957ec45937SGeorge Keishing        Parse through the nested dictionary and get the searched key value.
3967ec45937SGeorge Keishing
3977ec45937SGeorge Keishing        Description of argument(s):
3987ec45937SGeorge Keishing        data    Nested dictionary data from response message.
3997ec45937SGeorge Keishing        key     Search dictionary key element.
4007ec45937SGeorge Keishing        """
4017ec45937SGeorge Keishing
4027ec45937SGeorge Keishing        for k, v in data.items():
4037ec45937SGeorge Keishing            if isinstance(v, dict):
4047ec45937SGeorge Keishing                self.get_key_value_nested_dict(v, key)
4057ec45937SGeorge Keishing
4067ec45937SGeorge Keishing            if k == key:
4077ec45937SGeorge Keishing                target_list.append(v)
408