Skip to content

Commit

Permalink
GNOI Implementation of OS.Verify (#342)
Browse files Browse the repository at this point in the history
Why I did it
Supports OS.Verify for Upgrading OS

How I did it
Implement GNOI server and client for GNOI.OS.Verify

Backend PR: sonic-net/sonic-host-services#206

How to verify it
On Mellanox:
gnoi_client -target 127.0.0.1:50052 -logtostderr -insecure -module OS -rpc Verify
OS Verify
{"version":"SONiC-OS-20240510.23"}
  • Loading branch information
hdwhdw authored Jan 28, 2025
1 parent a538f49 commit a023991
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 23 deletions.
3 changes: 3 additions & 0 deletions common_utils/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const (
DBUS_HALT_SYSTEM
DBUS_IMAGE_DOWNLOAD
DBUS_IMAGE_INSTALL
DBUS_IMAGE_LIST
COUNTER_SIZE
)

Expand Down Expand Up @@ -100,6 +101,8 @@ func (c CounterType) String() string {
return "DBUS image download"
case DBUS_IMAGE_INSTALL:
return "DBUS image install"
case DBUS_IMAGE_LIST:
return "DBUS image list"
default:
return ""
}
Expand Down
76 changes: 59 additions & 17 deletions gnmi_server/gnoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
gnoi_system_pb "github.com/openconfig/gnoi/system"
gnoi_file_pb "github.com/openconfig/gnoi/file"
gnoi_os_pb "github.com/openconfig/gnoi/os"
log "github.com/golang/glog"
"time"
spb "github.com/sonic-net/sonic-gnmi/proto/gnoi"
Expand Down Expand Up @@ -115,6 +116,47 @@ func KillOrRestartProcess(restart bool, serviceName string) error {
return err
}

func (srv *OSServer) Verify(ctx context.Context, req *gnoi_os_pb.VerifyRequest) (*gnoi_os_pb.VerifyResponse, error) {
_, err := authenticate(srv.config, ctx, false)
if err != nil {
log.V(2).Infof("Failed to authenticate: %v", err)
return nil, err
}

log.V(1).Info("gNOI: Verify")
dbus, err := ssc.NewDbusClient()
if err != nil {
log.V(2).Infof("Failed to create dbus client: %v", err)
return nil, err
}

image_json, err := dbus.ListImages()
if err != nil {
log.V(2).Infof("Failed to list images: %v", err)
return nil, err
}

images := make(map[string]interface{})
err = json.Unmarshal([]byte(image_json), &images)
if err != nil {
log.V(2).Infof("Failed to unmarshal images: %v", err)
return nil, err
}

current, exists := images["current"]
if !exists {
return nil, status.Errorf(codes.Internal, "Key 'current' not found in images")
}
current_image, ok := current.(string)
if !ok {
return nil, status.Errorf(codes.Internal, "Failed to assert current image as string")
}
resp := &gnoi_os_pb.VerifyResponse{
Version: current_image,
}
return resp, nil
}

func (srv *SystemServer) KillProcess(ctx context.Context, req *gnoi_system_pb.KillProcessRequest) (*gnoi_system_pb.KillProcessResponse, error) {
_, err := authenticate(srv.config, ctx, true)
if err != nil {
Expand Down Expand Up @@ -286,7 +328,7 @@ func (srv *Server) Authenticate(ctx context.Context, req *spb_jwt.AuthenticateRe
return &spb_jwt.AuthenticateResponse{Token: tokenResp(req.Username, roles)}, nil
}
}

}
return nil, status.Errorf(codes.PermissionDenied, "Invalid Username or Password")

Expand Down Expand Up @@ -314,7 +356,7 @@ func (srv *Server) Refresh(ctx context.Context, req *spb_jwt.RefreshRequest) (*s
if time.Unix(claims.ExpiresAt, 0).Sub(time.Now()) > JwtRefreshInt {
return nil, status.Errorf(codes.InvalidArgument, "Invalid JWT Token")
}

return &spb_jwt.RefreshResponse{Token: tokenResp(claims.Username, claims.Roles)}, nil

}
Expand Down Expand Up @@ -357,13 +399,13 @@ func (srv *Server) CopyConfig(ctx context.Context, req *spb.CopyConfigRequest) (
return nil, err
}
log.V(1).Info("gNOI: Sonic CopyConfig")

resp := &spb.CopyConfigResponse{
Output: &spb.SonicOutput {

},
}

reqstr, err := json.Marshal(req)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
Expand All @@ -373,12 +415,12 @@ func (srv *Server) CopyConfig(ctx context.Context, req *spb.CopyConfigRequest) (
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}

err = json.Unmarshal(jsresp, resp)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}

