Skip to content

Commit 7646d3c

Browse files
committed
Integrate BMC support and Redfish APIs into SONiC
1 parent e59bbfc commit 7646d3c

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
@@ -1950,6 +1950,77 @@ save_log_files() {
19501950
enable_logrotate
19511951
}
19521952

1953+
###############################################################################
1954+
# Check BMC presence
1955+
# Arguments:
1956+
# None
1957+
# Returns:
1958+
# 0 if BMC is supported, 1 otherwise
1959+
###############################################################################
1960+
is_bmc_supported() {
1961+
local platform=$(python3 -c "from sonic_py_common import device_info; print(device_info.get_platform())")
1962+
# Check if the required file exists
1963+
if [ ! -f /usr/share/sonic/device/$platform/bmc.json ]; then
1964+
return 1
1965+
else
1966+
return 0
1967+
fi
1968+
}
1969+
1970+
###############################################################################
1971+
# Trigger BMC debug log dump task
1972+
# Arguments:
1973+
# None
1974+
# Returns:
1975+
# None
1976+
###############################################################################
1977+
trigger_bmc_debug_log_dump() {
1978+
trap 'handle_error $? $LINENO' ERR
1979+
if ! is_bmc_supported; then
1980+
echo "INFO: BMC is not found on this platform. Skipping..."
1981+
return
1982+
fi
1983+
# Trigger BMC redfish API to start BMC debug log dump task
1984+
local task_id=$(python3 /usr/local/bin/bmc_techsupport.py -m trigger)
1985+
echo "$task_id"
1986+
}
1987+
1988+
###############################################################################
1989+
# Save BMC debug log dump files
1990+
# Globals:
1991+
# MKDIR, CP, TARDIR, TECHSUPPORT_TIME_INFO
1992+
# Arguments:
1993+
# $1 - BMC debug log dump task ID
1994+
# Returns:
1995+
# None
1996+
###############################################################################
1997+
collect_bmc_files() {
1998+
$MKDIR $V -p $TARDIR/bmc
1999+
trap 'handle_error $? $LINENO' ERR
2000+
start_t=$(date +%s%3N)
2001+
if ! is_bmc_supported; then
2002+
return
2003+
fi
2004+
2005+
local bmc_debug_log_dump_task_id=$1
2006+
local TARBALL_XZ="/tmp/bmc_debug_log_dump.tar.xz"
2007+
# Remove existing tarball files if they exist
2008+
[ -f "$TARBALL_XZ" ] && rm -f "$TARBALL_XZ"
2009+
2010+
# Invoke BMC redfish API to extract BMC debug log dump to "/tmp/bmc_debug_log_dump.tar.xz"
2011+
python3 /usr/local/bin/bmc_techsupport.py -m collect -p "$TARBALL_XZ" -t "$bmc_debug_log_dump_task_id"
2012+
if [ -f "$TARBALL_XZ" ]; then
2013+
$CP $V -rf "$TARBALL_XZ" $TARDIR/bmc
2014+
else
2015+
echo "ERROR: File $TARBALL_XZ does not exist."
2016+
fi
2017+
2018+
# Cleanup
2019+
[ -f "$TARBALL_XZ" ] && rm -f "$TARBALL_XZ"
2020+
end_t=$(date +%s%3N)
2021+
echo "[ collect_bmc_files ] : $(($end_t-$start_t)) msec" >> $TECHSUPPORT_TIME_INFO
2022+
}
2023+
19532024
###############################################################################
19542025
# Save warmboot files
19552026
# Globals:
@@ -2176,6 +2247,12 @@ main() {
21762247
echo $BASE > $TECHSUPPORT_TIME_INFO
21772248
start_t=$(date +%s%3N)
21782249

2250+
# Trigger BMC debug log dump task - Must be the first task to run
2251+
bmc_debug_log_dump_task_id=$(trigger_bmc_debug_log_dump)
2252+
if [ "$bmc_debug_log_dump_task_id" == "-1" ]; then
2253+
echo "INFO: Fail to trigger BMC debug log dump. Skipping..."
2254+
fi
2255+
21792256
# Capture /proc state early
21802257
save_proc /proc/buddyinfo /proc/cmdline /proc/consoles \
21812258
/proc/cpuinfo /proc/devices /proc/diskstats /proc/dma \
@@ -2389,6 +2466,11 @@ main() {
23892466
save_log_files &
23902467
save_crash_files &
23912468
save_warmboot_files &
2469+
2470+
if [ "$bmc_debug_log_dump_task_id" != "-1" ]; then
2471+
collect_bmc_files $bmc_debug_log_dump_task_id &
2472+
fi
2473+
23922474
wait
23932475

23942476
save_to_tar

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
'scripts/memory_threshold_check.py',
190190
'scripts/memory_threshold_check_handler.py',
191191
'scripts/techsupport_cleanup.py',
192+
'scripts/bmc_techsupport.py',
192193
'scripts/storm_control.py',
193194
'scripts/verify_image_sign.sh',
194195
'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)