diff --git a/common_utils/context.go b/common_utils/context.go index 09bff486..4e19a871 100644 --- a/common_utils/context.go +++ b/common_utils/context.go @@ -6,11 +6,10 @@ import ( "sync/atomic" ) - // AuthInfo holds data about the authenticated user type AuthInfo struct { // Username - User string + User string AuthEnabled bool // Roles Roles []string @@ -37,6 +36,7 @@ const requestContextKey contextkey = 0 var requestCounter uint64 type CounterType int + const ( GNMI_GET CounterType = iota GNMI_GET_FAIL @@ -55,6 +55,8 @@ const ( DBUS_RESTART_SERVICE DBUS_FILE_STAT DBUS_HALT_SYSTEM + DBUS_IMAGE_DOWNLOAD + DBUS_IMAGE_INSTALL COUNTER_SIZE ) @@ -94,6 +96,10 @@ func (c CounterType) String() string { return "DBUS file stat" case DBUS_HALT_SYSTEM: return "DBUS halt system" + case DBUS_IMAGE_DOWNLOAD: + return "DBUS image download" + case DBUS_IMAGE_INSTALL: + return "DBUS image install" default: return "" } @@ -101,7 +107,6 @@ func (c CounterType) String() string { var globalCounters [COUNTER_SIZE]uint64 - // GetContext function returns the RequestContext object for a // gRPC request. RequestContext is maintained as a context value of // the request. Creates a new RequestContext object is not already @@ -137,4 +142,3 @@ func IncCounter(cnt CounterType) { atomic.AddUint64(&globalCounters[cnt], 1) SetMemCounters(&globalCounters) } - diff --git a/sonic_service_client/dbus_client.go b/sonic_service_client/dbus_client.go index d54dda13..ce663dbe 100644 --- a/sonic_service_client/dbus_client.go +++ b/sonic_service_client/dbus_client.go @@ -1,11 +1,12 @@ package host_service import ( - "time" "fmt" "reflect" - log "github.com/golang/glog" + "time" + "github.com/godbus/dbus/v5" + log "github.com/golang/glog" "github.com/sonic-net/sonic-gnmi/common_utils" ) @@ -14,19 +15,21 @@ type Service interface { ConfigSave(fileName string) error ApplyPatchYang(fileName string) error ApplyPatchDb(fileName string) error - CreateCheckPoint(cpName string) error + CreateCheckPoint(cpName string) error DeleteCheckPoint(cpName string) error StopService(service string) error RestartService(service string) error GetFileStat(path string) (map[string]string, error) HaltSystem() error + DownloadImage(url string, save_as string) error + InstallImage(where string) error } type DbusClient struct { busNamePrefix string busPathPrefix string intNamePrefix string - channel chan struct{} + channel chan struct{} } func NewDbusClient() (Service, error) { @@ -194,19 +197,39 @@ func (c *DbusClient) GetFileStat(path string) (map[string]string, error) { } func (c *DbusClient) HaltSystem() error { - // Increment the counter for the DBUS_HALT_SYSTEM event - common_utils.IncCounter(common_utils.DBUS_HALT_SYSTEM) + // Increment the counter for the DBUS_HALT_SYSTEM event + common_utils.IncCounter(common_utils.DBUS_HALT_SYSTEM) - // Set the module name and update the D-Bus properties - modName := "systemd" - busName := c.busNamePrefix + modName - busPath := c.busPathPrefix + modName - intName := c.intNamePrefix + modName + ".execute_reboot" + // Set the module name and update the D-Bus properties + modName := "systemd" + busName := c.busNamePrefix + modName + busPath := c.busPathPrefix + modName + intName := c.intNamePrefix + modName + ".execute_reboot" - //Set the method to HALT(3) the system - const RebootMethod_HALT = 3 + //Set the method to HALT(3) the system + const RebootMethod_HALT = 3 - // Invoke the D-Bus API to execute the halt command - _, err := DbusApi(busName, busPath, intName, 10, RebootMethod_HALT) - return err + // Invoke the D-Bus API to execute the halt command + _, err := DbusApi(busName, busPath, intName, 10, RebootMethod_HALT) + return err +} + +func (c *DbusClient) DownloadImage(url string, save_as string) error { + common_utils.IncCounter(common_utils.DBUS_IMAGE_DOWNLOAD) + modName := "image_service" + busName := c.busNamePrefix + modName + busPath := c.busPathPrefix + modName + intName := c.intNamePrefix + modName + ".download" + _, err := DbusApi(busName, busPath, intName /*timeout=*/, 900, url, save_as) + return err +} + +func (c *DbusClient) InstallImage(where string) error { + common_utils.IncCounter(common_utils.DBUS_IMAGE_INSTALL) + modName := "image_service" + busName := c.busNamePrefix + modName + busPath := c.busPathPrefix + modName + intName := c.intNamePrefix + modName + ".install" + _, err := DbusApi(busName, busPath, intName /*timeout=*/, 900, where) + return err } diff --git a/sonic_service_client/dbus_client_test.go b/sonic_service_client/dbus_client_test.go index aae99608..942bbe2e 100644 --- a/sonic_service_client/dbus_client_test.go +++ b/sonic_service_client/dbus_client_test.go @@ -1,8 +1,8 @@ package host_service import ( - "testing" "reflect" + "testing" "github.com/agiledragon/gomonkey/v2" "github.com/godbus/dbus/v5" @@ -27,7 +27,7 @@ func TestGetFileStat(t *testing.T) { "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 @@ -65,7 +65,7 @@ func TestGetFileStat(t *testing.T) { func TestGetFileStatNegative(t *testing.T) { errMsg := "This is the mock error message" - // Mocking the DBus API to return an error + // Mocking the DBus API to return an error mock1 := gomonkey.ApplyFunc(dbus.SystemBus, func() (conn *dbus.Conn, err error) { return &dbus.Conn{}, nil }) @@ -487,3 +487,161 @@ func TestDeleteCheckPointNegative(t *testing.T) { t.Errorf("Wrong error: %v", err) } } + +func TestDownloadImageSuccess(t *testing.T) { + url := "http://example/sonic-img" + save_as := "/tmp/sonic-img" + 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.image_service.download" { + t.Errorf("Wrong method: %v", method) + } + if len(args) != 2 { + t.Errorf("Wrong number of arguments: %v", len(args)) + } + if args[0] != url { + t.Errorf("Wrong URL: %v", args[0]) + } + if args[1] != save_as { + t.Errorf("Wrong save_as: %v", args[1]) + } + ret := &dbus.Call{} + ret.Err = nil + ret.Body = make([]interface{}, 2) + ret.Body[0] = int32(0) + ch <- ret + return &dbus.Call{} + }) + defer mock2.Reset() + + client, err := NewDbusClient() + if err != nil { + t.Errorf("NewDbusClient failed: %v", err) + } + err = client.DownloadImage(url, save_as) + if err != nil { + t.Errorf("Download should pass: %v", err) + } +} + +func TestDownloadImageFail(t *testing.T) { + url := "http://example/sonic-img" + save_as := "/tmp/sonic-img" + err_msg := "This is the mock error message" + + 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.image_service.download" { + t.Errorf("Wrong method: %v", method) + } + if len(args) != 2 { + t.Errorf("Wrong number of arguments: %v", len(args)) + } + if args[0] != url { + t.Errorf("Wrong URL: %v", args[0]) + } + if args[1] != save_as { + t.Errorf("Wrong save_as: %v", args[1]) + } + ret := &dbus.Call{} + ret.Err = nil + ret.Body = make([]interface{}, 2) + ret.Body[0] = int32(1) + ret.Body[1] = err_msg + ch <- ret + return &dbus.Call{} + }) + defer mock2.Reset() + + client, err := NewDbusClient() + if err != nil { + t.Errorf("NewDbusClient failed: %v", err) + } + err = client.DownloadImage(url, save_as) + if err == nil { + t.Errorf("Download should fail") + } + if err.Error() != err_msg { + t.Errorf("Expected error message '%s' but got '%v'", err_msg, err) + } +} +func TestInstallImageSuccess(t *testing.T) { + where := "/tmp/sonic-img" + 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.image_service.install" { + t.Errorf("Wrong method: %v", method) + } + if len(args) != 1 { + t.Errorf("Wrong number of arguments: %v", len(args)) + } + if args[0] != where { + t.Errorf("Wrong where: %v", args[0]) + } + ret := &dbus.Call{} + ret.Err = nil + ret.Body = make([]interface{}, 2) + ret.Body[0] = int32(0) + ch <- ret + return &dbus.Call{} + }) + defer mock2.Reset() + + client, err := NewDbusClient() + if err != nil { + t.Errorf("NewDbusClient failed: %v", err) + } + err = client.InstallImage(where) + if err != nil { + t.Errorf("InstallImage should pass: %v", err) + } +} +func TestInstallImageFail(t *testing.T) { + where := "/tmp/sonic-img" + err_msg := "This is the mock error message" + + 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.image_service.install" { + t.Errorf("Wrong method: %v", method) + } + if len(args) != 1 { + t.Errorf("Wrong number of arguments: %v", len(args)) + } + if args[0] != where { + t.Errorf("Wrong where: %v", args[0]) + } + ret := &dbus.Call{} + ret.Err = nil + ret.Body = make([]interface{}, 2) + ret.Body[0] = int32(1) + ret.Body[1] = err_msg + ch <- ret + return &dbus.Call{} + }) + defer mock2.Reset() + + client, err := NewDbusClient() + if err != nil { + t.Errorf("NewDbusClient failed: %v", err) + } + err = client.InstallImage(where) + if err == nil { + t.Errorf("InstallImage should fail") + } + if err.Error() != err_msg { + t.Errorf("Expected error message '%s' but got '%v'", err_msg, err) + } +}