return resp, nil
}

Expand All @@ -388,7 +430,7 @@ func (srv *Server) ShowTechsupport(ctx context.Context, req *spb.TechsupportRequ
return nil, err
}
log.V(1).Info("gNOI: Sonic ShowTechsupport")

resp := &spb.TechsupportResponse{
Output: &spb.TechsupportResponse_Output {

Expand All @@ -404,13 +446,13 @@ func (srv *Server) ShowTechsupport(ctx context.Context, req *spb.TechsupportRequ
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}

err = json.Unmarshal(jsresp, resp)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}


return resp, nil
}

Expand All @@ -420,7 +462,7 @@ func (srv *Server) ImageInstall(ctx context.Context, req *spb.ImageInstallReques
return nil, err
}
log.V(1).Info("gNOI: Sonic ImageInstall")

resp := &spb.ImageInstallResponse{
Output: &spb.SonicOutput {

Expand All @@ -436,13 +478,13 @@ func (srv *Server) ImageInstall(ctx context.Context, req *spb.ImageInstallReques
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}

err = json.Unmarshal(jsresp, resp)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}


return resp, nil
}

Expand All @@ -452,7 +494,7 @@ func (srv *Server) ImageRemove(ctx context.Context, req *spb.ImageRemoveRequest)
return nil, err
}
log.V(1).Info("gNOI: Sonic ImageRemove")

