Skip to content

Commit

Permalink
gNOI Reboot changes
Browse files Browse the repository at this point in the history
  • Loading branch information
ndas7 committed Oct 9, 2024
1 parent 1b6d8c0 commit 8639c97
Show file tree
Hide file tree
Showing 6 changed files with 932 additions and 150 deletions.
30 changes: 30 additions & 0 deletions common_utils/component_state_helper.go
Original file line number Diff line number Diff line change
@@ -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
}
55 changes: 55 additions & 0 deletions common_utils/notification_producer.go
Original file line number Diff line number Diff line change
@@ -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()
}
49 changes: 49 additions & 0 deletions common_utils/notification_producer_test.go
Original file line number Diff line number Diff line change
@@ -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!")
}
}
150 changes: 0 additions & 150 deletions gnmi_server/gnoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 8639c97

Please sign in to comment.