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