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