blob: a9bebd764bf7628b9ccff69d9afab526cf284542 [file] [log] [blame]
#!/usr/bin/env python3
r"""
Provide useful ipmi functions.
"""
import json
import re
import tempfile
import bmc_ssh_utils as bsu
import gen_cmd as gc
import gen_misc as gm
import gen_print as gp
import gen_robot_keyword as grk
import gen_robot_utils as gru
import ipmi_client as ic
import var_funcs as vf
from robot.libraries.BuiltIn import BuiltIn
gru.my_import_resource("ipmi_client.robot")
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 External 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 External 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 fetch_sdr_count(sdr_data):
r"""
Get IPMI SDR list and return the SDR OEM count.
The data is obtained by issuing the IPMI "sdr elist -vvv" command. An
example is shown below:
SDR record ID : 0x00cb
SDR record ID : 0x00cc
SDR record type : 0xc0
SDR record next : 0xffff
SDR record bytes: 11
Getting 11 bytes from SDR at offset 5
For the data shown above, the SDR record type with 0xc0 count will be returned.
"""
data = sdr_data.split("\n")
sdr_list = []
for i, j in enumerate(data):
a = j.split(":")
if a[0].strip() == "SDR record type":
sdr_list.append(a[1].strip())
return sdr_list.count("0xc0")
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 External 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
def get_user_access_ipmi(channel_number=1):
r"""
Run 'user list [<channel number>]' command and return the result as a list of dictionaries.
Example robot code:
${users_access}= user list 1
Rprint Vars users_access
Example output:
users:
[0]:
[id]: 1
[name]: root
[callin]: false
[link]: true
[auth]: true
[ipmi]: ADMINISTRATOR
[1]:
[id]: 2
[name]: axzIDwnz
[callin]: true
[link]: false
[auth]: true
[ipmi]: ADMINISTRATOR
"""
cmd_buf = "user list " + str(channel_number)
stdout, stderr, rc = execute_ipmi_cmd(cmd_buf, "external", print_output=0)
return vf.outbuf_to_report(stdout)
def get_channel_auth_capabilities(channel_number=1, privilege_level=4):
r"""
Get the channel authentication capabilities and return as a dictionary.
Example:
channel_auth_cap:
[channel_number]: 2
[ipmi_v1.5__auth_types]:
[kg_status]: default (all zeroes)
[per_message_authentication]: enabled
[user_level_authentication]: enabled
[non-null_user_names_exist]: yes
[null_user_names_exist]: no
[anonymous_login_enabled]: no
[channel_supports_ipmi_v1.5]: no
[channel_supports_ipmi_v2.0]: yes
"""
status, ret_values = grk.run_key_u(
"Run IPMI Standard Command channel authcap "
+ str(channel_number)
+ " "
+ str(privilege_level)
)
result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
return result
def fetch_date(date):
r"""
Removes prefix 0 in a date in given date
Example : 08/12/2021 then returns 8/12/2021
"""
date = date.lstrip("0")
return date
def fetch_added_sel_date(entry):
r"""
Split sel entry string with with | and join only the date with space
Example : If entry given is, "a | 02/14/2020 | 01:16:58 | Sensor_type #0x17 | | Asserted"
Then the result will be "02/14/2020 01:16:58"
"""
temp = entry.split(" | ")
date = temp[1] + " " + temp[2]
print(date)
return date
def prefix_bytes(listx):
r"""
prefixes byte strings in list
Example:
${listx} = ['01', '02', '03']
${listx}= Prefix Bytes ${listx}
then,
${listx}= ['0x01', '0x02', '0x03']
"""
listy = []
for item in listx:
item = "0x" + item
listy.append(item)
return listy
def modify_and_fetch_threshold(old_threshold, threshold_list):
r"""
Description of argument(s):
old_threshold List of threshold values of sensor,
threshold_list List of higher and lower of critical and non-critical values.
i,e [ "lcr", "lnc", "unc", "ucr" ]
Gets old threshold values from sensor and threshold levels,
then returns the list of new threshold and the dict of threshold levels
For example :
1. If old_threshold list is [ 1, 2, 3, 4] then the newthreshold_list will be [ 101, 102, 103, 104 ].
If old_threshold has 'na' the same will be appended to new list, eg: [ 101, 102, 103, 104, 'na'].
2. The newthreshold_list will be zipped to dictionary with threshold_list levels,
Example : threshold_dict = { 'lcr': 101, 'lnc': 102, 'unc': 103, 'ucr': 104 }
"""
# Adding the difference of 100 as less than this value,
# may not have greater impact as the sensor considered is a fan sensor.
# The set threshold may round off for certain values below 100.
n = 100
newthreshold_list = []
for th in old_threshold:
th = th.strip()
if th == "na":
newthreshold_list.append("na")
else:
x = int(float(th)) + n
newthreshold_list.append(x)
n = n + 100
threshold_dict = dict(zip(threshold_list, newthreshold_list))
return newthreshold_list, threshold_dict