Skip to content

Commit d0f5d76

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

File tree

5 files changed

+450
-0
lines changed

5 files changed

+450
-0
lines changed

scripts/bmc_techsupport.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 till we get the file or having 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(f'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(task_id=task_id, filename=log_dump_filename, path=log_dump_dir,
88+
timeout=TIMEOUT_FOR_GET_BMC_DEBUG_LOG_DUMP_IN_SECONDS)
89+
end_time = time.time()
90+
duration = end_time - start_time
91+
if ret != 0:
92+
log.log_error(f'BMC debug log dump does not finish within {TIMEOUT_FOR_GET_BMC_DEBUG_LOG_DUMP_IN_SECONDS} seconds: {err_msg}')
93+
raise Exception(err_msg)
94+
log.log_info(f'Finished successfully collecting BMC debug log dump. Duration: {duration} seconds')
95+
except Exception as e:
96+
log.log_error(f'Failed to collect BMC debug log dump - {str(e)}')
97+
98+
99+
def main(mode, task_id, filepath):
100+
try:
101+
extractor = BMCDebugDumpExtractor()
102+
if extractor.bmc is None:
103+
raise Exception('BMC instance is not available')
104+
except Exception as e:
105+
log.log_error(f'Failed to initialize BMCDebugDumpExtractor: {str(e)}')
106+
if mode == BMCDebugDumpExtractor.TRIGGER_MODE:
107+
print(f'{BMCDebugDumpExtractor.INVALID_TASK_ID}')
108+
return
109+
if mode == BMCDebugDumpExtractor.TRIGGER_MODE:
110+
extractor.trigger_debug_dump()
111+
elif mode == BMCDebugDumpExtractor.COLLECT_MODE:
112+
extractor.extract_debug_dump_file(task_id, filepath)
113+
114+
115+
if __name__ == "__main__":
116+
parser = argparse.ArgumentParser(description="BMC tech-support generator script.")
117+
parser.add_argument('-m', '--mode', choices=['collect', 'trigger'], required=True,
118+
help="Mode of operation: 'collect' for collecting debug dump or 'trigger' for triggering debug dump task.")
119+
parser.add_argument('-p', '--path', help="Path to save the BMC debug log dump file.")
120+
parser.add_argument('-t', '--task', help="Task-ID to monitor and collect the debug dump from.")
121+
args = parser.parse_args()
122+
mode = args.mode
123+
task_id = args.task
124+
filepath = args.path
125+
main(mode, task_id, filepath)

scripts/generate_dump

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,81 @@ save_log_files() {
19501950
enable_logrotate
19511951
}
19521952

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

2254+
# Trigger BMC debug log dump task - Must be the first task to run
2255+
bmc_debug_log_dump_task_id=$(trigger_bmc_debug_log_dump)
2256+
if [ "$bmc_debug_log_dump_task_id" == "-1" ]; then
2257+
echo "INFO: Fail to trigger BMC debug log dump. Skipping..."
2258+
fi
2259+
21792260
# Capture /proc state early
21802261
save_proc /proc/buddyinfo /proc/cmdline /proc/consoles \
21812262
/proc/cpuinfo /proc/devices /proc/diskstats /proc/dma \
@@ -2389,6 +2470,11 @@ main() {
23892470
save_log_files &
23902471
save_crash_files &
23912472
save_warmboot_files &
2473+
2474+
if [ "$bmc_debug_log_dump_task_id" != "-1" ]; then
2475+
collect_bmc_files $bmc_debug_log_dump_task_id &
2476+
fi
2477+
23922478
wait
23932479

23942480
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()
82+
@click.option('--json', is_flag=True, help="Output in JSON format")
83+
def 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)