| #!/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 |
| import json |
| |
| |
| 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(channel_number='', 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'). |
| """ |
| |
| channel_number = str(channel_number) |
| # 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 " + channel_number + " | 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 " + channel_number + " | 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"). |
| |
| Note: If userid is blank, this function will return a list of dictionaries. Each list entry represents |
| one userid record. |
| |
| 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)) |
| |
| if userid == "": |
| return vf.key_value_outbuf_to_dicts(ret_values, process_indent=1) |
| else: |
| return vf.key_value_outbuf_to_dict(ret_values, process_indent=1) |
| |
| |
| def channel_getciphers_ipmi(): |
| |
| r""" |
| Run 'channel getciphers ipmi' command and return the result as a list of dictionaries. |
| |
| Example robot code: |
| ${ipmi_channel_ciphers}= Channel Getciphers IPMI |
| Rprint Vars ipmi_channel_ciphers |
| |
| Example output: |
| ipmi_channel_ciphers: |
| [0]: |
| [id]: 3 |
| [iana]: N/A |
| [auth_alg]: hmac_sha1 |
| [integrity_alg]: hmac_sha1_96 |
| [confidentiality_alg]: aes_cbc_128 |
| [1]: |
| [id]: 17 |
| [iana]: N/A |
| [auth_alg]: hmac_sha256 |
| [integrity_alg]: sha256_128 |
| [confidentiality_alg]: aes_cbc_128 |
| """ |
| |
| cmd_buf = "channel getciphers ipmi | sed -re 's/ Alg/_Alg/g'" |
| stdout, stderr, rc = execute_ipmi_cmd(cmd_buf, "external", print_output=0) |
| return vf.outbuf_to_report(stdout) |
| |
| |
| def get_device_id_config(): |
| r""" |
| Get the device id config data and return as a dictionary. |
| |
| Example: |
| |
| dev_id_config = get_device_id_config() |
| print_vars(dev_id_config) |
| |
| dev_id_config: |
| [manuf_id]: 7244 |
| [addn_dev_support]: 141 |
| [prod_id]: 16976 |
| [aux]: 0 |
| [id]: 32 |
| [revision]: 129 |
| [device_revision]: 1 |
| """ |
| stdout, stderr, rc = bsu.bmc_execute_command("cat /usr/share/ipmi-providers/dev_id.json") |
| |
| result = json.loads(stdout) |
| |
| # Create device revision field for the user. |
| # Reference IPMI specification v2.0 "Get Device ID Command" |
| # [7] 1 = device provides Device SDRs |
| # 0 = device does not provide Device SDRs |
| # [6:4] reserved. Return as 0. |
| # [3:0] Device Revision, binary encoded. |
| |
| result['device_revision'] = result['revision'] & 0x0F |
| |
| return result |
| |
| |
| def get_chassis_status(): |
| r""" |
| Get IPMI chassis status data and return it as a dictionary. |
| |
| The data is obtained by issuing the IPMI "chassis status" command. An |
| example is shown below: |
| |
| System Power : off |
| Power Overload : false |
| Power Interlock : inactive |
| Main Power Fault : false |
| Power Control Fault : false |
| Power Restore Policy : previous |
| Last Power Event : |
| Chassis Intrusion : inactive |
| Front-Panel Lockout : inactive |
| Drive Fault : false |
| Cooling/Fan Fault : false |
| Sleep Button Disable : not allowed |
| Diag Button Disable : not allowed |
| Reset Button Disable : not allowed |
| Power Button Disable : allowed |
| Sleep Button Disabled : false |
| Diag Button Disabled : false |
| Reset Button Disabled : false |
| Power Button Disabled : false |
| |
| For the data shown above, the following dictionary will be returned. |
| |
| chassis_status: |
| [system_power]: off |
| [power_overload]: false |
| [power_interlock]: inactive |
| [main_power_fault]: false |
| [power_control_fault]: false |
| [power_restore_policy]: previous |
| [last_power_event]: |
| [chassis_intrusion]: inactive |
| [front-panel_lockout]: inactive |
| [drive_fault]: false |
| [cooling/fan_fault]: false |
| [sleep_button_disable]: not allowed |
| [diag_button_disable]: not allowed |
| [reset_button_disable]: not allowed |
| [power_button_disable]: allowed |
| [sleep_button_disabled]: false |
| [diag_button_disabled]: false |
| [reset_button_disabled]: false |
| [power_button_disabled]: false |
| """ |
| |
| status, ret_values = \ |
| grk.run_key_u("Run IPMI Standard Command chassis status") |
| result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1) |
| |
| return result |
| |
| |
| def get_channel_info(channel_number=1): |
| r""" |
| Get the channel info and return as a dictionary. |
| Example: |
| |
| channel_info: |
| [channel_0x2_info]: |
| [channel_medium_type]: 802.3 LAN |
| [channel_protocol_type]: IPMB-1.0 |
| [session_support]: multi-session |
| [active_session_count]: 0 |
| [protocol_vendor_id]: 7154 |
| [volatile(active)_settings]: |
| [alerting]: enabled |
| [per-message_auth]: enabled |
| [user_level_auth]: enabled |
| [access_mode]: always available |
| [non-volatile_settings]: |
| [alerting]: enabled |
| [per-message_auth]: enabled |
| [user_level_auth]: enabled |
| [access_mode]: always available |
| """ |
| |
| status, ret_values = \ |
| grk.run_key_u("Run IPMI Standard Command channel info " + str(channel_number)) |
| key_var_list = list(filter(None, ret_values.split("\n"))) |
| # To match the dict format, add a colon after 'Volatile(active) Settings' and 'Non-Volatile Settings' |
| # respectively. |
| key_var_list[6] = 'Volatile(active) Settings:' |
| key_var_list[11] = 'Non-Volatile Settings:' |
| result = vf.key_value_list_to_dict(key_var_list, process_indent=1) |
| return result |