1#!/usr/bin/env python 2 3r""" 4Provide useful ipmi functions. 5""" 6 7import re 8import gen_print as gp 9import gen_misc as gm 10import gen_cmd as gc 11import gen_robot_keyword as grk 12import gen_robot_utils as gru 13import bmc_ssh_utils as bsu 14import var_funcs as vf 15import ipmi_client as ic 16import tempfile 17gru.my_import_resource("ipmi_client.robot") 18from robot.libraries.BuiltIn import BuiltIn 19 20 21def get_sol_info(): 22 r""" 23 Get all SOL info and return it as a dictionary. 24 25 Example use: 26 27 Robot code: 28 ${sol_info}= get_sol_info 29 Rpvars sol_info 30 31 Output: 32 sol_info: 33 sol_info[Info]: SOL parameter 'Payload Channel (7)' 34 not supported - defaulting to 0x0e 35 sol_info[Character Send Threshold]: 1 36 sol_info[Force Authentication]: true 37 sol_info[Privilege Level]: USER 38 sol_info[Set in progress]: set-complete 39 sol_info[Retry Interval (ms)]: 100 40 sol_info[Non-Volatile Bit Rate (kbps)]: IPMI-Over-Serial-Setting 41 sol_info[Character Accumulate Level (ms)]: 100 42 sol_info[Enabled]: true 43 sol_info[Volatile Bit Rate (kbps)]: IPMI-Over-Serial-Setting 44 sol_info[Payload Channel]: 14 (0x0e) 45 sol_info[Payload Port]: 623 46 sol_info[Force Encryption]: true 47 sol_info[Retry Count]: 7 48 """ 49 50 status, ret_values = grk.run_key_u("Run IPMI Standard Command sol info") 51 52 # Create temp file path. 53 temp = tempfile.NamedTemporaryFile() 54 temp_file_path = temp.name 55 56 # Write sol info to temp file path. 57 text_file = open(temp_file_path, "w") 58 text_file.write(ret_values) 59 text_file.close() 60 61 # Use my_parm_file to interpret data. 62 sol_info = gm.my_parm_file(temp_file_path) 63 64 return sol_info 65 66 67def set_sol_setting(setting_name, setting_value): 68 r""" 69 Set SOL setting with given value. 70 71 # Description of argument(s): 72 # setting_name SOL setting which needs to be set (e.g. 73 # "retry-count"). 74 # setting_value Value which needs to be set (e.g. "7"). 75 """ 76 77 status, ret_values = grk.run_key_u("Run IPMI Standard Command sol set " 78 + setting_name + " " + setting_value) 79 80 return status 81 82 83def execute_ipmi_cmd(cmd_string, 84 ipmi_cmd_type='inband', 85 print_output=1, 86 ignore_err=0, 87 **options): 88 r""" 89 Run the given command string as an IPMI command and return the stdout, 90 stderr and the return code. 91 92 Description of argument(s): 93 cmd_string The command string to be run as an IPMI 94 command. 95 ipmi_cmd_type 'inband' or 'external'. 96 print_output If this is set, this function will print 97 the stdout/stderr generated by 98 the IPMI command. 99 ignore_err Ignore error means that a failure 100 encountered by running the command 101 string will not be raised as a python 102 exception. 103 options These are passed directly to the 104 create_ipmi_ext_command_string function. 105 See that function's prolog for details. 106 """ 107 108 if ipmi_cmd_type == 'inband': 109 IPMI_INBAND_CMD = BuiltIn().get_variable_value("${IPMI_INBAND_CMD}") 110 cmd_buf = IPMI_INBAND_CMD + " " + cmd_string 111 return bsu.os_execute_command(cmd_buf, 112 print_out=print_output, 113 ignore_err=ignore_err) 114 115 if ipmi_cmd_type == 'external': 116 cmd_buf = ic.create_ipmi_ext_command_string(cmd_string, **options) 117 rc, stdout, stderr = gc.shell_cmd(cmd_buf, 118 print_output=print_output, 119 ignore_err=ignore_err, 120 return_stderr=1) 121 return stdout, stderr, rc 122 123 124def get_lan_print_dict(channel_number='', ipmi_cmd_type='external'): 125 r""" 126 Get IPMI 'lan print' output and return it as a dictionary. 127 128 Here is an example of the IPMI lan print output: 129 130 Set in Progress : Set Complete 131 Auth Type Support : MD5 132 Auth Type Enable : Callback : MD5 133 : User : MD5 134 : Operator : MD5 135 : Admin : MD5 136 : OEM : MD5 137 IP Address Source : Static Address 138 IP Address : x.x.x.x 139 Subnet Mask : x.x.x.x 140 MAC Address : xx:xx:xx:xx:xx:xx 141 Default Gateway IP : x.x.x.x 142 802.1q VLAN ID : Disabled 143 Cipher Suite Priv Max : Not Available 144 Bad Password Threshold : Not Available 145 146 Given that data, this function will return the following dictionary. 147 148 lan_print_dict: 149 [Set in Progress]: Set Complete 150 [Auth Type Support]: MD5 151 [Auth Type Enable]: 152 [Callback]: MD5 153 [User]: MD5 154 [Operator]: MD5 155 [Admin]: MD5 156 [OEM]: MD5 157 [IP Address Source]: Static Address 158 [IP Address]: x.x.x.x 159 [Subnet Mask]: x.x.x.x 160 [MAC Address]: xx:xx:xx:xx:xx:xx 161 [Default Gateway IP]: x.x.x.x 162 [802.1q VLAN ID]: Disabled 163 [Cipher Suite Priv Max]: Not Available 164 [Bad Password Threshold]: Not Available 165 166 Description of argument(s): 167 ipmi_cmd_type The type of ipmi command to use (e.g. 168 'inband', 'external'). 169 """ 170 171 channel_number = str(channel_number) 172 # Notice in the example of data above that 'Auth Type Enable' needs some 173 # special processing. We essentially want to isolate its data and remove 174 # the 'Auth Type Enable' string so that key_value_outbuf_to_dict can 175 # process it as a sub-dictionary. 176 cmd_buf = "lan print " + channel_number + " | grep -E '^(Auth Type Enable)" +\ 177 "?[ ]+: ' | sed -re 's/^(Auth Type Enable)?[ ]+: //g'" 178 stdout1, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type, 179 print_output=0) 180 181 # Now get the remainder of the data and exclude the lines with no field 182 # names (i.e. the 'Auth Type Enable' sub-fields). 183 cmd_buf = "lan print " + channel_number + " | grep -E -v '^[ ]+: '" 184 stdout2, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type, 185 print_output=0) 186 187 # Make auth_type_enable_dict sub-dictionary... 188 auth_type_enable_dict = vf.key_value_outbuf_to_dict(stdout1, to_lower=0, 189 underscores=0) 190 191 # Create the lan_print_dict... 192 lan_print_dict = vf.key_value_outbuf_to_dict(stdout2, to_lower=0, 193 underscores=0) 194 # Re-assign 'Auth Type Enable' to contain the auth_type_enable_dict. 195 lan_print_dict['Auth Type Enable'] = auth_type_enable_dict 196 197 return lan_print_dict 198 199 200def get_ipmi_power_reading(strip_watts=1): 201 r""" 202 Get IPMI power reading data and return it as a dictionary. 203 204 The data is obtained by issuing the IPMI "power reading" command. An 205 example is shown below: 206 207 Instantaneous power reading: 234 Watts 208 Minimum during sampling period: 234 Watts 209 Maximum during sampling period: 234 Watts 210 Average power reading over sample period: 234 Watts 211 IPMI timestamp: Thu Jan 1 00:00:00 1970 212 Sampling period: 00000000 Seconds. 213 Power reading state is: deactivated 214 215 For the data shown above, the following dictionary will be returned. 216 217 result: 218 [instantaneous_power_reading]: 238 Watts 219 [minimum_during_sampling_period]: 238 Watts 220 [maximum_during_sampling_period]: 238 Watts 221 [average_power_reading_over_sample_period]: 238 Watts 222 [ipmi_timestamp]: Thu Jan 1 00:00:00 1970 223 [sampling_period]: 00000000 Seconds. 224 [power_reading_state_is]: deactivated 225 226 Description of argument(s): 227 strip_watts Strip all dictionary values of the 228 trailing " Watts" substring. 229 """ 230 231 status, ret_values = \ 232 grk.run_key_u("Run IPMI Standard Command dcmi power reading") 233 result = vf.key_value_outbuf_to_dict(ret_values) 234 235 if strip_watts: 236 result.update((k, re.sub(' Watts$', '', v)) for k, v in result.items()) 237 238 return result 239 240 241def get_mc_info(): 242 r""" 243 Get IPMI mc info data and return it as a dictionary. 244 245 The data is obtained by issuing the IPMI "mc info" command. An 246 example is shown below: 247 248 Device ID : 0 249 Device Revision : 0 250 Firmware Revision : 2.01 251 IPMI Version : 2.0 252 Manufacturer ID : 42817 253 Manufacturer Name : Unknown (0xA741) 254 Product ID : 16975 (0x424f) 255 Product Name : Unknown (0x424F) 256 Device Available : yes 257 Provides Device SDRs : yes 258 Additional Device Support : 259 Sensor Device 260 SEL Device 261 FRU Inventory Device 262 Chassis Device 263 Aux Firmware Rev Info : 264 0x00 265 0x00 266 0x00 267 0x00 268 269 For the data shown above, the following dictionary will be returned. 270 mc_info: 271 [device_id]: 0 272 [device_revision]: 0 273 [firmware_revision]: 2.01 274 [ipmi_version]: 2.0 275 [manufacturer_id]: 42817 276 [manufacturer_name]: Unknown (0xA741) 277 [product_id]: 16975 (0x424f) 278 [product_name]: Unknown (0x424F) 279 [device_available]: yes 280 [provides_device_sdrs]: yes 281 [additional_device_support]: 282 [additional_device_support][0]: Sensor Device 283 [additional_device_support][1]: SEL Device 284 [additional_device_support][2]: FRU Inventory Device 285 [additional_device_support][3]: Chassis Device 286 [aux_firmware_rev_info]: 287 [aux_firmware_rev_info][0]: 0x00 288 [aux_firmware_rev_info][1]: 0x00 289 [aux_firmware_rev_info][2]: 0x00 290 [aux_firmware_rev_info][3]: 0x00 291 """ 292 293 status, ret_values = \ 294 grk.run_key_u("Run IPMI Standard Command mc info") 295 result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 296 297 return result 298 299 300def get_sdr_info(): 301 r""" 302 Get IPMI sdr info data and return it as a dictionary. 303 304 The data is obtained by issuing the IPMI "sdr info" command. An 305 example is shown below: 306 307 SDR Version : 0x51 308 Record Count : 216 309 Free Space : unspecified 310 Most recent Addition : 311 Most recent Erase : 312 SDR overflow : no 313 SDR Repository Update Support : unspecified 314 Delete SDR supported : no 315 Partial Add SDR supported : no 316 Reserve SDR repository supported : no 317 SDR Repository Alloc info supported : no 318 319 For the data shown above, the following dictionary will be returned. 320 mc_info: 321 322 [sdr_version]: 0x51 323 [record_Count]: 216 324 [free_space]: unspecified 325 [most_recent_addition]: 326 [most_recent_erase]: 327 [sdr_overflow]: no 328 [sdr_repository_update_support]: unspecified 329 [delete_sdr_supported]: no 330 [partial_add_sdr_supported]: no 331 [reserve_sdr_repository_supported]: no 332 [sdr_repository_alloc_info_supported]: no 333 """ 334 335 status, ret_values = \ 336 grk.run_key_u("Run IPMI Standard Command sdr info") 337 result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 338 339 return result 340 341 342def get_aux_version(version_id): 343 r""" 344 Get IPMI Aux version info data and return it. 345 346 Description of argument(s): 347 version_id The data is obtained by from BMC 348 /etc/os-release 349 (e.g. "xxx-v2.1-438-g0030304-r3-gfea8585"). 350 351 In the prior example, the 3rd field is "438" is the commit version and 352 the 5th field is "r3" and value "3" is the release version. 353 354 Aux version return from this function 4380003. 355 """ 356 357 # Commit version. 358 count = re.findall("-(\\d{1,4})-", version_id) 359 360 # Release version. 361 release = re.findall("-r(\\d{1,4})", version_id) 362 if release: 363 aux_version = count[0] + "{0:0>4}".format(release[0]) 364 else: 365 aux_version = count[0] + "0000" 366 367 return aux_version 368 369 370def get_fru_info(): 371 r""" 372 Get fru info and return it as a list of dictionaries. 373 374 The data is obtained by issuing the IPMI "fru print -N 50" command. An 375 example is shown below: 376 377 FRU Device Description : Builtin FRU Device (ID 0) 378 Device not present (Unspecified error) 379 380 FRU Device Description : cpu0 (ID 1) 381 Board Mfg Date : Sun Dec 31 18:00:00 1995 382 Board Mfg : <Manufacturer Name> 383 Board Product : PROCESSOR MODULE 384 Board Serial : YA1934315964 385 Board Part Number : 02CY209 386 387 FRU Device Description : cpu1 (ID 2) 388 Board Mfg Date : Sun Dec 31 18:00:00 1995 389 Board Mfg : <Manufacturer Name> 390 Board Product : PROCESSOR MODULE 391 Board Serial : YA1934315965 392 Board Part Number : 02CY209 393 394 For the data shown above, the following list of dictionaries will be 395 returned. 396 397 fru_obj: 398 fru_obj[0]: 399 [fru_device_description]: Builtin FRU Device (ID 0) 400 [state]: Device not present (Unspecified error) 401 fru_obj[1]: 402 [fru_device_description]: cpu0 (ID 1) 403 [board_mfg_date]: Sun Dec 31 18:00:00 1995 404 [board_mfg]: <Manufacturer Name> 405 [board_product]: PROCESSOR MODULE 406 [board_serial]: YA1934315964 407 [board_part_number]: 02CY209 408 fru_obj[2]: 409 [fru_device_description]: cpu1 (ID 2) 410 [board_mfg_date]: Sun Dec 31 18:00:00 1995 411 [board_mfg]: <Manufacturer Name> 412 [board_product]: PROCESSOR MODULE 413 [board_serial]: YA1934315965 414 [board_part_number]: 02CY209 415 """ 416 417 status, ret_values = \ 418 grk.run_key_u("Run IPMI Standard Command fru print -N 50") 419 420 # Manipulate the "Device not present" line to create a "state" key. 421 ret_values = re.sub("Device not present", "state : Device not present", 422 ret_values) 423 424 return [vf.key_value_outbuf_to_dict(x) for x in re.split("\n\n", 425 ret_values)] 426 427 428def get_component_fru_info(component='cpu', 429 fru_objs=None): 430 r""" 431 Get fru info for the given component and return it as a list of 432 dictionaries. 433 434 This function calls upon get_fru_info and then filters out the unwanted 435 entries. See get_fru_info's prolog for a layout of the data. 436 437 Description of argument(s): 438 component The component (e.g. "cpu", "dimm", etc.). 439 fru_objs A fru_objs list such as the one returned 440 by get_fru_info. If this is None, then 441 this function will call get_fru_info to 442 obtain such a list. 443 Supplying this argument may improve 444 performance if this function is to be 445 called multiple times. 446 """ 447 448 if fru_objs is None: 449 fru_objs = get_fru_info() 450 return\ 451 [x for x in fru_objs 452 if re.match(component + '([0-9]+)? ', x['fru_device_description'])] 453 454 455def get_user_info(userid, channel_number=1): 456 r""" 457 Get user info using channel command and return it as a dictionary. 458 459 Description of argument(s): 460 userid The userid (e.g. "1", "2", etc.). 461 channel_number The user's channel number (e.g. "1"). 462 463 Note: If userid is blank, this function will return a list of dictionaries. Each list entry represents 464 one userid record. 465 466 The data is obtained by issuing the IPMI "channel getaccess" command. An 467 example is shown below for user id 1 and channel number 1. 468 469 Maximum User IDs : 15 470 Enabled User IDs : 1 471 User ID : 1 472 User Name : root 473 Fixed Name : No 474 Access Available : callback 475 Link Authentication : enabled 476 IPMI Messaging : enabled 477 Privilege Level : ADMINISTRATOR 478 Enable Status : enabled 479 480 For the data shown above, the following dictionary will be returned. 481 482 user_info: 483 [maximum_userids]: 15 484 [enabled_userids: 1 485 [userid] 1 486 [user_name] root 487 [fixed_name] No 488 [access_available] callback 489 [link_authentication] enabled 490 [ipmi_messaging] enabled 491 [privilege_level] ADMINISTRATOR 492 [enable_status] enabled 493 """ 494 495 status, ret_values = grk.run_key_u("Run IPMI Standard Command channel getaccess " 496 + str(channel_number) + " " + str(userid)) 497 498 if userid == "": 499 return vf.key_value_outbuf_to_dicts(ret_values, process_indent=1) 500 else: 501 return vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 502 503 504def channel_getciphers_ipmi(): 505 506 r""" 507 Run 'channel getciphers ipmi' command and return the result as a list of dictionaries. 508 509 Example robot code: 510 ${ipmi_channel_ciphers}= Channel Getciphers IPMI 511 Rprint Vars ipmi_channel_ciphers 512 513 Example output: 514 ipmi_channel_ciphers: 515 [0]: 516 [id]: 3 517 [iana]: N/A 518 [auth_alg]: hmac_sha1 519 [integrity_alg]: hmac_sha1_96 520 [confidentiality_alg]: aes_cbc_128 521 [1]: 522 [id]: 17 523 [iana]: N/A 524 [auth_alg]: hmac_sha256 525 [integrity_alg]: sha256_128 526 [confidentiality_alg]: aes_cbc_128 527 """ 528 529 cmd_buf = "channel getciphers ipmi | sed -re 's/ Alg/_Alg/g'" 530 stdout, stderr, rc = execute_ipmi_cmd(cmd_buf, "external", print_output=0) 531 return vf.outbuf_to_report(stdout) 532