|  | #!/usr/bin/env python | 
|  |  | 
|  | r""" | 
|  | Provide useful ipmi functions. | 
|  | """ | 
|  |  | 
|  | import re | 
|  | import gen_print as gp | 
|  | import gen_misc as gm | 
|  | import gen_cmd as gc | 
|  | import gen_robot_keyword as grk | 
|  | import gen_robot_utils as gru | 
|  | import bmc_ssh_utils as bsu | 
|  | import var_funcs as vf | 
|  | import ipmi_client as ic | 
|  | import tempfile | 
|  | gru.my_import_resource("ipmi_client.robot") | 
|  | from robot.libraries.BuiltIn import BuiltIn | 
|  |  | 
|  |  | 
|  | def get_sol_info(): | 
|  | r""" | 
|  | Get all SOL info and return it as a dictionary. | 
|  |  | 
|  | Example use: | 
|  |  | 
|  | Robot code: | 
|  | ${sol_info}=  get_sol_info | 
|  | Rpvars  sol_info | 
|  |  | 
|  | Output: | 
|  | sol_info: | 
|  | sol_info[Info]:                                SOL parameter 'Payload Channel (7)' | 
|  | not supported - defaulting to 0x0e | 
|  | sol_info[Character Send Threshold]:            1 | 
|  | sol_info[Force Authentication]:                true | 
|  | sol_info[Privilege Level]:                     USER | 
|  | sol_info[Set in progress]:                     set-complete | 
|  | sol_info[Retry Interval (ms)]:                 100 | 
|  | sol_info[Non-Volatile Bit Rate (kbps)]:        IPMI-Over-Serial-Setting | 
|  | sol_info[Character Accumulate Level (ms)]:     100 | 
|  | sol_info[Enabled]:                             true | 
|  | sol_info[Volatile Bit Rate (kbps)]:            IPMI-Over-Serial-Setting | 
|  | sol_info[Payload Channel]:                     14 (0x0e) | 
|  | sol_info[Payload Port]:                        623 | 
|  | sol_info[Force Encryption]:                    true | 
|  | sol_info[Retry Count]:                         7 | 
|  | """ | 
|  |  | 
|  | status, ret_values = grk.run_key_u("Run IPMI Standard Command  sol info") | 
|  |  | 
|  | # Create temp file path. | 
|  | temp = tempfile.NamedTemporaryFile() | 
|  | temp_file_path = temp.name | 
|  |  | 
|  | # Write sol info to temp file path. | 
|  | text_file = open(temp_file_path, "w") | 
|  | text_file.write(ret_values) | 
|  | text_file.close() | 
|  |  | 
|  | # Use my_parm_file to interpret data. | 
|  | sol_info = gm.my_parm_file(temp_file_path) | 
|  |  | 
|  | return sol_info | 
|  |  | 
|  |  | 
|  | def set_sol_setting(setting_name, setting_value): | 
|  | r""" | 
|  | Set SOL setting with given value. | 
|  |  | 
|  | # Description of argument(s): | 
|  | # setting_name                  SOL setting which needs to be set (e.g. | 
|  | #                               "retry-count"). | 
|  | # setting_value                 Value which needs to be set (e.g. "7"). | 
|  | """ | 
|  |  | 
|  | status, ret_values = grk.run_key_u("Run IPMI Standard Command  sol set " | 
|  | + setting_name + " " + setting_value) | 
|  |  | 
|  | return status | 
|  |  | 
|  |  | 
|  | def execute_ipmi_cmd(cmd_string, | 
|  | ipmi_cmd_type='inband', | 
|  | print_output=1, | 
|  | ignore_err=0, | 
|  | **options): | 
|  | r""" | 
|  | Run the given command string as an IPMI command and return the stdout, | 
|  | stderr and the return code. | 
|  |  | 
|  | Description of argument(s): | 
|  | cmd_string                      The command string to be run as an IPMI | 
|  | command. | 
|  | ipmi_cmd_type                   'inband' or 'external'. | 
|  | print_output                    If this is set, this function will print | 
|  | the stdout/stderr generated by | 
|  | the IPMI command. | 
|  | ignore_err                      Ignore error means that a failure | 
|  | encountered by running the command | 
|  | string will not be raised as a python | 
|  | exception. | 
|  | options                         These are passed directly to the | 
|  | create_ipmi_ext_command_string function. | 
|  | See that function's prolog for details. | 
|  | """ | 
|  |  | 
|  | if ipmi_cmd_type == 'inband': | 
|  | IPMI_INBAND_CMD = BuiltIn().get_variable_value("${IPMI_INBAND_CMD}") | 
|  | cmd_buf = IPMI_INBAND_CMD + " " + cmd_string | 
|  | return bsu.os_execute_command(cmd_buf, | 
|  | print_out=print_output, | 
|  | ignore_err=ignore_err) | 
|  |  | 
|  | if ipmi_cmd_type == 'external': | 
|  | cmd_buf = ic.create_ipmi_ext_command_string(cmd_string, **options) | 
|  | rc, stdout, stderr = gc.shell_cmd(cmd_buf, | 
|  | print_output=print_output, | 
|  | ignore_err=ignore_err, | 
|  | return_stderr=1) | 
|  | return stdout, stderr, rc | 
|  |  | 
|  |  | 
|  | def get_lan_print_dict(ipmi_cmd_type='external'): | 
|  | r""" | 
|  | Get IPMI 'lan print' output and return it as a dictionary. | 
|  |  | 
|  | Here is an example of the IPMI lan print output: | 
|  |  | 
|  | Set in Progress         : Set Complete | 
|  | Auth Type Support       : MD5 | 
|  | Auth Type Enable        : Callback : MD5 | 
|  | : User     : MD5 | 
|  | : Operator : MD5 | 
|  | : Admin    : MD5 | 
|  | : OEM      : MD5 | 
|  | IP Address Source       : Static Address | 
|  | IP Address              : x.x.x.x | 
|  | Subnet Mask             : x.x.x.x | 
|  | MAC Address             : xx:xx:xx:xx:xx:xx | 
|  | Default Gateway IP      : x.x.x.x | 
|  | 802.1q VLAN ID          : Disabled | 
|  | Cipher Suite Priv Max   : Not Available | 
|  | Bad Password Threshold  : Not Available | 
|  |  | 
|  | Given that data, this function will return the following dictionary. | 
|  |  | 
|  | lan_print_dict: | 
|  | [Set in Progress]:                              Set Complete | 
|  | [Auth Type Support]:                            MD5 | 
|  | [Auth Type Enable]: | 
|  | [Callback]:                                   MD5 | 
|  | [User]:                                       MD5 | 
|  | [Operator]:                                   MD5 | 
|  | [Admin]:                                      MD5 | 
|  | [OEM]:                                        MD5 | 
|  | [IP Address Source]:                            Static Address | 
|  | [IP Address]:                                   x.x.x.x | 
|  | [Subnet Mask]:                                  x.x.x.x | 
|  | [MAC Address]:                                  xx:xx:xx:xx:xx:xx | 
|  | [Default Gateway IP]:                           x.x.x.x | 
|  | [802.1q VLAN ID]:                               Disabled | 
|  | [Cipher Suite Priv Max]:                        Not Available | 
|  | [Bad Password Threshold]:                       Not Available | 
|  |  | 
|  | Description of argument(s): | 
|  | ipmi_cmd_type                   The type of ipmi command to use (e.g. | 
|  | 'inband', 'external'). | 
|  | """ | 
|  |  | 
|  | # Notice in the example of data above that 'Auth Type Enable' needs some | 
|  | # special processing.  We essentially want to isolate its data and remove | 
|  | # the 'Auth Type Enable' string so that key_value_outbuf_to_dict can | 
|  | # process it as a sub-dictionary. | 
|  | cmd_buf = "lan print | grep -E '^(Auth Type Enable)" +\ | 
|  | "?[ ]+: ' | sed -re 's/^(Auth Type Enable)?[ ]+: //g'" | 
|  | stdout1, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type, | 
|  | print_output=0) | 
|  |  | 
|  | # Now get the remainder of the data and exclude the lines with no field | 
|  | # names (i.e. the 'Auth Type Enable' sub-fields). | 
|  | cmd_buf = "lan print | grep -E -v '^[ ]+: '" | 
|  | stdout2, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type, | 
|  | print_output=0) | 
|  |  | 
|  | # Make auth_type_enable_dict sub-dictionary... | 
|  | auth_type_enable_dict = vf.key_value_outbuf_to_dict(stdout1, to_lower=0, | 
|  | underscores=0) | 
|  |  | 
|  | # Create the lan_print_dict... | 
|  | lan_print_dict = vf.key_value_outbuf_to_dict(stdout2, to_lower=0, | 
|  | underscores=0) | 
|  | # Re-assign 'Auth Type Enable' to contain the auth_type_enable_dict. | 
|  | lan_print_dict['Auth Type Enable'] = auth_type_enable_dict | 
|  |  | 
|  | return lan_print_dict | 
|  |  | 
|  |  | 
|  | def get_ipmi_power_reading(strip_watts=1): | 
|  | r""" | 
|  | Get IPMI power reading data and return it as a dictionary. | 
|  |  | 
|  | The data is obtained by issuing the IPMI "power reading" command.  An | 
|  | example is shown below: | 
|  |  | 
|  | Instantaneous power reading:                   234 Watts | 
|  | Minimum during sampling period:                234 Watts | 
|  | Maximum during sampling period:                234 Watts | 
|  | Average power reading over sample period:      234 Watts | 
|  | IPMI timestamp:                           Thu Jan  1 00:00:00 1970 | 
|  | Sampling period:                          00000000 Seconds. | 
|  | Power reading state is:                   deactivated | 
|  |  | 
|  | For the data shown above, the following dictionary will be returned. | 
|  |  | 
|  | result: | 
|  | [instantaneous_power_reading]:              238 Watts | 
|  | [minimum_during_sampling_period]:           238 Watts | 
|  | [maximum_during_sampling_period]:           238 Watts | 
|  | [average_power_reading_over_sample_period]: 238 Watts | 
|  | [ipmi_timestamp]:                           Thu Jan  1 00:00:00 1970 | 
|  | [sampling_period]:                          00000000 Seconds. | 
|  | [power_reading_state_is]:                   deactivated | 
|  |  | 
|  | Description of argument(s): | 
|  | strip_watts                     Strip all dictionary values of the | 
|  | trailing " Watts" substring. | 
|  | """ | 
|  |  | 
|  | status, ret_values = \ | 
|  | grk.run_key_u("Run IPMI Standard Command  dcmi power reading") | 
|  | result = vf.key_value_outbuf_to_dict(ret_values) | 
|  |  | 
|  | if strip_watts: | 
|  | result.update((k, re.sub(' Watts$', '', v)) for k, v in result.items()) | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | def get_mc_info(): | 
|  | r""" | 
|  | Get IPMI mc info data and return it as a dictionary. | 
|  |  | 
|  | The data is obtained by issuing the IPMI "mc info" command.  An | 
|  | example is shown below: | 
|  |  | 
|  | Device ID                 : 0 | 
|  | Device Revision           : 0 | 
|  | Firmware Revision         : 2.01 | 
|  | IPMI Version              : 2.0 | 
|  | Manufacturer ID           : 42817 | 
|  | Manufacturer Name         : Unknown (0xA741) | 
|  | Product ID                : 16975 (0x424f) | 
|  | Product Name              : Unknown (0x424F) | 
|  | Device Available          : yes | 
|  | Provides Device SDRs      : yes | 
|  | Additional Device Support : | 
|  | Sensor Device | 
|  | SEL Device | 
|  | FRU Inventory Device | 
|  | Chassis Device | 
|  | Aux Firmware Rev Info     : | 
|  | 0x00 | 
|  | 0x00 | 
|  | 0x00 | 
|  | 0x00 | 
|  |  | 
|  | For the data shown above, the following dictionary will be returned. | 
|  | mc_info: | 
|  | [device_id]:                       0 | 
|  | [device_revision]:                 0 | 
|  | [firmware_revision]:               2.01 | 
|  | [ipmi_version]:                    2.0 | 
|  | [manufacturer_id]:                 42817 | 
|  | [manufacturer_name]:               Unknown (0xA741) | 
|  | [product_id]:                      16975 (0x424f) | 
|  | [product_name]:                    Unknown (0x424F) | 
|  | [device_available]:                yes | 
|  | [provides_device_sdrs]:            yes | 
|  | [additional_device_support]: | 
|  | [additional_device_support][0]:  Sensor Device | 
|  | [additional_device_support][1]:  SEL Device | 
|  | [additional_device_support][2]:  FRU Inventory Device | 
|  | [additional_device_support][3]:  Chassis Device | 
|  | [aux_firmware_rev_info]: | 
|  | [aux_firmware_rev_info][0]:      0x00 | 
|  | [aux_firmware_rev_info][1]:      0x00 | 
|  | [aux_firmware_rev_info][2]:      0x00 | 
|  | [aux_firmware_rev_info][3]:      0x00 | 
|  | """ | 
|  |  | 
|  | status, ret_values = \ | 
|  | grk.run_key_u("Run IPMI Standard Command  mc info") | 
|  | result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | def get_sdr_info(): | 
|  | r""" | 
|  | Get IPMI sdr info data and return it as a dictionary. | 
|  |  | 
|  | The data is obtained by issuing the IPMI "sdr info" command.  An | 
|  | example is shown below: | 
|  |  | 
|  | SDR Version                         : 0x51 | 
|  | Record Count                        : 216 | 
|  | Free Space                          : unspecified | 
|  | Most recent Addition                : | 
|  | Most recent Erase                   : | 
|  | SDR overflow                        : no | 
|  | SDR Repository Update Support       : unspecified | 
|  | Delete SDR supported                : no | 
|  | Partial Add SDR supported           : no | 
|  | Reserve SDR repository supported    : no | 
|  | SDR Repository Alloc info supported : no | 
|  |  | 
|  | For the data shown above, the following dictionary will be returned. | 
|  | mc_info: | 
|  |  | 
|  | [sdr_version]:                         0x51 | 
|  | [record_Count]:                        216 | 
|  | [free_space]:                          unspecified | 
|  | [most_recent_addition]: | 
|  | [most_recent_erase]: | 
|  | [sdr_overflow]:                        no | 
|  | [sdr_repository_update_support]:       unspecified | 
|  | [delete_sdr_supported]:                no | 
|  | [partial_add_sdr_supported]:           no | 
|  | [reserve_sdr_repository_supported]:    no | 
|  | [sdr_repository_alloc_info_supported]: no | 
|  | """ | 
|  |  | 
|  | status, ret_values = \ | 
|  | grk.run_key_u("Run IPMI Standard Command  sdr info") | 
|  | result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | def get_aux_version(version_id): | 
|  | r""" | 
|  | Get IPMI Aux version info data and return it. | 
|  |  | 
|  | Description of argument(s): | 
|  | version_id                      The data is obtained by from BMC | 
|  | /etc/os-release | 
|  | (e.g. "xxx-v2.1-438-g0030304-r3-gfea8585"). | 
|  |  | 
|  | In the prior example, the 3rd field is "438" is the commit version and | 
|  | the 5th field is "r3" and value "3" is the release version. | 
|  |  | 
|  | Aux version return from this function 4380003. | 
|  | """ | 
|  |  | 
|  | # Commit version. | 
|  | count = re.findall("-(\\d{1,4})-", version_id) | 
|  |  | 
|  | # Release version. | 
|  | release = re.findall("-r(\\d{1,4})", version_id) | 
|  | if release: | 
|  | aux_version = count[0] + "{0:0>4}".format(release[0]) | 
|  | else: | 
|  | aux_version = count[0] + "0000" | 
|  |  | 
|  | return aux_version | 
|  |  | 
|  |  | 
|  | def get_fru_info(): | 
|  | r""" | 
|  | Get fru info and return it as a list of dictionaries. | 
|  |  | 
|  | The data is obtained by issuing the IPMI "fru print -N 50" command.  An | 
|  | example is shown below: | 
|  |  | 
|  | FRU Device Description : Builtin FRU Device (ID 0) | 
|  | Device not present (Unspecified error) | 
|  |  | 
|  | FRU Device Description : cpu0 (ID 1) | 
|  | Board Mfg Date        : Sun Dec 31 18:00:00 1995 | 
|  | Board Mfg             : <Manufacturer Name> | 
|  | Board Product         : PROCESSOR MODULE | 
|  | Board Serial          : YA1934315964 | 
|  | Board Part Number     : 02CY209 | 
|  |  | 
|  | FRU Device Description : cpu1 (ID 2) | 
|  | Board Mfg Date        : Sun Dec 31 18:00:00 1995 | 
|  | Board Mfg             : <Manufacturer Name> | 
|  | Board Product         : PROCESSOR MODULE | 
|  | Board Serial          : YA1934315965 | 
|  | Board Part Number     : 02CY209 | 
|  |  | 
|  | For the data shown above, the following list of dictionaries will be | 
|  | returned. | 
|  |  | 
|  | fru_obj: | 
|  | fru_obj[0]: | 
|  | [fru_device_description]:  Builtin FRU Device (ID 0) | 
|  | [state]:                   Device not present (Unspecified error) | 
|  | fru_obj[1]: | 
|  | [fru_device_description]:  cpu0 (ID 1) | 
|  | [board_mfg_date]:          Sun Dec 31 18:00:00 1995 | 
|  | [board_mfg]:               <Manufacturer Name> | 
|  | [board_product]:           PROCESSOR MODULE | 
|  | [board_serial]:            YA1934315964 | 
|  | [board_part_number]:       02CY209 | 
|  | fru_obj[2]: | 
|  | [fru_device_description]:  cpu1 (ID 2) | 
|  | [board_mfg_date]:          Sun Dec 31 18:00:00 1995 | 
|  | [board_mfg]:               <Manufacturer Name> | 
|  | [board_product]:           PROCESSOR MODULE | 
|  | [board_serial]:            YA1934315965 | 
|  | [board_part_number]:       02CY209 | 
|  | """ | 
|  |  | 
|  | status, ret_values = \ | 
|  | grk.run_key_u("Run IPMI Standard Command  fru print -N 50") | 
|  |  | 
|  | # Manipulate the "Device not present" line to create a "state" key. | 
|  | ret_values = re.sub("Device not present", "state : Device not present", | 
|  | ret_values) | 
|  |  | 
|  | return [vf.key_value_outbuf_to_dict(x) for x in re.split("\n\n", | 
|  | ret_values)] | 
|  |  | 
|  |  | 
|  | def get_component_fru_info(component='cpu', | 
|  | fru_objs=None): | 
|  | r""" | 
|  | Get fru info for the given component and return it as a list of | 
|  | dictionaries. | 
|  |  | 
|  | This function calls upon get_fru_info and then filters out the unwanted | 
|  | entries.  See get_fru_info's prolog for a layout of the data. | 
|  |  | 
|  | Description of argument(s): | 
|  | component                       The component (e.g. "cpu", "dimm", etc.). | 
|  | fru_objs                        A fru_objs list such as the one returned | 
|  | by get_fru_info.  If this is None, then | 
|  | this function will call get_fru_info to | 
|  | obtain such a list. | 
|  | Supplying this argument may improve | 
|  | performance if this function is to be | 
|  | called multiple times. | 
|  | """ | 
|  |  | 
|  | if fru_objs is None: | 
|  | fru_objs = get_fru_info() | 
|  | return\ | 
|  | [x for x in fru_objs | 
|  | if re.match(component + '([0-9]+)? ', x['fru_device_description'])] | 
|  |  | 
|  |  | 
|  | def get_user_info(userid, channel_number=1): | 
|  | r""" | 
|  | Get user info using channel command and return it as a dictionary. | 
|  |  | 
|  | Description of argument(s): | 
|  | userid          The userid (e.g. "1", "2", etc.). | 
|  | channel_number  The user's channel number (e.g. "1"). | 
|  |  | 
|  | The data is obtained by issuing the IPMI "channel getaccess" command.  An | 
|  | example is shown below for user id 1 and channel number 1. | 
|  |  | 
|  | Maximum User IDs     : 15 | 
|  | Enabled User IDs     : 1 | 
|  | User ID              : 1 | 
|  | User Name            : root | 
|  | Fixed Name           : No | 
|  | Access Available     : callback | 
|  | Link Authentication  : enabled | 
|  | IPMI Messaging       : enabled | 
|  | Privilege Level      : ADMINISTRATOR | 
|  | Enable Status        : enabled | 
|  |  | 
|  | For the data shown above, the following dictionary will be returned. | 
|  |  | 
|  | user_info: | 
|  | [maximum_userids]:     15 | 
|  | [enabled_userids:      1 | 
|  | [userid]               1 | 
|  | [user_name]            root | 
|  | [fixed_name]           No | 
|  | [access_available]     callback | 
|  | [link_authentication]  enabled | 
|  | [ipmi_messaging]       enabled | 
|  | [privilege_level]      ADMINISTRATOR | 
|  | [enable_status]        enabled | 
|  |  | 
|  | """ | 
|  |  | 
|  | status, ret_values = grk.run_key_u("Run IPMI Standard Command  channel getaccess " | 
|  | + str(channel_number) + " " + str(userid)) | 
|  |  | 
|  | result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) | 
|  |  | 
|  | return result |