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        r"""
172        Perform a GET enumerate request and return available resource paths.
173
174        Description of argument(s):
175        resource_path               URI resource absolute path (e.g.
176                                    "/redfish/v1/SessionService/Sessions").
177        return_json                 Indicates whether the result should be
178                                    returned as a json string or as a
179                                    dictionary.
180        """
181
182        gp.qprint_executing(style=gp.func_line_style_short)
183
184        return_json = int(return_json)
185
186        # Set quiet variable to keep subordinate get() calls quiet.
187        quiet = 1
188
189        # Variable to hold enumerated data.
190        self.__result = {}
191
192        # Variable to hold the pending list of resources for which enumeration.
193        # is yet to be obtained.
194        self.__pending_enumeration = set()
195
196        self.__pending_enumeration.add(resource_path)
197
198        # Variable having resources for which enumeration is completed.
199        enumerated_resources = set()
200
201        resources_to_be_enumerated = (resource_path,)
202
203        while resources_to_be_enumerated:
204            for resource in resources_to_be_enumerated:
205                # JsonSchemas data are not required in enumeration.
206                # Example: '/redfish/v1/JsonSchemas/' and sub resources.
207                if 'JsonSchemas' in resource:
208                    continue
209
210                self._rest_response_ = \
211                    self._redfish_.get(resource, valid_status_codes=[200, 404, 500])
212                # Enumeration is done for available resources ignoring the
213                # ones for which response is not obtained.
214                if self._rest_response_.status != 200:
215                    continue
216
217                self.walk_nested_dict(self._rest_response_.dict, url=resource)
218
219            enumerated_resources.update(set(resources_to_be_enumerated))
220            resources_to_be_enumerated = \
221                tuple(self.__pending_enumeration - enumerated_resources)
222
223        if return_json:
224            return json.dumps(self.__result, sort_keys=True,
225                              indent=4, separators=(',', ': '))
226        else:
227            return self.__result
228
229    def walk_nested_dict(self, data, url=''):
230        r"""
231        Parse through the nested dictionary and get the resource id paths.
232        Description of argument(s):
233        data    Nested dictionary data from response message.
234        url     Resource for which the response is obtained in data.
235        """
236        url = url.rstrip('/')
237
238        for key, value in data.items():
239
240            # Recursion if nested dictionary found.
241            if isinstance(value, dict):
242                self.walk_nested_dict(value)
243            else:
244                # Value contains a list of dictionaries having member data.
245                if 'Members' == key:
246                    if isinstance(value, list):
247                        for memberDict in value:
248                            self.__pending_enumeration.add(memberDict['@odata.id'])
249                if '@odata.id' == key:
250                    value = value.rstrip('/')
251                    # Data for the given url.
252                    if value == url:
253                        self.__result[url] = data
254                    # Data still needs to be looked up,
255                    else:
256                        self.__pending_enumeration.add(value)
257
258    def get_key_value_nested_dict(self, data, key):
259        r"""
260        Parse through the nested dictionary and get the searched key value.
261
262        Description of argument(s):
263        data    Nested dictionary data from response message.
264        key     Search dictionary key element.
265        """
266
267        for k, v in data.items():
268            if isinstance(v, dict):
269                self.get_key_value_nested_dict(v, key)
270
271            if k == key:
272                target_list.append(v)
273