1#!/usr/bin/env python
2
3r"""
4BMC redfish utility functions.
5"""
6
7import json
8from robot.libraries.BuiltIn import BuiltIn
9import gen_print as gp
10
11
12class bmc_redfish_utils(object):
13
14    def __init__(self):
15        r"""
16        Initialize the bmc_redfish_utils object.
17        """
18        # Obtain a reference to the global redfish object.
19        self._redfish_ = BuiltIn().get_library_instance('redfish')
20
21    def get_redfish_session_info(self):
22        r"""
23        Returns redfish sessions info dictionary.
24
25        {
26            'key': 'yLXotJnrh5nDhXj5lLiH' ,
27            'location': '/redfish/v1/SessionService/Sessions/nblYY4wlz0'
28        }
29        """
30        session_dict = {
31            "key": self._redfish_.get_session_key(),
32            "location": self._redfish_.get_session_location()
33        }
34        return session_dict
35
36    def get_attribute(self, resource_path, attribute):
37        r"""
38        Get resource attribute.
39
40        Description of argument(s):
41        resource_path               URI resource absolute path (e.g.
42                                    "/redfish/v1/Systems/1").
43        attribute                   Name of the attribute (e.g. 'PowerState').
44        """
45
46        resp = self._redfish_.get(resource_path)
47        if attribute in resp.dict:
48            return resp.dict[attribute]
49
50        return None
51
52    def get_properties(self, resource_path):
53        r"""
54        Returns dictionary of attributes for the resource.
55
56        Description of argument(s):
57        resource_path               URI resource absolute path (e.g.
58                                    "/redfish/v1/Systems/1").
59        """
60
61        resp = self._redfish_.get(resource_path)
62        return resp.dict
63
64    def get_target_actions(self, resource_path, target_attribute):
65        r"""
66        Returns resource target entry of the searched target attribute.
67
68        Description of argument(s):
69        resource_path      URI resource absolute path
70                           (e.g. "/redfish/v1/Systems/system").
71        target_attribute   Name of the attribute (e.g. 'ComputerSystem.Reset').
72
73        Example:
74        "Actions": {
75        "#ComputerSystem.Reset": {
76        "ResetType@Redfish.AllowableValues": [
77            "On",
78            "ForceOff",
79            "GracefulRestart",
80            "GracefulShutdown"
81        ],
82        "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
83        }
84        }
85        """
86
87        global target_list
88        target_list = []
89
90        resp_dict = self.get_attribute(resource_path, "Actions")
91        if resp_dict is None:
92            return None
93
94        # Recursively search the "target" key in the nested dictionary.
95        # Populate the target_list of target entries.
96        self.get_key_value_nested_dict(resp_dict, "target")
97
98        # Return the matching target URL entry.
99        for target in target_list:
100            # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
101            if target_attribute in target:
102                return target
103
104        return None
105
106    def get_member_list(self, resource_path):
107        r"""
108        Perform a GET list request and return available members entries.
109
110        Description of argument(s):
111        resource_path  URI resource absolute path
112                       (e.g. "/redfish/v1/SessionService/Sessions").
113
114        "Members": [
115            {
116             "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
117            }
118            {
119             "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
120            }
121        ],
122        """
123
124        member_list = []
125        resp_list_dict = self.get_attribute(resource_path, "Members")
126        if resp_list_dict is None:
127            return member_list
128
129        for member_id in range(0, len(resp_list_dict)):
130            member_list.append(resp_list_dict[member_id]["@odata.id"])
131
132        return member_list
133
134    def list_request(self, resource_path):
135        r"""
136        Perform a GET list request and return available resource paths.
137        Description of argument(s):
138        resource_path  URI resource absolute path
139                       (e.g. "/redfish/v1/SessionService/Sessions").
140        """
141        gp.qprint_executing(style=gp.func_line_style_short)
142        # Set quiet variable to keep subordinate get() calls quiet.
143        quiet = 1
144        self.__pending_enumeration = set()
145        self._rest_response_ = \
146            self._redfish_.get(resource_path,
147                               valid_status_codes=[200, 404, 500])
148
149        # Return empty list.
150        if self._rest_response_.status != 200:
151            return self.__pending_enumeration
152        self.walk_nested_dict(self._rest_response_.dict)
153        if not self.__pending_enumeration:
154            return resource_path
155        for resource in self.__pending_enumeration.copy():
156            self._rest_response_ = \
157                self._redfish_.get(resource,
158                                   valid_status_codes=[200, 404, 500])
159
160            if self._rest_response_.status != 200:
161                continue
162            self.walk_nested_dict(self._rest_response_.dict)
163        return list(sorted(self.__pending_enumeration))
164
165    def enumerate_request(self, resource_path, return_json=1):
166        r"""
167        Perform a GET enumerate request and return available resource paths.
168
169        Description of argument(s):
170        resource_path               URI resource absolute path (e.g.
171                                    "/redfish/v1/SessionService/Sessions").
172        return_json                 Indicates whether the result should be
173                                    returned as a json string or as a
174                                    dictionary.
175        """
176
177        gp.qprint_executing(style=gp.func_line_style_short)
178
179        return_json = int(return_json)
180
181        # Set quiet variable to keep subordinate get() calls quiet.
182        quiet = 1
183
184        # Variable to hold enumerated data.
185        self.__result = {}
186
187        # Variable to hold the pending list of resources for which enumeration.
188        # is yet to be obtained.
189        self.__pending_enumeration = set()
190
191        self.__pending_enumeration.add(resource_path)
192
193        # Variable having resources for which enumeration is completed.
194        enumerated_resources = set()
195
196        resources_to_be_enumerated = (resource_path,)
197
198        while resources_to_be_enumerated:
199            for resource in resources_to_be_enumerated:
200                # JsonSchemas data are not required in enumeration.
201                # Example: '/redfish/v1/JsonSchemas/' and sub resources.
202                if 'JsonSchemas' in resource:
203                    continue
204
205                self._rest_response_ = \
206                    self._redfish_.get(resource, valid_status_codes=[200, 404, 500])
207                # Enumeration is done for available resources ignoring the
208                # ones for which response is not obtained.
209                if self._rest_response_.status != 200:
210                    continue
211
212                self.walk_nested_dict(self._rest_response_.dict, url=resource)
213
214            enumerated_resources.update(set(resources_to_be_enumerated))
215            resources_to_be_enumerated = \
216                tuple(self.__pending_enumeration - enumerated_resources)
217
218        if return_json:
219            return json.dumps(self.__result, sort_keys=True,
220                              indent=4, separators=(',', ': '))
221        else:
222            return self.__result
223
224    def walk_nested_dict(self, data, url=''):
225        r"""
226        Parse through the nested dictionary and get the resource id paths.
227        Description of argument(s):
228        data    Nested dictionary data from response message.
229        url     Resource for which the response is obtained in data.
230        """
231        url = url.rstrip('/')
232
233        for key, value in data.items():
234
235            # Recursion if nested dictionary found.
236            if isinstance(value, dict):
237                self.walk_nested_dict(value)
238            else:
239                # Value contains a list of dictionaries having member data.
240                if 'Members' == key:
241                    if isinstance(value, list):
242                        for memberDict in value:
243                            self.__pending_enumeration.add(memberDict['@odata.id'])
244                if '@odata.id' == key:
245                    value = value.rstrip('/')
246                    # Data for the given url.
247                    if value == url:
248                        self.__result[url] = data
249                    # Data still needs to be looked up,
250                    else:
251                        self.__pending_enumeration.add(value)
252
253    def get_key_value_nested_dict(self, data, key):
254        r"""
255        Parse through the nested dictionary and get the searched key value.
256
257        Description of argument(s):
258        data    Nested dictionary data from response message.
259        key     Search dictionary key element.
260        """
261
262        for k, v in data.items():
263            if isinstance(v, dict):
264                self.get_key_value_nested_dict(v, key)
265
266            if k == key:
267                target_list.append(v)
268