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

Add gnmi_dump tool for debug and unit test #60

Merged
merged 8 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@ ifeq ($(CROSS_BUILD_ENVIRON),y)
$(GO) build -o ${GOBIN}/gnmi_set -mod=vendor github.com/jipanyang/gnxi/gnmi_set
$(GO) build -o ${GOBIN}/gnmi_cli -mod=vendor github.com/openconfig/gnmi/cmd/gnmi_cli
$(GO) build -o ${GOBIN}/gnoi_client -mod=vendor github.com/sonic-net/sonic-gnmi/gnoi_client
$(GO) build -o ${GOBIN}/gnmi_dump -mod=vendor github.com/sonic-net/sonic-gnmi/gnmi_dump
else
$(GO) install -mod=vendor $(BLD_FLAGS) github.com/sonic-net/sonic-gnmi/telemetry
$(GO) install -mod=vendor $(BLD_FLAGS) github.com/sonic-net/sonic-gnmi/dialout/dialout_client_cli
$(GO) install -mod=vendor github.com/jipanyang/gnxi/gnmi_get
$(GO) install -mod=vendor github.com/jipanyang/gnxi/gnmi_set
$(GO) install -mod=vendor github.com/openconfig/gnmi/cmd/gnmi_cli
$(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/gnoi_client
$(GO) install -mod=vendor github.com/sonic-net/sonic-gnmi/gnmi_dump
endif

check_gotest:
Expand Down Expand Up @@ -99,6 +101,7 @@ install:
$(INSTALL) -D $(BUILD_DIR)/gnmi_set $(DESTDIR)/usr/sbin/gnmi_set
$(INSTALL) -D $(BUILD_DIR)/gnmi_cli $(DESTDIR)/usr/sbin/gnmi_cli
$(INSTALL) -D $(BUILD_DIR)/gnoi_client $(DESTDIR)/usr/sbin/gnoi_client
$(INSTALL) -D $(BUILD_DIR)/gnmi_dump $(DESTDIR)/usr/sbin/gnmi_dump


deinstall:
Expand All @@ -107,5 +110,6 @@ deinstall:
rm $(DESTDIR)/usr/sbin/gnmi_get
rm $(DESTDIR)/usr/sbin/gnmi_set
rm $(DESTDIR)/usr/sbin/gnoi_client
rm $(DESTDIR)/usr/sbin/gnmi_dump


33 changes: 30 additions & 3 deletions common_utils/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ const requestContextKey contextkey = 0
// Request Id generator
var requestCounter uint64

var CountersName = [...]string {
"GNMI get",
"GNMI get fail",
"GNMI set",
"GNMI set fail",
}

var globalCounters [len(CountersName)]uint64


// GetContext function returns the RequestContext object for a
// gRPC request. RequestContext is maintained as a context value of
// the request. Creates a new RequestContext object is not already
Expand All @@ -55,8 +65,25 @@ func GetContext(ctx context.Context) (*RequestContext, context.Context) {

func GetUsername(ctx context.Context, username *string) {
rc, _ := GetContext(ctx)
if rc != nil {
*username = rc.Auth.User
}
if rc != nil {
*username = rc.Auth.User
}
}

func InitCounters() {
for i := 0; i < len(CountersName); i++ {
globalCounters[i] = 0
}
SetMemCounters(&globalCounters)
}

func IncCounter(name string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use string as an index? If an enum were used then there would be no need for the loop in line 81, right?

for i := 0; i < len(CountersName); i++ {
if CountersName[i] == name {
atomic.AddUint64(&globalCounters[i], 1)
break
}
}
SetMemCounters(&globalCounters)
}

64 changes: 64 additions & 0 deletions common_utils/shareMem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package common_utils

import (
"fmt"
"syscall"
"unsafe"
)

// Use share memory to dump GNMI internal counters,
// GNMI server and gnmi_dump should use memKey to access the share memory,
// memSize is 1024 bytes, so we can support 128 counters
// memMode is 0x380, this value is O_RDWR|IPC_CREAT,
// O_RDWR means: Owner can write and read the file, everyone else can't.
// IPC_CREAT means: Create a shared memory segment if a shared memory identifier does not exist for memKey.
var (
memKey = 7749
memSize = 1024
memMode = 0x380
)

func SetMemCounters(counters *[len(CountersName)]uint64) error {
shmid, _, err := syscall.Syscall(syscall.SYS_SHMGET, uintptr(memKey), uintptr(memSize), uintptr(memMode))
if int(shmid) == -1 {
return fmt.Errorf("syscall error, err: %v\n", err)
}

shmaddr, _, err := syscall.Syscall(syscall.SYS_SHMAT, shmid, 0, 0)
if int(shmaddr) == -1 {
return fmt.Errorf("syscall error, err: %v\n", err)
}
defer syscall.Syscall(syscall.SYS_SHMDT, shmaddr, 0, 0)

const size = unsafe.Sizeof(uint64(0))
addr := uintptr(unsafe.Pointer(shmaddr))

for i := 0; i < len(counters); i++ {
*(*uint64)(unsafe.Pointer(addr)) = counters[i]
addr += size
}
return nil
}

func GetMemCounters(counters *[len(CountersName)]uint64) error {
shmid, _, err := syscall.Syscall(syscall.SYS_SHMGET, uintptr(memKey), uintptr(memSize), uintptr(memMode))
if int(shmid) == -1 {
return fmt.Errorf("syscall error, err: %v\n", err)
}

shmaddr, _, err := syscall.Syscall(syscall.SYS_SHMAT, shmid, 0, 0)
if int(shmaddr) == -1 {
return fmt.Errorf("syscall error, err: %v\n", err)
}
defer syscall.Syscall(syscall.SYS_SHMDT, shmaddr, 0, 0)

const size = unsafe.Sizeof(uint64(0))
addr := uintptr(unsafe.Pointer(shmaddr))

for i := 0; i < len(counters); i++ {
counters[i] = *(*uint64)(unsafe.Pointer(addr))
addr += size
}
return nil
}

29 changes: 29 additions & 0 deletions gnmi_dump/gnmi_dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"flag"
"fmt"
"github.com/sonic-net/sonic-gnmi/common_utils"
)

const help = `
gnmi_dump is used to dump internal counters for debugging purpose,
including GNMI request counter, GNOI request counter and DBUS request counter.
`

func main() {
flag.Usage = func() {
fmt.Print(help)
}
flag.Parse()
var counters [len(common_utils.CountersName)]uint64
err := common_utils.GetMemCounters(&counters)
if err != nil {
fmt.Printf("Error: Fail to read counters, %v", err)
return
}
fmt.Printf("Dump GNMI counters\n")
for i := 0; i < len(common_utils.CountersName); i++ {
fmt.Printf("%v---%v\n", common_utils.CountersName[i], counters[i])
}
}
24 changes: 19 additions & 5 deletions gnmi_server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) {
return nil, errors.New("config not provided")
}

