| George Keishing | e7e9171 | 2021-09-03 11:28:44 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python3 | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 2 |  | 
 | 3 | r""" | 
| George Keishing | 97c9394 | 2019-03-04 12:45:07 -0600 | [diff] [blame] | 4 | See class prolog below for details. | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 5 | """ | 
 | 6 |  | 
| George Keishing | e635ddc | 2022-12-08 07:38:02 -0600 | [diff] [blame] | 7 | import json | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 8 | import re | 
 | 9 | import sys | 
| George Keishing | 36a1075 | 2022-02-24 04:14:17 -0600 | [diff] [blame] | 10 | from json.decoder import JSONDecodeError | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 11 |  | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 12 | import func_args as fa | 
| Michael Walsh | ce7c4b5 | 2019-03-20 17:33:15 -0500 | [diff] [blame] | 13 | import gen_print as gp | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 14 | from redfish.rest.v1 import InvalidCredentialsError | 
 | 15 | from redfish_plus import redfish_plus | 
 | 16 | from robot.libraries.BuiltIn import BuiltIn | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 17 |  | 
| Tony Lee | 05aa70b | 2021-01-28 19:18:27 +0800 | [diff] [blame] | 18 | MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}") | 
 | 19 |  | 
 | 20 |  | 
| George Keishing | 97c9394 | 2019-03-04 12:45:07 -0600 | [diff] [blame] | 21 | class bmc_redfish(redfish_plus): | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 22 |     r""" | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 23 |     bmc_redfish is a child class of redfish_plus that is designed to provide | 
| George Keishing | 97c9394 | 2019-03-04 12:45:07 -0600 | [diff] [blame] | 24 |     benefits specifically for using redfish to communicate with an OpenBMC. | 
 | 25 |  | 
 | 26 |     See the prologs of the methods below for details. | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 27 |     """ | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 28 |  | 
| Michael Walsh | ce7c4b5 | 2019-03-20 17:33:15 -0500 | [diff] [blame] | 29 |     def __init__(self, *args, **kwargs): | 
 | 30 |         r""" | 
 | 31 |         Do BMC-related redfish initialization. | 
 | 32 |  | 
 | 33 |         Presently, older versions of BMC code may not support redfish | 
 | 34 |         requests.  This can lead to unsightly error text being printed out for | 
 | 35 |         programs that may use lib/bmc_redfish_resource.robot even though they | 
 | 36 |         don't necessarily intend to make redfish requests. | 
 | 37 |  | 
 | 38 |         This class method will make an attempt to tolerate this situation.  At | 
 | 39 |         some future point, when all BMCs can be expected to support redfish, | 
 | 40 |         this class method may be considered for deletion.  If it is deleted, | 
 | 41 |         the self.__inited__ test code in the login() class method below should | 
 | 42 |         likewise be deleted. | 
 | 43 |         """ | 
 | 44 |         self.__inited__ = False | 
 | 45 |         try: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 46 |             if MTLS_ENABLED == "True": | 
| Tony Lee | 05aa70b | 2021-01-28 19:18:27 +0800 | [diff] [blame] | 47 |                 self.__inited__ = True | 
 | 48 |             else: | 
 | 49 |                 super(bmc_redfish, self).__init__(*args, **kwargs) | 
 | 50 |                 self.__inited__ = True | 
| Michael Walsh | ce7c4b5 | 2019-03-20 17:33:15 -0500 | [diff] [blame] | 51 |         except ValueError as get_exception: | 
 | 52 |             except_type, except_value, except_traceback = sys.exc_info() | 
 | 53 |             regex = r"The HTTP status code was not valid:[\r\n]+status:[ ]+502" | 
 | 54 |             result = re.match(regex, str(except_value), flags=re.MULTILINE) | 
 | 55 |             if not result: | 
 | 56 |                 gp.lprint_var(except_type) | 
 | 57 |                 gp.lprint_varx("except_value", str(except_value)) | 
