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, verify=None):
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
48        if verify:
49            if resp.dict[attribute] == verify:
50                return resp.dict[attribute]
51            else:
52                raise ValueError("Attribute value is not equal")
53        elif attribute in resp.dict:
54            return resp.dict[attribute]
55
56        return None
57
58    def get_properties(self, resource_path):
59        r"""
60        Returns dictionary of attributes for the resource.
61
62        Description of argument(s):
63        resource_path               URI resource absolute path (e.g.
64                                    /redfish/v1/Systems/1").
65        """
66
67        resp = self._redfish_.get(resource_path)
68        return resp.dict
69
70    def get_target_actions(self, resource_path, target_attribute):
71        r"""
72        Returns resource target entry of the searched target attribute.
73
74        Description of argument(s):
75        resource_path      URI resource absolute path
76                           (e.g. "/redfish/v1/Systems/system").
77        target_attribute   Name of the attribute (e.g. 'ComputerSystem.Reset').
78
79        Example:
80        "Actions": {
81        "#ComputerSystem.Reset": {
82        "ResetType@Redfish.AllowableValues": [
83            "On",
84            "ForceOff",
85            "GracefulRestart",
86            "GracefulShutdown"
87        ],
88        "target": "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
89        }
90        }
91        """
92
93        global target_list
94        target_list = []
95
96        resp_dict = self.get_attribute(resource_path, "Actions")
97        if resp_dict is None:
98            return None
99
100        # Recursively search the "target" key in the nested dictionary.
101        # Populate the target_list of target entries.
102        self.get_key_value_nested_dict(resp_dict, "target")
103        # Return the matching target URL entry.
104        for target in target_list:
105            # target "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
106            if target_attribute in target:
107                return target
108
109        return None
110
111    def get_member_list(self, resource_path):
112        r"""
113        Perform a GET list request and return available members entries.
114
115        Description of argument(s):
116        resource_path  URI resource absolute path
117                       (e.g. "/redfish/v1/SessionService/Sessions").
118
119        "Members": [
120            {
121             "@odata.id": "/redfish/v1/SessionService/Sessions/Z5HummWPZ7"
122            }
123            {
124             "@odata.id": "/redfish/v1/SessionService/Sessions/46CmQmEL7H"
125            }
126        ],
127        """
128
129        member_list = []
130        resp_list_dict = self.get_attribute(resource_path, "Members")
131        if resp_list_dict is None:
132            return member_list
133
134        for member_id in range(0, len(resp_list_dict)):
135            member_list.append(resp_list_dict[member_id]["@odata.id"])
136
137        return member_list
138
139    def list_request(self, resource_path):
140        r"""
141        Perform a GET list request and return available resource paths.
142        Description of argument(s):
143        resource_path  URI resource absolute path
144                       (e.g. "/redfish/v1/SessionService/Sessions").
145        """
146        gp.qprint_executing(style=gp.func_line_style_short)
147        # Set quiet variable to keep subordinate get() calls quiet.
148        quiet = 1
149        self.__pending_enumeration = set()
150        self._rest_response_ = \
151            self._redfish_.get(resource_path,
152                               valid_status_codes=[200, 404, 500])
153
154        # Return empty list.
155        if self._rest_response_.status != 200:
156            return self.__pending_enumeration
157        self.walk_nested_dict(self._rest_response_.dict)
158        if not self.__pending_enumeration:
159            return resource_path
160        for resource in self.__pending_enumeration.copy():
161            self._rest_response_ = \
162                self._redfish_.get(resource,
163                                   valid_status_codes=[200, 404, 500])
164
165            if self._rest_response_.status != 200:
166                continue
167            self.walk_nested_dict(self._rest_response_.dict)
168        return list(sorted(self.__pending_enumeration))
169
170    def enumerate_request(self, resource_path, return_json=1,
171                          include_dead_resources=False):
172        r"""
173        Perform a GET enumerate request and return available resource paths.
174
175        Description of argument(s):
176        resource_path               URI resource absolute path (e.g.
177                                    "/redfish/v1/SessionService/Sessions").
178        return_json                 Indicates whether the result should be
179                                    returned as a json string or as a
180                                    dictionary.
181        include_dead_resources      Check and return a list of dead/broken URI
182                                    resources.
183        """
184
185        gp.qprint_executing(style=gp.func_line_style_short)
186
187        return_json = int(return_json)
188
189        # Set quiet variable to keep subordinate get() calls quiet.
190        quiet = 1
191
192        # Variable to hold enumerated data.
193        self.__result = {}
194
195        # Variable to hold the pending list of resources for which enumeration.
196        # is yet to be obtained.
197        self.__pending_enumeration = set()
198
199        self.__pending_enumeration.add(resource_path)
200
201        # Variable having resources for which enumeration is completed.
202        enumerated_resources = set()
203
204        if include_dead_resources:
205            dead_resources = {}
206
207        resources_to_be_enumerated = (resource_path,)
208
209        while resources_to_be_enumerated:
210            for resource in resources_to_be_enumerated:
211                # JsonSchemas, SessionService or URLs containing # are not
212                # required in enumeration.
213                # Example: '/redfish/v1/JsonSchemas/' and sub resources.
214                #          '/redfish/v1/SessionService'
215                #          '/redfish/v1/Managers/bmc#/Oem'
216                if ('JsonSchemas' in resource) or ('SessionService' in resource)\
217                        or ('#' in resource):
218                    continue
219
220                self._rest_response_ = \
221                    self._redfish_.get(resource, valid_status_codes=[200, 404, 500])
222                # Enumeration is done for available resources ignoring the
223                # ones for which response is not obtained.
224                if self._rest_response_.status != 200:
225                    if include_dead_resources:
226                        try:
227                            dead_resources[self._rest_response_.status].append(
228                                resource)
229                        except KeyError:
230                            dead_resources[self._rest_response_.status] = \
231                                [resource]
232                    continue
233
234                self.walk_nested_dict(self._rest_response_.dict, url=resource)
235
236            enumerated_resources.update(set(resources_to_be_enumerated))
237            resources_to_be_enumerated = \
238                tuple(self.__pending_enumeration - enumerated_resources)
239
240        if return_json:
241            if include_dead_resources:
242                return json.dumps(self.__result, sort_keys=True,
243                                  indent=4, separators=(',', ': ')), dead_resources
244            else:
245                return json.dumps(self.__result, sort_keys=True,
246                                  indent=4, separators=(',', ': '))
247        else:
248            if include_dead_resources:
249                return self.__result, dead_resources
250            else:
251                return self.__result
252
253    def walk_nested_dict(self, data, url=''):
254        r"""
255        Parse through the nested dictionary and get the resource id paths.
256        Description of argument(s):
257        data    Nested dictionary data from response message.
258        url     Resource for which the response is obtained in data.
259        """
260        url = url.rstrip('/')
261
262        for key, value in data.items():
263
264            # Recursion if nested dictionary found.
265            if isinstance(value, dict):
266                self.walk_nested_dict(value)
267            else:
268                # Value contains a list of dictionaries having member data.
269                if 'Members' == key:
270                    if isinstance(value, list):
271                        for memberDict in value:
272                            self.__pending_enumeration.add(memberDict['@odata.id'])
273                if '@odata.id' == key:
274                    value = value.rstrip('/')
275                    # Data for the given url.
276                    if value == url:
277                        self.__result[url] = data
278                    # Data still needs to be looked up,
279                    else:
280                        self.__pending_enumeration.add(value)
281
282    def get_key_value_nested_dict(self, data, key):
283        r"""
284        Parse through the nested dictionary and get the searched key value.
285
286        Description of argument(s):
287        data    Nested dictionary data from response message.
288        key     Search dictionary key element.
289        """
290
291        for k, v in data.items():
292            if isinstance(v, dict):
293                self.get_key_value_nested_dict(v, key)
294
295            if k == key:
296                target_list.append(v)
297