1#!/usr/bin/env python
2
3r"""
4Using python based redfish library.
5Refer: https://github.com/DMTF/python-redfish-library
6"""
7
8import redfish
9import json
10from robot.libraries.BuiltIn import BuiltIn
11
12
13class HTTPSBadRequestError(Exception):
14    r"""
15    BMC redfish generic raised method for error(s).
16    """
17    pass
18
19
20class bmc_redfish(object):
21
22    ROBOT_LIBRARY_SCOPE = "TEST SUITE"
23    ROBOT_EXIT_ON_FAILURE = True
24
25    def __init__(self, hostname, username, password, *args, **kwargs):
26        r"""
27        Establish session connection to host.
28
29        Description of argument(s):
30        hostname       The host name or IP address of the server.
31        username       The username to be used to connect to the server.
32        password       The password to be used to connect to the server.
33        args/kwargs    Additional parms which are passed directly
34                       to the redfish_client function.
35        """
36        self._base_url_ = "https://" + hostname
37        self._username_ = username
38        self._password_ = password
39        self._default_prefix_ = "/redfish/v1"
40
41    def __enter__(self):
42        return self
43
44    def __del__(self):
45        del self
46
47    def login(self, *args, **kwargs):
48        r"""
49        Call the corresponding RestClientBase method and return the result.
50
51        Description of argument(s):
52        args/kwargs     These are passed directly to the corresponding
53                        RestClientBase method.
54        """
55
56        for arg in args:
57            hostname = self._base_url_.strip("https://")
58            # Class object constructor reinitialized.
59            self.__init__(hostname=hostname,
60                          username=arg['username'],
61                          password=arg['password'])
62
63        self._robj_ = redfish.redfish_client(base_url=self._base_url_,
64                                             username=self._username_,
65                                             password=self._password_,
66                                             default_prefix=self._default_prefix_)
67        self._robj_.login(auth=redfish.AuthMethod.SESSION)
68        self._session_location_ = self._robj_.get_session_location()
69
70    def get(self, resource_path, *args, **kwargs):
71        r"""
72        Perform a GET request and return response.
73
74        Description of argument(s):
75        resource_path    URI resource absolute path (e.g. "/redfish/v1/Systems/1").
76        args/kwargs      These are passed directly to the corresponding
77                         RestClientBase method.
78        """
79        self._rest_response_ = self._robj_.get(resource_path, *args, **kwargs)
80        return self._rest_response_
81
82    def post(self, resource_path, *args, **kwargs):
83        r"""
84        Perform a POST request.
85
86        Description of argument(s):
87        resource_path    URI resource relative path
88                         (e.g. "Systems/1/Actions/ComputerSystem.Reset").
89        args/kwargs      These are passed directly to the corresponding
90                         RestClientBase method.
91        """
92        self._rest_response_ = self._robj_.post('/redfish/v1/' + resource_path,
93                                                *args, **kwargs)
94        return self._rest_response_
95
96    def patch(self, resource_path, *args, **kwargs):
97        r"""
98        Perform a POST request.
99
100        Description of argument(s):
101        resource_path    URI resource relative path
102        args/kwargs      These are passed directly to the corresponding
103                         RestClientBase method.
104        """
105        self._rest_response_ = self._robj_.patch('/redfish/v1/' + resource_path,
106                                                 *args, **kwargs)
107        return self._rest_response_
108
109    def put(self, resource_path, actions, attr_data):
110        r"""
111        Perform a PUT request.
112
113        Description of argument(s):
114        resource_path    URI resource relative path.
115        args/kwargs      These are passed directly to the corresponding
116                         RestClientBase method.
117        """
118        self._rest_response_ = self._robj_.put('/redfish/v1/' + resource_path,
119                                               *args, **kwargs)
120        return self._rest_response_
121
122    def delete(self, resource_path):
123        r"""
124        Perform a DELETE request.
125
126        Description of argument(s):
127        resource_path  URI resource absoulute path
128                       (e.g. "/redfish/v1/SessionService/Sessions/8d1a9wiiNL").
129        """
130        self._rest_response_ = self._robj_.delete(resource_path)
131        return self._rest_response_
132
133    def logout(self):
134        r"""
135        Logout redfish connection session.
136        """
137        self._robj_.logout()
138
139    def get_attribute(self, resource_path, attribute):
140        r"""
141        Perform a GET request and return attribute value.
142
143        Description of argument(s):
144        resource_path    URI resource absolute path (e.g. "/redfish/v1/Systems/1").
145        attribute        Property name (e.g. "PowerState").
146        """
147
148        resp = self._robj_.get(resource_path)
149
150        if attribute in resp.dict:
151            return resp.dict[attribute]
152
153        return None
154
155    def list_request(self, resource_path):
156        r"""
157        Perform a GET list request and return available resource paths.
158
159        Description of argument(s):
160        resource_path    URI resource relative path (e.g. "Systems/1").
161        """
162
163        global resource_list
164        resource_list = []
165
166        self._rest_response_ = self._robj_.get('/redfish/v1/' + resource_path)
167
168        # Return empty list.
169        if self._rest_response_.status != 200:
170            return resource_list
171
172        self.walk_nested_dict(self._rest_response_.dict)
173
174        if not resource_list:
175            return uri_path
176
177        for resource in resource_list:
178            self._rest_response_ = self._robj_.get(resource)
179            if self._rest_response_.status != 200:
180                continue
181            self.walk_nested_dict(self._rest_response_.dict)
182
183        resource_list.sort()
184        return resource_list
185
186    def enumerate_request(self, resource_path):
187        r"""
188        Perform a GET enumerate request and return available resource paths.
189
190        Description of argument(s):
191        resource_path    URI resource relative path (e.g. "Systems/1").
192        """
193
194        url_list = self.list_request(resource_path)
195
196        resource_dict = {}
197
198        # Return empty dict.
199        if not url_list:
200            return resource_dict
201
202        for resource in url_list:
203            self._rest_response_ = self._robj_.get(resource)
204            if self._rest_response_.status != 200:
205                continue
206            resource_dict[resource] = self._rest_response_.dict
207
208        return json.dumps(resource_dict, sort_keys=True,
209                          indent=4, separators=(',', ': '))
210
211    def walk_nested_dict(self, data):
212        r"""
213        Parse through the nested dictionary and get the resource id paths.
214
215        Description of argument(s):
216        data    Nested dictionary data from response message.
217        """
218
219        for key, value in data.items():
220            if isinstance(value, dict):
221                self.walk_nested_dict(value)
222            else:
223                if 'Members' == key:
224                    if isinstance(value, list):
225                        for index in value:
226                            if index['@odata.id'] not in resource_list:
227                                resource_list.append(index['@odata.id'])
228                if '@odata.id' == key:
229                    if value not in resource_list and not value.endswith('/'):
230                        resource_list.append(value)
231