resp := &spb.ImageRemoveResponse{
Output: &spb.SonicOutput {

Expand All @@ -468,7 +510,7 @@ func (srv *Server) ImageRemove(ctx context.Context, req *spb.ImageRemoveRequest)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}

err = json.Unmarshal(jsresp, resp)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
Expand All @@ -482,7 +524,7 @@ func (srv *Server) ImageDefault(ctx context.Context, req *spb.ImageDefaultReques
return nil, err
}
log.V(1).Info("gNOI: Sonic ImageDefault")

resp := &spb.ImageDefaultResponse{
Output: &spb.SonicOutput {

Expand All @@ -504,6 +546,6 @@ func (srv *Server) ImageDefault(ctx context.Context, req *spb.ImageDefaultReques
return nil, status.Error(codes.Unknown, err.Error())
}


return resp, nil
}
11 changes: 11 additions & 0 deletions gnmi_server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
gnmi_extpb "github.com/openconfig/gnmi/proto/gnmi_ext"
gnoi_system_pb "github.com/openconfig/gnoi/system"
gnoi_file_pb "github.com/openconfig/gnoi/file"
gnoi_os_pb "github.com/openconfig/gnoi/os"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -70,6 +71,14 @@ type SystemServer struct {
gnoi_system_pb.UnimplementedSystemServer
}

// OSServer is the server API for System service.
// All implementations must embed UnimplementedSystemServer
// for forward compatibility
type OSServer struct {
*Server
gnoi_os_pb.UnimplementedOSServer
}

type AuthTypes map[string]bool

// Config is a collection of values for Server
Expand Down Expand Up @@ -180,6 +189,7 @@ func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) {

fileSrv := &FileServer{Server: srv}
systemSrv := &SystemServer{Server: srv}
osSrv := &OSServer{Server: srv}

var err error
if srv.config.Port < 0 {
Expand All @@ -194,6 +204,7 @@ func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) {
if srv.config.EnableTranslibWrite || srv.config.EnableNativeWrite {
gnoi_system_pb.RegisterSystemServer(srv.s, systemSrv)
gnoi_file_pb.RegisterFileServer(srv.s, fileSrv)
gnoi_os_pb.RegisterOSServer(srv.s, osSrv)
}
if srv.config.EnableTranslibWrite {
spb_gnoi.RegisterSonicServiceServer(srv.s, srv)
Expand Down
63 changes: 59 additions & 4 deletions gnmi_server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import (
gnmipb "github.com/openconfig/gnmi/proto/gnmi"
gnoi_system_pb "github.com/openconfig/gnoi/system"
gnoi_file_pb "github.com/openconfig/gnoi/file"
gnoi_os_pb "github.com/openconfig/gnoi/os"
"github.com/sonic-net/sonic-gnmi/common_utils"
"github.com/sonic-net/sonic-gnmi/swsscommon"
)
Expand Down Expand Up @@ -2989,7 +2990,7 @@ func TestGNOI(t *testing.T) {
if len(resp.Stats) == 0 {
t.Fatalf("Expected at least one StatInfo in response")
}

statInfo := resp.Stats[0]

if statInfo.LastModified != 1609459200000000000 {
Expand All @@ -3009,7 +3010,7 @@ func TestGNOI(t *testing.T) {
t.Run("FileStatFailure", func(t *testing.T) {
mockClient := &ssc.DbusClient{}
expectedError := fmt.Errorf("failed to get file stats")

mock := gomonkey.ApplyMethod(reflect.TypeOf(mockClient), "GetFileStat", func(_ *ssc.DbusClient, path string) (map[string]string, error) {
return nil, expectedError
})
Expand All @@ -3027,10 +3028,64 @@ func TestGNOI(t *testing.T) {
if resp != nil {
t.Fatalf("Expected nil response but got: %v", resp)
}

if !strings.Contains(err.Error(), expectedError.Error()) {
t.Errorf("Expected error to contain '%v' but got '%v'", expectedError, err)
}
}
})

t.Run("OSVerifySuccess", func(t *testing.T) {
mockClient := &ssc.DbusClient{}
expectedDbusOut := `{
"current": "current_image",
"next": "next_image",
"available": ["image1", "image2"]
}`
mock := gomonkey.ApplyMethod(reflect.TypeOf(mockClient), "ListImages", func(_ *ssc.DbusClient) (string, error) {
return expectedDbusOut, nil
})
defer mock.Reset()

// Prepare context and request
ctx := context.Background()
req := &gnoi_os_pb.VerifyRequest{}
osc := gnoi_os_pb.NewOSClient(conn)

resp, err := osc.Verify(ctx, req)
if err != nil {
t.Fatalf("OS Verify failed: %v", err)
}
// Validate the response
if len(resp.Version) == 0 {
t.Fatalf("Expected a version string in response")
}
})

t.Run("OSVerifyFailure", func(t *testing.T) {
mockClient := &ssc.DbusClient{}
expectedError := fmt.Errorf("failed to verify OS")

mock := gomonkey.ApplyMethod(reflect.TypeOf(mockClient), "ListImages", func(_ *ssc.DbusClient) (string, error) {
return "", expectedError
})
defer mock.Reset()

// Prepare context and request
ctx := context.Background()
req := &gnoi_os_pb.VerifyRequest{}
osc := gnoi_os_pb.NewOSClient(conn)

resp, err := osc.Verify(ctx, req)
if err == nil {
t.Fatalf("Expected error but got none")
}
if resp != nil {
t.Fatalf("Expected nil response but got: %v", resp)
}

if !strings.Contains(err.Error(), expectedError.Error()) {
t.Errorf("Expected error to contain '%v' but got '%v'", expectedError, err)
}
})

type configData struct {
Expand Down
8 changes: 8 additions & 0 deletions gnoi_client/gnoi_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/sonic-net/sonic-gnmi/gnoi_client/system"
"github.com/sonic-net/sonic-gnmi/gnoi_client/file"
"github.com/sonic-net/sonic-gnmi/gnoi_client/sonic"
gnoi_os "github.com/sonic-net/sonic-gnmi/gnoi_client/os" // So it does not collide with os.
"google.golang.org/grpc"
"os"
"os/signal"
Expand Down Expand Up @@ -51,6 +52,13 @@ func main() {
default:
panic("Invalid RPC Name")
}
case "OS":
switch *config.Rpc {
case "Verify":
gnoi_os.Verify(conn, ctx)
default:
panic("Invalid RPC Name")
}
case "Sonic":
switch *config.Rpc {
case "showtechsupport":
Expand Down
25 changes: 25 additions & 0 deletions gnoi_client/os/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package os

import (
"context"
"fmt"
"encoding/json"
pb "github.com/openconfig/gnoi/os"
"github.com/sonic-net/sonic-gnmi/gnoi_client/utils"
"google.golang.org/grpc"
)

func Verify(conn *grpc.ClientConn, ctx context.Context) {
fmt.Println("OS Verify")
ctx = utils.SetUserCreds(ctx)
osc := pb.NewOSClient(conn)
resp, err := osc.Verify(ctx, new(pb.VerifyRequest))
if err != nil {
panic(err.Error())
}
respstr, err := json.Marshal(resp)
if err != nil {
panic(err.Error())
}
fmt.Println(string(respstr))
}
Loading

0 comments on commit a023991

Please sign in to comment.