| George Keishing | 6224635 | 2022-08-01 01:20:06 -0500 | [diff] [blame] | 58 |                 raise (get_exception) | 
| Michael Walsh | e58df1c | 2019-08-07 09:57:43 -0500 | [diff] [blame] | 59 |         BuiltIn().set_global_variable("${REDFISH_SUPPORTED}", self.__inited__) | 
| George Keishing | b131ff3 | 2020-10-13 09:18:24 -0500 | [diff] [blame] | 60 |         BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", True) | 
| Michael Walsh | ce7c4b5 | 2019-03-20 17:33:15 -0500 | [diff] [blame] | 61 |  | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 62 |     def login(self, *args, **kwargs): | 
 | 63 |         r""" | 
| George Keishing | 97c9394 | 2019-03-04 12:45:07 -0600 | [diff] [blame] | 64 |         Assign BMC default values for username, password and auth arguments | 
 | 65 |         and call parent class login method. | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 66 |  | 
 | 67 |         Description of argument(s): | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 68 |         args                        See parent class method prolog for details. | 
 | 69 |         kwargs                      See parent class method prolog for details. | 
| George Keishing | e62d8b0 | 2018-11-29 12:01:56 -0600 | [diff] [blame] | 70 |         """ | 
| George Keishing | 4c39401 | 2019-02-01 06:03:02 -0600 | [diff] [blame] | 71 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 72 |         if MTLS_ENABLED == "True": | 
| Tony Lee | 05aa70b | 2021-01-28 19:18:27 +0800 | [diff] [blame] | 73 |             return None | 
| Michael Walsh | ce7c4b5 | 2019-03-20 17:33:15 -0500 | [diff] [blame] | 74 |         if not self.__inited__: | 
| Michael Walsh | 707ed0e | 2019-05-17 15:27:25 -0500 | [diff] [blame] | 75 |             message = "bmc_redfish.__init__() was never successfully run.  It " | 
 | 76 |             message += "is likely that the target BMC firmware code level " | 
 | 77 |             message += "does not support redfish.\n" | 
| Michael Walsh | ce7c4b5 | 2019-03-20 17:33:15 -0500 | [diff] [blame] | 78 |             raise ValueError(message) | 
| George Keishing | 97c9394 | 2019-03-04 12:45:07 -0600 | [diff] [blame] | 79 |         # Assign default values for username, password, auth where necessary. | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 80 |         openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}") | 
 | 81 |         openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}") | 
 | 82 |         username, args, kwargs = fa.pop_arg(openbmc_username, *args, **kwargs) | 
 | 83 |         password, args, kwargs = fa.pop_arg(openbmc_password, *args, **kwargs) | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 84 |         auth, args, kwargs = fa.pop_arg("session", *args, **kwargs) | 
| George Keishing | 4c39401 | 2019-02-01 06:03:02 -0600 | [diff] [blame] | 85 |  | 
| George Keishing | 36a1075 | 2022-02-24 04:14:17 -0600 | [diff] [blame] | 86 |         try: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 87 |             super(bmc_redfish, self).login( | 
 | 88 |                 username, password, auth, *args, **kwargs | 
 | 89 |             ) | 
| George Keishing | 36a1075 | 2022-02-24 04:14:17 -0600 | [diff] [blame] | 90 |         # Handle InvalidCredentialsError. | 
 | 91 |         # (raise redfish.rest.v1.InvalidCredentialsError if not [200, 201, 202, 204]) | 
 | 92 |         except InvalidCredentialsError: | 
 | 93 |             except_type, except_value, except_traceback = sys.exc_info() | 
 | 94 |             BuiltIn().log_to_console(str(except_type)) | 
 | 95 |             BuiltIn().log_to_console(str(except_value)) | 
 | 96 |             e_message = "Re-try login due to exception and " | 
 | 97 |             e_message += "it is likely error response from server side." | 
 | 98 |             BuiltIn().log_to_console(e_message) | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 99 |             super(bmc_redfish, self).login( | 
 | 100 |                 username, password, auth, *args, **kwargs | 
 | 101 |             ) | 
| George Keishing | 36a1075 | 2022-02-24 04:14:17 -0600 | [diff] [blame] | 102 |         # Handle JSONDecodeError and others. | 
 | 103 |         except JSONDecodeError: | 
 | 104 |             except_type, except_value, except_traceback = sys.exc_info() | 
 | 105 |             BuiltIn().log_to_console(str(except_type)) | 
 | 106 |             BuiltIn().log_to_console(str(except_value)) | 
 | 107 |             e_message = "Re-try login due to JSONDecodeError exception and " | 
 | 108 |             e_message += "it is likely error response from server side." | 
 | 109 |             BuiltIn().log_to_console(e_message) | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 110 |             super(bmc_redfish, self).login( | 
 | 111 |                 username, password, auth, *args, **kwargs | 
 | 112 |             ) | 
