Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions gnmi_server/platform_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -872,6 +874,178 @@ 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: <name: "platform" >
elem: <name: "pcieinfo" >
`,
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: <name: "platform" >
elem: <name: "pcieinfo" key: { key: "check" value: "true" } >
`,
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: <name: "platform" >
elem: <name: "pcieinfo" >
`,
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: <name: "platform" >
elem: <name: "pcieinfo" >
`,
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: <name: "platform" >
elem: <name: "pcieinfo" key: { key: "verbose" value: "true" } >
`,
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: <name: "platform" >
elem: <name: "pcieinfo" key: { key: "check" value: "true" } key: { key: "verbose" value: "true" } >
`,
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)
})
}
}

func TestGetShowPlatformSyseeprom(t *testing.T) {
tests := []struct {
desc string
Expand Down
15 changes: 15 additions & 0 deletions show_client/common/platform_apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ 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))
`

// ChassisComponentsPyScript retrieves all chassis components via Platform API
var ChassisComponentsPyScript = `
import json
Expand Down
66 changes: 65 additions & 1 deletion show_client/platform_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -124,6 +125,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
Expand Down Expand Up @@ -543,3 +549,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.GetPathsToPlatformAndHwskuDirsOnHost()

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
}
7 changes: 7 additions & 0 deletions show_client/show_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -241,4 +242,10 @@ var (
showCmdOptionVendorDesc,
sdc.BoolValue,
)

showCmdOptionCheck = sdc.NewShowCmdOption(
"check",
showCmdOptionCheckDesc,
sdc.BoolValue,
)
)
12 changes: 12 additions & 0 deletions show_client/show_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,18 @@ func init() {
nil,
)

// SHOW/platform/pcieinfo
sdc.RegisterCliPath(
[]string{"SHOW", "platform", "pcieinfo"},
getPlatformPcieinfo,
"SHOW/platform/pcieinfo[OPTIONS]: Show device PCIe information",
0,
0,
nil,
showCmdOptionCheck,
showCmdOptionVerbose,
)

// SHOW/platform/syseeprom
sdc.RegisterCliPath(
[]string{"SHOW", "platform", "syseeprom"},
Expand Down
1 change: 1 addition & 0 deletions testdata/PCIEINFO_CHECK.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"name":"PCI Device A","result":"Passed"},{"name":"PCI Device B","result":"Failed"},{"name":"PCIe Device Checking All Test","result":"Failed"}]
1 change: 1 addition & 0 deletions testdata/PCIEINFO_CHECK_RAW.json
Original file line number Diff line number Diff line change
@@ -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"}]
1 change: 1 addition & 0 deletions testdata/PCIEINFO_SHOW.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"bus":"03","dev":"00","fn":"0","id":"1db6","name":"Mellanox Device"}]
Loading