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 and SessionService data are not required in enumeration.
206                # Example: '/redfish/v1/JsonSchemas/' and sub resources.
207                #          '/redfish/v1/SessionService'
208                if ('JsonSchemas' in resource) or ('SessionService' in resource):
209                    continue
210
211                self._rest_response_ = \
212                    self._redfish_.get(resource, valid_status_codes=[200, 404, 500])
213                # Enumeration is done for available resources ignoring the
214                # ones for which response is not obtained.
215                if self._rest_response_.status != 200:
216                    continue
217
218                self.walk_nested_dict(self._rest_response_.dict, url=resource)
219
220            enumerated_resources.update(set(resources_to_be_enumerated))
221            resources_to_be_enumerated = \
222                tuple(self.__pending_enumeration - enumerated_resources)
223
224        if return_json:
225            return json.dumps(self.__result, sort_keys=True,
226                              indent=4, separators=(',', ': '))
227        else:
228            return self.__result
229
230    def walk_nested_dict(self, data, url=''):
231        r"""
232        Parse through the nested dictionary and get the resource id paths.
233        Description of argument(s):
234        data    Nested dictionary data from response message.
235        url     Resource for which the response is obtained in data.
236        """
237        url = url.rstrip('/')
238
239        for key, value in data.items():
240
241            # Recursion if nested dictionary found.
242            if isinstance(value, dict):
243                self.walk_nested_dict(value)
244            else:
245                # Value contains a list of dictionaries having member data.
246                if 'Members' == key:
247                    if isinstance(value, list):
248                        for memberDict in value:
249                            self.__pending_enumeration.add(memberDict['@odata.id'])
250                if '@odata.id' == key:
251                    value = value.rstrip('/')
252                    # Data for the given url.
253                    if value == url:
254                        self.__result[url] = data
255                    # Data still needs to be looked up,
256                    else:
257                        self.__pending_enumeration.add(value)
258
259    def get_key_value_nested_dict(self, data, key):
260        r"""
261        Parse through the nested dictionary and get the searched key value.
262
263        Description of argument(s):
264        data    Nested dictionary data from response message.
265        key     Search dictionary key element.
266        """
267
268        for k, v in data.items():
269            if isinstance(v, dict):
270                self.get_key_value_nested_dict(v, key)
271
272            if k == key:
273                target_list.append(v)
274