| George Keishing | 36a1075 | 2022-02-24 04:14:17 -0600 | [diff] [blame] | 113 |         except ValueError: | 
 | 114 |             except_type, except_value, except_traceback = sys.exc_info() | 
 | 115 |             BuiltIn().log_to_console(str(except_type)) | 
 | 116 |             BuiltIn().log_to_console(str(except_value)) | 
 | 117 |             e_message = "Unexpected exception." | 
 | 118 |             BuiltIn().log_to_console(e_message) | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 119 |  | 
| Tony Lee | 05aa70b | 2021-01-28 19:18:27 +0800 | [diff] [blame] | 120 |     def logout(self): | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 121 |         if MTLS_ENABLED == "True": | 
| Tony Lee | 05aa70b | 2021-01-28 19:18:27 +0800 | [diff] [blame] | 122 |             return None | 
 | 123 |         else: | 
 | 124 |             super(bmc_redfish, self).logout() | 
 | 125 |  | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 126 |     def get_properties(self, *args, **kwargs): | 
 | 127 |         r""" | 
 | 128 |         Return dictionary of attributes for a given path. | 
 | 129 |  | 
 | 130 |         The difference between calling this function and calling get() | 
 | 131 |         directly is that this function returns ONLY the dictionary portion of | 
 | 132 |         the response object. | 
 | 133 |  | 
 | 134 |         Example robot code: | 
 | 135 |  | 
 | 136 |         ${properties}=  Get Properties  /redfish/v1/Systems/system/ | 
| Michael Walsh | 39c0051 | 2019-07-17 10:54:06 -0500 | [diff] [blame] | 137 |         Rprint Vars  properties | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 138 |  | 
 | 139 |         Output: | 
 | 140 |  | 
 | 141 |         properties: | 
 | 142 |           [PowerState]:      Off | 
 | 143 |           [Processors]: | 
 | 144 |             [@odata.id]:     /redfish/v1/Systems/system/Processors | 
 | 145 |           [SerialNumber]:    1234567 | 
 | 146 |           ... | 
 | 147 |  | 
 | 148 |         Description of argument(s): | 
 | 149 |         args                        See parent class get() prolog for details. | 
 | 150 |         kwargs                      See parent class get() prolog for details. | 
 | 151 |         """ | 
 | 152 |  | 
 | 153 |         resp = self.get(*args, **kwargs) | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 154 |         return resp.dict if hasattr(resp, "dict") else {} | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 155 |  | 
 | 156 |     def get_attribute(self, path, attribute, default=None, *args, **kwargs): | 
 | 157 |         r""" | 
 | 158 |         Get and return the named attribute from the properties for a given | 
 | 159 |         path. | 
 | 160 |  | 
 | 161 |         This method has the following advantages over calling get_properties | 
 | 162 |         directly: | 
 | 163 |         - The caller can specify a default value to be returned if the | 
 | 164 |           attribute does not exist. | 
 | 165 |  | 
 | 166 |         Example robot code: | 
 | 167 |  | 
 | 168 |         ${attribute}=  Get Attribute  /redfish/v1/AccountService | 
 | 169 |         ...  MaxPasswordLength  default=600 | 
 | 170 |         Rprint Vars  attribute | 
 | 171 |  | 
 | 172 |         Output: | 
 | 173 |  | 
 | 174 |         attribute:           31 | 
 | 175 |  | 
 | 176 |         Description of argument(s): | 
 | 177 |         path                        The path (e.g. | 
 | 178 |                                     "/redfish/v1/AccountService"). | 
 | 179 |         attribute                   The name of the attribute to be retrieved | 
 | 180 |                                     (e.g. "MaxPasswordLength"). | 
 | 181 |         default                     The default value to be returned if the | 
 | 182 |                                     attribute does not exist (e.g. ""). | 
 | 183 |         args                        See parent class get() prolog for details. | 
 | 184 |         kwargs                      See parent class get() prolog for details. | 
 | 185 |         """ | 
 | 186 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 187 |         return self.get_properties(path, *args, **kwargs).get( | 
 | 188 |             attribute, default | 
 | 189 |         ) | 