common_utils.InitCounters()

s := grpc.NewServer(opts...)
reflection.Register(s)

Expand Down Expand Up @@ -274,26 +276,32 @@ func (s *Server) checkEncodingAndModel(encoding gnmipb.Encoding, models []*gnmip

// Get implements the Get RPC in gNMI spec.
func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetResponse, error) {
common_utils.IncCounter("GNMI get")
ctx, err := authenticate(s.config.UserAuth, ctx)
if err != nil {
common_utils.IncCounter("GNMI get fail")
return nil, err
}

if req.GetType() != gnmipb.GetRequest_ALL {
common_utils.IncCounter("GNMI get fail")
return nil, status.Errorf(codes.Unimplemented, "unsupported request type: %s", gnmipb.GetRequest_DataType_name[int32(req.GetType())])
}

if err = s.checkEncodingAndModel(req.GetEncoding(), req.GetUseModels()); err != nil {
common_utils.IncCounter("GNMI get fail")
return nil, status.Error(codes.Unimplemented, err.Error())
}

var target string
prefix := req.GetPrefix()
if prefix == nil {
common_utils.IncCounter("GNMI get fail")
return nil, status.Error(codes.Unimplemented, "No target specified in prefix")
} else {
target = prefix.GetTarget()
if target == "" {
common_utils.IncCounter("GNMI get fail")
return nil, status.Error(codes.Unimplemented, "Empty target data not supported yet")
}
}
Expand All @@ -315,11 +323,13 @@ func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetRe
}

if err != nil {
common_utils.IncCounter("GNMI get fail")
return nil, status.Error(codes.NotFound, err.Error())
}
notifications := make([]*gnmipb.Notification, len(paths))
spbValues, err := dc.Get(nil)
if err != nil {
common_utils.IncCounter("GNMI get fail")
return nil, status.Error(codes.NotFound, err.Error())
}

Expand All @@ -339,8 +349,14 @@ func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetRe
}

