From ebce5db18642e80ec794a814ccbdbe8ee881eafe Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 28 Apr 2026 11:40:31 +0000 Subject: [PATCH 1/5] Added show platform firmware status command --- gnmi_server/platform_firmware_cli_test.go | 409 ++++++++++++++++++ .../helpers/platform_firmware_helper.go | 248 +++++++++++ show_client/platform_cli.go | 33 ++ show_client/show_paths.go | 11 + 4 files changed, 701 insertions(+) create mode 100644 gnmi_server/platform_firmware_cli_test.go create mode 100644 show_client/helpers/platform_firmware_helper.go diff --git a/gnmi_server/platform_firmware_cli_test.go b/gnmi_server/platform_firmware_cli_test.go new file mode 100644 index 00000000..052d16bb --- /dev/null +++ b/gnmi_server/platform_firmware_cli_test.go @@ -0,0 +1,409 @@ +package gnmi + +import ( + "crypto/tls" + "fmt" + "strings" + "testing" + "time" + + "github.com/agiledragon/gomonkey/v2" + pb "github.com/openconfig/gnmi/proto/gnmi" + + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + + "github.com/sonic-net/sonic-gnmi/show_client/common" + "github.com/sonic-net/sonic-gnmi/show_client/helpers" +) + +func TestGetShowPlatformFirmwareStatus(t *testing.T) { + // Expected output matching actual device output (MSN2700) + expectedOutput := `[{"chassis":"MSN2700","module":"N/A","component":"ONIE","version":"2018.05-5.2.0004-9600","description":"ONIE - Open Network Install Environment"},{"chassis":"","module":"","component":"SSD","version":"0115-000","description":"SSD - Solid-State Drive"},{"chassis":"","module":"","component":"BIOS","version":"0ABZS017_02.02.002","description":"BIOS - Basic Input/Output System"},{"chassis":"","module":"","component":"CPLD1","version":"CPLD000085_REV2000","description":"CPLD - Complex Programmable Logic Device"},{"chassis":"","module":"","component":"CPLD2","version":"CPLD000128_REV0600","description":"CPLD - Complex Programmable Logic Device"},{"chassis":"","module":"","component":"CPLD3","version":"CPLD000000_REV0300","description":"CPLD - Complex Programmable Logic Device"}]` + + // Expected output for modular chassis test + expectedModularOutput := `[{"chassis":"ModularChassis","module":"","component":"BIOS","version":"1.0.0","description":"System BIOS"},{"chassis":"","module":"Module1","component":"FPGA","version":"2.1.0","description":"Module FPGA"},{"chassis":"","module":"","component":"CPLD","version":"3.0.0","description":"Module CPLD"}]` + + expectedEmptyOutput := `[]` + + tests := []struct { + desc string + pathTarget string + textPbPath string + wantRetCode codes.Code + wantRespVal interface{} + valTest bool + testInit func() func() + }{ + { + desc: "query SHOW platform firmware status success", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(expectedOutput), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock helper functions for MSN2700 non-modular chassis + patches := gomonkey.NewPatches() + + // Mock GetAllFirmwareData to return MSN2700 components + patches.ApplyFunc(helpers.GetAllFirmwareData, func() ([]helpers.FirmwareData, error) { + return []helpers.FirmwareData{ + { + Chassis: "MSN2700", + Module: "N/A", + Component: "ONIE", + Version: "2018.05-5.2.0004-9600", + Description: "ONIE - Open Network Install Environment", + }, + { + Chassis: "", + Module: "", + Component: "SSD", + Version: "0115-000", + Description: "SSD - Solid-State Drive", + }, + { + Chassis: "", + Module: "", + Component: "BIOS", + Version: "0ABZS017_02.02.002", + Description: "BIOS - Basic Input/Output System", + }, + { + Chassis: "", + Module: "", + Component: "CPLD1", + Version: "CPLD000085_REV2000", + Description: "CPLD - Complex Programmable Logic Device", + }, + { + Chassis: "", + Module: "", + Component: "CPLD2", + Version: "CPLD000128_REV0600", + Description: "CPLD - Complex Programmable Logic Device", + }, + { + Chassis: "", + Module: "", + Component: "CPLD3", + Version: "CPLD000000_REV0300", + Description: "CPLD - Complex Programmable Logic Device", + }, + }, nil + }) + + return func() { + patches.Reset() + } + }, + }, + { + desc: "query SHOW platform firmware status modular chassis", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(expectedModularOutput), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock helper function for modular chassis + patches := gomonkey.NewPatches() + + patches.ApplyFunc(helpers.GetAllFirmwareData, func() ([]helpers.FirmwareData, error) { + return []helpers.FirmwareData{ + { + Chassis: "ModularChassis", + Module: "", + Component: "BIOS", + Version: "1.0.0", + Description: "System BIOS", + }, + { + Chassis: "", + Module: "Module1", + Component: "FPGA", + Version: "2.1.0", + Description: "Module FPGA", + }, + { + Chassis: "", + Module: "", + Component: "CPLD", + Version: "3.0.0", + Description: "Module CPLD", + }, + }, nil + }) + + return func() { + patches.Reset() + } + }, + }, + { + desc: "query SHOW platform firmware status no components", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(expectedEmptyOutput), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock helper function returning empty data + patches := gomonkey.NewPatches() + + patches.ApplyFunc(helpers.GetAllFirmwareData, func() ([]helpers.FirmwareData, error) { + return []helpers.FirmwareData{}, nil + }) + + return func() { + patches.Reset() + } + }, + }, + { + desc: "test helper functions coverage - chassis name and components", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(`[{"chassis":"TestChassis","module":"N/A","component":"BIOS","version":"1.0.0","description":"Test BIOS"},{"chassis":"","module":"","component":"CPLD","version":"2.0.0","description":"Test CPLD"}]`), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock individual helper functions to test integration logic + patches := gomonkey.NewPatches() + + patches.ApplyFunc(helpers.GetChassisName, func() (string, error) { + return "TestChassis", nil + }) + + patches.ApplyFunc(helpers.GetChassisComponents, func() ([]helpers.ComponentInfo, error) { + return []helpers.ComponentInfo{ + { + Name: "BIOS", + FirmwareVersion: "1.0.0", + Description: "Test BIOS", + }, + { + Name: "CPLD", + FirmwareVersion: "2.0.0", + Description: "Test CPLD", + }, + }, nil + }) + + patches.ApplyFunc(helpers.GetModuleComponents, func() ([]helpers.ModuleComponentInfo, error) { + // Return empty for non-modular chassis + return []helpers.ModuleComponentInfo{}, nil + }) + + return func() { + patches.Reset() + } + }, + }, + { + desc: "test helper functions coverage - modular chassis with modules", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(`[{"chassis":"Modular","module":"","component":"BIOS","version":"1.0","description":"Chassis BIOS"},{"chassis":"","module":"LineCard1","component":"FPGA","version":"2.0","description":"Module FPGA"},{"chassis":"","module":"","component":"CPLD","version":"3.0","description":"Module CPLD"}]`), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock helper functions for modular chassis with modules + patches := gomonkey.NewPatches() + + patches.ApplyFunc(helpers.GetChassisName, func() (string, error) { + return "Modular", nil + }) + + patches.ApplyFunc(helpers.GetChassisComponents, func() ([]helpers.ComponentInfo, error) { + return []helpers.ComponentInfo{ + { + Name: "BIOS", + FirmwareVersion: "1.0", + Description: "Chassis BIOS", + }, + }, nil + }) + + patches.ApplyFunc(helpers.GetModuleComponents, func() ([]helpers.ModuleComponentInfo, error) { + return []helpers.ModuleComponentInfo{ + { + ModuleName: "LineCard1", + Name: "FPGA", + FirmwareVersion: "2.0", + Description: "Module FPGA", + }, + { + ModuleName: "LineCard1", + Name: "CPLD", + FirmwareVersion: "3.0", + Description: "Module CPLD", + }, + }, nil + }) + + return func() { + patches.Reset() + } + }, + }, + { + desc: "test helper functions error handling - platform API failures", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(expectedEmptyOutput), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock helper functions returning errors to test error handling + patches := gomonkey.NewPatches() + + patches.ApplyFunc(helpers.GetChassisName, func() (string, error) { + return "", fmt.Errorf("chassis name error") + }) + + patches.ApplyFunc(helpers.GetChassisComponents, func() ([]helpers.ComponentInfo, error) { + return nil, fmt.Errorf("chassis components error") + }) + + patches.ApplyFunc(helpers.GetModuleComponents, func() ([]helpers.ModuleComponentInfo, error) { + return nil, fmt.Errorf("module components error") + }) + + return func() { + patches.Reset() + } + }, + }, + { + desc: "test helper functions coverage - platform API command failures", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(expectedEmptyOutput), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock Platform API command failures to test error handling + patches := gomonkey.NewPatches() + + patches.ApplyFunc(common.GetDataFromHostCommand, func(command string) (string, error) { + // All Platform API calls fail + return "", fmt.Errorf("platform API error") + }) + + return func() { + patches.Reset() + } + }, + }, + { + desc: "test helper functions coverage - invalid JSON response", + pathTarget: "SHOW", + textPbPath: ` + elem: + elem: + elem: + `, + wantRetCode: codes.OK, + wantRespVal: []byte(expectedEmptyOutput), + valTest: true, + testInit: func() func() { + ResetDataSetsAndMappings(t) + + // Mock invalid JSON responses to test parsing error handling + patches := gomonkey.NewPatches() + + patches.ApplyFunc(common.GetDataFromHostCommand, func(command string) (string, error) { + // Return invalid JSON to test error handling in GetChassisComponents and GetModuleComponents + if strings.Contains(command, "json.dumps") { + return "invalid json}", nil + } + // Return valid chassis name + return "TestChassis", nil + }) + + return func() { + patches.Reset() + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + var cleanup func() + if tt.testInit != nil { + cleanup = tt.testInit() + } + defer func() { + if cleanup != nil { + cleanup() + } + }() + + 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/helpers/platform_firmware_helper.go b/show_client/helpers/platform_firmware_helper.go new file mode 100644 index 00000000..ff043c97 --- /dev/null +++ b/show_client/helpers/platform_firmware_helper.go @@ -0,0 +1,248 @@ +package helpers + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/sonic-net/sonic-gnmi/show_client/common" +) + +// ComponentInfo holds component data from Platform API +type ComponentInfo struct { + Name string + FirmwareVersion string + Description string +} + +// ModuleComponentInfo holds module component data +type ModuleComponentInfo struct { + ModuleName string + Name string + FirmwareVersion string + Description string +} + +// FirmwareData holds complete firmware information for a component +type FirmwareData struct { + Chassis string + Module string + Component string + Version string + Description string +} + +// GetAllFirmwareData retrieves complete firmware information using Platform API +func GetAllFirmwareData() ([]FirmwareData, error) { + firmwareList := make([]FirmwareData, 0) + + // Get chassis name + chassisName, err := GetChassisName() + if err != nil { + chassisName = "N/A" + } + + // Check if modular chassis to determine module name logic + isModularChassis := false + moduleComponents, moduleErr := GetModuleComponents() + if moduleErr == nil && len(moduleComponents) > 0 { + isModularChassis = true + } + + appendChassisName := true + appendModuleNA := !isModularChassis // Show "N/A" for non-modular chassis + + // Get chassis components + chassisComponents, err := GetChassisComponents() + if err == nil { + for _, component := range chassisComponents { + moduleField := "" + if appendModuleNA { + moduleField = "N/A" + appendModuleNA = false + } + + firmware := FirmwareData{ + Chassis: func() string { + if appendChassisName { + appendChassisName = false + return chassisName + } + return "" + }(), + Module: moduleField, + Component: component.Name, + Version: component.FirmwareVersion, + Description: component.Description, + } + firmwareList = append(firmwareList, firmware) + } + } + + // Get module components for modular chassis + if isModularChassis { + currentModuleName := "" + appendModuleName := false + + for _, moduleComp := range moduleComponents { + // New module - show module name for first component + if moduleComp.ModuleName != currentModuleName { + currentModuleName = moduleComp.ModuleName + appendModuleName = true + } + + moduleNameField := "" + if appendModuleName { + moduleNameField = moduleComp.ModuleName + appendModuleName = false + } + + firmware := FirmwareData{ + Chassis: func() string { + if appendChassisName { + appendChassisName = false + return chassisName + } + return "" + }(), + Module: moduleNameField, + Component: moduleComp.Name, + Version: moduleComp.FirmwareVersion, + Description: moduleComp.Description, + } + firmwareList = append(firmwareList, firmware) + } + } + + return firmwareList, nil +} + +// GetChassisName calls Platform API to get chassis name +func GetChassisName() (string, error) { + pythonScript := ` +try: + from sonic_platform.platform import Platform + chassis = Platform().get_chassis() + print(chassis.get_name() if hasattr(chassis, 'get_name') else 'N/A') +except Exception: + print('N/A') +` + escaped := strings.ReplaceAll(pythonScript, "'", `'\''`) + command := fmt.Sprintf("python3 -c '%s'", escaped) + + output, err := common.GetDataFromHostCommand(command) + if err != nil { + return "", err + } + + return strings.TrimSpace(output), nil +} + +// GetChassisComponents calls Platform API to get chassis components +func GetChassisComponents() ([]ComponentInfo, error) { + pythonScript := ` +import json +try: + from sonic_platform.platform import Platform + chassis = Platform().get_chassis() + components = [] + + if hasattr(chassis, 'get_all_components'): + for component in chassis.get_all_components(): + try: + components.append({ + 'name': component.get_name() if hasattr(component, 'get_name') else 'N/A', + 'firmware_version': component.get_firmware_version() if hasattr(component, 'get_firmware_version') else 'N/A', + 'description': component.get_description() if hasattr(component, 'get_description') else 'N/A' + }) + except Exception: + continue + + print(json.dumps(components)) +except Exception: + print('[]') +` + escaped := strings.ReplaceAll(pythonScript, "'", `'\''`) + command := fmt.Sprintf("python3 -c '%s'", escaped) + + output, err := common.GetDataFromHostCommand(command) + if err != nil { + return nil, err + } + + var rawComponents []map[string]string + if err := json.Unmarshal([]byte(output), &rawComponents); err != nil { + return nil, err + } + + // Parse into Go structs + components := make([]ComponentInfo, 0, len(rawComponents)) + for _, raw := range rawComponents { + components = append(components, ComponentInfo{ + Name: raw["name"], + FirmwareVersion: raw["firmware_version"], + Description: raw["description"], + }) + } + + return components, nil +} + +// GetModuleComponents calls Platform API to get module components +func GetModuleComponents() ([]ModuleComponentInfo, error) { + pythonScript := ` +import json +try: + from sonic_platform.platform import Platform + chassis = Platform().get_chassis() + components = [] + + if hasattr(chassis, 'get_all_modules'): + for module in chassis.get_all_modules(): + try: + module_name = module.get_name() if hasattr(module, 'get_name') else 'N/A' + + if hasattr(module, 'get_all_components'): + for component in module.get_all_components(): + try: + components.append({ + 'module_name': module_name, + 'name': component.get_name() if hasattr(component, 'get_name') else 'N/A', + 'firmware_version': component.get_firmware_version() if hasattr(component, 'get_firmware_version') else 'N/A', + 'description': component.get_description() if hasattr(component, 'get_description') else 'N/A' + }) + except Exception: + continue + except Exception: + continue + + print(json.dumps(components)) +except Exception: + print('[]') +` + escaped := strings.ReplaceAll(pythonScript, "'", `'\''`) + command := fmt.Sprintf("python3 -c '%s'", escaped) + + output, err := common.GetDataFromHostCommand(command) + if err != nil { + return nil, err + } + + var rawComponents []map[string]string + if err := json.Unmarshal([]byte(output), &rawComponents); err != nil { + return nil, err + } + + // Parse into Go structs + components := make([]ModuleComponentInfo, 0, len(rawComponents)) + for _, raw := range rawComponents { + components = append(components, ModuleComponentInfo{ + ModuleName: raw["module_name"], + Name: raw["name"], + FirmwareVersion: raw["firmware_version"], + Description: raw["description"], + }) + } + + return components, nil +} diff --git a/show_client/platform_cli.go b/show_client/platform_cli.go index 9d376482..fc1bcc93 100644 --- a/show_client/platform_cli.go +++ b/show_client/platform_cli.go @@ -93,6 +93,15 @@ type CurrentInfo struct { Timestamp string `json:"timestamp"` } +// FirmwareInfo represents firmware status information for a component +type FirmwareInfo struct { + Chassis string `json:"chassis"` + Module string `json:"module"` + Component string `json:"component"` + Version string `json:"version"` + Description string `json:"description"` +} + // getPlatformSummary implements the "show platform summary" command func getPlatformSummary(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error) { // Get version info to extract ASIC type @@ -393,3 +402,27 @@ func getPlatformCurrent(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error) } }) } + +// getPlatformFirmware implements the "show platform firmware status" command +func getPlatformFirmware(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error) { + // Get all firmware data using helper + firmwareDataList, err := helpers.GetAllFirmwareData() + if err != nil { + log.V(1).Infof("Error getting firmware data: %v", err) + return json.Marshal([]FirmwareInfo{}) + } + + // Convert helper struct to API response struct + firmwareList := make([]FirmwareInfo, 0, len(firmwareDataList)) + for _, data := range firmwareDataList { + firmwareList = append(firmwareList, FirmwareInfo{ + Chassis: data.Chassis, + Module: data.Module, + Component: data.Component, + Version: data.Version, + Description: data.Description, + }) + } + + return json.Marshal(firmwareList) +} diff --git a/show_client/show_paths.go b/show_client/show_paths.go index fcdf743d..4c733e0e 100644 --- a/show_client/show_paths.go +++ b/show_client/show_paths.go @@ -1131,4 +1131,15 @@ func init() { 0, nil, ) + + // SHOW/platform/firmware/status + sdc.RegisterCliPath( + []string{"SHOW", "platform", "firmware", "status"}, + getPlatformFirmware, + "SHOW/platform/firmware/status[OPTIONS]: Show platform component firmware status", + 0, + 0, + nil, + showCmdOptionVerbose, + ) } From 7d936ce40ae8bef8771134cff097bbde8d4d306e Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 4 May 2026 16:46:54 +0000 Subject: [PATCH 2/5] updating json format --- gnmi_server/platform_firmware_cli_test.go | 10 ++-- show_client/platform_cli.go | 72 ++++++++++++++++++----- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/gnmi_server/platform_firmware_cli_test.go b/gnmi_server/platform_firmware_cli_test.go index 052d16bb..d9d04236 100644 --- a/gnmi_server/platform_firmware_cli_test.go +++ b/gnmi_server/platform_firmware_cli_test.go @@ -21,12 +21,12 @@ import ( func TestGetShowPlatformFirmwareStatus(t *testing.T) { // Expected output matching actual device output (MSN2700) - expectedOutput := `[{"chassis":"MSN2700","module":"N/A","component":"ONIE","version":"2018.05-5.2.0004-9600","description":"ONIE - Open Network Install Environment"},{"chassis":"","module":"","component":"SSD","version":"0115-000","description":"SSD - Solid-State Drive"},{"chassis":"","module":"","component":"BIOS","version":"0ABZS017_02.02.002","description":"BIOS - Basic Input/Output System"},{"chassis":"","module":"","component":"CPLD1","version":"CPLD000085_REV2000","description":"CPLD - Complex Programmable Logic Device"},{"chassis":"","module":"","component":"CPLD2","version":"CPLD000128_REV0600","description":"CPLD - Complex Programmable Logic Device"},{"chassis":"","module":"","component":"CPLD3","version":"CPLD000000_REV0300","description":"CPLD - Complex Programmable Logic Device"}]` + expectedOutput := `{"chassis":"MSN2700","module":"N/A","components":[{"name":"ONIE","version":"2018.05-5.2.0004-9600","description":"ONIE - Open Network Install Environment"},{"name":"SSD","version":"0115-000","description":"SSD - Solid-State Drive"},{"name":"BIOS","version":"0ABZS017_02.02.002","description":"BIOS - Basic Input/Output System"},{"name":"CPLD1","version":"CPLD000085_REV2000","description":"CPLD - Complex Programmable Logic Device"},{"name":"CPLD2","version":"CPLD000128_REV0600","description":"CPLD - Complex Programmable Logic Device"},{"name":"CPLD3","version":"CPLD000000_REV0300","description":"CPLD - Complex Programmable Logic Device"}]}` // Expected output for modular chassis test - expectedModularOutput := `[{"chassis":"ModularChassis","module":"","component":"BIOS","version":"1.0.0","description":"System BIOS"},{"chassis":"","module":"Module1","component":"FPGA","version":"2.1.0","description":"Module FPGA"},{"chassis":"","module":"","component":"CPLD","version":"3.0.0","description":"Module CPLD"}]` + expectedModularOutput := `{"chassis":"ModularChassis","module":"Module1","components":[{"name":"BIOS","version":"1.0.0","description":"System BIOS"},{"name":"FPGA","version":"2.1.0","description":"Module FPGA"},{"name":"CPLD","version":"3.0.0","description":"Module CPLD"}]}` - expectedEmptyOutput := `[]` + expectedEmptyOutput := `{"chassis":"N/A","module":"N/A","components":[]}` tests := []struct { desc string @@ -190,7 +190,7 @@ func TestGetShowPlatformFirmwareStatus(t *testing.T) { elem: `, wantRetCode: codes.OK, - wantRespVal: []byte(`[{"chassis":"TestChassis","module":"N/A","component":"BIOS","version":"1.0.0","description":"Test BIOS"},{"chassis":"","module":"","component":"CPLD","version":"2.0.0","description":"Test CPLD"}]`), + wantRespVal: []byte(`{"chassis":"TestChassis","module":"N/A","components":[{"name":"BIOS","version":"1.0.0","description":"Test BIOS"},{"name":"CPLD","version":"2.0.0","description":"Test CPLD"}]}`), valTest: true, testInit: func() func() { ResetDataSetsAndMappings(t) @@ -236,7 +236,7 @@ func TestGetShowPlatformFirmwareStatus(t *testing.T) { elem: `, wantRetCode: codes.OK, - wantRespVal: []byte(`[{"chassis":"Modular","module":"","component":"BIOS","version":"1.0","description":"Chassis BIOS"},{"chassis":"","module":"LineCard1","component":"FPGA","version":"2.0","description":"Module FPGA"},{"chassis":"","module":"","component":"CPLD","version":"3.0","description":"Module CPLD"}]`), + wantRespVal: []byte(`{"chassis":"Modular","module":"LineCard1","components":[{"name":"BIOS","version":"1.0","description":"Chassis BIOS"},{"name":"FPGA","version":"2.0","description":"Module FPGA"},{"name":"CPLD","version":"3.0","description":"Module CPLD"}]}`), valTest: true, testInit: func() func() { ResetDataSetsAndMappings(t) diff --git a/show_client/platform_cli.go b/show_client/platform_cli.go index fc1bcc93..8ff96f25 100644 --- a/show_client/platform_cli.go +++ b/show_client/platform_cli.go @@ -93,15 +93,20 @@ type CurrentInfo struct { Timestamp string `json:"timestamp"` } -// FirmwareInfo represents firmware status information for a component -type FirmwareInfo struct { - Chassis string `json:"chassis"` - Module string `json:"module"` - Component string `json:"component"` +// ComponentInfo represents individual component firmware information +type ComponentInfo struct { + Name string `json:"name"` Version string `json:"version"` Description string `json:"description"` } +// FirmwareInfo represents the complete firmware status +type FirmwareInfo struct { + Chassis string `json:"chassis"` + Module string `json:"module"` + Components []ComponentInfo `json:"components"` +} + // getPlatformSummary implements the "show platform summary" command func getPlatformSummary(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error) { // Get version info to extract ASIC type @@ -409,20 +414,57 @@ func getPlatformFirmware(args sdc.CmdArgs, options sdc.OptionMap) ([]byte, error firmwareDataList, err := helpers.GetAllFirmwareData() if err != nil { log.V(1).Infof("Error getting firmware data: %v", err) - return json.Marshal([]FirmwareInfo{}) + return json.Marshal(FirmwareInfo{ + Chassis: "N/A", + Module: "N/A", + Components: []ComponentInfo{}, + }) } - // Convert helper struct to API response struct - firmwareList := make([]FirmwareInfo, 0, len(firmwareDataList)) - for _, data := range firmwareDataList { - firmwareList = append(firmwareList, FirmwareInfo{ - Chassis: data.Chassis, - Module: data.Module, - Component: data.Component, + if len(firmwareDataList) == 0 { + return json.Marshal(FirmwareInfo{ + Chassis: "N/A", + Module: "N/A", + Components: []ComponentInfo{}, + }) + } + + // Extract chassis and module from first entry, build components list + chassisName := "N/A" + moduleName := "N/A" + components := make([]ComponentInfo, 0, len(firmwareDataList)) + + for i, data := range firmwareDataList { + // Use chassis name from first non-empty entry + if i == 0 || (chassisName == "N/A" && data.Chassis != "") { + if data.Chassis != "" { + chassisName = data.Chassis + } + } + + // For module, use first non-empty module name, or keep "N/A" if all are empty + if i == 0 || (moduleName == "N/A" && data.Module != "") { + if data.Module != "" { + moduleName = data.Module + } + } + + // Create component info + component := ComponentInfo{ + Name: data.Component, Version: data.Version, Description: data.Description, - }) + } + + components = append(components, component) + } + + // Build final response structure + firmwareInfo := FirmwareInfo{ + Chassis: chassisName, + Module: moduleName, + Components: components, } - return json.Marshal(firmwareList) + return json.Marshal(firmwareInfo) } From 138405e24e5898e5e5b5c75d848ed336d7c37a7b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 5 May 2026 03:56:25 +0000 Subject: [PATCH 3/5] getting chassis name from CHASSIS_INFO table --- gnmi_server/platform_firmware_cli_test.go | 23 ++++++++++-------- .../helpers/platform_firmware_helper.go | 24 +++++++++---------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/gnmi_server/platform_firmware_cli_test.go b/gnmi_server/platform_firmware_cli_test.go index d9d04236..2c69d91f 100644 --- a/gnmi_server/platform_firmware_cli_test.go +++ b/gnmi_server/platform_firmware_cli_test.go @@ -195,13 +195,14 @@ func TestGetShowPlatformFirmwareStatus(t *testing.T) { testInit: func() func() { ResetDataSetsAndMappings(t) + // Add test data for CHASSIS_INFO to STATE_DB + stateDbClient := getRedisClientN(t, StateDbNum, "") + stateDbClient.HSet(context.Background(), "CHASSIS_INFO|chassis 1", "model", "TestChassis") + stateDbClient.Close() + // Mock individual helper functions to test integration logic patches := gomonkey.NewPatches() - patches.ApplyFunc(helpers.GetChassisName, func() (string, error) { - return "TestChassis", nil - }) - patches.ApplyFunc(helpers.GetChassisComponents, func() ([]helpers.ComponentInfo, error) { return []helpers.ComponentInfo{ { @@ -241,13 +242,14 @@ func TestGetShowPlatformFirmwareStatus(t *testing.T) { testInit: func() func() { ResetDataSetsAndMappings(t) + // Add test data for CHASSIS_INFO to STATE_DB + stateDbClient := getRedisClientN(t, StateDbNum, "") + stateDbClient.HSet(context.Background(), "CHASSIS_INFO|chassis 1", "model", "Modular") + stateDbClient.Close() + // Mock helper functions for modular chassis with modules patches := gomonkey.NewPatches() - patches.ApplyFunc(helpers.GetChassisName, func() (string, error) { - return "Modular", nil - }) - patches.ApplyFunc(helpers.GetChassisComponents, func() ([]helpers.ComponentInfo, error) { return []helpers.ComponentInfo{ { @@ -297,8 +299,9 @@ func TestGetShowPlatformFirmwareStatus(t *testing.T) { // Mock helper functions returning errors to test error handling patches := gomonkey.NewPatches() - patches.ApplyFunc(helpers.GetChassisName, func() (string, error) { - return "", fmt.Errorf("chassis name error") + // Mock database query failure for chassis info + patches.ApplyFunc(common.GetMapFromQueries, func(queries [][]string) (map[string]interface{}, error) { + return nil, fmt.Errorf("database query error") }) patches.ApplyFunc(helpers.GetChassisComponents, func() ([]helpers.ComponentInfo, error) { diff --git a/show_client/helpers/platform_firmware_helper.go b/show_client/helpers/platform_firmware_helper.go index ff043c97..ff8a3490 100644 --- a/show_client/helpers/platform_firmware_helper.go +++ b/show_client/helpers/platform_firmware_helper.go @@ -119,23 +119,23 @@ func GetAllFirmwareData() ([]FirmwareData, error) { // GetChassisName calls Platform API to get chassis name func GetChassisName() (string, error) { - pythonScript := ` -try: - from sonic_platform.platform import Platform - chassis = Platform().get_chassis() - print(chassis.get_name() if hasattr(chassis, 'get_name') else 'N/A') -except Exception: - print('N/A') -` - escaped := strings.ReplaceAll(pythonScript, "'", `'\''`) - command := fmt.Sprintf("python3 -c '%s'", escaped) + // Query CHASSIS_INFO database table for chassis model + queries := [][]string{ + {"STATE_DB", "CHASSIS_INFO"}, + } - output, err := common.GetDataFromHostCommand(command) + chassisData, err := common.GetMapFromQueries(queries) if err != nil { return "", err } - return strings.TrimSpace(output), nil + // Extract chassis name (model) from database + if chassisInfo, ok := chassisData["chassis 1"].(map[string]interface{}); ok { + model := common.GetValueOrDefault(chassisInfo, "model", "N/A") + return model, nil + } + + return "N/A", nil } // GetChassisComponents calls Platform API to get chassis components From d27b404603f73fbc71bb6939710fbf566836276c Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 5 May 2026 08:46:32 +0000 Subject: [PATCH 4/5] moved python script to platform_apis.go --- show_client/common/platform_apis.go | 56 +++++++++++++++++++ .../helpers/platform_firmware_helper.go | 56 +------------------ 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/show_client/common/platform_apis.go b/show_client/common/platform_apis.go index a1f0c897..571a1c27 100644 --- a/show_client/common/platform_apis.go +++ b/show_client/common/platform_apis.go @@ -17,3 +17,59 @@ 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())})) ` + +// ChassisComponentsPyScript retrieves all chassis components via Platform API +var ChassisComponentsPyScript = ` +import json +try: + from sonic_platform.platform import Platform + chassis = Platform().get_chassis() + components = [] + + if hasattr(chassis, 'get_all_components'): + for component in chassis.get_all_components(): + try: + components.append({ + 'name': component.get_name() if hasattr(component, 'get_name') else 'N/A', + 'firmware_version': component.get_firmware_version() if hasattr(component, 'get_firmware_version') else 'N/A', + 'description': component.get_description() if hasattr(component, 'get_description') else 'N/A' + }) + except Exception: + continue + + print(json.dumps(components)) +except Exception: + print('[]') +` + +// ModuleComponentsPyScript retrieves all module components via Platform API +var ModuleComponentsPyScript = ` +import json +try: + from sonic_platform.platform import Platform + chassis = Platform().get_chassis() + components = [] + + if hasattr(chassis, 'get_all_modules'): + for module in chassis.get_all_modules(): + try: + module_name = module.get_name() if hasattr(module, 'get_name') else 'N/A' + + if hasattr(module, 'get_all_components'): + for component in module.get_all_components(): + try: + components.append({ + 'module_name': module_name, + 'name': component.get_name() if hasattr(component, 'get_name') else 'N/A', + 'firmware_version': component.get_firmware_version() if hasattr(component, 'get_firmware_version') else 'N/A', + 'description': component.get_description() if hasattr(component, 'get_description') else 'N/A' + }) + except Exception: + continue + except Exception: + continue + + print(json.dumps(components)) +except Exception: + print('[]') +` diff --git a/show_client/helpers/platform_firmware_helper.go b/show_client/helpers/platform_firmware_helper.go index ff8a3490..62f091d4 100644 --- a/show_client/helpers/platform_firmware_helper.go +++ b/show_client/helpers/platform_firmware_helper.go @@ -140,29 +140,7 @@ func GetChassisName() (string, error) { // GetChassisComponents calls Platform API to get chassis components func GetChassisComponents() ([]ComponentInfo, error) { - pythonScript := ` -import json -try: - from sonic_platform.platform import Platform - chassis = Platform().get_chassis() - components = [] - - if hasattr(chassis, 'get_all_components'): - for component in chassis.get_all_components(): - try: - components.append({ - 'name': component.get_name() if hasattr(component, 'get_name') else 'N/A', - 'firmware_version': component.get_firmware_version() if hasattr(component, 'get_firmware_version') else 'N/A', - 'description': component.get_description() if hasattr(component, 'get_description') else 'N/A' - }) - except Exception: - continue - - print(json.dumps(components)) -except Exception: - print('[]') -` - escaped := strings.ReplaceAll(pythonScript, "'", `'\''`) + escaped := strings.ReplaceAll(common.ChassisComponentsPyScript, "'", `'\''`) command := fmt.Sprintf("python3 -c '%s'", escaped) output, err := common.GetDataFromHostCommand(command) @@ -190,37 +168,7 @@ except Exception: // GetModuleComponents calls Platform API to get module components func GetModuleComponents() ([]ModuleComponentInfo, error) { - pythonScript := ` -import json -try: - from sonic_platform.platform import Platform - chassis = Platform().get_chassis() - components = [] - - if hasattr(chassis, 'get_all_modules'): - for module in chassis.get_all_modules(): - try: - module_name = module.get_name() if hasattr(module, 'get_name') else 'N/A' - - if hasattr(module, 'get_all_components'): - for component in module.get_all_components(): - try: - components.append({ - 'module_name': module_name, - 'name': component.get_name() if hasattr(component, 'get_name') else 'N/A', - 'firmware_version': component.get_firmware_version() if hasattr(component, 'get_firmware_version') else 'N/A', - 'description': component.get_description() if hasattr(component, 'get_description') else 'N/A' - }) - except Exception: - continue - except Exception: - continue - - print(json.dumps(components)) -except Exception: - print('[]') -` - escaped := strings.ReplaceAll(pythonScript, "'", `'\''`) + escaped := strings.ReplaceAll(common.ModuleComponentsPyScript, "'", `'\''`) command := fmt.Sprintf("python3 -c '%s'", escaped) output, err := common.GetDataFromHostCommand(command) From 3ec90c9936b080ef59a864b36904b5ec6c4bfa79 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 5 May 2026 09:17:55 +0000 Subject: [PATCH 5/5] removed GetChassisName --- .../helpers/platform_firmware_helper.go | 30 ++++--------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/show_client/helpers/platform_firmware_helper.go b/show_client/helpers/platform_firmware_helper.go index 62f091d4..6f8a7659 100644 --- a/show_client/helpers/platform_firmware_helper.go +++ b/show_client/helpers/platform_firmware_helper.go @@ -36,10 +36,11 @@ type FirmwareData struct { func GetAllFirmwareData() ([]FirmwareData, error) { firmwareList := make([]FirmwareData, 0) - // Get chassis name - chassisName, err := GetChassisName() - if err != nil { - chassisName = "N/A" + // Get chassis info from database + chassisInfo, err := common.GetChassisInfo() + chassisName := "N/A" + if err == nil { + chassisName = chassisInfo["model"] } // Check if modular chassis to determine module name logic @@ -117,27 +118,6 @@ func GetAllFirmwareData() ([]FirmwareData, error) { return firmwareList, nil } -// GetChassisName calls Platform API to get chassis name -func GetChassisName() (string, error) { - // Query CHASSIS_INFO database table for chassis model - queries := [][]string{ - {"STATE_DB", "CHASSIS_INFO"}, - } - - chassisData, err := common.GetMapFromQueries(queries) - if err != nil { - return "", err - } - - // Extract chassis name (model) from database - if chassisInfo, ok := chassisData["chassis 1"].(map[string]interface{}); ok { - model := common.GetValueOrDefault(chassisInfo, "model", "N/A") - return model, nil - } - - return "N/A", nil -} - // GetChassisComponents calls Platform API to get chassis components func GetChassisComponents() ([]ComponentInfo, error) { escaped := strings.ReplaceAll(common.ChassisComponentsPyScript, "'", `'\''`)