| Michael Walsh | 5cc8919 | 2019-03-12 16:43:38 -0500 | [diff] [blame] | 190 |  | 
 | 191 |     def get_session_info(self): | 
 | 192 |         r""" | 
 | 193 |         Get and return session info as a tuple consisting of session_key and | 
 | 194 |         session_location. | 
 | 195 |         """ | 
 | 196 |  | 
 | 197 |         return self.get_session_key(), self.get_session_location() | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 198 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 199 |     def enumerate( | 
 | 200 |         self, resource_path, return_json=1, include_dead_resources=False | 
 | 201 |     ): | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 202 |         r""" | 
 | 203 |         Perform a GET enumerate request and return available resource paths. | 
 | 204 |  | 
 | 205 |         Description of argument(s): | 
 | 206 |         resource_path               URI resource absolute path (e.g. "/redfish/v1/SessionService/Sessions"). | 
 | 207 |         return_json                 Indicates whether the result should be returned as a json string or as a | 
 | 208 |                                     dictionary. | 
 | 209 |         include_dead_resources      Check and return a list of dead/broken URI resources. | 
 | 210 |         """ | 
 | 211 |  | 
 | 212 |         gp.qprint_executing(style=gp.func_line_style_short) | 
 | 213 |         # Set quiet variable to keep subordinate get() calls quiet. | 
 | 214 |         quiet = 1 | 
 | 215 |  | 
 | 216 |         self.__result = {} | 
 | 217 |         # Variable to hold the pending list of resources for which enumeration is yet to be obtained. | 
 | 218 |         self.__pending_enumeration = set() | 
 | 219 |         self.__pending_enumeration.add(resource_path) | 
 | 220 |  | 
 | 221 |         # Variable having resources for which enumeration is completed. | 
 | 222 |         enumerated_resources = set() | 
 | 223 |         dead_resources = {} | 
 | 224 |         resources_to_be_enumerated = (resource_path,) | 
 | 225 |         while resources_to_be_enumerated: | 
 | 226 |             for resource in resources_to_be_enumerated: | 
 | 227 |                 # JsonSchemas, SessionService or URLs containing # are not required in enumeration. | 
 | 228 |                 # Example: '/redfish/v1/JsonSchemas/' and sub resources. | 
 | 229 |                 #          '/redfish/v1/SessionService' | 
| ganesanb | 4d43028 | 2023-04-27 14:33:23 +0000 | [diff] [blame] | 230 |                 #          '/redfish/v1/Managers/${MANAGER_ID}#/Oem' | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 231 |                 if ( | 
 | 232 |                     ("JsonSchemas" in resource) | 
 | 233 |                     or ("SessionService" in resource) | 
 | 234 |                     or ("#" in resource) | 
 | 235 |                 ): | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 236 |                     continue | 
 | 237 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 238 |                 self._rest_response_ = self.get( | 
 | 239 |                     resource, valid_status_codes=[200, 404, 500] | 
 | 240 |                 ) | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 241 |                 # Enumeration is done for available resources ignoring the ones for which response is not | 
 | 242 |                 # obtained. | 
 | 243 |                 if self._rest_response_.status != 200: | 
 | 244 |                     if include_dead_resources: | 
 | 245 |                         try: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 246 |                             dead_resources[self._rest_response_.status].append( | 
 | 247 |                                 resource | 
 | 248 |                             ) | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 249 |                         except KeyError: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 250 |                             dead_resources[self._rest_response_.status] = [ | 
 | 251 |                                 resource | 
 | 252 |                             ] | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 253 |                     continue | 
 | 254 |                 self.walk_nested_dict(self._rest_response_.dict, url=resource) | 
 | 255 |  | 
 | 256 |             enumerated_resources.update(set(resources_to_be_enumerated)) | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 257 |             resources_to_be_enumerated = tuple( | 
 | 258 |                 self.__pending_enumeration - enumerated_resources | 
 | 259 |             ) | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 260 |  | 
 | 261 |         if return_json: | 
 | 262 |             if include_dead_resources: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 263 |                 return ( | 
 | 264 |                     json.dumps( | 
 | 265 |                         self.__result, | 
 | 266 |                         sort_keys=True, | 
 | 267 |                         indent=4, | 
 | 268 |                         separators=(",", ": "), | 
 | 269 |                     ), | 
 | 270 |                     dead_resources, | 
 | 271 |                 ) | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 272 |             else: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 273 |                 return json.dumps( | 
 | 274 |                     self.__result, | 
 | 275 |                     sort_keys=True, | 
 | 276 |                     indent=4, | 
 | 277 |                     separators=(",", ": "), | 
 | 278 |                 ) | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 279 |         else: | 
 | 280 |             if include_dead_resources: | 
 | 281 |                 return self.__result, dead_resources | 
 | 282 |             else: | 
 | 283 |                 return self.__result | 
 | 284 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 285 |     def walk_nested_dict(self, data, url=""): | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 286 |         r""" | 
 | 287 |         Parse through the nested dictionary and get the resource id paths. | 
 | 288 |  | 
 | 289 |         Description of argument(s): | 
 | 290 |         data                        Nested dictionary data from response message. | 
 | 291 |         url                         Resource for which the response is obtained in data. | 
 | 292 |         """ | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 293 |         url = url.rstrip("/") | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 294 |  | 
 | 295 |         for key, value in data.items(): | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 296 |             # Recursion if nested dictionary found. | 
 | 297 |             if isinstance(value, dict): | 
 | 298 |                 self.walk_nested_dict(value) | 
 | 299 |             else: | 
 | 300 |                 # Value contains a list of dictionaries having member data. | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 301 |                 if "Members" == key: | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 302 |                     if isinstance(value, list): | 
 | 303 |                         for memberDict in value: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 304 |                             self.__pending_enumeration.add( | 
 | 305 |                                 memberDict["@odata.id"] | 
 | 306 |                             ) | 
 | 307 |                 if "@odata.id" == key: | 
 | 308 |                     value = value.rstrip("/") | 
