From 8639c97f41a36e36a4ec0b5b12522d7dac578e9b Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 01/18] gNOI Reboot changes --- common_utils/component_state_helper.go | 30 ++ common_utils/notification_producer.go | 55 +++ common_utils/notification_producer_test.go | 49 +++ gnmi_server/gnoi.go | 150 ------- gnmi_server/gnoi_system.go | 437 +++++++++++++++++++++ gnmi_server/gnoi_system_test.go | 361 +++++++++++++++++ 6 files changed, 932 insertions(+), 150 deletions(-) create mode 100644 common_utils/component_state_helper.go create mode 100644 common_utils/notification_producer.go create mode 100644 common_utils/notification_producer_test.go create mode 100644 gnmi_server/gnoi_system.go create mode 100644 gnmi_server/gnoi_system_test.go diff --git a/common_utils/component_state_helper.go b/common_utils/component_state_helper.go new file mode 100644 index 00000000..58be2bbc --- /dev/null +++ b/common_utils/component_state_helper.go @@ -0,0 +1,30 @@ +package common_utils + +import ( + "fmt" + + sdcfg "github.com/sonic-net/sonic-gnmi/sonic_db_config" + "github.com/go-redis/redis" + log "github.com/golang/glog" +) + +const ( + dbName = "STATE_DB" +) + +func getRedisDBClient() (*redis.Client, error) { + rclient := redis.NewClient(&redis.Options{ + Network: "tcp", + Addr: sdcfg.GetDbTcpAddr(dbName), + Password: "", // no password set + DB: sdcfg.GetDbId(dbName), + DialTimeout: 0, + }) + if rclient == nil { + return nil, fmt.Errorf("Cannot create redis client.") + } + if _, err := rclient.Ping().Result(); err != nil { + return nil, err + } + return rclient, nil +} diff --git a/common_utils/notification_producer.go b/common_utils/notification_producer.go new file mode 100644 index 00000000..f8acfce2 --- /dev/null +++ b/common_utils/notification_producer.go @@ -0,0 +1,55 @@ +package common_utils + +import ( + "encoding/json" + + "github.com/go-redis/redis" + log "github.com/golang/glog" +) + +// NotificationProducer provides utilities for sending messages using notification channel. +// NewNotificationProducer must be called for a new producer. +// Close must be called when finished. +type NotificationProducer struct { + ch string + rc *redis.Client +} + +// NewNotificationProducer returns a new NotificationProducer. +func NewNotificationProducer(ch string) (*NotificationProducer, error) { + n := new(NotificationProducer) + n.ch = ch + + // Create redis client. + var err error + n.rc, err = getRedisDBClient() + if err != nil { + return nil, err + } + + return n, nil +} + +// Close performs cleanup works. +// Close must be called when finished. +func (n *NotificationProducer) Close() { + if n.rc != nil { + n.rc.Close() + } +} + +func (n *NotificationProducer) Send(op, data string, kvs map[string]string) error { + fvs := []string{op, data} + for k, v := range kvs { + fvs = append(fvs, k) + fvs = append(fvs, v) + } + + val, err := json.Marshal(fvs) + if err != nil { + log.Error(err.Error()) + return err + } + log.Infof("Publishing to channel %s: %v.", n.ch, string(val)) + return n.rc.Publish(n.ch, val).Err() +} \ No newline at end of file diff --git a/common_utils/notification_producer_test.go b/common_utils/notification_producer_test.go new file mode 100644 index 00000000..a5107024 --- /dev/null +++ b/common_utils/notification_producer_test.go @@ -0,0 +1,49 @@ +package common_utils + +import ( + "testing" +) + +const ( + channel string = "channel" +) + +func TestNotificationProducerSucceedsWithEmptyOp(t *testing.T) { + n, _ := NewNotificationProducer(channel) + defer n.Close() + if err := n.Send("", "somedata", map[string]string{}); err != nil { + t.Fatalf("Expected no error!") + } +} + +func TestNotificationProducerSucceedsWithEmptyData(t *testing.T) { + n, _ := NewNotificationProducer(channel) + defer n.Close() + if err := n.Send("someop", "", map[string]string{}); err != nil { + t.Fatalf("Expected no error!") + } +} + +func TestNotificationProducerSucceedsWithEmptyOpAndData(t *testing.T) { + n, _ := NewNotificationProducer(channel) + defer n.Close() + if err := n.Send("", "", map[string]string{}); err != nil { + t.Fatalf("Expected no error!") + } +} + +func TestNotificationProducerSucceedsWithEmptyKeyValues(t *testing.T) { + n, _ := NewNotificationProducer(channel) + defer n.Close() + if err := n.Send("someop", "somedata", map[string]string{}); err != nil { + t.Fatalf("Expected no error!") + } +} + +func TestNotificationProducerSucceeds(t *testing.T) { + n, _ := NewNotificationProducer(channel) + defer n.Close() + if err := n.Send("someop", "somedata", map[string]string{"somekey": "somevalue"}); err != nil { + t.Fatalf("Expected no error!") + } +} diff --git a/gnmi_server/gnoi.go b/gnmi_server/gnoi.go index f11a9a81..ff27e18e 100644 --- a/gnmi_server/gnoi.go +++ b/gnmi_server/gnoi.go @@ -2,20 +2,15 @@ package gnmi 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" transutil "github.com/sonic-net/sonic-gnmi/transl_utils" - io "io/ioutil" ssc "github.com/sonic-net/sonic-gnmi/sonic_service_client" spb_jwt "github.com/sonic-net/sonic-gnmi/proto/gnoi/jwt" - "github.com/sonic-net/sonic-gnmi/common_utils" "google.golang.org/grpc/status" "google.golang.org/grpc/codes" "os/user" @@ -94,151 +89,6 @@ func (srv *FileServer) Get(req *gnoi_file_pb.GetRequest, stream gnoi_file_pb.Fi return status.Errorf(codes.Unimplemented, "") } -func KillOrRestartProcess(restart bool, serviceName string) error { - sc, err := ssc.NewDbusClient() - if err != nil { - return err - } - if restart { - log.V(2).Infof("Restarting service %s...", serviceName) - err = sc.RestartService(serviceName) - if err != nil { - log.V(2).Infof("Failed to restart service %s: %v", serviceName, err) - } - } else { - log.V(2).Infof("Stopping service %s...", serviceName) - err = sc.StopService(serviceName) - if err != nil { - log.V(2).Infof("Failed to stop service %s: %v", serviceName, err) - } - } - return err -} - -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 - } - - serviceName := req.GetName() - restart := req.GetRestart() - if req.GetPid() != 0 { - return nil, status.Errorf(codes.Unimplemented, "Pid option is not implemented") - } - if req.GetSignal() != gnoi_system_pb.KillProcessRequest_SIGNAL_TERM { - return nil, status.Errorf(codes.Unimplemented, "KillProcess only supports SIGNAL_TERM (option 1) for graceful process termination. Please specify SIGNAL_TERM") - } - log.V(1).Info("gNOI: KillProcess with optional restart") - log.V(1).Info("Request: ", req) - err = KillOrRestartProcess(restart, serviceName) - if err != nil { - return nil, err - } - var resp gnoi_system_pb.KillProcessResponse - return &resp, nil -} - -func RebootSystem(fileName string) error { - log.V(2).Infof("Rebooting with %s...", fileName) - sc, err := ssc.NewDbusClient() - if err != nil { - return err - } - err = sc.ConfigReload(fileName) - return err -} - -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) - if err != nil { - return nil, err - } - log.V(1).Info("gNOI: Reboot") - log.V(1).Info("Request:", req) - log.V(1).Info("Reboot system now, delay is ignored...") - // TODO: Support GNOI reboot delay - // Delay in nanoseconds before issuing reboot. - // https://github.com/openconfig/gnoi/blob/master/system/system.proto#L102-L115 - config_db_json, err := io.ReadFile(fileName) - if errors.Is(err, os.ErrNotExist) { - fileName = "" - } - err = RebootSystem(string(config_db_json)) - if err != nil { - return nil, err - } - var resp gnoi_system_pb.RebootResponse - return &resp, nil -} - -// TODO: Support GNOI RebootStatus -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 - } - log.V(1).Info("gNOI: RebootStatus") - return nil, status.Errorf(codes.Unimplemented, "") -} - -// TODO: Support GNOI CancelReboot -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 *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 { - return err - } - log.V(1).Info("gNOI: Ping") - return status.Errorf(codes.Unimplemented, "") -} -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 { - return err - } - log.V(1).Info("gNOI: Traceroute") - return status.Errorf(codes.Unimplemented, "") -} -func (srv *SystemServer) SetPackage(rs gnoi_system_pb.System_SetPackageServer) error { - ctx := rs.Context() - _, err := authenticate(srv.config, ctx) - if err != nil { - return err - } - log.V(1).Info("gNOI: SetPackage") - return status.Errorf(codes.Unimplemented, "") -} -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 *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 - } - log.V(1).Info("gNOI: Time") - var tm gnoi_system_pb.TimeResponse - tm.Time = uint64(time.Now().UnixNano()) - return &tm, nil -} - func (srv *Server) Authenticate(ctx context.Context, req *spb_jwt.AuthenticateRequest) (*spb_jwt.AuthenticateResponse, error) { // Can't enforce normal authentication here.. maybe only enforce client cert auth if enabled? // ctx,err := authenticate(srv.config, ctx) diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go new file mode 100644 index 00000000..938bdd1e --- /dev/null +++ b/gnmi_server/gnoi_system.go @@ -0,0 +1,437 @@ +package gnmi + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/sonic-net/sonic-mgmt-common/translib/transformer" + "github.com/sonic-net/sonic-gnmi/common_utils" + ssc "github.com/sonic-net/sonic-gnmi/sonic_service_client" + "github.com/go-redis/redis" + log "github.com/golang/glog" + "github.com/golang/protobuf/proto" + syspb "github.com/openconfig/gnoi/system" + "github.com/openconfig/gnoi/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + pjson "google.golang.org/protobuf/encoding/protojson" +) + +const ( + rebootKey = "Reboot" + rebootStatusKey = "RebootStatus" + rebootCancelKey = "CancelReboot" + rebootReqCh = "Reboot_Request_Channel" + rebootRespCh = "Reboot_Response_Channel" + dataMsgFld = "MESSAGE" + notificationTimeout = 10 * time.Second +) + +type sysXfmrInterface interface { + resetOptics(string) (string, error) +} + +type sysXfmrImpl struct{} + +func (t sysXfmrImpl) resetOptics(req string) (string, error) { + return transformer.ResetOptics(req) +} + +var sysXfmr sysXfmrInterface + +func init() { + sysXfmr = sysXfmrImpl{} +} + +// Vaild reboot method map. +var validRebootMap = map[syspb.RebootMethod]bool{ + syspb.RebootMethod_COLD: true, + syspb.RebootMethod_WARM: true, + syspb.RebootMethod_POWERDOWN: true, + syspb.RebootMethod_NSF: true, +} + +// Validates reboot request. +func validRebootReq(req *syspb.RebootRequest) error { + if _, ok := validRebootMap[req.GetMethod()]; !ok { + log.Error("Invalid request: reboot method is not supported.") + return fmt.Errorf("Invalid request: reboot method is not supported.") + } + // Back end does not support delayed reboot request. + if req.GetDelay() > 0 { + log.Error("Invalid request: reboot is not immediate.") + return fmt.Errorf("Invalid request: reboot is not immediate.") + } + if req.GetMessage() == "" { + log.Error("Invalid request: message is empty.") + return fmt.Errorf("Invalid request: message is empty.") + } + + if len(req.GetSubcomponents()) == 0 { + return nil + } + if err := validateModules(req.GetSubcomponents()); err != nil { + log.Error(err.Error()) + return err + } + + return nil +} + +// Validates if all subcomponents are valid transceivers. +func validateModules(comps []*types.Path) error { + for _, comp := range comps { + if _, err := validateAndGetXcvr(comp); err != nil { + log.V(ERROR).Info(err.Error()) + return err + } + } + return nil +} + +func processModuleReset(comps []*types.Path) error { + for _, comp := range comps { + xcvr, err := validateAndGetXcvr(comp) + if err != nil { + log.V(ERROR).Info(err.Error()) + return err + } + + port := strings.TrimPrefix(xcvr, "Ethernet") + cmd := "reset_modules port=" + port + log.V(INFO).Info("Reset command: ", cmd) + if _, err := sysXfmr.resetOptics(cmd); err != nil { + log.V(ERROR).Info(err.Error()) + return err + } + } + return nil +} + +func KillOrRestartProcess(restart bool, serviceName string) error { + sc, err := ssc.NewDbusClient() + if err != nil { + return err + } + if restart { + log.V(2).Infof("Restarting service %s...", serviceName) + err = sc.RestartService(serviceName) + if err != nil { + log.V(2).Infof("Failed to restart service %s: %v", serviceName, err) + } + } else { + log.V(2).Infof("Stopping service %s...", serviceName) + err = sc.StopService(serviceName) + if err != nil { + log.V(2).Infof("Failed to stop service %s: %v", serviceName, err) + } + } + return err +} + +func (srv *Server) KillProcess(ctx context.Context, req *gnoi_system_pb.KillProcessRequest) (*gnoi_system_pb.KillProcessResponse, error) { + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return nil, err + } + + serviceName := req.GetName() + restart := req.GetRestart() + if req.GetPid() != 0 { + return nil, status.Errorf(codes.Unimplemented, "Pid option is not implemented") + } + if req.GetSignal() != gnoi_system_pb.KillProcessRequest_SIGNAL_TERM { + return nil, status.Errorf(codes.Unimplemented, "KillProcess only supports SIGNAL_TERM (option 1) for graceful process termination. Please specify SIGNAL_TERM") + } + log.V(1).Info("gNOI: KillProcess with optional restart") + log.V(1).Info("Request: ", req) + err = KillOrRestartProcess(restart, serviceName) + if err != nil { + return nil, err + } + var resp gnoi_system_pb.KillProcessResponse + return &resp, nil +} + +// Processes message payload as op, data, field-value pairs. +func processMsgPayload(pload string) (string, string, map[string]string, error) { + var payload []string + if err := json.Unmarshal([]byte(pload), &payload); err != nil { + log.V(0).Info(err.Error()) + return "", "", nil, err + } + + if len(payload) < 2 || len(payload)%2 != 0 { + return "", "", nil, fmt.Errorf("Payload is malformed: %v\n", strings.Join(payload, ",")) + } + + op := payload[0] + data := payload[1] + fvs := map[string]string{} + for i := 2; i < len(payload); i += 2 { + fvs[payload[i]] = payload[i+1] + } + return op, data, fvs, nil +} + +// Converts a SWSS error code string into a gRPC code. +func swssToErrorCode(statusStr string) codes.Code { + switch statusStr { + case "SWSS_RC_SUCCESS": + return codes.OK + case "SWSS_RC_UNKNOWN": + return codes.Unknown + case "SWSS_RC_IN_USE", "SWSS_RC_INVALID_PARAM": + return codes.InvalidArgument + case "SWSS_RC_DEADLINE_EXCEEDED": + return codes.DeadlineExceeded + case "SWSS_RC_NOT_FOUND": + return codes.NotFound + case "SWSS_RC_EXISTS": + return codes.AlreadyExists + case "SWSS_RC_PERMISSION_DENIED": + return codes.PermissionDenied + case "SWSS_RC_FULL", "SWSS_RC_NO_MEMORY": + return codes.ResourceExhausted + case "SWSS_RC_UNIMPLEMENTED": + return codes.Unimplemented + case "SWSS_RC_INTERNAL": + return codes.Internal + case "SWSS_RC_NOT_EXECUTED", "SWSS_RC_FAILED_PRECONDITION": + return codes.FailedPrecondition + case "SWSS_RC_UNAVAIL": + return codes.Unavailable + } + return codes.Internal +} + +func sendRebootReqOnNotifCh(ctx context.Context, req proto.Message, sc *redis.Client, rebootNotifKey string) (resp proto.Message, err error, msgDataStr string) { + np, err := common_utils.NewNotificationProducer(rebootReqCh) + if err != nil { + log.V(INFO).Infof("[Reboot_Log] Error in setting up NewNotificationProducer: %v", err) + return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr + } + defer np.Close() + + // Subscribe to the response channel. + sub := sc.Subscribe(context.Background(), rebootRespCh) + if _, err = sub.Receive(context.Background()); err != nil { + log.V(INFO).Infof("[Reboot_Log] Error in setting up subscription to response channel: %v", err) + return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr + } + defer sub.Close() + channel := sub.Channel() + + switch rebootNotifKey { + case rebootKey: + req = req.(*syspb.RebootRequest) + resp = &syspb.RebootResponse{} + case rebootStatusKey: + req = req.(*syspb.RebootStatusRequest) + resp = &syspb.RebootStatusResponse{} + case rebootCancelKey: + req = req.(*syspb.CancelRebootRequest) + resp = &syspb.CancelRebootResponse{} + } + + reqStr, err := json.Marshal(req) + if err != nil { + log.V(INFO).Infof("[Reboot_Log] Error in marshalling JSON: %v", err) + return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr + } + // Publish to notification channel. + if err := np.Send(rebootNotifKey, "", map[string]string{dataMsgFld: string(reqStr)}); err != nil { + log.V(INFO).Infof("[Reboot_Log] Error in publishing to notification channel: %v", err) + return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr + } + + // Wait for response on Reboot_Response_Channel. + tc := time.After(notificationTimeout) + for { + select { + case msg := <-channel: + op, data, fvs, err := processMsgPayload(msg.Payload) + if err != nil { + log.V(INFO).Infof("[Reboot_Log] Error while receiving Response Notification = [%v] for message [%v]", err.Error(), msg) + return nil, status.Errorf(codes.Internal, fmt.Sprintf("Error while receiving Response Notification: [%s] for message [%s]", err.Error(), msg)), msgDataStr + } + log.V(INFO).Infof("[Reboot_Log] Received on the Reboot notification channel: op = [%v], data = [%v], fvs = [%v]", op, data, fvs) + + if op != rebootNotifKey { + log.V(INFO).Infof("[Reboot_Log] Op: [%v] doesn't match for `%v`!", op, rebootNotifKey) + continue + } + if fvs != nil { + if _, ok := fvs[dataMsgFld]; ok { + msgDataStr = fvs[dataMsgFld] + } + } + if swssCode := swssToErrorCode(data); swssCode != codes.OK { + log.V(INFO).Infof("[Reboot_Log] Response Notification returned SWSS Error code: %v, error = %v", swssCode, msgDataStr) + return nil, status.Errorf(swssCode, "Response Notification returned SWSS Error code: "+msgDataStr), msgDataStr + } + return resp, nil, msgDataStr + + case <-tc: + // Crossed the reboot response notification timeout. + log.V(INFO).Infof("[Reboot_Log] Response Notification timeout from Warmboot Manager!") + return nil, status.Errorf(codes.Internal, "Response Notification timeout from Warmboot Manager!"), msgDataStr + } + } +} + +// Reboot implements the corresponding RPC. +func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb.RebootResponse, error) { + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return nil, err + } + log.V(2).Info("gNOI: Reboot") + if err := validRebootReq(req); err != nil { + return nil, status.Errorf(codes.InvalidArgument, err.Error()) + } + // Initialize State DB. + rclient, err := getRedisDBClient(stateDB) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + defer rclient.Close() + + // Module reset. + if len(req.GetSubcomponents()) > 0 { + if err := processModuleReset(req.GetSubcomponents()); err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + return &syspb.RebootResponse{}, nil + } + + // System reboot. + resp, err, _ := sendRebootReqOnNotifCh(ctx, req, rclient, rebootKey) + if err != nil { + return nil, err + } + if resp == nil { + log.V(2).Info("Reboot request received empty response from Warmboot Manager.") + return &syspb.RebootResponse{}, nil + } + return resp.(*syspb.RebootResponse), nil +} + +// RebootStatus implements the corresponding RPC. +func (srv *Server) RebootStatus(ctx context.Context, req *syspb.RebootStatusRequest) (*syspb.RebootStatusResponse, error) { + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return nil, err + } + log.V(1).Info("gNOI: RebootStatus") + resp := &syspb.RebootStatusResponse{} + // Initialize State DB. + rclient, err := getRedisDBClient(stateDB) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + defer rclient.Close() + + respStr, err, msgData := sendRebootReqOnNotifCh(ctx, req, rclient, rebootStatusKey) + if err != nil { + log.V(INFO).Infof("gNOI: Received error for RebootStatusResponse: %v", err) + return nil, status.Errorf(codes.Internal, err.Error()) + } + if msgData == "" || respStr == nil { + log.V(INFO).Info("gNOI: Received empty RebootStatusResponse") + return nil, status.Errorf(codes.Internal, "Received empty RebootStatusResponse") + } + if err := pjson.Unmarshal([]byte(msgData), resp); err != nil { + log.V(INFO).Infof("gNOI: Cannot unmarshal the response: [%v]; err: [%v]", msgData, err) + return nil, status.Errorf(codes.Internal, fmt.Sprintf("Cannot unmarshal the response: [%s]; err: [%s]", msgData, err.Error())) + } + log.V(INFO).Infof("gNOI: Returning RebootStatusResponse: resp = [%v]\n, msgData = [%v]", resp, msgData) + return resp, nil +} + +// CancelReboot RPC implements the corresponding RPC. +func (srv *Server) CancelReboot(ctx context.Context, req *syspb.CancelRebootRequest) (*syspb.CancelRebootResponse, error) { + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return nil, err + } + log.V(1).Info("gNOI: CancelReboot") + if req.GetMessage() == "" { + log.V(ERROR).Info("Invalid CancelReboot request: message is empty.") + return nil, status.Errorf(codes.Internal, "Invalid CancelReboot request: message is empty.") + } + // Initialize State DB. + rclient, err := getRedisDBClient(stateDB) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + defer rclient.Close() + + resp, err, _ := sendRebootReqOnNotifCh(ctx, req, rclient, rebootCancelKey) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + if resp == nil { + return &syspb.CancelRebootResponse{}, nil + } + return resp.(*syspb.CancelRebootResponse), err +} + +// Ping is unimplemented. +func (srv *Server) Ping(req *syspb.PingRequest, stream syspb.System_PingServer) error { + ctx := stream.Context() + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return err + } + log.V(1).Info("gNOI: Ping") + return status.Errorf(codes.Unimplemented, "Method system.Ping is unimplemented.") +} + +// Traceroute is unimplemented. +func (srv *Server) Traceroute(req *syspb.TracerouteRequest, stream syspb.System_TracerouteServer) error { + ctx := stream.Context() + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return err + } + log.V(1).Info("gNOI: Traceroute") + return status.Errorf(codes.Unimplemented, "Method system.Traceroute is unimplemented.") +} + +// SetPackage is unimplemented. +func (srv *Server) SetPackage(stream syspb.System_SetPackageServer) error { + ctx := stream.Context() + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return err + } + log.V(1).Info("gNOI: SetPackage") + return status.Errorf(codes.Unimplemented, "Method system.SetPackage is unimplemented.") +} + +// SwitchControlProcessor implements the corresponding RPC. +func (srv *Server) SwitchControlProcessor(ctx context.Context, req *syspb.SwitchControlProcessorRequest) (*syspb.SwitchControlProcessorResponse, error) { + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return nil, err + } + log.V(1).Info("gNOI: SwitchControlProcessor") + return &syspb.SwitchControlProcessorResponse{}, nil +} + +// Time implements the corresponding RPC. +func (srv *Server) Time(ctx context.Context, req *syspb.TimeRequest) (*syspb.TimeResponse, error) { + ctx, err := authenticate(srv.config, ctx) + if err != nil { + return nil, err + } + log.V(1).Info("gNOI: Time") + var tm syspb.TimeResponse + tm.Time = uint64(time.Now().UnixNano()) + return &tm, nil +} \ No newline at end of file diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go new file mode 100644 index 00000000..566ebfd5 --- /dev/null +++ b/gnmi_server/gnoi_system_test.go @@ -0,0 +1,361 @@ +package gnmi + +import ( + "context" + "crypto/tls" + "fmt" + "regexp" + "testing" + "time" + + "github.com/go-redis/redis" + syspb "github.com/openconfig/gnoi/system" + "github.com/openconfig/gnoi/types" + "github.com/sonic-net/sonic-gnmi/common_utils" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" +) + +// Mock interface implementation that returns success! +type mocksysXfmrSuccess struct{} + +func (t mocksysXfmrSuccess) resetOptics(req string) (string, error) { + return "{}", nil +} + +// Mock interface implementation that returns error! +type mocksysXfmrFailure struct{} + +func (t mocksysXfmrFailure) resetOptics(req string) (string, error) { + return "", status.Errorf(codes.Internal, fmt.Sprintf("BackEnd returns an error!")) +} + +func testErr(err error, code codes.Code, pattern string, t *testing.T) { + t.Helper() + if err == nil { + t.Fatal("Expected error condition.") + } + e, _ := status.FromError(err) + if e.Code() != code { + t.Error("Error code: expected ", code, ", received ", e.Code()) + } + res, _ := regexp.MatchString(pattern, e.Message()) + if !res { + t.Error("Error message: expected ", pattern, ", received ", e.Message()) + } +} + +func errorCodeToSwss(errCode codes.Code) string { + switch errCode { + case codes.OK: + return "SWSS_RC_SUCCESS" + case codes.Unknown: + return "SWSS_RC_UNKNOWN" + case codes.InvalidArgument: + return "SWSS_RC_INVALID_PARAM" + case codes.DeadlineExceeded: + return "SWSS_RC_DEADLINE_EXCEEDED" + case codes.NotFound: + return "SWSS_RC_NOT_FOUND" + case codes.AlreadyExists: + return "SWSS_RC_EXISTS" + case codes.PermissionDenied: + return "SWSS_RC_PERMISSION_DENIED" + case codes.ResourceExhausted: + return "SWSS_RC_FULL" + case codes.Unimplemented: + return "SWSS_RC_UNIMPLEMENTED" + case codes.Internal: + return "SWSS_RC_INTERNAL" + case codes.FailedPrecondition: + return "SWSS_RC_FAILED_PRECONDITION" + case codes.Unavailable: + return "SWSS_RC_UNAVAIL" + } + return "" +} + +func warmbootManagerResponse(t *testing.T, sc *redis.Client, expectedResponse codes.Code, done chan bool, key string) { + sub := sc.Subscribe(context.Background(), "Reboot_Request_Channel") + if _, err := sub.Receive(context.Background()); err != nil { + t.Errorf("nsfManagerResponse failed to subscribe to request channel: %v", err) + return + } + defer sub.Close() + channel := sub.Channel() + + np, err := common_utils.NewNotificationProducer("Reboot_Response_Channel") + if err != nil { + t.Errorf("warmbootManagerResponse failed to create notification producer: %v", err) + return + } + defer np.Close() + + tc := time.After(5 * time.Second) + select { + case msg := <-channel: + t.Logf("warmbootManagerResponse received request: %v", msg) + // Respond to the request + if err := np.Send(key, errorCodeToSwss(expectedResponse), map[string]string{}); err != nil { + t.Errorf("warmbootManagerResponse failed to send response: %v", err) + return + } + case <-done: + return + case <-tc: + t.Error("warmbootManagerResponse timed out waiting for request") + return + } +} + +func TestSystem(t *testing.T) { + s := createServer(t) + go runServer(t, s) + defer s.Stop(t) + + targetAddr := fmt.Sprintf("127.0.0.1:%d", s.config.Port) + 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 %s failed: %v", targetAddr, err) + } + defer conn.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sc := syspb.NewSystemClient(conn) + rclient, err := getRedisDBClient(stateDB) + if err != nil { + t.Fatalf("Cannot connect to the redis server: %v.", err.Error()) + } + defer rclient.Close() + + t.Run("RebootFailsIfRebootMethodIsNotSupported", func(t *testing.T) { + req := &syspb.RebootRequest{ + Delay: 0, + Message: "Cold reboot due to ...", + } + + for _, method := range []syspb.RebootMethod{syspb.RebootMethod_UNKNOWN, syspb.RebootMethod_HALT, syspb.RebootMethod_POWERUP} { + req.Method = method + _, err := sc.Reboot(ctx, req) + testErr(err, codes.InvalidArgument, "reboot method is not supported.", t) + } + }) + t.Run("RebootFailsIfItIsDelayed", func(t *testing.T) { + req := &syspb.RebootRequest{ + Method: syspb.RebootMethod_COLD, + Delay: 1, + Message: "Cold reboot due to ...", + } + + _, err := sc.Reboot(ctx, req) + testErr(err, codes.InvalidArgument, "reboot is not immediate.", t) + }) + t.Run("RebootFailsIfMessageIsNotSet", func(t *testing.T) { + req := &syspb.RebootRequest{ + Method: syspb.RebootMethod_COLD, + Delay: 0, + } + + _, err := sc.Reboot(ctx, req) + testErr(err, codes.InvalidArgument, "message is empty.", t) + }) + t.Run("RebootFailsWithTimeout", func(t *testing.T) { + req := &syspb.RebootRequest{ + Delay: 0, + Message: "Reboot due to ...", + } + + for _, method := range []syspb.RebootMethod{syspb.RebootMethod_COLD, syspb.RebootMethod_POWERDOWN, syspb.RebootMethod_WARM, syspb.RebootMethod_NSF} { + req.Method = method + _, err := sc.Reboot(ctx, req) + testErr(err, codes.Internal, "Response Notification timeout from NSF Manager!", t) + } + }) + t.Run("RebootFailsForInvalidOptics", func(t *testing.T) { + req := &syspb.RebootRequest{ + Method: syspb.RebootMethod_COLD, + Delay: 0, + Message: "Reset optics due to ...", + Subcomponents: []*types.Path{ + &types.Path{ + Origin: "openconfig", + Elem: []*types.PathElem{ + { + Name: "components", + }, + { + Name: "component", + Key: map[string]string{ + "name": "Ethernet1234", + "someField": "someValue", + }, + }, + }, + }, + }, + } + + _, err := sc.Reboot(ctx, req) + testErr(err, codes.InvalidArgument, "Transceiver is malformed", t) + }) + t.Run("RebootFailsIfResetOpticsBackEndFails", func(t *testing.T) { + req := &syspb.RebootRequest{ + Method: syspb.RebootMethod_COLD, + Delay: 0, + Message: "Reset optics due to ...", + Subcomponents: []*types.Path{ + &types.Path{ + Origin: "openconfig", + Elem: []*types.PathElem{ + { + Name: "components", + }, + { + Name: "component", + Key: map[string]string{ + "name": "Ethernet1234", + }, + }, + }, + }, + }, + } + + sysXfmr = mocksysXfmrFailure{} + _, err := sc.Reboot(ctx, req) + testErr(err, codes.Internal, "BackEnd returns an error!", t) + }) + t.Run("RebootSucceedsIfResetOpticsBackEndSucceeds", func(t *testing.T) { + req := &syspb.RebootRequest{ + Method: syspb.RebootMethod_COLD, + Delay: 0, + Message: "Reset optics due to ...", + Subcomponents: []*types.Path{ + &types.Path{ + Origin: "openconfig", + Elem: []*types.PathElem{ + { + Name: "components", + }, + { + Name: "component", + Key: map[string]string{ + "name": "Ethernet1234", + }, + }, + }, + }, + }, + } + + sysXfmr = mocksysXfmrSuccess{} + _, err := sc.Reboot(ctx, req) + if err != nil { + t.Fatal("Expected success, got error: ", err.Error()) + } + }) + t.Run("RebootSucceeds", func(t *testing.T) { + // Start goroutine for mock Warmboot Manager to respond to Reboot requests + done := make(chan bool, 1) + go warmbootManagerResponse(t, rclient, codes.OK, done, rebootKey) + defer func() { done <- true }() + + req := &syspb.RebootRequest{ + Delay: 0, + Message: "Starting NSF reboot ...", + } + sysXfmr = mocksysXfmrSuccess{} + for _, method := range []syspb.RebootMethod{syspb.RebootMethod_COLD, syspb.RebootMethod_POWERDOWN, syspb.RebootMethod_WARM, syspb.RebootMethod_NSF} { + req.Method = method + _, err := sc.Reboot(ctx, req) + if err != nil { + t.Fatal("Expected success, got error: ", err.Error()) + } + } + }) + t.Run("RebootStatusFailsWithTimeout", func(t *testing.T) { + _, err := sc.RebootStatus(ctx, &syspb.RebootStatusRequest{}) + testErr(err, codes.Internal, "Response Notification timeout from NSF Manager!", t) + }) + t.Run("RebootStatusRequestSucceeds", func(t *testing.T) { + // Start goroutine for mock Warmboot Manager to respond to RebootStatus requests + done := make(chan bool, 1) + go warmbootManagerResponse(t, rclient, codes.OK, done, rebootStatusKey) + defer func() { done <- true }() + + sysXfmr = mocksysXfmrSuccess{} + _, err := sc.RebootStatus(ctx, &syspb.RebootStatusRequest{}) + if err != nil { + t.Fatal("Expected success, got error: ", err.Error()) + } + }) + t.Run("CancelRebootFailsWithEmptyMessage", func(t *testing.T) { + _, err := sc.CancelReboot(ctx, &syspb.CancelRebootRequest{}) + testErr(err, codes.Internal, "Invalid CancelReboot request: message is empty.", t) + }) + t.Run("CancelRebootFailsWithTimeout", func(t *testing.T) { + req := &syspb.CancelRebootRequest{ + Message: "Cancelling NSF Reboot due to hardware constraints", + } + _, err := sc.CancelReboot(ctx, req) + testErr(err, codes.Internal, "Response Notification timeout from NSF Manager!", t) + }) + t.Run("CancelRebootRequestSucceeds", func(t *testing.T) { + // Start goroutine for mock Warmboot Manager to respond to CancelReboot requests + done := make(chan bool, 1) + go warmbootManagerResponse(t, rclient, codes.OK, done, rebootCancelKey) + defer func() { done <- true }() + + req := &syspb.CancelRebootRequest{ + Message: "Cancelling Warm Reboot due to hardware constraints", + } + sysXfmr = mocksysXfmrSuccess{} + _, err := sc.CancelReboot(ctx, req) + if err != nil { + t.Fatal("Expected success, got error: ", err.Error()) + } + }) + t.Run("PingSucceeds", func(t *testing.T) { + opt := grpc.EmptyCallOption{} + _, err := sc.Ping(ctx, &syspb.PingRequest{}, opt) + if err != nil { + t.Fatal(err.Error()) + } + }) + t.Run("TracerouteSucceeds", func(t *testing.T) { + opt := grpc.EmptyCallOption{} + _, err := sc.Traceroute(ctx, &syspb.TracerouteRequest{}, opt) + if err != nil { + t.Fatal(err.Error()) + } + }) + t.Run("SetPackageSucceeds", func(t *testing.T) { + opt := grpc.EmptyCallOption{} + _, err := sc.SetPackage(ctx, opt) + if err != nil { + t.Fatal(err.Error()) + } + }) + t.Run("SwitchControlProcessorSucceeds", func(t *testing.T) { + _, err := sc.SwitchControlProcessor(ctx, &syspb.SwitchControlProcessorRequest{}) + if err != nil { + t.Fatal(err.Error()) + } + }) + t.Run("TimeSucceeds", func(t *testing.T) { + resp, err := sc.Time(ctx, &syspb.TimeRequest{}) + if err != nil { + t.Fatal(err.Error()) + } + ctime := uint64(time.Now().UnixNano()) + if ctime-resp.Time < 0 || ctime-resp.Time > 1e9 { + t.Fatalf("Invalid System Time %d", resp.Time) + } + }) +} \ No newline at end of file From 193f80ddce48541c0497e780f9bcb86fd99ba1e0 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 02/18] gNOI Reboot changes --- gnmi_server/gnoi_system.go | 53 ------------------- gnmi_server/gnoi_system_test.go | 94 --------------------------------- 2 files changed, 147 deletions(-) diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go index 938bdd1e..e7c386fc 100644 --- a/gnmi_server/gnoi_system.go +++ b/gnmi_server/gnoi_system.go @@ -31,22 +31,6 @@ const ( notificationTimeout = 10 * time.Second ) -type sysXfmrInterface interface { - resetOptics(string) (string, error) -} - -type sysXfmrImpl struct{} - -func (t sysXfmrImpl) resetOptics(req string) (string, error) { - return transformer.ResetOptics(req) -} - -var sysXfmr sysXfmrInterface - -func init() { - sysXfmr = sysXfmrImpl{} -} - // Vaild reboot method map. var validRebootMap = map[syspb.RebootMethod]bool{ syspb.RebootMethod_COLD: true, @@ -74,41 +58,7 @@ func validRebootReq(req *syspb.RebootRequest) error { if len(req.GetSubcomponents()) == 0 { return nil } - if err := validateModules(req.GetSubcomponents()); err != nil { - log.Error(err.Error()) - return err - } - - return nil -} - -// Validates if all subcomponents are valid transceivers. -func validateModules(comps []*types.Path) error { - for _, comp := range comps { - if _, err := validateAndGetXcvr(comp); err != nil { - log.V(ERROR).Info(err.Error()) - return err - } - } - return nil -} -func processModuleReset(comps []*types.Path) error { - for _, comp := range comps { - xcvr, err := validateAndGetXcvr(comp) - if err != nil { - log.V(ERROR).Info(err.Error()) - return err - } - - port := strings.TrimPrefix(xcvr, "Ethernet") - cmd := "reset_modules port=" + port - log.V(INFO).Info("Reset command: ", cmd) - if _, err := sysXfmr.resetOptics(cmd); err != nil { - log.V(ERROR).Info(err.Error()) - return err - } - } return nil } @@ -303,9 +253,6 @@ func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb // Module reset. if len(req.GetSubcomponents()) > 0 { - if err := processModuleReset(req.GetSubcomponents()); err != nil { - return nil, status.Errorf(codes.Internal, err.Error()) - } return &syspb.RebootResponse{}, nil } diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index 566ebfd5..0d8b1fb3 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -21,17 +21,6 @@ import ( // Mock interface implementation that returns success! type mocksysXfmrSuccess struct{} -func (t mocksysXfmrSuccess) resetOptics(req string) (string, error) { - return "{}", nil -} - -// Mock interface implementation that returns error! -type mocksysXfmrFailure struct{} - -func (t mocksysXfmrFailure) resetOptics(req string) (string, error) { - return "", status.Errorf(codes.Internal, fmt.Sprintf("BackEnd returns an error!")) -} - func testErr(err error, code codes.Code, pattern string, t *testing.T) { t.Helper() if err == nil { @@ -177,89 +166,6 @@ func TestSystem(t *testing.T) { testErr(err, codes.Internal, "Response Notification timeout from NSF Manager!", t) } }) - t.Run("RebootFailsForInvalidOptics", func(t *testing.T) { - req := &syspb.RebootRequest{ - Method: syspb.RebootMethod_COLD, - Delay: 0, - Message: "Reset optics due to ...", - Subcomponents: []*types.Path{ - &types.Path{ - Origin: "openconfig", - Elem: []*types.PathElem{ - { - Name: "components", - }, - { - Name: "component", - Key: map[string]string{ - "name": "Ethernet1234", - "someField": "someValue", - }, - }, - }, - }, - }, - } - - _, err := sc.Reboot(ctx, req) - testErr(err, codes.InvalidArgument, "Transceiver is malformed", t) - }) - t.Run("RebootFailsIfResetOpticsBackEndFails", func(t *testing.T) { - req := &syspb.RebootRequest{ - Method: syspb.RebootMethod_COLD, - Delay: 0, - Message: "Reset optics due to ...", - Subcomponents: []*types.Path{ - &types.Path{ - Origin: "openconfig", - Elem: []*types.PathElem{ - { - Name: "components", - }, - { - Name: "component", - Key: map[string]string{ - "name": "Ethernet1234", - }, - }, - }, - }, - }, - } - - sysXfmr = mocksysXfmrFailure{} - _, err := sc.Reboot(ctx, req) - testErr(err, codes.Internal, "BackEnd returns an error!", t) - }) - t.Run("RebootSucceedsIfResetOpticsBackEndSucceeds", func(t *testing.T) { - req := &syspb.RebootRequest{ - Method: syspb.RebootMethod_COLD, - Delay: 0, - Message: "Reset optics due to ...", - Subcomponents: []*types.Path{ - &types.Path{ - Origin: "openconfig", - Elem: []*types.PathElem{ - { - Name: "components", - }, - { - Name: "component", - Key: map[string]string{ - "name": "Ethernet1234", - }, - }, - }, - }, - }, - } - - sysXfmr = mocksysXfmrSuccess{} - _, err := sc.Reboot(ctx, req) - if err != nil { - t.Fatal("Expected success, got error: ", err.Error()) - } - }) t.Run("RebootSucceeds", func(t *testing.T) { // Start goroutine for mock Warmboot Manager to respond to Reboot requests done := make(chan bool, 1) From 0b3232fa1223cfb6859c7a59eef3b486c4dacb64 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 03/18] gNOI Reboot changes --- gnmi_server/gnoi_system.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go index e7c386fc..233a63f6 100644 --- a/gnmi_server/gnoi_system.go +++ b/gnmi_server/gnoi_system.go @@ -8,7 +8,6 @@ import ( "strings" "time" - "github.com/sonic-net/sonic-mgmt-common/translib/transformer" "github.com/sonic-net/sonic-gnmi/common_utils" ssc "github.com/sonic-net/sonic-gnmi/sonic_service_client" "github.com/go-redis/redis" From 5cc436af9d00e7114572291f6619c398dcf6bbb1 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 04/18] gNOI Reboot changes --- common_utils/component_state_helper.go | 16 +++++++++++++--- gnmi_server/gnoi_system.go | 6 +++--- gnmi_server/gnoi_system_test.go | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/common_utils/component_state_helper.go b/common_utils/component_state_helper.go index 58be2bbc..3045cca8 100644 --- a/common_utils/component_state_helper.go +++ b/common_utils/component_state_helper.go @@ -5,7 +5,6 @@ import ( sdcfg "github.com/sonic-net/sonic-gnmi/sonic_db_config" "github.com/go-redis/redis" - log "github.com/golang/glog" ) const ( @@ -13,11 +12,22 @@ const ( ) func getRedisDBClient() (*redis.Client, error) { + ns, _ := sdcfg.GetDbDefaultNamespace() + addr, err := sdcfg.GetDbTcpAddr(dbName, ns) + if err != nil { + log.Errorf("Addr err: %v", err) + return + } + db, err := sdcfg.GetDbId("STATE_DB", ns) + if err != nil { + log.Errorf("DB err: %v", err) + return + } rclient := redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr(dbName), + Addr: addr, Password: "", // no password set - DB: sdcfg.GetDbId(dbName), + DB: db, DialTimeout: 0, }) if rclient == nil { diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go index 233a63f6..f3c02371 100644 --- a/gnmi_server/gnoi_system.go +++ b/gnmi_server/gnoi_system.go @@ -244,7 +244,7 @@ func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb return nil, status.Errorf(codes.InvalidArgument, err.Error()) } // Initialize State DB. - rclient, err := getRedisDBClient(stateDB) + rclient, err := getRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } @@ -276,7 +276,7 @@ func (srv *Server) RebootStatus(ctx context.Context, req *syspb.RebootStatusRequ log.V(1).Info("gNOI: RebootStatus") resp := &syspb.RebootStatusResponse{} // Initialize State DB. - rclient, err := getRedisDBClient(stateDB) + rclient, err := getRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } @@ -311,7 +311,7 @@ func (srv *Server) CancelReboot(ctx context.Context, req *syspb.CancelRebootRequ return nil, status.Errorf(codes.Internal, "Invalid CancelReboot request: message is empty.") } // Initialize State DB. - rclient, err := getRedisDBClient(stateDB) + rclient, err := getRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index 0d8b1fb3..510137c8 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -117,7 +117,7 @@ func TestSystem(t *testing.T) { defer cancel() sc := syspb.NewSystemClient(conn) - rclient, err := getRedisDBClient(stateDB) + rclient, err := getRedisDBClient() if err != nil { t.Fatalf("Cannot connect to the redis server: %v.", err.Error()) } From 39bb44aeadd3ecde151553d1ff3d695acb1a348b Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 05/18] gNOI Reboot changes --- common_utils/component_state_helper.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common_utils/component_state_helper.go b/common_utils/component_state_helper.go index 3045cca8..205b7f03 100644 --- a/common_utils/component_state_helper.go +++ b/common_utils/component_state_helper.go @@ -16,18 +16,20 @@ func getRedisDBClient() (*redis.Client, error) { addr, err := sdcfg.GetDbTcpAddr(dbName, ns) if err != nil { log.Errorf("Addr err: %v", err) - return + return nil, err } db, err := sdcfg.GetDbId("STATE_DB", ns) if err != nil { log.Errorf("DB err: %v", err) - return + return nil, err } rclient := redis.NewClient(&redis.Options{ Network: "tcp", Addr: addr, + Addr: addr, Password: "", // no password set DB: db, + DB: db, DialTimeout: 0, }) if rclient == nil { From 7f204d6d9d0ef8b1fa37bcbc5cdccfe7b9280ce9 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 06/18] gNOI Reboot changes --- common_utils/component_state_helper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common_utils/component_state_helper.go b/common_utils/component_state_helper.go index 205b7f03..ba359d2f 100644 --- a/common_utils/component_state_helper.go +++ b/common_utils/component_state_helper.go @@ -5,6 +5,7 @@ import ( sdcfg "github.com/sonic-net/sonic-gnmi/sonic_db_config" "github.com/go-redis/redis" + log "github.com/golang/glog" ) const ( From ba827987d81170278256a749a08573003fb02ac9 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 07/18] gNOI Reboot changes --- common_utils/component_state_helper.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/common_utils/component_state_helper.go b/common_utils/component_state_helper.go index ba359d2f..3902e83a 100644 --- a/common_utils/component_state_helper.go +++ b/common_utils/component_state_helper.go @@ -27,10 +27,8 @@ func getRedisDBClient() (*redis.Client, error) { rclient := redis.NewClient(&redis.Options{ Network: "tcp", Addr: addr, - Addr: addr, Password: "", // no password set DB: db, - DB: db, DialTimeout: 0, }) if rclient == nil { From d2b22f370c93b5e041423d2bc036c9845ae81f60 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 08/18] gNOI Reboot changes --- gnmi_server/gnoi_system.go | 38 ++++++++++++++++----------------- gnmi_server/gnoi_system_test.go | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go index f3c02371..9463b7dc 100644 --- a/gnmi_server/gnoi_system.go +++ b/gnmi_server/gnoi_system.go @@ -82,7 +82,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 *Server) KillProcess(ctx context.Context, req *syspb.KillProcessRequest) (*syspb.KillProcessResponse, error) { ctx, err := authenticate(srv.config, ctx) if err != nil { return nil, err @@ -93,7 +93,7 @@ func (srv *Server) KillProcess(ctx context.Context, req *gnoi_system_pb.KillProc if req.GetPid() != 0 { return nil, status.Errorf(codes.Unimplemented, "Pid option is not implemented") } - if req.GetSignal() != gnoi_system_pb.KillProcessRequest_SIGNAL_TERM { + if req.GetSignal() != syspb.KillProcessRequest_SIGNAL_TERM { return nil, status.Errorf(codes.Unimplemented, "KillProcess only supports SIGNAL_TERM (option 1) for graceful process termination. Please specify SIGNAL_TERM") } log.V(1).Info("gNOI: KillProcess with optional restart") @@ -102,7 +102,7 @@ func (srv *Server) KillProcess(ctx context.Context, req *gnoi_system_pb.KillProc if err != nil { return nil, err } - var resp gnoi_system_pb.KillProcessResponse + var resp syspb.KillProcessResponse return &resp, nil } @@ -161,15 +161,15 @@ func swssToErrorCode(statusStr string) codes.Code { func sendRebootReqOnNotifCh(ctx context.Context, req proto.Message, sc *redis.Client, rebootNotifKey string) (resp proto.Message, err error, msgDataStr string) { np, err := common_utils.NewNotificationProducer(rebootReqCh) if err != nil { - log.V(INFO).Infof("[Reboot_Log] Error in setting up NewNotificationProducer: %v", err) + log.V(1).Infof("[Reboot_Log] Error in setting up NewNotificationProducer: %v", err) return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr } defer np.Close() // Subscribe to the response channel. - sub := sc.Subscribe(context.Background(), rebootRespCh) - if _, err = sub.Receive(context.Background()); err != nil { - log.V(INFO).Infof("[Reboot_Log] Error in setting up subscription to response channel: %v", err) + sub := sc.Subscribe(rebootRespCh) + if _, err = sub.Receive(); err != nil { + log.V(1).Infof("[Reboot_Log] Error in setting up subscription to response channel: %v", err) return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr } defer sub.Close() @@ -189,12 +189,12 @@ func sendRebootReqOnNotifCh(ctx context.Context, req proto.Message, sc *redis.Cl reqStr, err := json.Marshal(req) if err != nil { - log.V(INFO).Infof("[Reboot_Log] Error in marshalling JSON: %v", err) + log.V(1).Infof("[Reboot_Log] Error in marshalling JSON: %v", err) return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr } // Publish to notification channel. if err := np.Send(rebootNotifKey, "", map[string]string{dataMsgFld: string(reqStr)}); err != nil { - log.V(INFO).Infof("[Reboot_Log] Error in publishing to notification channel: %v", err) + log.V(1).Infof("[Reboot_Log] Error in publishing to notification channel: %v", err) return nil, status.Errorf(codes.Internal, err.Error()), msgDataStr } @@ -205,13 +205,13 @@ func sendRebootReqOnNotifCh(ctx context.Context, req proto.Message, sc *redis.Cl case msg := <-channel: op, data, fvs, err := processMsgPayload(msg.Payload) if err != nil { - log.V(INFO).Infof("[Reboot_Log] Error while receiving Response Notification = [%v] for message [%v]", err.Error(), msg) + log.V(1).Infof("[Reboot_Log] Error while receiving Response Notification = [%v] for message [%v]", err.Error(), msg) return nil, status.Errorf(codes.Internal, fmt.Sprintf("Error while receiving Response Notification: [%s] for message [%s]", err.Error(), msg)), msgDataStr } - log.V(INFO).Infof("[Reboot_Log] Received on the Reboot notification channel: op = [%v], data = [%v], fvs = [%v]", op, data, fvs) + log.V(1).Infof("[Reboot_Log] Received on the Reboot notification channel: op = [%v], data = [%v], fvs = [%v]", op, data, fvs) if op != rebootNotifKey { - log.V(INFO).Infof("[Reboot_Log] Op: [%v] doesn't match for `%v`!", op, rebootNotifKey) + log.V(1).Infof("[Reboot_Log] Op: [%v] doesn't match for `%v`!", op, rebootNotifKey) continue } if fvs != nil { @@ -220,14 +220,14 @@ func sendRebootReqOnNotifCh(ctx context.Context, req proto.Message, sc *redis.Cl } } if swssCode := swssToErrorCode(data); swssCode != codes.OK { - log.V(INFO).Infof("[Reboot_Log] Response Notification returned SWSS Error code: %v, error = %v", swssCode, msgDataStr) + log.V(1).Infof("[Reboot_Log] Response Notification returned SWSS Error code: %v, error = %v", swssCode, msgDataStr) return nil, status.Errorf(swssCode, "Response Notification returned SWSS Error code: "+msgDataStr), msgDataStr } return resp, nil, msgDataStr case <-tc: // Crossed the reboot response notification timeout. - log.V(INFO).Infof("[Reboot_Log] Response Notification timeout from Warmboot Manager!") + log.V(1).Infof("[Reboot_Log] Response Notification timeout from Warmboot Manager!") return nil, status.Errorf(codes.Internal, "Response Notification timeout from Warmboot Manager!"), msgDataStr } } @@ -284,18 +284,18 @@ func (srv *Server) RebootStatus(ctx context.Context, req *syspb.RebootStatusRequ respStr, err, msgData := sendRebootReqOnNotifCh(ctx, req, rclient, rebootStatusKey) if err != nil { - log.V(INFO).Infof("gNOI: Received error for RebootStatusResponse: %v", err) + log.V(1).Infof("gNOI: Received error for RebootStatusResponse: %v", err) return nil, status.Errorf(codes.Internal, err.Error()) } if msgData == "" || respStr == nil { - log.V(INFO).Info("gNOI: Received empty RebootStatusResponse") + log.V(1).Info("gNOI: Received empty RebootStatusResponse") return nil, status.Errorf(codes.Internal, "Received empty RebootStatusResponse") } if err := pjson.Unmarshal([]byte(msgData), resp); err != nil { - log.V(INFO).Infof("gNOI: Cannot unmarshal the response: [%v]; err: [%v]", msgData, err) + log.V(1).Infof("gNOI: Cannot unmarshal the response: [%v]; err: [%v]", msgData, err) return nil, status.Errorf(codes.Internal, fmt.Sprintf("Cannot unmarshal the response: [%s]; err: [%s]", msgData, err.Error())) } - log.V(INFO).Infof("gNOI: Returning RebootStatusResponse: resp = [%v]\n, msgData = [%v]", resp, msgData) + log.V(1).Infof("gNOI: Returning RebootStatusResponse: resp = [%v]\n, msgData = [%v]", resp, msgData) return resp, nil } @@ -307,7 +307,7 @@ func (srv *Server) CancelReboot(ctx context.Context, req *syspb.CancelRebootRequ } log.V(1).Info("gNOI: CancelReboot") if req.GetMessage() == "" { - log.V(ERROR).Info("Invalid CancelReboot request: message is empty.") + log.V(1).Info("Invalid CancelReboot request: message is empty.") return nil, status.Errorf(codes.Internal, "Invalid CancelReboot request: message is empty.") } // Initialize State DB. diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index 510137c8..bc84571b 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -67,8 +67,8 @@ func errorCodeToSwss(errCode codes.Code) string { } func warmbootManagerResponse(t *testing.T, sc *redis.Client, expectedResponse codes.Code, done chan bool, key string) { - sub := sc.Subscribe(context.Background(), "Reboot_Request_Channel") - if _, err := sub.Receive(context.Background()); err != nil { + sub := sc.Subscribe("Reboot_Request_Channel") + if _, err := sub.Receive(); err != nil { t.Errorf("nsfManagerResponse failed to subscribe to request channel: %v", err) return } From a62130c8f5ad2c6876b42335e98af0648e4806d0 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 09/18] gNOI Reboot changes --- gnmi_server/gnoi_system.go | 8 +++----- gnmi_server/gnoi_system_test.go | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go index 9463b7dc..692ef74a 100644 --- a/gnmi_server/gnoi_system.go +++ b/gnmi_server/gnoi_system.go @@ -3,7 +3,6 @@ package gnmi import ( "context" "encoding/json" - "errors" "fmt" "strings" "time" @@ -14,7 +13,6 @@ import ( log "github.com/golang/glog" "github.com/golang/protobuf/proto" syspb "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnoi/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" pjson "google.golang.org/protobuf/encoding/protojson" @@ -244,7 +242,7 @@ func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb return nil, status.Errorf(codes.InvalidArgument, err.Error()) } // Initialize State DB. - rclient, err := getRedisDBClient() + rclient, err := common_utils.getRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } @@ -276,7 +274,7 @@ func (srv *Server) RebootStatus(ctx context.Context, req *syspb.RebootStatusRequ log.V(1).Info("gNOI: RebootStatus") resp := &syspb.RebootStatusResponse{} // Initialize State DB. - rclient, err := getRedisDBClient() + rclient, err := common_utils.getRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } @@ -311,7 +309,7 @@ func (srv *Server) CancelReboot(ctx context.Context, req *syspb.CancelRebootRequ return nil, status.Errorf(codes.Internal, "Invalid CancelReboot request: message is empty.") } // Initialize State DB. - rclient, err := getRedisDBClient() + rclient, err := common_utils.getRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index bc84571b..e48131f0 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -117,7 +117,7 @@ func TestSystem(t *testing.T) { defer cancel() sc := syspb.NewSystemClient(conn) - rclient, err := getRedisDBClient() + rclient, err := common_utils.getRedisDBClient() if err != nil { t.Fatalf("Cannot connect to the redis server: %v.", err.Error()) } From 2413dc0175523628d26e893abc9c9b47da5bb0eb Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 10/18] gNOI Reboot changes --- common_utils/component_state_helper.go | 2 +- gnmi_server/gnoi.go | 4 ++++ gnmi_server/gnoi_system.go | 6 +++--- gnmi_server/gnoi_system_test.go | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/common_utils/component_state_helper.go b/common_utils/component_state_helper.go index 3902e83a..81748633 100644 --- a/common_utils/component_state_helper.go +++ b/common_utils/component_state_helper.go @@ -12,7 +12,7 @@ const ( dbName = "STATE_DB" ) -func getRedisDBClient() (*redis.Client, error) { +func GetRedisDBClient() (*redis.Client, error) { ns, _ := sdcfg.GetDbDefaultNamespace() addr, err := sdcfg.GetDbTcpAddr(dbName, ns) if err != nil { diff --git a/gnmi_server/gnoi.go b/gnmi_server/gnoi.go index ff27e18e..71f65568 100644 --- a/gnmi_server/gnoi.go +++ b/gnmi_server/gnoi.go @@ -18,6 +18,10 @@ import ( jwt "github.com/dgrijalva/jwt-go" ) +const ( + stateDB string = "STATE_DB" +) + func ReadFileStat(path string) (*gnoi_file_pb.StatInfo, error) { sc, err := ssc.NewDbusClient() if err != nil { diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go index 692ef74a..fad695a0 100644 --- a/gnmi_server/gnoi_system.go +++ b/gnmi_server/gnoi_system.go @@ -242,7 +242,7 @@ func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb return nil, status.Errorf(codes.InvalidArgument, err.Error()) } // Initialize State DB. - rclient, err := common_utils.getRedisDBClient() + rclient, err := common_utils.GetRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } @@ -274,7 +274,7 @@ func (srv *Server) RebootStatus(ctx context.Context, req *syspb.RebootStatusRequ log.V(1).Info("gNOI: RebootStatus") resp := &syspb.RebootStatusResponse{} // Initialize State DB. - rclient, err := common_utils.getRedisDBClient() + rclient, err := common_utils.GetRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } @@ -309,7 +309,7 @@ func (srv *Server) CancelReboot(ctx context.Context, req *syspb.CancelRebootRequ return nil, status.Errorf(codes.Internal, "Invalid CancelReboot request: message is empty.") } // Initialize State DB. - rclient, err := common_utils.getRedisDBClient() + rclient, err := common_utils.GetRedisDBClient() if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index e48131f0..a728683c 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -117,7 +117,7 @@ func TestSystem(t *testing.T) { defer cancel() sc := syspb.NewSystemClient(conn) - rclient, err := common_utils.getRedisDBClient() + rclient, err := common_utils.GetRedisDBClient() if err != nil { t.Fatalf("Cannot connect to the redis server: %v.", err.Error()) } From 9e722613fdfa2367026d4cd69f82a4356ad7c369 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 11/18] gNOI Reboot changes --- common_utils/notification_producer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common_utils/notification_producer.go b/common_utils/notification_producer.go index f8acfce2..653baee0 100644 --- a/common_utils/notification_producer.go +++ b/common_utils/notification_producer.go @@ -22,7 +22,7 @@ func NewNotificationProducer(ch string) (*NotificationProducer, error) { // Create redis client. var err error - n.rc, err = getRedisDBClient() + n.rc, err = GetRedisDBClient() if err != nil { return nil, err } From 126d126c3a2a825a13a644c175fb35d8eb1005ba Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 12/18] gNOI Reboot changes --- gnmi_server/server.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/gnmi_server/server.go b/gnmi_server/server.go index be490f54..d8c2975d 100644 --- a/gnmi_server/server.go +++ b/gnmi_server/server.go @@ -51,6 +51,7 @@ type Server struct { // comes from a master controller. ReqFromMaster func(req *gnmipb.SetRequest, masterEID *uint128) error masterEID uint128 + gnoi_system_pb.UnimplementedSystemServer } @@ -62,14 +63,6 @@ type FileServer struct { 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 -} - type AuthTypes map[string]bool // Config is a collection of values for Server @@ -176,7 +169,6 @@ func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) { } fileSrv := &FileServer{Server: srv} - systemSrv := &SystemServer{Server: srv} var err error if srv.config.Port < 0 { @@ -189,7 +181,7 @@ 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, systemSrv) + gnoi_system_pb.RegisterSystemServer(srv.s, srv) gnoi_file_pb.RegisterFileServer(srv.s, fileSrv) } if srv.config.EnableTranslibWrite { From 3366ea527d71efa1a087049b8466ccd00a6b80ba Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 13/18] gNOI Reboot changes --- gnmi_server/gnoi_system_test.go | 13 +++---------- gnmi_server/server_test.go | 21 ++++----------------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index a728683c..99ced681 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -10,7 +10,6 @@ import ( "github.com/go-redis/redis" syspb "github.com/openconfig/gnoi/system" - "github.com/openconfig/gnoi/types" "github.com/sonic-net/sonic-gnmi/common_utils" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -18,9 +17,6 @@ import ( "google.golang.org/grpc/status" ) -// Mock interface implementation that returns success! -type mocksysXfmrSuccess struct{} - func testErr(err error, code codes.Code, pattern string, t *testing.T) { t.Helper() if err == nil { @@ -100,9 +96,9 @@ func warmbootManagerResponse(t *testing.T, sc *redis.Client, expectedResponse co } func TestSystem(t *testing.T) { - s := createServer(t) + s := createServer(t, 8081) go runServer(t, s) - defer s.Stop(t) + defer s.Stop() targetAddr := fmt.Sprintf("127.0.0.1:%d", s.config.Port) tlsConfig := &tls.Config{InsecureSkipVerify: true} @@ -176,7 +172,6 @@ func TestSystem(t *testing.T) { Delay: 0, Message: "Starting NSF reboot ...", } - sysXfmr = mocksysXfmrSuccess{} for _, method := range []syspb.RebootMethod{syspb.RebootMethod_COLD, syspb.RebootMethod_POWERDOWN, syspb.RebootMethod_WARM, syspb.RebootMethod_NSF} { req.Method = method _, err := sc.Reboot(ctx, req) @@ -195,7 +190,6 @@ func TestSystem(t *testing.T) { go warmbootManagerResponse(t, rclient, codes.OK, done, rebootStatusKey) defer func() { done <- true }() - sysXfmr = mocksysXfmrSuccess{} _, err := sc.RebootStatus(ctx, &syspb.RebootStatusRequest{}) if err != nil { t.Fatal("Expected success, got error: ", err.Error()) @@ -221,7 +215,6 @@ func TestSystem(t *testing.T) { req := &syspb.CancelRebootRequest{ Message: "Cancelling Warm Reboot due to hardware constraints", } - sysXfmr = mocksysXfmrSuccess{} _, err := sc.CancelReboot(ctx, req) if err != nil { t.Fatal("Expected success, got error: ", err.Error()) @@ -254,7 +247,7 @@ func TestSystem(t *testing.T) { t.Fatal(err.Error()) } }) - t.Run("TimeSucceeds", func(t *testing.T) { + t.Run("SystemTime", func(t *testing.T) { resp, err := sc.Time(ctx, &syspb.TimeRequest{}) if err != nil { t.Fatal(err.Error()) diff --git a/gnmi_server/server_test.go b/gnmi_server/server_test.go index bee65d08..3da21aea 100644 --- a/gnmi_server/server_test.go +++ b/gnmi_server/server_test.go @@ -2832,18 +2832,6 @@ func TestGNOI(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 240*time.Second) defer cancel() - t.Run("SystemTime", func(t *testing.T) { - sc := gnoi_system_pb.NewSystemClient(conn) - resp, err := sc.Time(ctx, new(gnoi_system_pb.TimeRequest)) - if err != nil { - t.Fatal(err.Error()) - } - ctime := uint64(time.Now().UnixNano()) - if ctime-resp.Time < 0 || ctime-resp.Time > 1e9 { - t.Fatalf("Invalid System Time %d", resp.Time) - } - }) - t.Run("SonicShowTechsupport", func(t *testing.T) { t.Skip("Not supported yet") sc := sgpb.NewSonicServiceClient(conn) @@ -4413,29 +4401,28 @@ 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 := systemSrv.Ping(new(gnoi_system_pb.PingRequest), new(MockPingServer)) + err := s.Ping(new(gnoi_system_pb.PingRequest), new(MockPingServer)) if err == nil { t.Errorf("Ping should failed, because not implement.") } - systemSrv.Traceroute(new(gnoi_system_pb.TracerouteRequest), new(MockTracerouteServer)) + s.Traceroute(new(gnoi_system_pb.TracerouteRequest), new(MockTracerouteServer)) if err == nil { t.Errorf("Traceroute should failed, because not implement.") } - systemSrv.SetPackage(new(MockSetPackageServer)) + s.SetPackage(new(MockSetPackageServer)) if err == nil { t.Errorf("SetPackage should failed, because not implement.") } ctx := context.Background() - systemSrv.SwitchControlProcessor(ctx, new(gnoi_system_pb.SwitchControlProcessorRequest)) + s.SwitchControlProcessor(ctx, new(gnoi_system_pb.SwitchControlProcessorRequest)) if err == nil { t.Errorf("SwitchControlProcessor should failed, because not implement.") } From 055ce49e3db5a6446aad4a34bfbb727e2cd73593 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 14/18] gNOI Reboot changes --- gnoi_client/gnoi_client.go | 26 +++++++++++++++++++------- test/test_gnoi.py | 20 +++++++------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/gnoi_client/gnoi_client.go b/gnoi_client/gnoi_client.go index 7d480e2b..82e8aa72 100644 --- a/gnoi_client/gnoi_client.go +++ b/gnoi_client/gnoi_client.go @@ -156,20 +156,29 @@ func fileStat(fc gnoi_file_pb.FileClient, ctx context.Context) { func systemReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { fmt.Println("System Reboot") ctx = setUserCreds(ctx) - req := &gnoi_system_pb.RebootRequest {} - json.Unmarshal([]byte(*args), req) - _,err := sc.Reboot(ctx, req) + req := &gnoi_system_pb.RebootRequest{} + if err := json.Unmarshal([]byte(*args), req); err != nil { + panic(err.Error()) + } + resp, err := sc.Reboot(ctx, req) + if err != nil { + panic(err.Error()) + } + respstr, err := json.Marshal(resp) if err != nil { panic(err.Error()) } + fmt.Println(string(respstr)) } func systemCancelReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { fmt.Println("System CancelReboot") ctx = setUserCreds(ctx) - req := &gnoi_system_pb.CancelRebootRequest {} - json.Unmarshal([]byte(*args), req) - resp,err := sc.CancelReboot(ctx, req) + req := &gnoi_system_pb.CancelRebootRequest{} + if err := json.Unmarshal([]byte(*args), req); err != nil { + panic(err.Error()) + } + resp, err := sc.CancelReboot(ctx, req) if err != nil { panic(err.Error()) } @@ -183,7 +192,10 @@ func systemCancelReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { func systemRebootStatus(sc gnoi_system_pb.SystemClient, ctx context.Context) { fmt.Println("System RebootStatus") ctx = setUserCreds(ctx) - req := &gnoi_system_pb.RebootStatusRequest {} + req := &gnoi_system_pb.RebootStatusRequest{} + if err := json.Unmarshal([]byte(*args), req); err != nil { + panic(err.Error()) + } resp,err := sc.RebootStatus(ctx, req) if err != nil { panic(err.Error()) diff --git a/test/test_gnoi.py b/test/test_gnoi.py index 61b7c067..c78bf8ee 100644 --- a/test/test_gnoi.py +++ b/test/test_gnoi.py @@ -11,25 +11,19 @@ def test_gnoi_time(self): assert 'time' in msg, 'Invalid response: %s'%msg def test_gnoi_reboot(self): - ret, old_cnt = gnmi_dump('DBUS config reload') - assert ret == 0, 'Fail to read counter' - ret, msg = gnoi_reboot(1, 0, 'Test reboot') - assert ret == 0, msg - - ret, new_cnt = gnmi_dump('DBUS config reload') - assert ret == 0, 'Fail to read counter' - assert new_cnt == old_cnt+1, 'DBUS API is not invoked' + assert ret == 2, msg + assert 'Response Notification timeout from Warmboot Manager' in msg def test_gnoi_rebootstatus(self): ret, msg = gnoi_rebootstatus() - assert ret != 0, 'RebootStatus should fail' + msg - assert 'Unimplemented' in msg + assert ret == 2, msg + assert 'Response Notification timeout from Warmboot Manager' in msg def test_gnoi_cancelreboot(self): - ret, msg = gnoi_cancelreboot('Test reboot') - assert ret != 0, 'CancelReboot should fail' + msg - assert 'Unimplemented' in msg + ret, msg = gnoi_cancelreboot('Test Cancel reboot') + assert ret == 2, msg + assert 'Response Notification timeout from Warmboot Manager' in msg def test_gnoi_killprocess(self): ret, old_cnt = gnmi_dump('DBUS stop service') From 4816079e79d563b9ae36ab09d0288fa3a2309e4f Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 13:25:31 -0700 Subject: [PATCH 15/18] gNOI Reboot changes --- gnoi_client/gnoi_client.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gnoi_client/gnoi_client.go b/gnoi_client/gnoi_client.go index 82e8aa72..06e8d192 100644 --- a/gnoi_client/gnoi_client.go +++ b/gnoi_client/gnoi_client.go @@ -165,10 +165,20 @@ func systemReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { panic(err.Error()) } respstr, err := json.Marshal(resp) + req := &gnoi_system_pb.RebootRequest{} + if err := json.Unmarshal([]byte(*args), req); err != nil { + panic(err.Error()) + } + resp, err := sc.Reboot(ctx, req) + if err != nil { + panic(err.Error()) + } + respstr, err := json.Marshal(resp) if err != nil { panic(err.Error()) } fmt.Println(string(respstr)) + fmt.Println(string(respstr)) } func systemCancelReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { @@ -179,6 +189,11 @@ func systemCancelReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { panic(err.Error()) } resp, err := sc.CancelReboot(ctx, req) + req := &gnoi_system_pb.CancelRebootRequest{} + if err := json.Unmarshal([]byte(*args), req); err != nil { + panic(err.Error()) + } + resp, err := sc.CancelReboot(ctx, req) if err != nil { panic(err.Error()) } @@ -193,9 +208,6 @@ func systemRebootStatus(sc gnoi_system_pb.SystemClient, ctx context.Context) { fmt.Println("System RebootStatus") ctx = setUserCreds(ctx) req := &gnoi_system_pb.RebootStatusRequest{} - if err := json.Unmarshal([]byte(*args), req); err != nil { - panic(err.Error()) - } resp,err := sc.RebootStatus(ctx, req) if err != nil { panic(err.Error()) From 13cbcfbdd2661debaedefd5d2ee2c5ed426006a3 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 19:29:12 -0700 Subject: [PATCH 16/18] gNOI System Reboot changes --- gnoi_client/gnoi_client.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/gnoi_client/gnoi_client.go b/gnoi_client/gnoi_client.go index 06e8d192..fe1ece73 100644 --- a/gnoi_client/gnoi_client.go +++ b/gnoi_client/gnoi_client.go @@ -165,19 +165,9 @@ func systemReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { panic(err.Error()) } respstr, err := json.Marshal(resp) - req := &gnoi_system_pb.RebootRequest{} - if err := json.Unmarshal([]byte(*args), req); err != nil { - panic(err.Error()) - } - resp, err := sc.Reboot(ctx, req) if err != nil { panic(err.Error()) } - respstr, err := json.Marshal(resp) - if err != nil { - panic(err.Error()) - } - fmt.Println(string(respstr)) fmt.Println(string(respstr)) } @@ -189,11 +179,6 @@ func systemCancelReboot(sc gnoi_system_pb.SystemClient, ctx context.Context) { panic(err.Error()) } resp, err := sc.CancelReboot(ctx, req) - req := &gnoi_system_pb.CancelRebootRequest{} - if err := json.Unmarshal([]byte(*args), req); err != nil { - panic(err.Error()) - } - resp, err := sc.CancelReboot(ctx, req) if err != nil { panic(err.Error()) } From 1e10c75f3c4e0dc6912f0db54355a678da8be3b4 Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 19:29:12 -0700 Subject: [PATCH 17/18] gNOI System Reboot changes --- gnmi_server/gnoi_system.go | 6 +++--- gnmi_server/gnoi_system_test.go | 38 +++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/gnmi_server/gnoi_system.go b/gnmi_server/gnoi_system.go index fad695a0..37faddff 100644 --- a/gnmi_server/gnoi_system.go +++ b/gnmi_server/gnoi_system.go @@ -225,8 +225,8 @@ func sendRebootReqOnNotifCh(ctx context.Context, req proto.Message, sc *redis.Cl case <-tc: // Crossed the reboot response notification timeout. - log.V(1).Infof("[Reboot_Log] Response Notification timeout from Warmboot Manager!") - return nil, status.Errorf(codes.Internal, "Response Notification timeout from Warmboot Manager!"), msgDataStr + log.V(1).Infof("[Reboot_Log] Response Notification timeout from Reboot Backend!") + return nil, status.Errorf(codes.Internal, "Response Notification timeout from Reboot Backend!"), msgDataStr } } } @@ -259,7 +259,7 @@ func (srv *Server) Reboot(ctx context.Context, req *syspb.RebootRequest) (*syspb return nil, err } if resp == nil { - log.V(2).Info("Reboot request received empty response from Warmboot Manager.") + log.V(2).Info("Reboot request received empty response from Reboot Backend.") return &syspb.RebootResponse{}, nil } return resp.(*syspb.RebootResponse), nil diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index 99ced681..7b84277b 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -62,7 +62,7 @@ func errorCodeToSwss(errCode codes.Code) string { return "" } -func warmbootManagerResponse(t *testing.T, sc *redis.Client, expectedResponse codes.Code, done chan bool, key string) { +func rebootBackendResponse(t *testing.T, sc *redis.Client, expectedResponse codes.Code, fvs map[string]string, done chan bool, key string) { sub := sc.Subscribe("Reboot_Request_Channel") if _, err := sub.Receive(); err != nil { t.Errorf("nsfManagerResponse failed to subscribe to request channel: %v", err) @@ -73,7 +73,7 @@ func warmbootManagerResponse(t *testing.T, sc *redis.Client, expectedResponse co np, err := common_utils.NewNotificationProducer("Reboot_Response_Channel") if err != nil { - t.Errorf("warmbootManagerResponse failed to create notification producer: %v", err) + t.Errorf("rebootBackendResponse failed to create notification producer: %v", err) return } defer np.Close() @@ -81,16 +81,16 @@ func warmbootManagerResponse(t *testing.T, sc *redis.Client, expectedResponse co tc := time.After(5 * time.Second) select { case msg := <-channel: - t.Logf("warmbootManagerResponse received request: %v", msg) + t.Logf("rebootBackendResponse received request: %v", msg) // Respond to the request - if err := np.Send(key, errorCodeToSwss(expectedResponse), map[string]string{}); err != nil { - t.Errorf("warmbootManagerResponse failed to send response: %v", err) + if err := np.Send(key, errorCodeToSwss(expectedResponse), fvs); err != nil { + t.Errorf("rebootBackendResponse failed to send response: %v", err) return } case <-done: return case <-tc: - t.Error("warmbootManagerResponse timed out waiting for request") + t.Error("rebootBackendResponse timed out waiting for request") return } } @@ -159,18 +159,20 @@ func TestSystem(t *testing.T) { for _, method := range []syspb.RebootMethod{syspb.RebootMethod_COLD, syspb.RebootMethod_POWERDOWN, syspb.RebootMethod_WARM, syspb.RebootMethod_NSF} { req.Method = method _, err := sc.Reboot(ctx, req) - testErr(err, codes.Internal, "Response Notification timeout from NSF Manager!", t) + testErr(err, codes.Internal, "Response Notification timeout from Reboot Backend!", t) } }) t.Run("RebootSucceeds", func(t *testing.T) { - // Start goroutine for mock Warmboot Manager to respond to Reboot requests + // Start goroutine for mock Reboot Backend to respond to Reboot requests done := make(chan bool, 1) - go warmbootManagerResponse(t, rclient, codes.OK, done, rebootKey) + fvs := make(map[string]string) + fvs["MESSAGE"] = "{}" + go rebootBackendResponse(t, rclient, codes.OK, fvs, done, rebootKey) defer func() { done <- true }() req := &syspb.RebootRequest{ Delay: 0, - Message: "Starting NSF reboot ...", + Message: "Starting Reboot ...", } for _, method := range []syspb.RebootMethod{syspb.RebootMethod_COLD, syspb.RebootMethod_POWERDOWN, syspb.RebootMethod_WARM, syspb.RebootMethod_NSF} { req.Method = method @@ -182,12 +184,14 @@ func TestSystem(t *testing.T) { }) t.Run("RebootStatusFailsWithTimeout", func(t *testing.T) { _, err := sc.RebootStatus(ctx, &syspb.RebootStatusRequest{}) - testErr(err, codes.Internal, "Response Notification timeout from NSF Manager!", t) + testErr(err, codes.Internal, "Response Notification timeout from Reboot Backend!", t) }) t.Run("RebootStatusRequestSucceeds", func(t *testing.T) { - // Start goroutine for mock Warmboot Manager to respond to RebootStatus requests + // Start goroutine for mock Reboot Backend to respond to RebootStatus requests done := make(chan bool, 1) - go warmbootManagerResponse(t, rclient, codes.OK, done, rebootStatusKey) + fvs := make(map[string]string) + fvs["MESSAGE"] = "{\"active\": true, \"method\":\"NSF\",\"status\":{\"status\":\"STATUS_SUCCESS\"}}" + go rebootBackendResponse(t, rclient, codes.OK, fvs, done, rebootStatusKey) defer func() { done <- true }() _, err := sc.RebootStatus(ctx, &syspb.RebootStatusRequest{}) @@ -204,12 +208,14 @@ func TestSystem(t *testing.T) { Message: "Cancelling NSF Reboot due to hardware constraints", } _, err := sc.CancelReboot(ctx, req) - testErr(err, codes.Internal, "Response Notification timeout from NSF Manager!", t) + testErr(err, codes.Internal, "Response Notification timeout from Reboot Backend!", t) }) t.Run("CancelRebootRequestSucceeds", func(t *testing.T) { - // Start goroutine for mock Warmboot Manager to respond to CancelReboot requests + // Start goroutine for mock Reboot Backend to respond to CancelReboot requests done := make(chan bool, 1) - go warmbootManagerResponse(t, rclient, codes.OK, done, rebootCancelKey) + fvs := make(map[string]string) + fvs["MESSAGE"] = "{}" + go rebootBackendResponse(t, rclient, codes.OK, fvs, done, rebootCancelKey) defer func() { done <- true }() req := &syspb.CancelRebootRequest{ From 6cb45d1fbc7181df7c0bf613e038d51311c5335b Mon Sep 17 00:00:00 2001 From: Neha Das Date: Wed, 9 Oct 2024 19:29:12 -0700 Subject: [PATCH 18/18] gNOI System Reboot changes --- gnmi_server/gnoi_system_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnmi_server/gnoi_system_test.go b/gnmi_server/gnoi_system_test.go index 7b84277b..16bb97b9 100644 --- a/gnmi_server/gnoi_system_test.go +++ b/gnmi_server/gnoi_system_test.go @@ -65,7 +65,7 @@ func errorCodeToSwss(errCode codes.Code) string { func rebootBackendResponse(t *testing.T, sc *redis.Client, expectedResponse codes.Code, fvs map[string]string, done chan bool, key string) { sub := sc.Subscribe("Reboot_Request_Channel") if _, err := sub.Receive(); err != nil { - t.Errorf("nsfManagerResponse failed to subscribe to request channel: %v", err) + t.Errorf("rebootBackendResponse failed to subscribe to request channel: %v", err) return } defer sub.Close() @@ -205,7 +205,7 @@ func TestSystem(t *testing.T) { }) t.Run("CancelRebootFailsWithTimeout", func(t *testing.T) { req := &syspb.CancelRebootRequest{ - Message: "Cancelling NSF Reboot due to hardware constraints", + Message: "Cancelling Reboot due to hardware constraints", } _, err := sc.CancelReboot(ctx, req) testErr(err, codes.Internal, "Response Notification timeout from Reboot Backend!", t)