| 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 |  | 
| George Keishing | 46e64a3 | 2019-02-05 22:45:10 -0600 | [diff] [blame] | 22 | ROBOT_LIBRARY_SCOPE = "TEST SUITE" | 
| George Keishing | 2823d2a | 2019-02-05 22:19:19 -0600 | [diff] [blame] | 23 | ROBOT_EXIT_ON_FAILURE = True | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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 | """ | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 36 | self._base_url_ = "https://" + hostname | 
|  | 37 | self._username_ = username | 
|  | 38 | self._password_ = password | 
|  | 39 | self._default_prefix_ = "/redfish/v1" | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 40 |  | 
|  | 41 | def __enter__(self): | 
|  | 42 | return self | 
|  | 43 |  | 
| George Keishing | 2823d2a | 2019-02-05 22:19:19 -0600 | [diff] [blame] | 44 | def __del__(self): | 
| George Keishing | 46e64a3 | 2019-02-05 22:45:10 -0600 | [diff] [blame] | 45 | del self | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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 | """ | 
| George Keishing | 4c39401 | 2019-02-01 06:03:02 -0600 | [diff] [blame] | 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_) | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 67 | self._robj_.login(auth=redfish.AuthMethod.SESSION) | 
| George Keishing | 4c39401 | 2019-02-01 06:03:02 -0600 | [diff] [blame] | 68 | self._session_location_ = self._robj_.get_session_location() | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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): | 
| George Keishing | 6510cfb | 2019-01-31 12:28:36 -0600 | [diff] [blame] | 75 | resource_path    URI resource absolute path (e.g. "/redfish/v1/Systems/1"). | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 76 | args/kwargs      These are passed directly to the corresponding | 
|  | 77 | RestClientBase method. | 
|  | 78 | """ | 
| George Keishing | 6510cfb | 2019-01-31 12:28:36 -0600 | [diff] [blame] | 79 | self._rest_response_ = self._robj_.get(resource_path, *args, **kwargs) | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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) | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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) | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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) | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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): | 
| George Keishing | 5d46755 | 2019-02-08 23:30:48 -0600 | [diff] [blame] | 127 | resource_path  URI resource absolute path | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 128 | (e.g. "/redfish/v1/SessionService/Sessions/8d1a9wiiNL"). | 
|  | 129 | """ | 
|  | 130 | self._rest_response_ = self._robj_.delete(resource_path) | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 131 | return self._rest_response_ | 
|  | 132 |  | 
|  | 133 | def logout(self): | 
|  | 134 | r""" | 
|  | 135 | Logout redfish connection session. | 
|  | 136 | """ | 
|  | 137 | self._robj_.logout() | 
|  | 138 |  | 
| George Keishing | 8393376 | 2019-02-02 04:50:08 -0600 | [diff] [blame] | 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 |  | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 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): | 
| George Keishing | 8cccbd7 | 2019-02-12 13:04:24 -0600 | [diff] [blame] | 160 | resource_path  URI resource absolute path | 
|  | 161 | (e.g. "/redfish/v1/SessionService/Sessions"). | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 162 | """ | 
|  | 163 |  | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 164 | global resource_list | 
|  | 165 | resource_list = [] | 
| George Keishing | 2296e8c | 2019-02-01 05:49:58 -0600 | [diff] [blame] | 166 |  | 
| George Keishing | 8cccbd7 | 2019-02-12 13:04:24 -0600 | [diff] [blame] | 167 | self._rest_response_ = self._robj_.get(resource_path) | 
| George Keishing | 2296e8c | 2019-02-01 05:49:58 -0600 | [diff] [blame] | 168 |  | 
|  | 169 | # Return empty list. | 
|  | 170 | if self._rest_response_.status != 200: | 
|  | 171 | return resource_list | 
|  | 172 |  | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 173 | self.walk_nested_dict(self._rest_response_.dict) | 
|  | 174 |  | 
|  | 175 | if not resource_list: | 
|  | 176 | return uri_path | 
|  | 177 |  | 
|  | 178 | for resource in resource_list: | 
|  | 179 | self._rest_response_ = self._robj_.get(resource) | 
|  | 180 | if self._rest_response_.status != 200: | 
|  | 181 | continue | 
|  | 182 | self.walk_nested_dict(self._rest_response_.dict) | 
|  | 183 |  | 
|  | 184 | resource_list.sort() | 
| George Keishing | 5a73ee0 | 2019-01-28 08:21:47 -0600 | [diff] [blame] | 185 | return resource_list | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 186 |  | 
|  | 187 | def enumerate_request(self, resource_path): | 
|  | 188 | r""" | 
|  | 189 | Perform a GET enumerate request and return available resource paths. | 
|  | 190 |  | 
|  | 191 | Description of argument(s): | 
| George Keishing | 8cccbd7 | 2019-02-12 13:04:24 -0600 | [diff] [blame] | 192 | resource_path  URI resource absolute path | 
|  | 193 | (e.g. "/redfish/v1/SessionService/Sessions"). | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 194 | """ | 
|  | 195 |  | 
| George Keishing | 2296e8c | 2019-02-01 05:49:58 -0600 | [diff] [blame] | 196 | url_list = self.list_request(resource_path) | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 197 |  | 
|  | 198 | resource_dict = {} | 
| George Keishing | 2296e8c | 2019-02-01 05:49:58 -0600 | [diff] [blame] | 199 |  | 
|  | 200 | # Return empty dict. | 
|  | 201 | if not url_list: | 
|  | 202 | return resource_dict | 
|  | 203 |  | 
|  | 204 | for resource in url_list: | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 205 | self._rest_response_ = self._robj_.get(resource) | 
|  | 206 | if self._rest_response_.status != 200: | 
|  | 207 | continue | 
|  | 208 | resource_dict[resource] = self._rest_response_.dict | 
|  | 209 |  | 
|  | 210 | return json.dumps(resource_dict, sort_keys=True, | 
|  | 211 | indent=4, separators=(',', ': ')) | 
|  | 212 |  | 
|  | 213 | def walk_nested_dict(self, data): | 
|  | 214 | r""" | 
|  | 215 | Parse through the nested dictionary and get the resource id paths. | 
|  | 216 |  | 
|  | 217 | Description of argument(s): | 
|  | 218 | data    Nested dictionary data from response message. | 
|  | 219 | """ | 
|  | 220 |  | 
|  | 221 | for key, value in data.items(): | 
|  | 222 | if isinstance(value, dict): | 
|  | 223 | self.walk_nested_dict(value) | 
|  | 224 | else: | 
|  | 225 | if 'Members' == key: | 
|  | 226 | if isinstance(value, list): | 
|  | 227 | for index in value: | 
|  | 228 | if index['@odata.id'] not in resource_list: | 
|  | 229 | resource_list.append(index['@odata.id']) | 
|  | 230 | if '@odata.id' == key: | 
|  | 231 | if value not in resource_list and not value.endswith('/'): | 
|  | 232 | resource_list.append(value) |