| Michael Walsh | 1a611fb | 2020-01-14 17:22:07 -0600 | [diff] [blame] | 309 |                     # Data for the given url. | 
 | 310 |                     if value == url: | 
 | 311 |                         self.__result[url] = data | 
 | 312 |                     # Data still needs to be looked up, | 
 | 313 |                     else: | 
 | 314 |                         self.__pending_enumeration.add(value) | 
| George Keishing | 46191a3 | 2021-06-10 13:58:43 -0500 | [diff] [blame] | 315 |  | 
 | 316 |     def get_members_list(self, resource_path, filter=None): | 
 | 317 |         r""" | 
 | 318 |         Return members list in a given URL. | 
 | 319 |  | 
 | 320 |         Description of argument(s): | 
 | 321 |         resource_path    URI resource absolute path (e.g. "/redfish/v1/AccountService/Accounts"). | 
 | 322 |         filter           strings or regex | 
 | 323 |  | 
 | 324 |         /redfish/v1/AccountService/Accounts/ | 
 | 325 |         { | 
 | 326 |             "@odata.id": "/redfish/v1/AccountService/Accounts", | 
 | 327 |             "@odata.type": "#ManagerAccountCollection.ManagerAccountCollection", | 
 | 328 |             "Description": "BMC User Accounts", | 
 | 329 |             "Members": [ | 
 | 330 |                 { | 
 | 331 |                     "@odata.id": "/redfish/v1/AccountService/Accounts/root" | 
 | 332 |                 }, | 
 | 333 |                 { | 
 | 334 |                     "@odata.id": "/redfish/v1/AccountService/Accounts/admin" | 
 | 335 |                 } | 
 | 336 |            ], | 
 | 337 |            "Members@odata.count": 2, | 
 | 338 |            "Name": "Accounts Collection" | 
 | 339 |         } | 
 | 340 |  | 
 | 341 |         Return list of members if no filter is applied as: | 
 | 342 |         ['/redfish/v1/AccountService/Accounts/root', "/redfish/v1/AccountService/Accounts/admin"] | 
 | 343 |  | 
 | 344 |         Return list of members if filter (e.g "root") is applied as: | 
 | 345 |         ['/redfish/v1/AccountService/Accounts/root'] | 
 | 346 |  | 
 | 347 |  | 
 | 348 |         Calling from robot code: | 
 | 349 |            ${resp}=  Redfish.Get Members List  /redfish/v1/AccountService/Accounts | 
 | 350 |            ${resp}=  Redfish.Get Members List  /redfish/v1/AccountService/Accounts  filter=root | 
 | 351 |         """ | 
 | 352 |  | 
 | 353 |         member_list = [] | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 354 |         self._rest_response_ = self.get( | 
 | 355 |             resource_path, valid_status_codes=[200] | 
 | 356 |         ) | 
| George Keishing | 46191a3 | 2021-06-10 13:58:43 -0500 | [diff] [blame] | 357 |  | 
 | 358 |         try: | 
 | 359 |             for member in self._rest_response_.dict["Members"]: | 
 | 360 |                 member_list.append(member["@odata.id"]) | 
 | 361 |         except KeyError: | 
 | 362 |             # Non Members child objects at the top level, ignore. | 
 | 363 |             pass | 
 | 364 |  | 
 | 365 |         # Filter elements in the list and return matched elements. | 
 | 366 |         if filter is not None: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 367 |             regex = ".*/" + filter + "[^/]*$" | 
| George Keishing | 46191a3 | 2021-06-10 13:58:43 -0500 | [diff] [blame] | 368 |             return [x for x in member_list if re.match(regex, x)] | 
 | 369 |  | 
 | 370 |         return member_list |