From 0ed700b3a67ee8bcee2af334c9a5e1b7f7515371 Mon Sep 17 00:00:00 2001 From: Sonic Build Admin Date: Wed, 1 Jul 2026 18:21:47 +0000 Subject: [PATCH] [techsupport] Skip switch-only collectors on Switch-BMC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What I did `generate_dump` (the engine behind `show techsupport`) ran the full switch data-plane collection unconditionally. On a SONiC **Switch-BMC** — a control-plane-only platform with no ASIC/SDK, no front-panel ports, no FRR/vtysh, no swss/bgp/syncd containers, no STP, and an ARM/aspeed SoC that lacks some sysfs paths and tools — this produced a large number of spurious errors and irrelevant/empty sections in the techsupport bundle. This change makes `generate_dump` Switch-BMC aware so `show techsupport` completes cleanly on a Switch-BMC, while behavior on regular switches is unchanged. Part of the SONiC Switch-BMC enablement. #### How I did it - Add an `IS_SWITCH_BMC` flag, set from `switch_bmc=1` in `platform_env.conf` (mirroring the existing `IS_SUPERVISOR` handling). - Gate switch-only collectors on it so they self-skip on a Switch-BMC: FRR/BGP/EVPN/BFD, ASIC/SDK health events, front-panel interface/transceiver, gearbox, STP, swss/bgp docker logs, `hdparm`, the SAI dump, and the port-counter snapshots. - Keep platform-agnostic collectors: the generic `show ip interface` L3 summary (incl. management), LLDP, NAT/conntrack, and platform info. - Harden generic fallbacks so absent/empty paths no longer error: - `record_missing_path` replaces the broken `echo > $TARDIR/$f` fallback in `save_proc`/`save_sys`; - `save_pstore` handles absent/empty/populated without a failing glob; - `sed_if_exists` guards optional secret-scrub files. #### How to verify it On an AST2700-based SONiC Switch-BMC (`switch_bmc=1`): 1. `show techsupport` → completes with **exit 0**, no spurious data-plane errors, and a valid gzip tarball under `/var/dump`. 2. Confirm the switch-only sections are skipped (no FRR/BGP/STP/transceiver errors) while the platform/LLDP/NAT/management-interface sections are present. On a regular (non-Switch-BMC) switch the behavior is unchanged: `IS_SWITCH_BMC=false`, so every collector runs exactly as before, and the empty-port-config path stays fatal. Validated on a live AST2700 SONiC Switch-BMC: multiple full `show techsupport` runs (default, `--silent`, `--debug-dump`), all exit 0 and gzip-valid; the non-BMC fatal paths confirmed unchanged. #### Previous command output (if the output of a command-line utility has changed) On a Switch-BMC, `show techsupport` previously emitted many collector failures to the console/log (e.g. FRR/`vtysh`, STP/`stpctl`, front-panel `interface transceiver`, swss/bgp docker-log lookups) and wrote empty/error sections into the bundle. #### New command output (if the output of a command-line utility has changed) On a Switch-BMC, `show techsupport` now completes with exit 0 and no spurious errors; the inapplicable switch-only sections are omitted, and the platform/LLDP/NAT/management sections are retained. Signed-off-by: Sonic Build Admin --- scripts/generate_dump | 155 +++++++++++++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 32 deletions(-) diff --git a/scripts/generate_dump b/scripts/generate_dump index 0b47dcbc2..a8f031b69 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -52,6 +52,10 @@ RETURN_CODE=$EXT_SUCCESS DEBUG_DUMP=false ROUTE_TAB_LIMIT_DIRECT_ITERATION=24000 IS_SUPERVISOR=false +# True on a SONiC Switch-BMC (set below from switch_bmc=1 in platform_env.conf). +# Switch data-plane collectors (ASIC/SDK, ports, FRR/BGP, STP, SAI) self-skip. +# Distinct from is_bmc_supported() (Redfish debug-dump availability). +IS_SWITCH_BMC=false ENABLE_FLOW_DUMP=false @@ -102,6 +106,35 @@ escape_quotes() { echo $1 | sed 's/\"/\\\"/g' } +############################################################################### +# Record an absent source path as a ".not-found" marker (parent dir +# created first) so it is not mistaken for collected data. +# Arguments: +# root: destination root (e.g. $TARDIR) +# src: absolute source path that was not found +############################################################################### +record_missing_path() { + local root="$1" + local src="$2" + local marker="$root/${src#/}.not-found" + $MKDIR $V -p "$(dirname "$marker")" + echo "$src not found" > "$marker" +} + +############################################################################### +# In-place sed a file only if it exists - avoids errors scrubbing optional +# config files absent on minimal images. +# Arguments: +# expr: sed -E expression +# file: target file path +############################################################################### +sed_if_exists() { + local expr="$1" + local file="$2" + [ -e "$file" ] || return 0 + sed -i -E "$expr" "$file" +} + save_bcmcmd() { trap 'handle_error $? $LINENO' ERR local start_t=$(date +%s%3N) @@ -672,7 +705,7 @@ save_nat_info() { ############################################################################### save_bfd_info() { trap 'handle_error $? $LINENO' ERR - if $IS_SUPERVISOR; then + if $IS_SUPERVISOR || $IS_SWITCH_BMC; then return fi save_vtysh "show bfd peers" "frr.bfd.peers" @@ -712,7 +745,7 @@ save_ip_info() { ############################################################################### save_bgp_info() { trap 'handle_error $? $LINENO' ERR - if $IS_SUPERVISOR; then + if $IS_SUPERVISOR || $IS_SWITCH_BMC; then return fi save_vtysh "show ip bgp summary" "bgp.summary" @@ -752,7 +785,7 @@ save_bgp_info() { ############################################################################### save_evpn_info() { trap 'handle_error $? $LINENO' ERR - if $IS_SUPERVISOR; then + if $IS_SUPERVISOR || $IS_SWITCH_BMC; then return fi save_vtysh "show bgp l2vpn evpn" "bgp.l2vpn.evpn" @@ -788,7 +821,7 @@ save_evpn_info() { ############################################################################### save_frr_info() { trap 'handle_error $? $LINENO' ERR - if $IS_SUPERVISOR; then + if $IS_SUPERVISOR || $IS_SWITCH_BMC; then return fi save_vtysh "show running-config" "frr.running_config" @@ -871,7 +904,11 @@ save_proc() { echo "$CP $V -r $f $TARDIR/proc" fi else - ( [ -e $f ] && $CP $V -r $f $TARDIR/proc ) || echo "$f not found" > $TARDIR/$f + if [ -e "$f" ]; then + $CP $V -r "$f" $TARDIR/proc + else + record_missing_path "$TARDIR" "$f" + fi fi done @@ -909,7 +946,11 @@ save_sys() { echo "$cp_cmd $V -r -p $f $TARDIR/sys" fi else - ( [ -e $f ] && $cp_cmd $V -r -p $f $TARDIR/sys ) || echo "$f not found" > $TARDIR/$f + if [ -e "$f" ]; then + $cp_cmd $V -r -p "$f" $TARDIR/sys + else + record_missing_path "$TARDIR" "$f" + fi fi done @@ -1035,9 +1076,18 @@ save_pstore() { echo "$CP $V -r -p $f/* $TARDIR/pstore" fi else - ls --time-style='+%d-%m-%Y %H:%M:%S' -last $f >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE - ls --time-style='+%d-%m-%Y %H:%M:%S' -last $f/* >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE - ( [ -e $f ] && $CP $V -r -p $f/* $TARDIR/pstore ) || echo "$f not found" > $TARDIR/$f + # Handle absent / empty / populated without globbing an empty dir. + if [ ! -e "$f" ]; then + echo "$f not found" >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE + else + ls --time-style='+%d-%m-%Y %H:%M:%S' -last "$f" >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE + if [ -n "$(find "$f" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]; then + # "$f/." copies contents glob-free (no unmatched "$f/*"). + $CP $V -r -p "$f/." $TARDIR/pstore + else + echo "$f is empty" >> $TARDIR/pstore/$PSTORE_DIR_LIST_INFO_FILE + fi + fi fi done @@ -1185,6 +1235,11 @@ with open('$filepath') as json_file: ############################################################################### save_saidump_by_route_size() { trap 'handle_error $? $LINENO' ERR + # Route-size probe uses vtysh and the SAI dump uses syncd - neither exists + # on a Switch-BMC. + if $IS_SWITCH_BMC; then + return + fi for (( i=0; i<$NUM_ASICS; i++ )) do @@ -2457,6 +2512,10 @@ get_asic_count() { ############################################################################### save_counter_snapshot() { trap 'handle_error $? $LINENO' ERR + # Front-panel port/ASIC counters - no front-panel ports on a Switch-BMC. + if $IS_SWITCH_BMC; then + return + fi local asic_name="$1" local idx=$2 counter_t=$(date +'%d/%m/%Y %H:%M:%S:%6N') @@ -2466,7 +2525,9 @@ save_counter_snapshot() { save_cmd "show interface counters fec-stats" "interface.counters.fec-stats_$idx" for netdev in /sys/class/net/Ethernet* ; do - iface=$(basename $netdev) + # Skip the literal "Ethernet*" when the glob matches nothing. + [ -e "$netdev" ] || continue + iface=$(basename "$netdev") save_cmd "show interface counters fec-histogram $iface" "interface.counters.fec-histogram.$iface.$idx" done @@ -2535,6 +2596,10 @@ save_container_files() { # Save gearbox data ############################################################################### save_gearbox_data() { + # Gearbox PHY data - not present on a Switch-BMC. + if $IS_SWITCH_BMC; then + return + fi save_cmd "gearboxutil phys status" "gearbox.phys_status" & save_cmd "gearboxutil interfaces status" "gearbox.interfaces_status" & save_cmd "gearboxutil interfaces fec-stats" "gearbox.interfaces_fec_stats" & @@ -2678,10 +2743,14 @@ main() { local asic="$(/usr/local/bin/sonic-cfggen -y /etc/sonic/sonic_version.yml -v asic_type)" local device_type=`sonic-db-cli CONFIG_DB hget 'DEVICE_METADATA|localhost' type` local platform=$(python3 -c "from sonic_py_common import device_info; print(device_info.get_platform())") - # 1st counter snapshot early. Need 2 snapshots to make sense of counters trend. + + # 1st counter snapshot early (self-skips on a Switch-BMC); need 2 for a trend. save_counter_snapshot $asic 1 - save_cmd "show asic-sdk-health-event received" "asic.sdk.health.event" & + if ! $IS_SWITCH_BMC; then + # ASIC/SDK health event - not supported on a Switch-BMC platform. + save_cmd "show asic-sdk-health-event received" "asic.sdk.health.event" & + fi save_cmd "systemd-analyze blame" "systemd.analyze.blame" & save_cmd "systemd-analyze dump" "systemd.analyze.dump" & @@ -2712,17 +2781,22 @@ main() { save_bridge_info & wait + # FRR/BGP/EVPN and gearbox collectors self-skip on a Switch-BMC. save_frr_info & save_bgp_info & save_evpn_info & wait - save_cmd "show interface status -d all" "interface.status" & - save_cmd "show interface transceiver presence" "interface.xcvrs.presence" & - save_cmd "show interface transceiver eeprom --dom" "interface.xcvrs.eeprom" & + # Generic L3 summary (incl. management interfaces) - keep on all platforms. save_cmd "show ip interface -d all" "ip.interface" & - save_cmd "sfputil show eeprom-hexdump" "interface.xcvrs.eeprom.raw" & + if ! $IS_SWITCH_BMC; then + # Front-panel interface/transceiver collectors - none on a Switch-BMC. + save_cmd "show interface status -d all" "interface.status" & + save_cmd "show interface transceiver presence" "interface.xcvrs.presence" & + save_cmd "show interface transceiver eeprom --dom" "interface.xcvrs.eeprom" & + save_cmd "sfputil show eeprom-hexdump" "interface.xcvrs.eeprom.raw" & + fi save_gearbox_data & wait @@ -2731,16 +2805,22 @@ main() { save_cmd "docker exec lldp${sfx} lldpcli show statistics" "lldp${sfx}.statistics" & save_cmd "docker exec lldp${sfx} lldpcli show configuration" "lldp${sfx}.config" & save_cmd "docker exec lldp${sfx} lldpcli show interfaces" "lldp${sfx}.interfaces" & - save_cmd "docker logs bgp${sfx}" "docker.bgp${sfx}.log" & - save_cmd "docker logs swss${sfx}" "docker.swss${sfx}.log" & + if ! $IS_SWITCH_BMC; then + # bgp/swss containers do not run on a Switch-BMC. + save_cmd "docker logs bgp${sfx}" "docker.bgp${sfx}.log" & + save_cmd "docker logs swss${sfx}" "docker.swss${sfx}.log" & + fi done wait - save_cmd "stpctl all" "stp.log" - save_cmd "show spanning-tree" "stp.show" - save_cmd "show spanning-tree statistics" "stp.stats" - save_cmd "show spanning-tree bpdu_guard" "stp.bg" - save_cmd "show spanning-tree root_guard" "stp.rg" + if ! $IS_SWITCH_BMC; then + # Spanning-tree - no STP daemon / stpctl on a Switch-BMC. + save_cmd "stpctl all" "stp.log" + save_cmd "show spanning-tree" "stp.show" + save_cmd "show spanning-tree statistics" "stp.stats" + save_cmd "show spanning-tree bpdu_guard" "stp.bg" + save_cmd "show spanning-tree root_guard" "stp.rg" + fi save_cmd "ps aux" "ps.aux" & save_cmd "top -b -n 1" "top" & @@ -2784,10 +2864,14 @@ main() { save_cmd "who -a" "who" & save_cmd "swapon -s" "swapon" & wait - save_cmd "hdparm -i /dev/sda" "hdparm" & + if ! $IS_SWITCH_BMC; then + # hdparm not installed; a Switch-BMC boots from eMMC/flash (no /dev/sda). + save_cmd "hdparm -i /dev/sda" "hdparm" & + fi save_cmd "ps -AwwL -o user,pid,lwp,ppid,nlwp,pcpu,pri,nice,vsize,rss,tty,stat,wchan:12,start,bsdtime,command" "ps.extended" & wait + # SAI dump (vtysh route-size probe + syncd) self-skips on a Switch-BMC. save_saidump_by_route_size if [ "$asic" = "barefoot" ]; then @@ -2828,7 +2912,7 @@ main() { start_dpu_flow_dump & - # 2nd counter snapshot late. Need 2 snapshots to make sense of counters trend. + # 2nd counter snapshot late (self-skips on a Switch-BMC); need 2 for a trend. save_counter_snapshot $asic 2 # Copying the /etc files to a directory and then tar it @@ -2945,24 +3029,26 @@ remove_secret_from_etc_files() { echo "Remove secret from etc files." # Remove tacacs passkey from tacplus_nss.conf local secret_regex='s/(secret=)([^,|\S]*)(.*)/\1****\3/g' - sed -i -E $secret_regex $dumppath/etc/tacplus_nss.conf + sed_if_exists "$secret_regex" "$dumppath/etc/tacplus_nss.conf" # Remove radius passkey from radius_nss.conf - sed -i -E $secret_regex $dumppath/etc/radius_nss.conf + sed_if_exists "$secret_regex" "$dumppath/etc/radius_nss.conf" # Remove tacacs passkey from common-auth-sonic - sed -i -E 's/(secret=)(\S*)/\1****/g' $dumppath/etc/pam.d/common-auth-sonic + sed_if_exists 's/(secret=)(\S*)/\1****/g' "$dumppath/etc/pam.d/common-auth-sonic" # Remove tacacs passkey from pam_radius_auth.conf - sed -i -E 's/^([^#]\S*\s*)(\S*)/\1****/g' $dumppath/etc/pam_radius_auth.conf + sed_if_exists 's/^([^#]\S*\s*)(\S*)/\1****/g' "$dumppath/etc/pam_radius_auth.conf" # Remove radius passkey from per-server conf file /etc/pam_radius_auth.d/{ip}_{port}.conf - for filename in $dumppath/etc/pam_radius_auth.d/*.conf; do - sed -i -E 's/^([^#]\S*\s*)(\S*)/\1****/g' $filename + # Guard the glob: skip when no *.conf files exist (dir absent or empty). + for filename in "$dumppath"/etc/pam_radius_auth.d/*.conf; do + [ -e "$filename" ] || continue + sed -i -E 's/^([^#]\S*\s*)(\S*)/\1****/g' "$filename" done # Remove snmp community string from snmp.yml - sed -i -E 's/(\s*snmp_\S*community\s*:\s*)(\S*)/\1****/g' $dumppath/etc/sonic/snmp.yml + sed_if_exists 's/(\s*snmp_\S*community\s*:\s*)(\S*)/\1****/g' "$dumppath/etc/sonic/snmp.yml" # Remove secret from /etc/sonic/config_db.json remove_secret_from_config_db_dump_file $dumppath/etc/sonic/config_db.json @@ -3120,6 +3206,7 @@ fi ## platform_name=$(show platform summary --json | python -c 'import sys, json; print(json.load(sys.stdin)["platform"])') supervisor=0 +switch_bmc=0 PLATFORM_ENV_CONF=/usr/share/sonic/device/${platform_name}/platform_env.conf if [ -f "$PLATFORM_ENV_CONF" ]; then source $PLATFORM_ENV_CONF @@ -3127,6 +3214,10 @@ fi if [[ x"$supervisor" == x"1" ]]; then IS_SUPERVISOR=true fi +# Switch-BMC detection, mirroring the supervisor handling above. +if [[ x"$switch_bmc" == x"1" ]]; then + IS_SWITCH_BMC=true +fi if $MKDIR "${LOCKDIR}" &>/dev/null; then trap 'handle_exit' EXIT