From ab45decb906d7165cfb12a971d728903881bb6ad Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 4 May 2026 15:54:19 +0000 Subject: [PATCH 1/5] added pcieinfo --- gnmi_server/platform_cli_test.go | 174 ++++++++++++++++++++++++++++ show_client/common/host.go | 13 +++ show_client/common/platform_apis.go | 15 +++ show_client/platform_cli.go | 66 ++++++++++- testdata/PCIEINFO_CHECK.json | 1 + testdata/PCIEINFO_CHECK_RAW.json | 1 + testdata/PCIEINFO_SHOW.json | 1 + 7 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 testdata/PCIEINFO_CHECK.json create mode 100644 testdata/PCIEINFO_CHECK_RAW.json create mode 100644 testdata/PCIEINFO_SHOW.json diff --git a/gnmi_server/platform_cli_test.go b/gnmi_server/platform_cli_test.go index 44172141..29d5c49d 100644 --- a/gnmi_server/platform_cli_test.go +++ b/gnmi_server/platform_cli_test.go @@ -7,6 +7,8 @@ package gnmi import ( "crypto/tls" "fmt" + "os" + "strings" "testing" "time" @@ -870,3 +872,175 @@ func TestGetShowPlatformSsdhealth(t *testing.T) { }) } } + +func TestGetShowPlatformPcieinfo(t *testing.T) { + showOutputFilename := "../testdata/PCIEINFO_SHOW.json" + checkOutputFilename := "../testdata/PCIEINFO_CHECK.json" + checkRawOutputFilename := "../testdata/PCIEINFO_CHECK_RAW.json" + invalidOutputFilename := "../testdata/INVALID_JSON.txt" + + showOutputBytes, err := os.ReadFile(showOutputFilename) + if err != nil { + t.Fatalf("read file %v err: %v", showOutputFilename, err) + } + checkOutputBytes, err := os.ReadFile(checkOutputFilename) + if err != nil { + t.Fatalf("read file %v err: %v", checkOutputFilename, err) + } + checkRawOutputBytes, err := os.ReadFile(checkRawOutputFilename) + if err != nil { + t.Fatalf("read file %v err: %v", checkRawOutputFilename, err) + } + + tests := []struct { + desc string + pathTarget string + textPbPath string + wantRetCode codes.Code + wantRespVal interface{} + valTest bool + testInit func() *gomonkey.Patches + }{ + { + desc: "query SHOW platform pcieinfo", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: showOutputBytes, + valTest: true, + testInit: func() *gomonkey.Patches { + return gomonkey.ApplyFunc(sccommon.GetDataFromHostCommand, func(cmd string) (string, error) { + if strings.Contains(cmd, "get_pcie_device") { + return string(showOutputBytes), nil + } + return "", fmt.Errorf("unexpected command: %s", cmd) + }) + }, + }, + { + desc: "query SHOW platform pcieinfo check", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: checkOutputBytes, + valTest: true, + testInit: func() *gomonkey.Patches { + return gomonkey.ApplyFunc(sccommon.GetDataFromHostCommand, func(cmd string) (string, error) { + if strings.Contains(cmd, "get_pcie_check") { + // Return raw output with extra fields (bus/dev/fn/id) as a real platform would. + // The handler must strip them and return only name/result. + return string(checkRawOutputBytes), nil + } + return "", fmt.Errorf("unexpected command: %s", cmd) + }) + }, + }, + { + desc: "query SHOW platform pcieinfo invalid JSON output", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + `, + wantRetCode: codes.NotFound, + wantRespVal: nil, + valTest: false, + testInit: func() *gomonkey.Patches { + return MockNSEnterOutput(t, invalidOutputFilename) + }, + }, + { + desc: "query SHOW platform pcieinfo host command error", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + `, + wantRetCode: codes.NotFound, + wantRespVal: nil, + valTest: false, + testInit: func() *gomonkey.Patches { + return gomonkey.ApplyFunc(sccommon.GetDataFromHostCommand, func(cmd string) (string, error) { + return "", fmt.Errorf("simulated command failure") + }) + }, + }, + { + desc: "query SHOW platform pcieinfo with verbose flag", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: showOutputBytes, + valTest: true, + testInit: func() *gomonkey.Patches { + return gomonkey.ApplyFunc(sccommon.GetDataFromHostCommand, func(cmd string) (string, error) { + if strings.Contains(cmd, "get_pcie_device") { + return string(showOutputBytes), nil + } + return "", fmt.Errorf("unexpected command: %s", cmd) + }) + }, + }, + { + desc: "query SHOW platform pcieinfo check with verbose flag", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: checkOutputBytes, + valTest: true, + testInit: func() *gomonkey.Patches { + return gomonkey.ApplyFunc(sccommon.GetDataFromHostCommand, func(cmd string) (string, error) { + if strings.Contains(cmd, "get_pcie_check") { + return string(checkRawOutputBytes), nil + } + return "", fmt.Errorf("unexpected command: %s", cmd) + }) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + var patches *gomonkey.Patches + if tt.testInit != nil { + patches = tt.testInit() + } + defer func() { + if patches != nil { + patches.Reset() + } + }() + + s := createServer(t, ServerPort) + go runServer(t, s) + defer s.ForceStop() + + tlsConfig := &tls.Config{InsecureSkipVerify: true} + opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} + + conn, err := grpc.Dial(TargetAddr, opts...) + if err != nil { + t.Fatalf("Dialing to %q failed: %v", TargetAddr, err) + } + defer conn.Close() + + gClient := pb.NewGNMIClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), QueryTimeout*time.Second) + defer cancel() + + runTestGet(t, ctx, gClient, tt.pathTarget, tt.textPbPath, tt.wantRetCode, tt.wantRespVal, tt.valTest) + }) + } +} diff --git a/show_client/common/host.go b/show_client/common/host.go index 99e2e167..b384c20d 100644 --- a/show_client/common/host.go +++ b/show_client/common/host.go @@ -252,6 +252,19 @@ func GetPlatformConfigFilePath() string { return "" } +// GetPlatformPath returns the best available platform path. +// For host-executed commands, prefer the host device path derived from platform +// even when the directory is not visible from inside the container. +// Falls back to container platform path only when platform is unavailable. +func GetPlatformPath() string { + platform := GetPlatform() + if platform != "" { + return filepath.Join(HostDevicePath, platform) + } + + return ContainerPlatformPath +} + func IsExpectedValue(val string, expectedVal string) bool { if strings.TrimSpace(val) == expectedVal { return true diff --git a/show_client/common/platform_apis.go b/show_client/common/platform_apis.go index a1f0c897..7fe5190c 100644 --- a/show_client/common/platform_apis.go +++ b/show_client/common/platform_apis.go @@ -17,3 +17,18 @@ except ImportError as e: s = SsdUtil('%s') print(json.dumps({'model': str(s.get_model()), 'firmware': str(s.get_firmware()), 'serial': str(s.get_serial()), 'health': str(s.get_health()), 'temperature': str(s.get_temperature()), 'vendor_output': str(s.get_vendor_output())})) ` + +// PcieInfoPyScript is the Python script template that loads the platform-specific +// or generic Pcie/PcieUtil and retrieves PCIe information as JSON. +// It expects two %s format parameters: platform path, then the API call (e.g., pcie.get_pcie_device() or pcie.get_pcie_check()). +var PcieInfoPyScript = ` +import json +platform_path = %s +try: + from sonic_platform.pcie import Pcie + pcie = Pcie(platform_path) +except ImportError: + from sonic_platform_base.sonic_pcie.pcie_common import PcieUtil + pcie = PcieUtil(platform_path) +print(json.dumps(%s)) +` diff --git a/show_client/platform_cli.go b/show_client/platform_cli.go index 8c3f7347..c3ac40dc 100644 --- a/show_client/platform_cli.go +++ b/show_client/platform_cli.go @@ -15,7 +15,8 @@ import ( ) const ( - chassisKey = "chassis 1" + chassisKey = "chassis 1" + pcieCheckSummaryName = "PCIe Device Checking All Test" ) // PlatformSummary represents the output structure for show platform summary @@ -110,6 +111,11 @@ type SsdHealthInfo struct { VendorOutput string `json:"vendor_output,omitempty"` } +type pcieCheckResult struct { + Name string `json:"name"` + Result string `json:"result"` +} + // getPlatformSummary implements the "show platform summary" command func getPlatformSummary(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error) { // Get version info to extract ASIC type @@ -444,3 +450,61 @@ func getPlatformSsdhealth(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, erro return json.Marshal(ssdHealth) } + +func getPlatformPcieinfo(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error) { + apiCall := "pcie.get_pcie_device()" + if checkOpt, ok := options["check"].Bool(); ok && checkOpt { + apiCall = "pcie.get_pcie_check()" + } + + platformPath := common.GetPlatformPath() + + pyScript := fmt.Sprintf(common.PcieInfoPyScript, strconv.Quote(platformPath), apiCall) + escaped := strings.ReplaceAll(pyScript, "'", `'\''`) + pyCmd := fmt.Sprintf("python3 -c '%s'", escaped) + + output, err := common.GetDataFromHostCommand(pyCmd) + if err != nil { + trimmedOutput := strings.TrimSpace(output) + log.Errorf("Failed to get PCIe info from host command: %v, output: %s", err, trimmedOutput) + return nil, fmt.Errorf("failed to get PCIe info from host command: %w, output: %s", err, trimmedOutput) + } + + output = strings.TrimSpace(output) + if output == "" { + return []byte("[]"), nil + } + + if !json.Valid([]byte(output)) { + return nil, fmt.Errorf("invalid JSON output from PCIe host command: %s", strings.TrimSpace(output)) + } + + if checkOpt, ok := options["check"].Bool(); ok && checkOpt { + var normalized []pcieCheckResult + if err := json.Unmarshal([]byte(output), &normalized); err != nil { + return nil, fmt.Errorf("failed to parse PCIe check output: %w", err) + } + + overallResult := "Passed" + for i := range normalized { + if normalized[i].Result != "Passed" { + normalized[i].Result = "Failed" + overallResult = "Failed" + } + } + + normalized = append(normalized, pcieCheckResult{ + Name: pcieCheckSummaryName, + Result: overallResult, + }) + + normalizedBytes, err := json.Marshal(normalized) + if err != nil { + return nil, fmt.Errorf("failed to encode PCIe check output: %w", err) + } + + return normalizedBytes, nil + } + + return []byte(output), nil +} diff --git a/testdata/PCIEINFO_CHECK.json b/testdata/PCIEINFO_CHECK.json new file mode 100644 index 00000000..46e1d74d --- /dev/null +++ b/testdata/PCIEINFO_CHECK.json @@ -0,0 +1 @@ +[{"name":"PCI Device A","result":"Passed"},{"name":"PCI Device B","result":"Failed"},{"name":"PCIe Device Checking All Test","result":"Failed"}] diff --git a/testdata/PCIEINFO_CHECK_RAW.json b/testdata/PCIEINFO_CHECK_RAW.json new file mode 100644 index 00000000..e416c5d0 --- /dev/null +++ b/testdata/PCIEINFO_CHECK_RAW.json @@ -0,0 +1 @@ +[{"bus":"03","dev":"00","fn":"0","id":"1db6","name":"PCI Device A","result":"Passed"},{"bus":"03","dev":"01","fn":"0","id":"1db7","name":"PCI Device B","result":"Failed"}] diff --git a/testdata/PCIEINFO_SHOW.json b/testdata/PCIEINFO_SHOW.json new file mode 100644 index 00000000..45a359c4 --- /dev/null +++ b/testdata/PCIEINFO_SHOW.json @@ -0,0 +1 @@ +[{"bus":"03","dev":"00","fn":"0","id":"1db6","name":"Mellanox Device"}] From 8b5766fd75b528331cbb76ce7fec9e77ca9cb125 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 4 May 2026 18:10:18 +0000 Subject: [PATCH 2/5] added to show paths --- show_client/show_paths.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/show_client/show_paths.go b/show_client/show_paths.go index 3d57186f..0f712d1f 100644 --- a/show_client/show_paths.go +++ b/show_client/show_paths.go @@ -1173,4 +1173,16 @@ func init() { 0, nil, ) + + // SHOW/platform/pcieinfo + sdc.RegisterCliPath( + []string{"SHOW", "platform", "pcieinfo"}, + getPlatformPcieinfo, + "SHOW/platform/pcieinfo[OPTIONS]: Show device PCIe information", + 0, + 0, + nil, + showCmdOptionCheck, + showCmdOptionVerbose, + ) } From c28e64eca665b12cedc7cfbf50cf4b4d5473154f Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 4 May 2026 20:01:14 +0000 Subject: [PATCH 3/5] added opt --- show_client/show_opts.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/show_client/show_opts.go b/show_client/show_opts.go index 028ffc2a..3340233e 100644 --- a/show_client/show_opts.go +++ b/show_client/show_opts.go @@ -36,6 +36,7 @@ const ( showCmdOptionPsuIndexDesc = "[index=INTEGER] Display a specific PSU by index" showCmdOptionHistoryDesc = "[history=true] Display historical PFC statistics" showCmdOptionVendorDesc = "[vendor=true] Show vendor output (extended output if provided by platform vendor)" + showCmdOptionCheckDesc = "[check=true] Validate PCIe devices against expected list" ) // Option keys @@ -241,4 +242,10 @@ var ( showCmdOptionVendorDesc, sdc.BoolValue, ) + + showCmdOptionCheck = sdc.NewShowCmdOption( + "check", + showCmdOptionCheckDesc, + sdc.BoolValue, + ) ) From 7d9734d7c81f1b1b3a05647494703261c3d5f8aa Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 5 May 2026 15:29:05 +0000 Subject: [PATCH 4/5] fixes --- gnmi_server/platform_cli_test.go | 2 +- show_client/common/host.go | 13 ------------- show_client/common/platform_apis.go | 2 +- show_client/platform_cli.go | 2 +- show_client/show_paths.go | 4 ++-- 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/gnmi_server/platform_cli_test.go b/gnmi_server/platform_cli_test.go index a1456493..1105fde4 100644 --- a/gnmi_server/platform_cli_test.go +++ b/gnmi_server/platform_cli_test.go @@ -1210,4 +1210,4 @@ func TestGetShowPlatformSyseeprom(t *testing.T) { runTestGet(t, ctx, gClient, tt.pathTarget, tt.textPbPath, tt.wantRetCode, tt.wantRespVal, tt.valTest) }) } -} \ No newline at end of file +} diff --git a/show_client/common/host.go b/show_client/common/host.go index b384c20d..99e2e167 100644 --- a/show_client/common/host.go +++ b/show_client/common/host.go @@ -252,19 +252,6 @@ func GetPlatformConfigFilePath() string { return "" } -// GetPlatformPath returns the best available platform path. -// For host-executed commands, prefer the host device path derived from platform -// even when the directory is not visible from inside the container. -// Falls back to container platform path only when platform is unavailable. -func GetPlatformPath() string { - platform := GetPlatform() - if platform != "" { - return filepath.Join(HostDevicePath, platform) - } - - return ContainerPlatformPath -} - func IsExpectedValue(val string, expectedVal string) bool { if strings.TrimSpace(val) == expectedVal { return true diff --git a/show_client/common/platform_apis.go b/show_client/common/platform_apis.go index 86c9b9af..f0311f51 100644 --- a/show_client/common/platform_apis.go +++ b/show_client/common/platform_apis.go @@ -46,4 +46,4 @@ if not eeprom: sys.exit(1) sys_eeprom_data = eeprom.read_eeprom() eeprom.decode_eeprom(sys_eeprom_data) -` \ No newline at end of file +` diff --git a/show_client/platform_cli.go b/show_client/platform_cli.go index 6177b3b1..5a805890 100644 --- a/show_client/platform_cli.go +++ b/show_client/platform_cli.go @@ -481,7 +481,7 @@ func getPlatformPcieinfo(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error apiCall = "pcie.get_pcie_check()" } - platformPath := common.GetPlatformPath() + platformPath, _ := common.GetPathsToPlatformAndHwskuDirsOnHost() pyScript := fmt.Sprintf(common.PcieInfoPyScript, strconv.Quote(platformPath), apiCall) escaped := strings.ReplaceAll(pyScript, "'", `'\''`) diff --git a/show_client/show_paths.go b/show_client/show_paths.go index 6c0b9a4d..f286676c 100644 --- a/show_client/show_paths.go +++ b/show_client/show_paths.go @@ -1184,7 +1184,7 @@ func init() { nil, showCmdOptionCheck, showCmdOptionVerbose, - ) + ) // SHOW/platform/syseeprom sdc.RegisterCliPath( @@ -1195,4 +1195,4 @@ func init() { 0, nil, ) -} \ No newline at end of file +} From ba31a9424219bc7dbc12bbe2f653b55eeb8aeb1b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 5 May 2026 15:37:33 +0000 Subject: [PATCH 5/5] gofmt fix --- show_client/common/platform_apis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/show_client/common/platform_apis.go b/show_client/common/platform_apis.go index a54142e5..a2a14c84 100644 --- a/show_client/common/platform_apis.go +++ b/show_client/common/platform_apis.go @@ -102,4 +102,4 @@ if not eeprom: sys.exit(1) sys_eeprom_data = eeprom.read_eeprom() eeprom.decode_eeprom(sys_eeprom_data) -` \ No newline at end of file +`