1#!/usr/bin/env python3 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 19import json 20 21 22def get_sol_info(): 23 r""" 24 Get all SOL info and return it as a dictionary. 25 26 Example use: 27 28 Robot code: 29 ${sol_info}= get_sol_info 30 Rpvars sol_info 31 32 Output: 33 sol_info: 34 sol_info[Info]: SOL parameter 'Payload Channel (7)' 35 not supported - defaulting to 0x0e 36 sol_info[Character Send Threshold]: 1 37 sol_info[Force Authentication]: true 38 sol_info[Privilege Level]: USER 39 sol_info[Set in progress]: set-complete 40 sol_info[Retry Interval (ms)]: 100 41 sol_info[Non-Volatile Bit Rate (kbps)]: IPMI-Over-Serial-Setting 42 sol_info[Character Accumulate Level (ms)]: 100 43 sol_info[Enabled]: true 44 sol_info[Volatile Bit Rate (kbps)]: IPMI-Over-Serial-Setting 45 sol_info[Payload Channel]: 14 (0x0e) 46 sol_info[Payload Port]: 623 47 sol_info[Force Encryption]: true 48 sol_info[Retry Count]: 7 49 """ 50 51 status, ret_values = grk.run_key_u("Run IPMI Standard Command sol info") 52 53 # Create temp file path. 54 temp = tempfile.NamedTemporaryFile() 55 temp_file_path = temp.name 56 57 # Write sol info to temp file path. 58 text_file = open(temp_file_path, "w") 59 text_file.write(ret_values) 60 text_file.close() 61 62 # Use my_parm_file to interpret data. 63 sol_info = gm.my_parm_file(temp_file_path) 64 65 return sol_info 66 67 68def set_sol_setting(setting_name, setting_value): 69 r""" 70 Set SOL setting with given value. 71 72 # Description of argument(s): 73 # setting_name SOL setting which needs to be set (e.g. 74 # "retry-count"). 75 # setting_value Value which needs to be set (e.g. "7"). 76 """ 77 78 status, ret_values = grk.run_key_u("Run IPMI Standard Command sol set " 79 + setting_name + " " + setting_value) 80 81 return status 82 83 84def execute_ipmi_cmd(cmd_string, 85 ipmi_cmd_type='inband', 86 print_output=1, 87 ignore_err=0, 88 **options): 89 r""" 90 Run the given command string as an IPMI command and return the stdout, 91 stderr and the return code. 92 93 Description of argument(s): 94 cmd_string The command string to be run as an IPMI 95 command. 96 ipmi_cmd_type 'inband' or 'external'. 97 print_output If this is set, this function will print 98 the stdout/stderr generated by 99 the IPMI command. 100 ignore_err Ignore error means that a failure 101 encountered by running the command 102 string will not be raised as a python 103 exception. 104 options These are passed directly to the 105 create_ipmi_ext_command_string function. 106 See that function's prolog for details. 107 """ 108 109 if ipmi_cmd_type == 'inband': 110 IPMI_INBAND_CMD = BuiltIn().get_variable_value("${IPMI_INBAND_CMD}") 111 cmd_buf = IPMI_INBAND_CMD + " " + cmd_string 112 return bsu.os_execute_command(cmd_buf, 113 print_out=print_output, 114 ignore_err=ignore_err) 115 116 if ipmi_cmd_type == 'external': 117 cmd_buf = ic.create_ipmi_ext_command_string(cmd_string, **options) 118 rc, stdout, stderr = gc.shell_cmd(cmd_buf, 119 print_output=print_output, 120 ignore_err=ignore_err, 121 return_stderr=1) 122 return stdout, stderr, rc 123 124 125def get_lan_print_dict(channel_number='', ipmi_cmd_type='external'): 126 r""" 127 Get IPMI 'lan print' output and return it as a dictionary. 128 129 Here is an example of the IPMI lan print output: 130 131 Set in Progress : Set Complete 132 Auth Type Support : MD5 133 Auth Type Enable : Callback : MD5 134 : User : MD5 135 : Operator : MD5 136 : Admin : MD5 137 : OEM : MD5 138 IP Address Source : Static Address 139 IP Address : x.x.x.x 140 Subnet Mask : x.x.x.x 141 MAC Address : xx:xx:xx:xx:xx:xx 142 Default Gateway IP : x.x.x.x 143 802.1q VLAN ID : Disabled 144 Cipher Suite Priv Max : Not Available 145 Bad Password Threshold : Not Available 146 147 Given that data, this function will return the following dictionary. 148 149 lan_print_dict: 150 [Set in Progress]: Set Complete 151 [Auth Type Support]: MD5 152 [Auth Type Enable]: 153 [Callback]: MD5 154 [User]: MD5 155 [Operator]: MD5 156 [Admin]: MD5 157 [OEM]: MD5 158 [IP Address Source]: Static Address 159 [IP Address]: x.x.x.x 160 [Subnet Mask]: x.x.x.x 161 [MAC Address]: xx:xx:xx:xx:xx:xx 162 [Default Gateway IP]: x.x.x.x 163 [802.1q VLAN ID]: Disabled 164 [Cipher Suite Priv Max]: Not Available 165 [Bad Password Threshold]: Not Available 166 167 Description of argument(s): 168 ipmi_cmd_type The type of ipmi command to use (e.g. 169 'inband', 'external'). 170 """ 171 172 channel_number = str(channel_number) 173 # Notice in the example of data above that 'Auth Type Enable' needs some 174 # special processing. We essentially want to isolate its data and remove 175 # the 'Auth Type Enable' string so that key_value_outbuf_to_dict can 176 # process it as a sub-dictionary. 177 cmd_buf = "lan print " + channel_number + " | grep -E '^(Auth Type Enable)" +\ 178 "?[ ]+: ' | sed -re 's/^(Auth Type Enable)?[ ]+: //g'" 179 stdout1, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type, 180 print_output=0) 181 182 # Now get the remainder of the data and exclude the lines with no field 183 # names (i.e. the 'Auth Type Enable' sub-fields). 184 cmd_buf = "lan print " + channel_number + " | grep -E -v '^[ ]+: '" 185 stdout2, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type, 186 print_output=0) 187 188 # Make auth_type_enable_dict sub-dictionary... 189 auth_type_enable_dict = vf.key_value_outbuf_to_dict(stdout1, to_lower=0, 190 underscores=0) 191 192 # Create the lan_print_dict... 193 lan_print_dict = vf.key_value_outbuf_to_dict(stdout2, to_lower=0, 194 underscores=0) 195 # Re-assign 'Auth Type Enable' to contain the auth_type_enable_dict. 196 lan_print_dict['Auth Type Enable'] = auth_type_enable_dict 197 198 return lan_print_dict 199 200 201def get_ipmi_power_reading(strip_watts=1): 202 r""" 203 Get IPMI power reading data and return it as a dictionary. 204 205 The data is obtained by issuing the IPMI "power reading" command. An 206 example is shown below: 207 208 Instantaneous power reading: 234 Watts 209 Minimum during sampling period: 234 Watts 210 Maximum during sampling period: 234 Watts 211 Average power reading over sample period: 234 Watts 212 IPMI timestamp: Thu Jan 1 00:00:00 1970 213 Sampling period: 00000000 Seconds. 214 Power reading state is: deactivated 215 216 For the data shown above, the following dictionary will be returned. 217 218 result: 219 [instantaneous_power_reading]: 238 Watts 220 [minimum_during_sampling_period]: 238 Watts 221 [maximum_during_sampling_period]: 238 Watts 222 [average_power_reading_over_sample_period]: 238 Watts 223 [ipmi_timestamp]: Thu Jan 1 00:00:00 1970 224 [sampling_period]: 00000000 Seconds. 225 [power_reading_state_is]: deactivated 226 227 Description of argument(s): 228 strip_watts Strip all dictionary values of the 229 trailing " Watts" substring. 230 """ 231 232 status, ret_values = \ 233 grk.run_key_u("Run IPMI Standard Command dcmi power reading") 234 result = vf.key_value_outbuf_to_dict(ret_values) 235 236 if strip_watts: 237 result.update((k, re.sub(' Watts$', '', v)) for k, v in result.items()) 238 239 return result 240 241 242def get_mc_info(): 243 r""" 244 Get IPMI mc info data and return it as a dictionary. 245 246 The data is obtained by issuing the IPMI "mc info" command. An 247 example is shown below: 248 249 Device ID : 0 250 Device Revision : 0 251 Firmware Revision : 2.01 252 IPMI Version : 2.0 253 Manufacturer ID : 42817 254 Manufacturer Name : Unknown (0xA741) 255 Product ID : 16975 (0x424f) 256 Product Name : Unknown (0x424F) 257 Device Available : yes 258 Provides Device SDRs : yes 259 Additional Device Support : 260 Sensor Device 261 SEL Device 262 FRU Inventory Device 263 Chassis Device 264 Aux Firmware Rev Info : 265 0x00 266 0x00 267 0x00 268 0x00 269 270 For the data shown above, the following dictionary will be returned. 271 mc_info: 272 [device_id]: 0 273 [device_revision]: 0 274 [firmware_revision]: 2.01 275 [ipmi_version]: 2.0 276 [manufacturer_id]: 42817 277 [manufacturer_name]: Unknown (0xA741) 278 [product_id]: 16975 (0x424f) 279 [product_name]: Unknown (0x424F) 280 [device_available]: yes 281 [provides_device_sdrs]: yes 282 [additional_device_support]: 283 [additional_device_support][0]: Sensor Device 284 [additional_device_support][1]: SEL Device 285 [additional_device_support][2]: FRU Inventory Device 286 [additional_device_support][3]: Chassis Device 287 [aux_firmware_rev_info]: 288 [aux_firmware_rev_info][0]: 0x00 289 [aux_firmware_rev_info][1]: 0x00 290 [aux_firmware_rev_info][2]: 0x00 291 [aux_firmware_rev_info][3]: 0x00 292 """ 293 294 status, ret_values = \ 295 grk.run_key_u("Run IPMI Standard Command mc info") 296 result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 297 298 return result 299 300 301def get_sdr_info(): 302 r""" 303 Get IPMI sdr info data and return it as a dictionary. 304 305 The data is obtained by issuing the IPMI "sdr info" command. An 306 example is shown below: 307 308 SDR Version : 0x51 309 Record Count : 216 310 Free Space : unspecified 311 Most recent Addition : 312 Most recent Erase : 313 SDR overflow : no 314 SDR Repository Update Support : unspecified 315 Delete SDR supported : no 316 Partial Add SDR supported : no 317 Reserve SDR repository supported : no 318 SDR Repository Alloc info supported : no 319 320 For the data shown above, the following dictionary will be returned. 321 mc_info: 322 323 [sdr_version]: 0x51 324 [record_Count]: 216 325 [free_space]: unspecified 326 [most_recent_addition]: 327 [most_recent_erase]: 328 [sdr_overflow]: no 329 [sdr_repository_update_support]: unspecified 330 [delete_sdr_supported]: no 331 [partial_add_sdr_supported]: no 332 [reserve_sdr_repository_supported]: no 333 [sdr_repository_alloc_info_supported]: no 334 """ 335 336 status, ret_values = \ 337 grk.run_key_u("Run IPMI Standard Command sdr info") 338 result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 339 340 return result 341 342 343def get_aux_version(version_id): 344 r""" 345 Get IPMI Aux version info data and return it. 346 347 Description of argument(s): 348 version_id The data is obtained by from BMC 349 /etc/os-release 350 (e.g. "xxx-v2.1-438-g0030304-r3-gfea8585"). 351 352 In the prior example, the 3rd field is "438" is the commit version and 353 the 5th field is "r3" and value "3" is the release version. 354 355 Aux version return from this function 4380003. 356 """ 357 358 # Commit version. 359 count = re.findall("-(\\d{1,4})-", version_id) 360 361 # Release version. 362 release = re.findall("-r(\\d{1,4})", version_id) 363 if release: 364 aux_version = count[0] + "{0:0>4}".format(release[0]) 365 else: 366 aux_version = count[0] + "0000" 367 368 return aux_version 369 370 371def get_fru_info(): 372 r""" 373 Get fru info and return it as a list of dictionaries. 374 375 The data is obtained by issuing the IPMI "fru print -N 50" command. An 376 example is shown below: 377 378 FRU Device Description : Builtin FRU Device (ID 0) 379 Device not present (Unspecified error) 380 381 FRU Device Description : cpu0 (ID 1) 382 Board Mfg Date : Sun Dec 31 18:00:00 1995 383 Board Mfg : <Manufacturer Name> 384 Board Product : PROCESSOR MODULE 385 Board Serial : YA1934315964 386 Board Part Number : 02CY209 387 388 FRU Device Description : cpu1 (ID 2) 389 Board Mfg Date : Sun Dec 31 18:00:00 1995 390 Board Mfg : <Manufacturer Name> 391 Board Product : PROCESSOR MODULE 392 Board Serial : YA1934315965 393 Board Part Number : 02CY209 394 395 For the data shown above, the following list of dictionaries will be 396 returned. 397 398 fru_obj: 399 fru_obj[0]: 400 [fru_device_description]: Builtin FRU Device (ID 0) 401 [state]: Device not present (Unspecified error) 402 fru_obj[1]: 403 [fru_device_description]: cpu0 (ID 1) 404 [board_mfg_date]: Sun Dec 31 18:00:00 1995 405 [board_mfg]: <Manufacturer Name> 406 [board_product]: PROCESSOR MODULE 407 [board_serial]: YA1934315964 408 [board_part_number]: 02CY209 409 fru_obj[2]: 410 [fru_device_description]: cpu1 (ID 2) 411 [board_mfg_date]: Sun Dec 31 18:00:00 1995 412 [board_mfg]: <Manufacturer Name> 413 [board_product]: PROCESSOR MODULE 414 [board_serial]: YA1934315965 415 [board_part_number]: 02CY209 416 """ 417 418 status, ret_values = \ 419 grk.run_key_u("Run IPMI Standard Command fru print -N 50") 420 421 # Manipulate the "Device not present" line to create a "state" key. 422 ret_values = re.sub("Device not present", "state : Device not present", 423 ret_values) 424 425 return [vf.key_value_outbuf_to_dict(x) for x in re.split("\n\n", 426 ret_values)] 427 428 429def get_component_fru_info(component='cpu', 430 fru_objs=None): 431 r""" 432 Get fru info for the given component and return it as a list of 433 dictionaries. 434 435 This function calls upon get_fru_info and then filters out the unwanted 436 entries. See get_fru_info's prolog for a layout of the data. 437 438 Description of argument(s): 439 component The component (e.g. "cpu", "dimm", etc.). 440 fru_objs A fru_objs list such as the one returned 441 by get_fru_info. If this is None, then 442 this function will call get_fru_info to 443 obtain such a list. 444 Supplying this argument may improve 445 performance if this function is to be 446 called multiple times. 447 """ 448 449 if fru_objs is None: 450 fru_objs = get_fru_info() 451 return\ 452 [x for x in fru_objs 453 if re.match(component + '([0-9]+)? ', x['fru_device_description'])] 454 455 456def get_user_info(userid, channel_number=1): 457 r""" 458 Get user info using channel command and return it as a dictionary. 459 460 Description of argument(s): 461 userid The userid (e.g. "1", "2", etc.). 462 channel_number The user's channel number (e.g. "1"). 463 464 Note: If userid is blank, this function will return a list of dictionaries. Each list entry represents 465 one userid record. 466 467 The data is obtained by issuing the IPMI "channel getaccess" command. An 468 example is shown below for user id 1 and channel number 1. 469 470 Maximum User IDs : 15 471 Enabled User IDs : 1 472 User ID : 1 473 User Name : root 474 Fixed Name : No 475 Access Available : callback 476 Link Authentication : enabled 477 IPMI Messaging : enabled 478 Privilege Level : ADMINISTRATOR 479 Enable Status : enabled 480 481 For the data shown above, the following dictionary will be returned. 482 483 user_info: 484 [maximum_userids]: 15 485 [enabled_userids: 1 486 [userid] 1 487 [user_name] root 488 [fixed_name] No 489 [access_available] callback 490 [link_authentication] enabled 491 [ipmi_messaging] enabled 492 [privilege_level] ADMINISTRATOR 493 [enable_status] enabled 494 """ 495 496 status, ret_values = grk.run_key_u("Run IPMI Standard Command channel getaccess " 497 + str(channel_number) + " " + str(userid)) 498 499 if userid == "": 500 return vf.key_value_outbuf_to_dicts(ret_values, process_indent=1) 501 else: 502 return vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 503 504 505def channel_getciphers_ipmi(): 506 507 r""" 508 Run 'channel getciphers ipmi' command and return the result as a list of dictionaries. 509 510 Example robot code: 511 ${ipmi_channel_ciphers}= Channel Getciphers IPMI 512 Rprint Vars ipmi_channel_ciphers 513 514 Example output: 515 ipmi_channel_ciphers: 516 [0]: 517 [id]: 3 518 [iana]: N/A 519 [auth_alg]: hmac_sha1 520 [integrity_alg]: hmac_sha1_96 521 [confidentiality_alg]: aes_cbc_128 522 [1]: 523 [id]: 17 524 [iana]: N/A 525 [auth_alg]: hmac_sha256 526 [integrity_alg]: sha256_128 527 [confidentiality_alg]: aes_cbc_128 528 """ 529 530 cmd_buf = "channel getciphers ipmi | sed -re 's/ Alg/_Alg/g'" 531 stdout, stderr, rc = execute_ipmi_cmd(cmd_buf, "external", print_output=0) 532 return vf.outbuf_to_report(stdout) 533 534 535def get_device_id_config(): 536 r""" 537 Get the device id config data and return as a dictionary. 538 539 Example: 540 541 dev_id_config = get_device_id_config() 542 print_vars(dev_id_config) 543 544 dev_id_config: 545 [manuf_id]: 7244 546 [addn_dev_support]: 141 547 [prod_id]: 16976 548 [aux]: 0 549 [id]: 32 550 [revision]: 129 551 [device_revision]: 1 552 """ 553 stdout, stderr, rc = bsu.bmc_execute_command("cat /usr/share/ipmi-providers/dev_id.json") 554 555 result = json.loads(stdout) 556 557 # Create device revision field for the user. 558 # Reference IPMI specification v2.0 "Get Device ID Command" 559 # [7] 1 = device provides Device SDRs 560 # 0 = device does not provide Device SDRs 561 # [6:4] reserved. Return as 0. 562 # [3:0] Device Revision, binary encoded. 563 564 result['device_revision'] = result['revision'] & 0x0F 565 566 return result 567 568 569def get_chassis_status(): 570 r""" 571 Get IPMI chassis status data and return it as a dictionary. 572 573 The data is obtained by issuing the IPMI "chassis status" command. An 574 example is shown below: 575 576 System Power : off 577 Power Overload : false 578 Power Interlock : inactive 579 Main Power Fault : false 580 Power Control Fault : false 581 Power Restore Policy : previous 582 Last Power Event : 583 Chassis Intrusion : inactive 584 Front-Panel Lockout : inactive 585 Drive Fault : false 586 Cooling/Fan Fault : false 587 Sleep Button Disable : not allowed 588 Diag Button Disable : not allowed 589 Reset Button Disable : not allowed 590 Power Button Disable : allowed 591 Sleep Button Disabled : false 592 Diag Button Disabled : false 593 Reset Button Disabled : false 594 Power Button Disabled : false 595 596 For the data shown above, the following dictionary will be returned. 597 598 chassis_status: 599 [system_power]: off 600 [power_overload]: false 601 [power_interlock]: inactive 602 [main_power_fault]: false 603 [power_control_fault]: false 604 [power_restore_policy]: previous 605 [last_power_event]: 606 [chassis_intrusion]: inactive 607 [front-panel_lockout]: inactive 608 [drive_fault]: false 609 [cooling/fan_fault]: false 610 [sleep_button_disable]: not allowed 611 [diag_button_disable]: not allowed 612 [reset_button_disable]: not allowed 613 [power_button_disable]: allowed 614 [sleep_button_disabled]: false 615 [diag_button_disabled]: false 616 [reset_button_disabled]: false 617 [power_button_disabled]: false 618 """ 619 620 status, ret_values = \ 621 grk.run_key_u("Run IPMI Standard Command chassis status") 622 result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 623 624 return result 625 626 627def get_channel_info(channel_number=1): 628 r""" 629 Get the channel info and return as a dictionary. 630 Example: 631 632 channel_info: 633 [channel_0x2_info]: 634 [channel_medium_type]: 802.3 LAN 635 [channel_protocol_type]: IPMB-1.0 636 [session_support]: multi-session 637 [active_session_count]: 0 638 [protocol_vendor_id]: 7154 639 [volatile(active)_settings]: 640 [alerting]: enabled 641 [per-message_auth]: enabled 642 [user_level_auth]: enabled 643 [access_mode]: always available 644 [non-volatile_settings]: 645 [alerting]: enabled 646 [per-message_auth]: enabled 647 [user_level_auth]: enabled 648 [access_mode]: always available 649 """ 650 651 status, ret_values = \ 652 grk.run_key_u("Run IPMI Standard Command channel info " + str(channel_number)) 653 key_var_list = list(filter(None, ret_values.split("\n"))) 654 # To match the dict format, add a colon after 'Volatile(active) Settings' and 'Non-Volatile Settings' 655 # respectively. 656 key_var_list[6] = 'Volatile(active) Settings:' 657 key_var_list[11] = 'Non-Volatile Settings:' 658 result = vf.key_value_list_to_dict(key_var_list, process_indent=1) 659 return result 660 661 662def get_user_access_ipmi(channel_number=1): 663 664 r""" 665 Run 'user list [<channel number>]' command and return the result as a list of dictionaries. 666 667 Example robot code: 668 ${users_access}= user list 1 669 Rprint Vars users_access 670 671 Example output: 672 users: 673 [0]: 674 [id]: 1 675 [name]: root 676 [callin]: false 677 [link]: true 678 [auth]: true 679 [ipmi]: ADMINISTRATOR 680 [1]: 681 [id]: 2 682 [name]: axzIDwnz 683 [callin]: true 684 [link]: false 685 [auth]: true 686 [ipmi]: ADMINISTRATOR 687 """ 688 689 cmd_buf = "user list " + str(channel_number) 690 stdout, stderr, rc = execute_ipmi_cmd(cmd_buf, "external", print_output=0) 691 return vf.outbuf_to_report(stdout) 692 693 694def get_channel_auth_capabilities(channel_number=1): 695 r""" 696 Get the channel authentication capabilities and return as a dictionary. 697 698 Example: 699 700 channel_auth_cap: 701 [channel_number]: 2 702 [ipmi_v1.5__auth_types]: 703 [kg_status]: default (all zeroes) 704 [per_message_authentication]: enabled 705 [user_level_authentication]: enabled 706 [non-null_user_names_exist]: yes 707 [null_user_names_exist]: no 708 [anonymous_login_enabled]: no 709 [channel_supports_ipmi_v1.5]: no 710 [channel_supports_ipmi_v2.0]: yes 711 """ 712 713 status, ret_values = \ 714 grk.run_key_u("Run IPMI Standard Command channel authcap " + str(channel_number) + " 4") 715 result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) 716 717 return result 718