Skip to content

Commit 4e9080d

Browse files
authored
Integrate BMC support and Redfish APIs into SONiC (sonic-net#4104)
This PR is dependent on the following PRs: sonic-net/sonic-platform-common#605 sonic-net/sonic-buildimage#24345 What I did Added BMC dump collection to the show techsupport output. Introduced new CLI commands for retrieving BMC information. How I did it Updated generate_dump script to trigger asynchronous BMC dump collection at the beginning of the techsupport process and collect the dump before packaging the final tarball. Implemented new BMC CLI commands under show platform bmc by extending the existing CLI framework. The CLIs internally query the BMC API to retrieve BMC summary and EEPROM data. How to verify it show techsupport show platform bmc summary show platform bmc eeprom
1 parent 1f6613f commit 4e9080d

File tree

5 files changed

+588
-0
lines changed

5 files changed

+588
-0
lines changed

scripts/bmc_techsupport.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
bmc_techsupport script.
5+
This script is invoked by the generate_dump script for BMC techsupport fetching,
6+
but also can be invoked manually to trigger and collect BMC debug log dump.
7+
8+
The usage of this script is divided into two parts:
9+
1. Triggering BMC debug log dump Redfish task
10+
* In this case the script triggers a POST request to BMC to start collecting debug log dump.
11+
* In this script we will print the new task-id to the console output
12+
to collect the debug log dump once the task-id has finished.
13+
* This step is non-blocking, task-id is returned immediately.
14+
* It is invoked with the parameter '--mode trigger'
15+
E.g.: /usr/local/bin/bmc_techsupport.py --mode trigger
16+
17+
2. Collecting BMC debug log dump
18+
* In this step we will wait for the task-id to finish if it has not finished.
19+
* Blocking action until we get the file or encounter an ERROR or Timeout.
20+
* It is invoked with the parameter '--mode collect --task <task-id> --path <path>'
21+
E.g.: /usr/local/bin/bmc_techsupport.py --mode collect --path <path> --task <task-id>
22+
23+
Basically, in the generate_dump script we will call the first method
24+
at the beginning of its process and the second method towards the end of the process.
25+
"""
26+
27+
28+
import argparse
29+
import os
30+
import sonic_platform
31+
import time
32+
from sonic_py_common.syslogger import SysLogger
33+
34+
35+
TIMEOUT_FOR_GET_BMC_DEBUG_LOG_DUMP_IN_SECONDS = 60
36+
SYSLOG_IDENTIFIER = "bmc_techsupport"
37+
log = SysLogger(SYSLOG_IDENTIFIER)
38+
39+
40+
class BMCDebugDumpExtractor:
41+
'''
42+
Class to trigger and extract BMC debug log dump
43+
'''
44+
45+
INVALID_TASK_ID = '-1'
46+
TRIGGER_MODE = 'trigger'
47+
COLLECT_MODE = 'collect'
48+
49+
def __init__(self):
50+
platform = sonic_platform.platform.Platform()
51+
chassis = platform.get_chassis()
52+
self.bmc = chassis.get_bmc()
53+
54+
def trigger_debug_dump(self):
55+
'''
56+
Trigger BMC debug log dump and prints the running task id to the console output
57+
'''
58+
try:
59+
task_id = BMCDebugDumpExtractor.INVALID_TASK_ID
60+
log.log_info("Triggering BMC debug log dump Redfish task")
61+
(ret, (task_id, err_msg)) = self.bmc.trigger_bmc_debug_log_dump()
62+
if ret != 0:
63+
raise Exception(err_msg)
64+
log.log_info(f'Successfully triggered BMC debug log dump - Task-id: {task_id}')
65+
except Exception as e:
66+
log.log_error(f'Failed to trigger BMC debug log dump - {str(e)}')
67+
finally:
68+
# generate_dump script captures the task id from the console output via $(...) syntax
69+
print(f'{task_id}')
70+
71+
def extract_debug_dump_file(self, task_id, filepath):
72+
'''
73+
Extract BMC debug log dump file for the given task id and save it to the given filepath
74+
'''
75+
try:
76+
if task_id is None or task_id == BMCDebugDumpExtractor.INVALID_TASK_ID:
77+
raise Exception('Invalid Task-ID')
78+
log_dump_dir = os.path.dirname(filepath)
79+
log_dump_filename = os.path.basename(filepath)
80+
if not log_dump_dir or not log_dump_filename:
81+
raise Exception(f'Invalid given filepath: {filepath}')
82+
if not log_dump_filename.endswith('.tar.xz'):
83+
raise Exception(f'Invalid given filepath extension, should be .tar.xz: {log_dump_filename}')
84+
85+
start_time = time.time()
86+
log.log_info("Collecting BMC debug log dump")
87+
ret, err_msg = self.bmc.get_bmc_debug_log_dump(
88+
task_id=task_id,
89+
filename=log_dump_filename,
90+
path=log_dump_dir,
91+
timeout=TIMEOUT_FOR_GET_BMC_DEBUG_LOG_DUMP_IN_SECONDS
92+
)
93+
end_time = time.time()
94+
duration = end_time - start_time
95+
if ret != 0:
96+
timeout_msg = (
97+
f'BMC debug log dump does not finish within '
98+
f'{TIMEOUT_FOR_GET_BMC_DEBUG_LOG_DUMP_IN_SECONDS} seconds: {err_msg}'
99+
)
100+
log.log_error(timeout_msg)
101+
raise Exception(err_msg)
102+
log.log_info(f'Finished successfully collecting BMC debug log dump. Duration: {duration} seconds')
103+
except Exception as e:
104+
log.log_error(f'Failed to collect BMC debug log dump - {str(e)}')
105+
106+
107+
def main(mode, task_id, filepath):
108+
try:
109+
extractor = BMCDebugDumpExtractor()
110+
if extractor.bmc is None:
111+
raise Exception('BMC instance is not available')
112+
except Exception as e:
113+
log.log_error(f'Failed to initialize BMCDebugDumpExtractor: {str(e)}')
114+
if mode == BMCDebugDumpExtractor.TRIGGER_MODE:
115+
print(f'{BMCDebugDumpExtractor.INVALID_TASK_ID}')
116+
return
117+
if mode == BMCDebugDumpExtractor.TRIGGER_MODE:
118+
extractor.trigger_debug_dump()
119+
elif mode == BMCDebugDumpExtractor.COLLECT_MODE:
120+
if not task_id or not filepath:
121+
log.log_error("Both --task and --path arguments are required for 'collect' mode")
122+
return
123+
extractor.extract_debug_dump_file(task_id, filepath)
124+
125+
126+
if __name__ == "__main__":
127+
parser = argparse.ArgumentParser(description="BMC tech-support generator script.")
128+
parser.add_argument(
129+
'-m', '--mode',
130+
choices=['collect', 'trigger'],
131+
required=True,
132+
help="Mode of operation: 'collect' for collecting debug dump or 'trigger' for triggering debug dump task."
133+
)
134+
parser.add_argument('-p', '--path', help="Path to save the BMC debug log dump file.")
135+
parser.add_argument('-t', '--task', help="Task-ID to monitor and collect the debug dump from.")
136+
args = parser.parse_args()
137+
mode = args.mode
138+
task_id = args.task
139+
filepath = args.path
140+
main(mode, task_id, filepath)

scripts/generate_dump

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,6 +2051,77 @@ save_log_files() {
20512051
enable_logrotate
20522052
}
20532053

2054+
###############################################################################
2055+
# Check BMC presence
2056+
# Arguments:
2057+
# None
2058+
# Returns:
2059+
# 0 if BMC is supported, 1 otherwise
2060+
###############################################################################
2061+
is_bmc_supported() {
2062+
local platform=$(python3 -c "from sonic_py_common import device_info; print(device_info.get_platform())")
2063+
# Check if the required file exists
2064+
if [ ! -f /usr/share/sonic/device/$platform/bmc.json ]; then
2065+
return 1
2066+
else
2067+
return 0
2068+
fi
2069+
}
2070+
2071+
###############################################################################
2072+
# Trigger BMC debug log dump task
2073+
# Arguments:
2074+
# None
2075+
# Returns:
2076+
# None
2077+
###############################################################################
2078+
trigger_bmc_debug_log_dump() {
2079+
trap 'handle_error $? $LINENO' ERR
2080+
if ! is_bmc_supported; then
2081+
echo "INFO: BMC is not found on this platform. Skipping..."
2082+
return
2083+
fi
2084+
# Trigger BMC redfish API to start BMC debug log dump task
2085+
local task_id=$(python3 /usr/local/bin/bmc_techsupport.py -m trigger)
2086+
echo "$task_id"
2087+
}
2088+
2089+
###############################################################################
2090+
# Save BMC debug log dump files
2091+
# Globals:
2092+
# MKDIR, CP, TARDIR, TECHSUPPORT_TIME_INFO
2093+
# Arguments:
2094+
# $1 - BMC debug log dump task ID
2095+
# Returns:
2096+
# None
2097+
###############################################################################
2098+
collect_bmc_files() {
2099+
$MKDIR $V -p $TARDIR/bmc
2100+
trap 'handle_error $? $LINENO' ERR
2101+
start_t=$(date +%s%3N)
2102+
if ! is_bmc_supported; then
2103+
return
2104+
fi
2105+
2106+
local bmc_debug_log_dump_task_id=$1
2107+
local TARBALL_XZ="/tmp/bmc_debug_log_dump.tar.xz"
2108+
# Remove existing tarball files if they exist
2109+
[ -f "$TARBALL_XZ" ] && rm -f "$TARBALL_XZ"
2110+
2111+
# Invoke BMC redfish API to extract BMC debug log dump to "/tmp/bmc_debug_log_dump.tar.xz"
2112+
python3 /usr/local/bin/bmc_techsupport.py -m collect -p "$TARBALL_XZ" -t "$bmc_debug_log_dump_task_id"
2113+
if [ -f "$TARBALL_XZ" ]; then
2114+
$CP $V -rf "$TARBALL_XZ" $TARDIR/bmc
2115+
else
2116+
echo "ERROR: File $TARBALL_XZ does not exist."
2117+
fi
2118+
2119+
# Cleanup
2120+
[ -f "$TARBALL_XZ" ] && rm -f "$TARBALL_XZ"
2121+
end_t=$(date +%s%3N)
2122+
echo "[ collect_bmc_files ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
2123+
}
2124+
20542125
###############################################################################
20552126
# Save warmboot files
20562127
# Globals:
@@ -2277,6 +2348,12 @@ main() {
22772348
echo $BASE > $TECHSUPPORT_TIME_INFO
22782349
start_t=$(date +%s%3N)
22792350

2351+
# Trigger BMC debug log dump task - Must be the first task to run
2352+
bmc_debug_log_dump_task_id=$(trigger_bmc_debug_log_dump)
2353+
if [ "$bmc_debug_log_dump_task_id" == "-1" ]; then
2354+
echo "INFO: Fail to trigger BMC debug log dump. Skipping..."
2355+
fi
2356+
22802357
# Capture /proc state early
22812358
save_proc /proc/buddyinfo /proc/cmdline /proc/consoles \
22822359
/proc/cpuinfo /proc/devices /proc/diskstats /proc/dma \
@@ -2493,6 +2570,11 @@ main() {
24932570
save_log_files &
24942571
save_crash_files &
24952572
save_warmboot_files &
2573+
2574+
if [ "$bmc_debug_log_dump_task_id" != "-1" ]; then
2575+
collect_bmc_files $bmc_debug_log_dump_task_id &
2576+
fi
2577+
24962578
wait
24972579

24982580
save_to_tar

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190
'scripts/memory_threshold_check.py',
191191
'scripts/memory_threshold_check_handler.py',
192192
'scripts/techsupport_cleanup.py',
193+
'scripts/bmc_techsupport.py',
193194
'scripts/storm_control.py',
194195
'scripts/verify_image_sign.sh',
195196
'scripts/verify_image_sign_common.sh',

show/platform.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,110 @@ def summary(json):
7070
click.echo("Switch Type: {}".format(switch_type))
7171

7272

73+
# 'bmc' subcommand ("show platform bmc")
74+
@platform.group()
75+
def bmc():
76+
"""Show BMC information"""
77+
pass
78+
79+
80+
# 'summary' subcommand ("show platform bmc summary")
81+
@bmc.command(name='summary')
82+
@click.option('--json', is_flag=True, help="Output in JSON format")
83+
def bmc_summary(json):
84+
"""Show BMC summary information"""
85+
try:
86+
import sonic_platform
87+
chassis = sonic_platform.platform.Platform().get_chassis()
88+
bmc = chassis.get_bmc()
89+
90+
if bmc is None:
91+
click.echo("BMC is not available on this platform")
92+
return
93+
94+
eeprom_info = bmc.get_eeprom()
95+
if not eeprom_info:
96+
click.echo("Failed to retrieve BMC EEPROM information")
97+
return
98+
99+
# Extract the required fields
100+
manufacturer = eeprom_info.get('Manufacturer', 'N/A')
101+
model = eeprom_info.get('Model', 'N/A')
102+
part_number = eeprom_info.get('PartNumber', 'N/A')
103+
power_state = eeprom_info.get('PowerState', 'N/A')
104+
serial_number = eeprom_info.get('SerialNumber', 'N/A')
105+
bmc_version = bmc.get_version()
106+
107+
if json:
108+
bmc_summary = {
109+
'Manufacturer': manufacturer,
110+
'Model': model,
111+
'PartNumber': part_number,
112+
'SerialNumber': serial_number,
113+
'PowerState': power_state,
114+
'FirmwareVersion': bmc_version
115+
}
116+
click.echo(clicommon.json_dump(bmc_summary))
117+
else:
118+
click.echo(f"Manufacturer: {manufacturer}")
119+
click.echo(f"Model: {model}")
120+
click.echo(f"PartNumber: {part_number}")
121+
click.echo(f"SerialNumber: {serial_number}")
122+
click.echo(f"PowerState: {power_state}")
123+
click.echo(f"FirmwareVersion: {bmc_version}")
124+
125+
except Exception as e:
126+
click.echo(f"Error retrieving BMC information: {str(e)}")
127+
128+
129+
# 'eeprom' subcommand ("show platform bmc eeprom")
130+
@bmc.command()
131+
@click.option('--json', is_flag=True, help="Output in JSON format")
132+
def eeprom(json):
133+
"""Show BMC EEPROM information"""
134+
try:
135+
import sonic_platform
136+
chassis = sonic_platform.platform.Platform().get_chassis()
137+
bmc = chassis.get_bmc()
138+
139+
if bmc is None:
140+
click.echo("BMC is not available on this platform")
141+
return
142+
143+
# Get BMC EEPROM information
144+
eeprom_info = bmc.get_eeprom()
145+
146+
if not eeprom_info:
147+
click.echo("Failed to retrieve BMC EEPROM information")
148+
return
149+
150+
# Extract the required fields
151+
manufacturer = eeprom_info.get('Manufacturer', 'N/A')
152+
model = eeprom_info.get('Model', 'N/A')
153+
part_number = eeprom_info.get('PartNumber', 'N/A')
154+
power_state = eeprom_info.get('PowerState', 'N/A')
155+
serial_number = eeprom_info.get('SerialNumber', 'N/A')
156+
157+
if json:
158+
bmc_eeprom = {
159+
'Manufacturer': manufacturer,
160+
'Model': model,
161+
'PartNumber': part_number,
162+
'PowerState': power_state,
163+
'SerialNumber': serial_number
164+
}
165+
click.echo(clicommon.json_dump(bmc_eeprom))
166+
else:
167+
click.echo(f"Manufacturer: {manufacturer}")
168+
click.echo(f"Model: {model}")
169+
click.echo(f"PartNumber: {part_number}")
170+
click.echo(f"PowerState: {power_state}")
171+
click.echo(f"SerialNumber: {serial_number}")
172+
173+
except Exception as e:
174+
click.echo(f"Error retrieving BMC EEPROM information: {str(e)}")
175+
176+
73177
# 'syseeprom' subcommand ("show platform syseeprom")
74178
@platform.command()
75179
@click.option('--verbose', is_flag=True, help="Enable verbose output")

0 commit comments

Comments
 (0)