From e4da26c77f54f0c825b7ca0725efbd2ca0ad05e2 Mon Sep 17 00:00:00 2001 From: Judson Wilson Date: Thu, 9 Jul 2026 09:13:40 +0300 Subject: [PATCH] Add `config sdk nvidia-bluefield sdk techsupport-ct-dump ` command. The command is for enabling, in `show techsupport` on NVIDIA SmartSwitch DPUs, a platform-specific sdk dump of the Connection Table through the saisdkdump utility. This part of the implementation creates or deletes a sentinel file in the syncd container, which is the signal used by the `show techsupport` command to determine if the feature is enabled. Signed-off-by: Judson Wilson --- config/plugins/nvidia_bluefield.py | 70 +++++++++++++++++ scripts/generate_dump | 37 ++++++++- tests/config_nvidia_bluefield_test.py | 104 +++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 2 deletions(-) diff --git a/config/plugins/nvidia_bluefield.py b/config/plugins/nvidia_bluefield.py index 9a27183b4a..99fb91ea87 100644 --- a/config/plugins/nvidia_bluefield.py +++ b/config/plugins/nvidia_bluefield.py @@ -46,6 +46,47 @@ CFG_REC_DIR = "config-record" PKT_REC_DIR = "packet-drop" +# SDK techsupport CT dump sentinel path. Keep in sync across: +# sonic-utilities/config/plugins/nvidia_bluefield.py (SDK_TECHSUPPORT_CT_DUMP_SENTINEL) +# sonic-utilities/scripts/generate_dump (sdk_techsupport_ct_dump_sentinel) +# sonic-sairedis/syncd/scripts/syncd_init_common.sh (config_syncd_nvidia_bluefield) +SDK_TECHSUPPORT_CT_DUMP_RUN_DIR = '/var/run/sonic-platform-nvidia-bluefield' +SDK_TECHSUPPORT_CT_DUMP_SENTINEL = f'{SDK_TECHSUPPORT_CT_DUMP_RUN_DIR}/sdk-techsupport-ct-dump.enabled' + + +def is_sdk_techsupport_ct_dump_enabled(docker_client): + """Probe whether SDK CT dumps are enabled for techsupport. + + The probe command succeeds (rc == 0) whenever it runs in syncd, and prints + the current state, so a missing sentinel (disabled) is distinguished from a + failed probe (syncd unreachable / exec error). + + Returns: + tuple: (rc, enabled) where rc is the probe return code. ``enabled`` is a + bool that is only meaningful when rc == 0; it is None on probe failure. + """ + rc, stdout = run_in_syncd( + f"sh -c 'if [ -f {SDK_TECHSUPPORT_CT_DUMP_SENTINEL} ]; then echo enabled; else echo disabled; fi'", + docker_client) + + if rc != 0: + return rc, None + + return rc, stdout.strip() == 'enabled' + + +def set_sdk_techsupport_ct_dump_state(state, docker_client): + """Enable or disable SDK CT dumps in techsupport via sentinel file.""" + if state == 'enabled': + cmd = ( + f"sh -c 'mkdir -p {SDK_TECHSUPPORT_CT_DUMP_RUN_DIR} && " + f"touch {SDK_TECHSUPPORT_CT_DUMP_SENTINEL}'" + ) + else: + cmd = f"sh -c 'rm -f {SDK_TECHSUPPORT_CT_DUMP_SENTINEL}'" + + return run_in_syncd(cmd, docker_client) + def run_in_syncd(cmd, docker_client): """Run a command in the syncd container using Docker Python SDK. @@ -328,6 +369,35 @@ def config_record(state): sys.exit(rc) +@sdk.command('techsupport-ct-dump') +@click.argument('state', type=click.Choice(['enabled', 'disabled'])) +def techsupport_ct_dump(state): + """Enable or disable SDK Connection Table dumps in techsupport""" + import docker + docker_client = docker.from_env() + + rc, ct_dump_enabled = is_sdk_techsupport_ct_dump_enabled(docker_client) + if rc != 0: + click.echo( + "Could not probe SDK Connection Table dump state in techsupport " + f"(syncd error, rc={rc})", err=True) + sys.exit(rc) + + if ct_dump_enabled and state == 'enabled': + click.echo("SDK Connection Table dump in techsupport is already enabled") + sys.exit(0) + elif not ct_dump_enabled and state == 'disabled': + click.echo("SDK Connection Table dump in techsupport is already disabled") + sys.exit(0) + + rc, stdout = set_sdk_techsupport_ct_dump_state(state, docker_client) + if rc != 0: + click.echo(f"Could not set SDK Connection Table dump in techsupport to {state}: {stdout}", err=True) + sys.exit(rc) + + click.echo(f"SDK Connection Table dump in techsupport {state}.") + + def register(cli): version_info = device_info.get_sonic_version_info() if (version_info and version_info.get('asic_type') == 'nvidia-bluefield'): diff --git a/scripts/generate_dump b/scripts/generate_dump index b66f092583..eee04a059f 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -2153,9 +2153,41 @@ collect_nvidia_bluefield() { local timeout_cmd="timeout --foreground ${TIMEOUT_MIN}m" local sai_dump_folder="/root/saisdkdump" local sai_dump_filename="${sai_dump_folder}/sai_sdk_dump_$(date +"%m_%d_%Y_%I_%M_%p")" + # SDK techsupport CT dump sentinel path. Keep in sync across: + # config/plugins/nvidia_bluefield.py (SDK_TECHSUPPORT_CT_DUMP_SENTINEL) + # scripts/generate_dump (sdk_techsupport_ct_dump_sentinel) + # sonic-sairedis/syncd/scripts/syncd_init_common.sh (config_syncd_nvidia_bluefield) + local sdk_techsupport_ct_dump_sentinel="/var/run/sonic-platform-nvidia-bluefield/sdk-techsupport-ct-dump.enabled" + local sdk_techsupport_saisdkdump_profile="" ${CMD_PREFIX}docker exec syncd mkdir -p $sai_dump_folder - ${CMD_PREFIX}docker exec syncd saisdkdump -f $sai_dump_filename + + local prepare_ct_dump_profile_script=' +profile="$1" +# Start from the running SAI profile if present, otherwise an empty file +if [ -f /tmp/sai.profile ]; then cp /tmp/sai.profile "$profile"; else : > "$profile"; fi +# Guard against a source profile with no trailing newline. +if [ -s "$profile" ] && [ "$(tail -c1 "$profile")" != "" ]; then + echo >> "$profile" +fi +# Drop any existing CT_TABLE_DUMP_ENABLE=... lines, then append CT_TABLE_DUMP_ENABLE=true. +sed -i "/^CT_TABLE_DUMP_ENABLE=/d" "$profile" +echo CT_TABLE_DUMP_ENABLE=true >> "$profile" +' + + if docker exec syncd test -f $sdk_techsupport_ct_dump_sentinel; then + if $NOOP; then + echo "docker exec syncd mktemp /tmp/techsupport-saisdkdump.XXXXXX" + sdk_techsupport_saisdkdump_profile="/tmp/techsupport-saisdkdump.XXXXXX" + else + sdk_techsupport_saisdkdump_profile=$(docker exec syncd mktemp /tmp/techsupport-saisdkdump.XXXXXX) + fi + + ${CMD_PREFIX}docker exec syncd sh -c "$prepare_ct_dump_profile_script" _ "$sdk_techsupport_saisdkdump_profile" + ${CMD_PREFIX}docker exec syncd saisdkdump -f $sai_dump_filename -p $sdk_techsupport_saisdkdump_profile + else + ${CMD_PREFIX}docker exec syncd saisdkdump -f $sai_dump_filename + fi if [ $? != 0 ]; then echo "Failed to collect saisdkdump." @@ -2169,6 +2201,9 @@ collect_nvidia_bluefield() { ${CMD_PREFIX}rm -rf $sai_dump_folder ${CMD_PREFIX}docker exec syncd rm -rf $sai_dump_folder + if [ -n "$sdk_techsupport_saisdkdump_profile" ]; then + ${CMD_PREFIX}docker exec syncd rm -f "$sdk_techsupport_saisdkdump_profile" + fi DUMP_FILE=/usr/bin/platform-dump.sh if [ -f "$DUMP_FILE" ]; then diff --git a/tests/config_nvidia_bluefield_test.py b/tests/config_nvidia_bluefield_test.py index 31cfa95acb..35d9155362 100644 --- a/tests/config_nvidia_bluefield_test.py +++ b/tests/config_nvidia_bluefield_test.py @@ -29,7 +29,11 @@ run_nasa_cli, cleanup_dump_files, get_packet_debug_mode, - get_sai_debug_mode + get_sai_debug_mode, + is_sdk_techsupport_ct_dump_enabled, + set_sdk_techsupport_ct_dump_state, + SDK_TECHSUPPORT_CT_DUMP_SENTINEL, + SDK_TECHSUPPORT_CT_DUMP_RUN_DIR, ) @@ -161,6 +165,35 @@ def test_debug_mode_disabled_error(self): assert status == 'disabled' assert filename is None + def test_is_sdk_techsupport_ct_dump_enabled(self): + self.container.exec_run.return_value = (0, b"enabled\n") + assert is_sdk_techsupport_ct_dump_enabled(self.docker_client) == (0, True) + cmd = self.container.exec_run.call_args.args[0] + assert cmd == ( + f"sh -c 'if [ -f {SDK_TECHSUPPORT_CT_DUMP_SENTINEL} ]; " + f"then echo enabled; else echo disabled; fi'" + ) + + self.container.exec_run.return_value = (0, b"disabled\n") + assert is_sdk_techsupport_ct_dump_enabled(self.docker_client) == (0, False) + + # A failed probe (syncd error) must be distinguishable from "disabled". + self.container.exec_run.return_value = (1, b"") + assert is_sdk_techsupport_ct_dump_enabled(self.docker_client) == (1, None) + + def test_set_sdk_techsupport_ct_dump_state(self): + set_sdk_techsupport_ct_dump_state('enabled', self.docker_client) + cmd = self.container.exec_run.call_args.args[0] + assert cmd == ( + f"sh -c 'mkdir -p {SDK_TECHSUPPORT_CT_DUMP_RUN_DIR} && " + f"touch {SDK_TECHSUPPORT_CT_DUMP_SENTINEL}'" + ) + + self.container.exec_run.reset_mock() + set_sdk_techsupport_ct_dump_state('disabled', self.docker_client) + cmd = self.container.exec_run.call_args.args[0] + assert cmd == f"sh -c 'rm -f {SDK_TECHSUPPORT_CT_DUMP_SENTINEL}'" + class TestNvidiaBluefieldCliSdk(TestCase): @@ -262,3 +295,72 @@ def test_config_record_cli( assert '/usr/sbin/cli/nasa_cli.py -u --exit_on_failure -l /tmp/nasa_cli_cmd.txt' in cmd_run assert result.exit_code == 0 assert "Config recording disabled" in result.output + + @mock.patch('docker.from_env') + @mock.patch('config.plugins.nvidia_bluefield.is_sdk_techsupport_ct_dump_enabled', return_value=(0, False)) + @mock.patch('config.plugins.nvidia_bluefield.set_sdk_techsupport_ct_dump_state', return_value=(0, "")) + @mock.patch('sonic_py_common.device_info.get_sonic_version_info', return_value=ASIC_TYPE_NVDA_BF) + def test_techsupport_ct_dump_cli( + self, + m_device_info, # noqa: ARG002 + m_set_state, + m_is_enabled, + m_docker + ): + helper = util_base.UtilHelper() + helper.load_and_register_plugins(plugins, config.config) + runner = CliRunner() + + result = runner.invoke( + config.config.commands["platform"].commands["nvidia-bluefield"].commands["sdk"], + ["techsupport-ct-dump", "enabled"] + ) + assert result.exit_code == 0 + assert "SDK Connection Table dump in techsupport enabled." in result.output + m_set_state.assert_called_once_with('enabled', m_docker.return_value) + + m_set_state.reset_mock() + m_is_enabled.return_value = (0, True) + + result = runner.invoke( + config.config.commands["platform"].commands["nvidia-bluefield"].commands["sdk"], + ["techsupport-ct-dump", "enabled"] + ) + assert result.exit_code == 0 + assert "already enabled" in result.output + m_set_state.assert_not_called() + + m_is_enabled.return_value = (0, True) + m_set_state.reset_mock() + m_set_state.return_value = (0, "") + + result = runner.invoke( + config.config.commands["platform"].commands["nvidia-bluefield"].commands["sdk"], + ["techsupport-ct-dump", "disabled"] + ) + assert result.exit_code == 0 + assert "SDK Connection Table dump in techsupport disabled." in result.output + m_set_state.assert_called_once_with('disabled', m_docker.return_value) + + m_set_state.reset_mock() + m_is_enabled.return_value = (0, False) + + result = runner.invoke( + config.config.commands["platform"].commands["nvidia-bluefield"].commands["sdk"], + ["techsupport-ct-dump", "disabled"] + ) + assert result.exit_code == 0 + assert "already disabled" in result.output + m_set_state.assert_not_called() + + # A failed probe must report the syncd error, not exit successfully. + m_set_state.reset_mock() + m_is_enabled.return_value = (1, None) + + result = runner.invoke( + config.config.commands["platform"].commands["nvidia-bluefield"].commands["sdk"], + ["techsupport-ct-dump", "enabled"] + ) + assert result.exit_code == 1 + assert "Could not probe SDK Connection Table dump state" in result.output + m_set_state.assert_not_called()