Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gNOI System Reboot changes #296

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
41 changes: 41 additions & 0 deletions common_utils/component_state_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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) {
ns, _ := sdcfg.GetDbDefaultNamespace()
addr, err := sdcfg.GetDbTcpAddr(dbName, ns)
if err != nil {
log.Errorf("Addr err: %v", err)
return nil, err
}
db, err := sdcfg.GetDbId("STATE_DB", ns)
if err != nil {
log.Errorf("DB err: %v", err)
return nil, err
}
rclient := redis.NewClient(&redis.Options{
Network: "tcp",
Addr: addr,
Password: "", // no password set
DB: db,
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!")
}
}
154 changes: 4 additions & 150 deletions gnmi_server/gnoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ 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"
"encoding/json"
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 {
Expand Down Expand Up @@ -94,151 +93,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
Loading