Skip to content

Commit

Permalink
Merge pull request sonic-net#274 from isabelmsft/file_stat_gnoi
Browse files Browse the repository at this point in the history
Add GNOI File.Stat API
  • Loading branch information
isabelmsft authored Aug 12, 2024
2 parents 015de94 + 46df786 commit 64ed32b
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 41 deletions.
3 changes: 3 additions & 0 deletions common_utils/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (
DBUS_CONFIG_RELOAD
DBUS_STOP_SERVICE
DBUS_RESTART_SERVICE
DBUS_FILE_STAT
COUNTER_SIZE
)

Expand Down Expand Up @@ -88,6 +89,8 @@ func (c CounterType) String() string {
return "DBUS stop service"
case DBUS_RESTART_SERVICE:
return "DBUS restart service"
case DBUS_FILE_STAT:
return "DBUS file stat"
default:
return ""
}
Expand Down
94 changes: 83 additions & 11 deletions gnmi_server/gnoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import (
"context"
"errors"
"os"
"strconv"
"strings"
gnoi_system_pb "github.com/openconfig/gnoi/system"
gnoi_file_pb "github.com/openconfig/gnoi/file"
log "github.com/golang/glog"
"time"
spb "github.com/sonic-net/sonic-gnmi/proto/gnoi"
Expand All @@ -20,6 +23,77 @@ import (
jwt "github.com/dgrijalva/jwt-go"
)

func ReadFileStat(path string) (*gnoi_file_pb.StatInfo, error) {
sc, err := ssc.NewDbusClient()
if err != nil {
return nil, err
}

log.V(2).Infof("Reading file stat at path %s...", path)
data, err := sc.GetFileStat(path)
if err != nil {
log.V(2).Infof("Failed to read file stat at path %s: %v. Error ", path, err)
return nil, err
}
// Parse the data and populate StatInfo
lastModified, err := strconv.ParseUint(data["last_modified"], 10, 64)
if err != nil {
return nil, err
}

permissions, err := strconv.ParseUint(data["permissions"], 8, 32)
if err != nil {
return nil, err
}

size, err := strconv.ParseUint(data["size"], 10, 64)
if err != nil {
return nil, err
}

umaskStr := data["umask"]
if strings.HasPrefix(umaskStr, "o") {
umaskStr = umaskStr[1:] // Remove leading "o"
}
umask, err := strconv.ParseUint(umaskStr, 8, 32)
if err != nil {
return nil, err
}

statInfo := &gnoi_file_pb.StatInfo{
Path: data["path"],
LastModified: lastModified,
Permissions: uint32(permissions),
Size: size,
Umask: uint32(umask),
}
return statInfo, nil
}

func (srv *FileServer) Stat(ctx context.Context, req *gnoi_file_pb.StatRequest) (*gnoi_file_pb.StatResponse, error) {
_, err := authenticate(srv.config, ctx)
if err != nil {
return nil, err
}
path := req.GetPath()
log.V(1).Info("gNOI: Read File Stat")
log.V(1).Info("Request: ", req)
statInfo, err := ReadFileStat(path)
if err != nil {
return nil, err
}
resp := &gnoi_file_pb.StatResponse{
Stats: []*gnoi_file_pb.StatInfo{statInfo},
}
return resp, nil
}

// TODO: Support GNOI File Get
func (srv *FileServer) Get(req *gnoi_file_pb.GetRequest, stream gnoi_file_pb.File_GetServer) error {
log.V(1).Info("gNOI: File Get")
return status.Errorf(codes.Unimplemented, "")
}

func KillOrRestartProcess(restart bool, serviceName string) error {
sc, err := ssc.NewDbusClient()
if err != nil {
Expand All @@ -41,7 +115,7 @@ func KillOrRestartProcess(restart bool, serviceName string) error {
return err
}

func (srv *Server) KillProcess(ctx context.Context, req *gnoi_system_pb.KillProcessRequest) (*gnoi_system_pb.KillProcessResponse, error) {
func (srv *SystemServer) KillProcess(ctx context.Context, req *gnoi_system_pb.KillProcessRequest) (*gnoi_system_pb.KillProcessResponse, error) {
_, err := authenticate(srv.config, ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -75,7 +149,7 @@ func RebootSystem(fileName string) error {
return err
}

func (srv *Server) Reboot(ctx context.Context, req *gnoi_system_pb.RebootRequest) (*gnoi_system_pb.RebootResponse, error) {
func (srv *SystemServer) Reboot(ctx context.Context, req *gnoi_system_pb.RebootRequest) (*gnoi_system_pb.RebootResponse, error) {
fileName := common_utils.GNMI_WORK_PATH + "/config_db.json.tmp"

_, err := authenticate(srv.config, ctx)
Expand All @@ -101,7 +175,7 @@ func (srv *Server) Reboot(ctx context.Context, req *gnoi_system_pb.RebootRequest
}

// TODO: Support GNOI RebootStatus
func (srv *Server) RebootStatus(ctx context.Context, req *gnoi_system_pb.RebootStatusRequest) (*gnoi_system_pb.RebootStatusResponse, error) {
func (srv *SystemServer) RebootStatus(ctx context.Context, req *gnoi_system_pb.RebootStatusRequest) (*gnoi_system_pb.RebootStatusResponse, error) {
_, err := authenticate(srv.config, ctx)
if err != nil {
return nil, err
Expand All @@ -111,15 +185,15 @@ func (srv *Server) RebootStatus(ctx context.Context, req *gnoi_system_pb.RebootS
}

// TODO: Support GNOI CancelReboot
func (srv *Server) CancelReboot(ctx context.Context, req *gnoi_system_pb.CancelRebootRequest) (*gnoi_system_pb.CancelRebootResponse, error) {
func (srv *SystemServer) CancelReboot(ctx context.Context, req *gnoi_system_pb.CancelRebootRequest) (*gnoi_system_pb.CancelRebootResponse, error) {
_, err := authenticate(srv.config, ctx)
if err != nil {
return nil, err
}
log.V(1).Info("gNOI: CancelReboot")
return nil, status.Errorf(codes.Unimplemented, "")
}
func (srv *Server) Ping(req *gnoi_system_pb.PingRequest, rs gnoi_system_pb.System_PingServer) error {
func (srv *SystemServer) Ping(req *gnoi_system_pb.PingRequest, rs gnoi_system_pb.System_PingServer) error {
ctx := rs.Context()
_, err := authenticate(srv.config, ctx)
if err != nil {
Expand All @@ -128,7 +202,7 @@ func (srv *Server) Ping(req *gnoi_system_pb.PingRequest, rs gnoi_system_pb.Syste
log.V(1).Info("gNOI: Ping")
return status.Errorf(codes.Unimplemented, "")
}
func (srv *Server) Traceroute(req *gnoi_system_pb.TracerouteRequest, rs gnoi_system_pb.System_TracerouteServer) error {
func (srv *SystemServer) Traceroute(req *gnoi_system_pb.TracerouteRequest, rs gnoi_system_pb.System_TracerouteServer) error {
ctx := rs.Context()
_, err := authenticate(srv.config, ctx)
if err != nil {
Expand All @@ -137,7 +211,7 @@ func (srv *Server) Traceroute(req *gnoi_system_pb.TracerouteRequest, rs gnoi_sys
log.V(1).Info("gNOI: Traceroute")
return status.Errorf(codes.Unimplemented, "")
}
func (srv *Server) SetPackage(rs gnoi_system_pb.System_SetPackageServer) error {
func (srv *SystemServer) SetPackage(rs gnoi_system_pb.System_SetPackageServer) error {
ctx := rs.Context()
_, err := authenticate(srv.config, ctx)
if err != nil {
Expand All @@ -146,15 +220,15 @@ func (srv *Server) SetPackage(rs gnoi_system_pb.System_SetPackageServer) error {
log.V(1).Info("gNOI: SetPackage")
return status.Errorf(codes.Unimplemented, "")
}
func (srv *Server) SwitchControlProcessor(ctx context.Context, req *gnoi_system_pb.SwitchControlProcessorRequest) (*gnoi_system_pb.SwitchControlProcessorResponse, error) {
func (srv *SystemServer) SwitchControlProcessor(ctx context.Context, req *gnoi_system_pb.SwitchControlProcessorRequest) (*gnoi_system_pb.SwitchControlProcessorResponse, error) {
_, err := authenticate(srv.config, ctx)
if err != nil {
return nil, err
}
log.V(1).Info("gNOI: SwitchControlProcessor")
return nil, status.Errorf(codes.Unimplemented, "")
}
func (srv *Server) Time(ctx context.Context, req *gnoi_system_pb.TimeRequest) (*gnoi_system_pb.TimeResponse, error) {
func (srv *SystemServer) Time(ctx context.Context, req *gnoi_system_pb.TimeRequest) (*gnoi_system_pb.TimeResponse, error) {
_, err := authenticate(srv.config, ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -373,8 +447,6 @@ func (srv *Server) ImageRemove(ctx context.Context, req *spb.ImageRemoveRequest)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}


return resp, nil
}

Expand Down
25 changes: 23 additions & 2 deletions gnmi_server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
gnmipb "github.com/openconfig/gnmi/proto/gnmi"
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"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -50,7 +51,22 @@ type Server struct {
// comes from a master controller.
ReqFromMaster func(req *gnmipb.SetRequest, masterEID *uint128) error
masterEID uint128
// UnimplementedSystemServer is embedded to satisfy SystemServer interface requirements
}


// FileServer is the server API for File service.
// All implementations must embed UnimplementedFileServer
// for forward compatibility
type FileServer struct {
*Server
gnoi_file_pb.UnimplementedFileServer
}

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

Expand Down Expand Up @@ -158,6 +174,10 @@ func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) {
ReqFromMaster: ReqFromMasterDisabledMA,
masterEID: uint128{High: 0, Low: 0},
}

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

var err error
if srv.config.Port < 0 {
srv.config.Port = 0
Expand All @@ -169,7 +189,8 @@ func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) {
gnmipb.RegisterGNMIServer(srv.s, srv)
spb_jwt_gnoi.RegisterSonicJwtServiceServer(srv.s, srv)
if srv.config.EnableTranslibWrite || srv.config.EnableNativeWrite {
gnoi_system_pb.RegisterSystemServer(srv.s, srv)
gnoi_system_pb.RegisterSystemServer(srv.s, systemSrv)
gnoi_file_pb.RegisterFileServer(srv.s, fileSrv)
}
if srv.config.EnableTranslibWrite {
spb_gnoi.RegisterSonicServiceServer(srv.s, srv)
Expand Down
84 changes: 78 additions & 6 deletions gnmi_server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import (
cacheclient "github.com/openconfig/gnmi/client"
gnmipb "github.com/openconfig/gnmi/proto/gnmi"
gnoi_system_pb "github.com/openconfig/gnoi/system"
gnoi_file_pb "github.com/openconfig/gnoi/file"
"github.com/sonic-net/sonic-gnmi/common_utils"
"github.com/sonic-net/sonic-gnmi/swsscommon"
)
Expand Down Expand Up @@ -2862,6 +2863,76 @@ func TestGNOI(t *testing.T) {
}
})

t.Run("FileStatSuccess", func(t *testing.T) {
mockClient := &ssc.DbusClient{}
expectedResult := map[string]string{
"last_modified": "1609459200000000000",
"permissions": "644",
"size": "1024",
"umask": "o022",
}
mock := gomonkey.ApplyMethod(reflect.TypeOf(mockClient), "GetFileStat", func(_ *ssc.DbusClient, path string) (map[string]string, error) {
return expectedResult, nil
})
defer mock.Reset()

// Prepare context and request
ctx := context.Background()
req := &gnoi_file_pb.StatRequest{Path: "/etc/sonic/config_db.json"}
fc := gnoi_file_pb.NewFileClient(conn)

resp, err := fc.Stat(ctx, req)
if err != nil {
t.Fatalf("FileStat failed: %v", err)
}
// Validate the response
if len(resp.Stats) == 0 {
t.Fatalf("Expected at least one StatInfo in response")
}

statInfo := resp.Stats[0]

if statInfo.LastModified != 1609459200000000000 {
t.Errorf("Expected last_modified %d but got %d", 1609459200000000000, statInfo.LastModified)
}
if statInfo.Permissions != 420 {
t.Errorf("Expected permissions 420 but got %d", statInfo.Permissions)
}
if statInfo.Size != 1024 {
t.Errorf("Expected size 1024 but got %d", statInfo.Size)
}
if statInfo.Umask != 18 {
t.Errorf("Expected umask 18 but got %d", statInfo.Umask)
}
})

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
})
defer mock.Reset()

// Prepare context and request
ctx := context.Background()
req := &gnoi_file_pb.StatRequest{Path: "/etc/sonic/config_db.json"}
fc := gnoi_file_pb.NewFileClient(conn)

resp, err := fc.Stat(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 {
source string
destination string
Expand Down Expand Up @@ -4191,14 +4262,14 @@ func TestSaveOnSet(t *testing.T) {
fakeDBC.Reset()

// Successful Dbus call
goodDbus := gomonkey.ApplyFuncReturn(ssc.DbusApi, nil)
goodDbus := gomonkey.ApplyFuncReturn(ssc.DbusApi, nil, nil)
if err := SaveOnSetEnabled(); err != nil {
t.Error("Unexpected DBUS failure")
}
goodDbus.Reset()

// Fail Dbus call
badDbus := gomonkey.ApplyFuncReturn(ssc.DbusApi, fmt.Errorf("Fail Send"))
badDbus := gomonkey.ApplyFuncReturn(ssc.DbusApi, nil, fmt.Errorf("Fail Send"))
defer badDbus.Reset()
if err := SaveOnSetEnabled(); err == nil {
t.Error("Expected DBUS failure")
Expand Down Expand Up @@ -4342,28 +4413,29 @@ func (x *MockSetPackageServer) Recv() (*gnoi_system_pb.SetPackageRequest, error)
func TestGnoiAuthorization(t *testing.T) {
s := createServer(t, 8081)
go runServer(t, s)
systemSrv := &SystemServer{Server: s}
mockAuthenticate := gomonkey.ApplyFunc(s.Authenticate, func(ctx context.Context, req *spb_jwt.AuthenticateRequest) (*spb_jwt.AuthenticateResponse, error) {
return nil, nil
})
defer mockAuthenticate.Reset()

err := s.Ping(new(gnoi_system_pb.PingRequest), new(MockPingServer))
err := systemSrv.Ping(new(gnoi_system_pb.PingRequest), new(MockPingServer))
if err == nil {
t.Errorf("Ping should failed, because not implement.")
}

s.Traceroute(new(gnoi_system_pb.TracerouteRequest), new(MockTracerouteServer))
systemSrv.Traceroute(new(gnoi_system_pb.TracerouteRequest), new(MockTracerouteServer))
if err == nil {
t.Errorf("Traceroute should failed, because not implement.")
}

s.SetPackage(new(MockSetPackageServer))
systemSrv.SetPackage(new(MockSetPackageServer))
if err == nil {
t.Errorf("SetPackage should failed, because not implement.")
}

ctx := context.Background()
s.SwitchControlProcessor(ctx, new(gnoi_system_pb.SwitchControlProcessorRequest))
systemSrv.SwitchControlProcessor(ctx, new(gnoi_system_pb.SwitchControlProcessorRequest))
if err == nil {
t.Errorf("SwitchControlProcessor should failed, because not implement.")
}
Expand Down
Loading

0 comments on commit 64ed32b

Please sign in to comment.