func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetResponse, error) {
common_utils.IncCounter("GNMI set")
if s.config.EnableTranslibWrite == false {
common_utils.IncCounter("GNMI set fail")
return nil, grpc.Errorf(codes.Unimplemented, "GNMI is in read-only mode")
}
ctx, err := authenticate(s.config.UserAuth, ctx)
if err != nil {
common_utils.IncCounter("GNMI set fail")
return nil, err
}
var results []*gnmipb.UpdateResult
Expand Down Expand Up @@ -388,13 +404,11 @@ func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetRe
/* Add to Set response results. */
results = append(results, &res)
}
if s.config.EnableTranslibWrite {
Copy link
Collaborator

@qiluo-msft qiluo-msft Nov 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EnableTranslibWrite

Why removing this check? If this is significant change, add some PR description, even add it into PR title. #Closed

Copy link
Contributor Author

@ganglyu ganglyu Nov 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I move the check to line 353 to simplify logic for set fail, when EnableTranslibWrite is false, no need to authenticate and no need to update result.

err = dc.Set(req.GetDelete(), req.GetReplace(), req.GetUpdate())
} else {
return nil, grpc.Errorf(codes.Unimplemented, "Telemetry is in read-only mode")
err = dc.Set(req.GetDelete(), req.GetReplace(), req.GetUpdate())
if err != nil {
common_utils.IncCounter("GNMI set fail")
}


return &gnmipb.SetResponse{
Prefix: req.GetPrefix(),
Response: results,
Expand Down
117 changes: 116 additions & 1 deletion gnmi_server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,25 @@ func createServer(t *testing.T, port int64) *Server {
return s
}

func createReadServer(t *testing.T, port int64) *Server {
certificate, err := testcert.NewCert()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a helper function it should be marked as such by calling t.Helper() at its beginning.

if err != nil {
t.Errorf("could not load server key pair: %s", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be t.Fatalf(...) as t.Errorf(...) does not stop execution of the test and it will continue despite lack of the certificate.

}
tlsCfg := &tls.Config{
ClientAuth: tls.RequestClientCert,
Certificates: []tls.Certificate{certificate},
}

opts := []grpc.ServerOption{grpc.Creds(credentials.NewTLS(tlsCfg))}
cfg := &Config{Port: port, EnableTranslibWrite: false}
s, err := NewServer(cfg, opts)
if err != nil {
t.Errorf("Failed to create gNMI server: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment here. This should be t.Fatalf(...) instead of t.Errorf(...)

}
return s
}

func createAuthServer(t *testing.T, port int64) *Server {
certificate, err := testcert.NewCert()
if err != nil {
Expand All @@ -118,7 +137,7 @@ func createAuthServer(t *testing.T, port int64) *Server {
}

opts := []grpc.ServerOption{grpc.Creds(credentials.NewTLS(tlsCfg))}
cfg := &Config{Port: port, UserAuth: AuthTypes{"password": true, "cert": true, "jwt": true}}
cfg := &Config{Port: port, EnableTranslibWrite: true, UserAuth: AuthTypes{"password": true, "cert": true, "jwt": true}}
s, err := NewServer(cfg, opts)
if err != nil {
t.Errorf("Failed to create gNMI server: %v", err)
Expand Down Expand Up @@ -732,6 +751,102 @@ func TestGnmiSet(t *testing.T) {
s.s.Stop()
}

func TestGnmiSetReadOnly(t *testing.T) {
s := createReadServer(t, 8081)
go runServer(t, s)
defer s.s.Stop()

tlsConfig := &tls.Config{InsecureSkipVerify: true}
opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))}

targetAddr := "127.0.0.1:8081"
conn, err := grpc.Dial(targetAddr, opts...)
if err != nil {
t.Fatalf("Dialing to %q failed: %v", targetAddr, err)
}
defer conn.Close()

gClient := pb.NewGNMIClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

req := &pb.SetRequest{}
_, err = gClient.Set(ctx, req)
gotRetStatus, ok := status.FromError(err)
if !ok {
t.Fatal("got a non-grpc error from grpc call")
}
wantRetCode := codes.Unimplemented
if gotRetStatus.Code() != wantRetCode {
t.Log("err: ", err)
t.Fatalf("got return code %v, want %v", gotRetStatus.Code(), wantRetCode)
}
}

func TestGnmiSetAuthFail(t *testing.T) {
s := createAuthServer(t, 8081)
go runServer(t, s)
defer s.s.Stop()

tlsConfig := &tls.Config{InsecureSkipVerify: true}
opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))}

targetAddr := "127.0.0.1:8081"
conn, err := grpc.Dial(targetAddr, opts...)
if err != nil {
t.Fatalf("Dialing to %q failed: %v", targetAddr, err)
}
defer conn.Close()

gClient := pb.NewGNMIClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

req := &pb.SetRequest{}
_, err = gClient.Set(ctx, req)
gotRetStatus, ok := status.FromError(err)
if !ok {
t.Fatal("got a non-grpc error from grpc call")
}
wantRetCode := codes.Unauthenticated
if gotRetStatus.Code() != wantRetCode {
t.Log("err: ", err)
t.Fatalf("got return code %v, want %v", gotRetStatus.Code(), wantRetCode)
}
}

func TestGnmiGetAuthFail(t *testing.T) {
s := createAuthServer(t, 8081)
go runServer(t, s)
defer s.s.Stop()

tlsConfig := &tls.Config{InsecureSkipVerify: true}
opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))}

targetAddr := "127.0.0.1:8081"
conn, err := grpc.Dial(targetAddr, opts...)
if err != nil {
t.Fatalf("Dialing to %q failed: %v", targetAddr, err)
}
defer conn.Close()

gClient := pb.NewGNMIClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

req := &pb.GetRequest{}
_, err = gClient.Get(ctx, req)
gotRetStatus, ok := status.FromError(err)
if !ok {
t.Fatal("got a non-grpc error from grpc call")
}
wantRetCode := codes.Unauthenticated
if gotRetStatus.Code() != wantRetCode {
t.Log("err: ", err)
t.Fatalf("got return code %v, want %v", gotRetStatus.Code(), wantRetCode)
}
}

func runGnmiTestGet(t *testing.T, namespace string) {
//t.Log("Start gNMI client")
tlsConfig := &tls.Config{InsecureSkipVerify: true}
Expand Down