diff --git a/common_utils/context.go b/common_utils/context.go index e2ca6145..a9059f4a 100644 --- a/common_utils/context.go +++ b/common_utils/context.go @@ -53,6 +53,7 @@ const ( DBUS_CONFIG_RELOAD DBUS_STOP_SERVICE DBUS_RESTART_SERVICE + DBUS_FILE_STAT COUNTER_SIZE ) @@ -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 "" } diff --git a/gnmi_server/gnoi.go b/gnmi_server/gnoi.go index 2feb0e32..f11a9a81 100644 --- a/gnmi_server/gnoi.go +++ b/gnmi_server/gnoi.go @@ -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" @@ -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 { @@ -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 @@ -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) @@ -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 @@ -111,7 +185,7 @@ 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 @@ -119,7 +193,7 @@ func (srv *Server) CancelReboot(ctx context.Context, req *gnoi_system_pb.CancelR 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 { @@ -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 { @@ -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 { @@ -146,7 +220,7 @@ 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 @@ -154,7 +228,7 @@ func (srv *Server) SwitchControlProcessor(ctx context.Context, req *gnoi_system_ 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 @@ -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 } diff --git a/gnmi_server/server.go b/gnmi_server/server.go index e9910222..be490f54 100644 --- a/gnmi_server/server.go +++ b/gnmi_server/server.go @@ -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" @@ -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 } @@ -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 @@ -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) diff --git a/gnmi_server/server_test.go b/gnmi_server/server_test.go index 4ec9e71f..bee65d08 100644 --- a/gnmi_server/server_test.go +++ b/gnmi_server/server_test.go @@ -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" ) @@ -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 @@ -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") @@ -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.") } diff --git a/gnoi_client/gnoi_client.go b/gnoi_client/gnoi_client.go index 04a81e03..7d480e2b 100644 --- a/gnoi_client/gnoi_client.go +++ b/gnoi_client/gnoi_client.go @@ -3,6 +3,7 @@ package main import ( "google.golang.org/grpc" gnoi_system_pb "github.com/openconfig/gnoi/system" + gnoi_file_pb "github.com/openconfig/gnoi/file" spb "github.com/sonic-net/sonic-gnmi/proto/gnoi" spb_jwt "github.com/sonic-net/sonic-gnmi/proto/gnoi/jwt" "context" @@ -62,6 +63,14 @@ func main() { default: panic("Invalid RPC Name") } + case "File": + fc := gnoi_file_pb.NewFileClient(conn) + switch *rpc { + case "Stat": + fileStat(fc, ctx) + default: + panic("Invalid RPC Name") + } case "Sonic": switch *rpc { case "showtechsupport": @@ -125,6 +134,25 @@ func killProcess(sc gnoi_system_pb.SystemClient, ctx context.Context) { } } +func fileStat(fc gnoi_file_pb.FileClient, ctx context.Context) { + fmt.Println("File Stat") + ctx = setUserCreds(ctx) + req := &gnoi_file_pb.StatRequest {} + err := json.Unmarshal([]byte(*args), req) + if err != nil { + panic(err.Error()) + } + resp,err := fc.Stat(ctx, req) + if err != nil { + panic(err.Error()) + } + respstr, err := json.Marshal(resp) + if err != nil { + panic(err.Error()) + } + fmt.Println(string(respstr)) +} + func systemReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { fmt.Println("System Reboot") ctx = setUserCreds(ctx) @@ -321,4 +349,4 @@ func clearNeighbors(sc spb.SonicServiceClient, ctx context.Context) { panic(err.Error()) } fmt.Println(string(respstr)) -} +} \ No newline at end of file diff --git a/go.sum b/go.sum index 38afd9d1..7946b4e3 100644 --- a/go.sum +++ b/go.sum @@ -18,9 +18,14 @@ github.com/c9s/goprocinfo v0.0.0-20191125144613-4acdd056c72d/go.mod h1:uEyr4WpAH github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU= github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.1-0.20210802184156-9742bd7fca1c+incompatible h1:kFnl8B5YgOXou7f+dsklKcGSXph/nubNx7I6d6RoFuE= github.com/dgrijalva/jwt-go v3.2.1-0.20210802184156-9742bd7fca1c+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= @@ -28,6 +33,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg= @@ -65,11 +71,13 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/protobuf v3.11.4+incompatible/go.mod h1:lUQ9D1ePzbH2PrIS7ob/bjm9HXyH5WHB0Akwh7URreM= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jipanyang/gnmi v0.0.0-20180820232453-cb4d464fa018 h1:M++7b2XCTGqQwqu+AB0B3XzXiV+vVawnXJ4tvxUMrTU= github.com/jipanyang/gnmi v0.0.0-20180820232453-cb4d464fa018/go.mod h1:+aiusdWGFuKzi7B8/Y75kTlIA3UDF+sUBfY5+1e2NLs= github.com/jipanyang/gnxi v0.0.0-20181221084354-f0a90cca6fd0 h1:Dr/hrfbZxVlT/VvLfv8glHZAf9dLfuTSCJQq7cRGydo= @@ -103,11 +111,19 @@ github.com/openconfig/ygot v0.7.1 h1:kqDRYQpowXTr7EhGwr2BBDKJzqs+H8aFYjffYQ8lBsw github.com/openconfig/ygot v0.7.1/go.mod h1:5MwNX6DMP1QMf2eQjW+aJN/KNslVqRJtbfSL3SO6Urk= github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f h1:WyCn68lTiytVSkk7W1K9nBiSGTSRlUOdyTnSjwrIlok= github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f/go.mod h1:/iRjX3DdSK956SzsUdV55J+wIsQ+2IBWmBrB4RvZfk4= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -211,7 +227,9 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM= diff --git a/sonic_service_client/dbus_client.go b/sonic_service_client/dbus_client.go index 96982a6d..79835436 100644 --- a/sonic_service_client/dbus_client.go +++ b/sonic_service_client/dbus_client.go @@ -18,6 +18,7 @@ type Service interface { DeleteCheckPoint(cpName string) error StopService(service string) error RestartService(service string) error + GetFileStat(path string) (map[string]string, error) } type DbusClient struct { @@ -39,55 +40,62 @@ func NewDbusClient() (Service, error) { return &client, err } -func DbusApi(busName string, busPath string, intName string, timeout int, args ...interface{}) error { +func DbusApi(busName string, busPath string, intName string, timeout int, args ...interface{}) (interface{}, error) { common_utils.IncCounter(common_utils.DBUS) conn, err := dbus.SystemBus() if err != nil { log.V(2).Infof("Failed to connect to system bus: %v", err) common_utils.IncCounter(common_utils.DBUS_FAIL) - return err + return nil, err } ch := make(chan *dbus.Call, 1) obj := conn.Object(busName, dbus.ObjectPath(busPath)) obj.Go(intName, 0, ch, args...) + select { case call := <-ch: if call.Err != nil { common_utils.IncCounter(common_utils.DBUS_FAIL) - return call.Err + return nil, call.Err } result := call.Body if len(result) == 0 { common_utils.IncCounter(common_utils.DBUS_FAIL) - return fmt.Errorf("Dbus result is empty %v", result) + return nil, fmt.Errorf("Dbus result is empty %v", result) } if ret, ok := result[0].(int32); ok { if ret == 0 { - return nil + if len(result) != 2 { + common_utils.IncCounter(common_utils.DBUS_FAIL) + return nil, fmt.Errorf("Dbus result is invalid %v", result) + } + return result[1], nil } else { if len(result) != 2 { common_utils.IncCounter(common_utils.DBUS_FAIL) - return fmt.Errorf("Dbus result is invalid %v", result) + return nil, fmt.Errorf("Dbus result is invalid %v", result) } if msg, check := result[1].(string); check { common_utils.IncCounter(common_utils.DBUS_FAIL) - return fmt.Errorf(msg) + return nil, fmt.Errorf(msg) + } else if msg, check := result[1].(map[string]string); check { + common_utils.IncCounter(common_utils.DBUS_FAIL) + return nil, fmt.Errorf(msg["error"]) } else { common_utils.IncCounter(common_utils.DBUS_FAIL) - return fmt.Errorf("Invalid result message type %v %v", result[1], reflect.TypeOf(result[1])) + return nil, fmt.Errorf("Invalid result message type %v %v", result[1], reflect.TypeOf(result[1])) } } } else { common_utils.IncCounter(common_utils.DBUS_FAIL) - return fmt.Errorf("Invalid result type %v %v", result[0], reflect.TypeOf(result[0])) + return nil, fmt.Errorf("Invalid result type %v %v", result[0], reflect.TypeOf(result[0])) } case <-time.After(time.Duration(timeout) * time.Second): log.V(2).Infof("DbusApi: timeout") common_utils.IncCounter(common_utils.DBUS_FAIL) - return fmt.Errorf("Timeout %v", timeout) + return nil, fmt.Errorf("Timeout %v", timeout) } - return nil } func (c *DbusClient) ConfigReload(config string) error { @@ -96,7 +104,7 @@ func (c *DbusClient) ConfigReload(config string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".reload" - err := DbusApi(busName, busPath, intName, 10, config) + _, err := DbusApi(busName, busPath, intName, 10, config) return err } @@ -106,7 +114,7 @@ func (c *DbusClient) ConfigSave(fileName string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".save" - err := DbusApi(busName, busPath, intName, 10, fileName) + _, err := DbusApi(busName, busPath, intName, 10, fileName) return err } @@ -116,7 +124,7 @@ func (c *DbusClient) ApplyPatchYang(patch string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".apply_patch_yang" - err := DbusApi(busName, busPath, intName, 180, patch) + _, err := DbusApi(busName, busPath, intName, 180, patch) return err } @@ -126,7 +134,7 @@ func (c *DbusClient) ApplyPatchDb(patch string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".apply_patch_db" - err := DbusApi(busName, busPath, intName, 180, patch) + _, err := DbusApi(busName, busPath, intName, 180, patch) return err } @@ -136,7 +144,7 @@ func (c *DbusClient) CreateCheckPoint(fileName string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".create_checkpoint" - err := DbusApi(busName, busPath, intName, 10, fileName) + _, err := DbusApi(busName, busPath, intName, 10, fileName) return err } @@ -146,7 +154,7 @@ func (c *DbusClient) DeleteCheckPoint(fileName string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".delete_checkpoint" - err := DbusApi(busName, busPath, intName, 10, fileName) + _, err := DbusApi(busName, busPath, intName, 10, fileName) return err } @@ -156,7 +164,7 @@ func (c *DbusClient) StopService(service string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".stop_service" - err := DbusApi(busName, busPath, intName, 90, service) + _, err := DbusApi(busName, busPath, intName, 90, service) return err } @@ -166,6 +174,20 @@ func (c *DbusClient) RestartService(service string) error { busName := c.busNamePrefix + modName busPath := c.busPathPrefix + modName intName := c.intNamePrefix + modName + ".restart_service" - err := DbusApi(busName, busPath, intName, 90, service) + _, err := DbusApi(busName, busPath, intName, 90, service) return err } + +func (c *DbusClient) GetFileStat(path string) (map[string]string, error) { + common_utils.IncCounter(common_utils.DBUS_FILE_STAT) + modName := "file" + busName := c.busNamePrefix + modName + busPath := c.busPathPrefix + modName + intName := c.intNamePrefix + modName + ".get_file_stat" + result, err := DbusApi(busName, busPath, intName, 10, path) + if err != nil { + return nil, err + } + data, _ := result.(map[string]string) + return data, nil +} \ No newline at end of file diff --git a/sonic_service_client/dbus_client_test.go b/sonic_service_client/dbus_client_test.go index aced4123..aae99608 100644 --- a/sonic_service_client/dbus_client_test.go +++ b/sonic_service_client/dbus_client_test.go @@ -19,6 +19,86 @@ func TestSystemBusNegative(t *testing.T) { } } +func TestGetFileStat(t *testing.T) { + expectedResult := map[string]string{ + "path": "/etc/sonic", + "last_modified": "1609459200000000000", // Example timestamp + "permissions": "644", + "size": "1024", + "umask": "022", + } + + // Mocking the DBus API to return the expected result + mock1 := gomonkey.ApplyFunc(dbus.SystemBus, func() (conn *dbus.Conn, err error) { + return &dbus.Conn{}, nil + }) + defer mock1.Reset() + mock2 := gomonkey.ApplyMethod(reflect.TypeOf(&dbus.Object{}), "Go", func(obj *dbus.Object, method string, flags dbus.Flags, ch chan *dbus.Call, args ...interface{}) *dbus.Call { + if method != "org.SONiC.HostService.file.get_file_stat" { + t.Errorf("Wrong method: %v", method) + } + ret := &dbus.Call{} + ret.Err = nil + ret.Body = make([]interface{}, 2) + ret.Body[0] = int32(0) // Indicating success + ret.Body[1] = expectedResult + ch <- ret + return &dbus.Call{} + }) + defer mock2.Reset() + + client, err := NewDbusClient() + if err != nil { + t.Errorf("NewDbusClient failed: %v", err) + } + result, err := client.GetFileStat("/etc/sonic") + if err != nil { + t.Errorf("GetFileStat should pass: %v", err) + } + for key, value := range expectedResult { + if result[key] != value { + t.Errorf("Expected %s for key %s but got %s", value, key, result[key]) + } + } +} + +func TestGetFileStatNegative(t *testing.T) { + errMsg := "This is the mock error message" + + // Mocking the DBus API to return an error + mock1 := gomonkey.ApplyFunc(dbus.SystemBus, func() (conn *dbus.Conn, err error) { + return &dbus.Conn{}, nil + }) + defer mock1.Reset() + + mock2 := gomonkey.ApplyMethod(reflect.TypeOf(&dbus.Object{}), "Go", func(obj *dbus.Object, method string, flags dbus.Flags, ch chan *dbus.Call, args ...interface{}) *dbus.Call { + if method != "org.SONiC.HostService.file.get_file_stat" { + t.Errorf("Wrong method: %v", method) + } + ret := &dbus.Call{} + ret.Err = nil + ret.Body = make([]interface{}, 2) + ret.Body[0] = int32(1) // Indicating failure + ret.Body[1] = map[string]string{"error": errMsg} + ch <- ret + return &dbus.Call{} + }) + defer mock2.Reset() + + client, err := NewDbusClient() + if err != nil { + t.Errorf("NewDbusClient failed: %v", err) + } + + _, err = client.GetFileStat("/invalid/path") + if err == nil { + t.Errorf("GetFileStat should fail") + } + if err.Error() != errMsg { + t.Errorf("Expected error message '%s' but got '%v'", errMsg, err) + } +} + func TestConfigReload(t *testing.T) { mock1 := gomonkey.ApplyFunc(dbus.SystemBus, func() (conn *dbus.Conn, err error) { return &dbus.Conn{}, nil diff --git a/test/test_gnoi.py b/test/test_gnoi.py index e01ef1ac..61b7c067 100644 --- a/test/test_gnoi.py +++ b/test/test_gnoi.py @@ -74,4 +74,4 @@ def test_gnoi_restartprocess_invalid(self): ret, new_cnt = gnmi_dump('DBUS restart service') assert ret == 0, 'Fail to read counter' - assert new_cnt == old_cnt, 'DBUS API invoked unexpectedly' + assert new_cnt == old_cnt, 'DBUS API invoked unexpectedly' \ No newline at end of file diff --git a/test/utils.py b/test/utils.py index 4d8500e1..ffb9fd4d 100644 --- a/test/utils.py +++ b/test/utils.py @@ -315,4 +315,4 @@ def gnoi_refresh_with_jwt(token): cmd += '-jwt_token ' + token + ' ' cmd += '-module Sonic -rpc refresh ' ret, msg = run_cmd(cmd) - return ret, msg + return ret, msg \ No newline at end of file