diff --git a/Makefile b/Makefile index c4c20c637..dcf12ad8a 100644 --- a/Makefile +++ b/Makefile @@ -60,12 +60,14 @@ check: sudo cp ./testdata/database_config.json ${DBDIR} sudo mkdir -p /usr/models/yang || true sudo find $(MGMT_COMMON_DIR)/models -name '*.yang' -exec cp {} /usr/models/yang/ \; - -$(GO) test -mod=vendor $(BLD_FLAGS) -v github.com/Azure/sonic-telemetry/gnmi_server - -$(GO) test -mod=vendor $(BLD_FLAGS) -v github.com/Azure/sonic-telemetry/dialout/dialout_client + -sudo $(GO) test -v github.com/Azure/sonic-telemetry/sonic_db_config + -sudo $(GO) test -mod=vendor $(BLD_FLAGS) -v github.com/Azure/sonic-telemetry/gnmi_server + -sudo $(GO) test -mod=vendor $(BLD_FLAGS) -v github.com/Azure/sonic-telemetry/dialout/dialout_client clean: $(RM) -r build $(RM) -r vendor + sudo $(RM) -r ${DBDIR} $(TELEMETRY_TEST_BIN): $(TEST_FILES) $(SRC_FILES) mkdir -p $(@D) diff --git a/dialout/dialout_client/dialout_client.go b/dialout/dialout_client/dialout_client.go index 451a4c10c..7e46cdf9d 100644 --- a/dialout/dialout_client/dialout_client.go +++ b/dialout/dialout_client/dialout_client.go @@ -8,11 +8,11 @@ import ( spb "github.com/Azure/sonic-telemetry/proto" sdc "github.com/Azure/sonic-telemetry/sonic_data_client" sdcfg "github.com/Azure/sonic-telemetry/sonic_db_config" + "github.com/Workiva/go-datastructures/queue" "github.com/go-redis/redis" log "github.com/golang/glog" gpb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" - "github.com/Workiva/go-datastructures/queue" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -462,7 +462,7 @@ func setupDestGroupClients(ctx context.Context, destGroupName string) { // start/stop/update telemetry publist client as requested // TODO: more validation on db data func processTelemetryClientConfig(ctx context.Context, redisDb *redis.Client, key string, op string) error { - separator, _ := sdc.GetTableKeySeparator("CONFIG_DB") + separator, _ := sdc.GetTableKeySeparator("CONFIG_DB", sdcfg.GetDbDefaultNamespace()) tableKey := "TELEMETRY_CLIENT" + separator + key fv, err := redisDb.HGetAll(tableKey).Result() if err != nil { @@ -642,13 +642,13 @@ func processTelemetryClientConfig(ctx context.Context, redisDb *redis.Client, ke // read configDB data for telemetry client and start publishing service for client subscription func DialOutRun(ctx context.Context, ccfg *ClientConfig) error { clientCfg = ccfg - dbn := sdcfg.GetDbId("CONFIG_DB") + dbn := sdcfg.GetDbId("CONFIG_DB", sdcfg.GetDbDefaultNamespace()) var redisDb *redis.Client if sdc.UseRedisLocalTcpPort == false { redisDb = redis.NewClient(&redis.Options{ Network: "unix", - Addr: sdcfg.GetDbSock("CONFIG_DB"), + Addr: sdcfg.GetDbSock("CONFIG_DB", sdcfg.GetDbDefaultNamespace()), Password: "", // no password set DB: dbn, DialTimeout: 0, @@ -656,14 +656,14 @@ func DialOutRun(ctx context.Context, ccfg *ClientConfig) error { } else { redisDb = redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr("CONFIG_DB"), + Addr: sdcfg.GetDbTcpAddr("CONFIG_DB", sdcfg.GetDbDefaultNamespace()), Password: "", // no password set DB: dbn, DialTimeout: 0, }) } - separator, _ := sdc.GetTableKeySeparator("CONFIG_DB") + separator, _ := sdc.GetTableKeySeparator("CONFIG_DB", sdcfg.GetDbDefaultNamespace()) pattern := "__keyspace@" + strconv.Itoa(int(dbn)) + "__:TELEMETRY_CLIENT" + separator prefixLen := len(pattern) pattern += "*" diff --git a/dialout/dialout_client/dialout_client_test.go b/dialout/dialout_client/dialout_client_test.go index 1a8b76ebd..531d0d98e 100644 --- a/dialout/dialout_client/dialout_client_test.go +++ b/dialout/dialout_client/dialout_client_test.go @@ -97,9 +97,9 @@ func runServer(t *testing.T, s *sds.Server) { func getRedisClient(t *testing.T) *redis.Client { rclient := redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr("COUNTERS_DB"), + Addr: sdcfg.GetDbTcpAddr("COUNTERS_DB", sdcfg.GetDbDefaultNamespace()), Password: "", // no password set - DB: sdcfg.GetDbId("COUNTERS_DB"), + DB: sdcfg.GetDbId("COUNTERS_DB", sdcfg.GetDbDefaultNamespace()), DialTimeout: 0, }) _, err := rclient.Ping().Result() @@ -126,9 +126,9 @@ func exe_cmd(t *testing.T, cmd string) { func getConfigDbClient(t *testing.T) *redis.Client { rclient := redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr("CONFIG_DB"), + Addr: sdcfg.GetDbTcpAddr("CONFIG_DB", sdcfg.GetDbDefaultNamespace()), Password: "", // no password set - DB: sdcfg.GetDbId("CONFIG_DB"), + DB: sdcfg.GetDbId("CONFIG_DB", sdcfg.GetDbDefaultNamespace()), DialTimeout: 0, }) _, err := rclient.Ping().Result() diff --git a/gnmi_server/client_subscribe.go b/gnmi_server/client_subscribe.go index c1bf22e0e..1c2d6f313 100644 --- a/gnmi_server/client_subscribe.go +++ b/gnmi_server/client_subscribe.go @@ -123,7 +123,7 @@ func (c *Client) Run(stream gnmipb.GNMI_SubscribeServer) (err error) { if target == "OTHERS" { dc, err = sdc.NewNonDbClient(paths, prefix) - } else if isTargetDb(target) == true { + } else if _, ok, _, _ := sdc.IsTargetDb(target); ok { dc, err = sdc.NewDbClient(paths, prefix) } else { /* For any other target or no target create new Transl Client. */ diff --git a/gnmi_server/server.go b/gnmi_server/server.go index 4b0a9e365..f21f8d68a 100644 --- a/gnmi_server/server.go +++ b/gnmi_server/server.go @@ -1,29 +1,29 @@ package gnmi import ( + "bytes" "errors" "fmt" - "net" - "strings" - "sync" + "github.com/Azure/sonic-mgmt-common/translib" "github.com/Azure/sonic-telemetry/common_utils" + spb "github.com/Azure/sonic-telemetry/proto" + spb_gnoi "github.com/Azure/sonic-telemetry/proto/gnoi" + spb_jwt_gnoi "github.com/Azure/sonic-telemetry/proto/gnoi/jwt" + sdc "github.com/Azure/sonic-telemetry/sonic_data_client" log "github.com/golang/glog" + "github.com/golang/protobuf/proto" + gnmipb "github.com/openconfig/gnmi/proto/gnmi" + gnmi_extpb "github.com/openconfig/gnmi/proto/gnmi_ext" + gnoi_system_pb "github.com/openconfig/gnoi/system" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" - "github.com/golang/protobuf/proto" - gnoi_system_pb "github.com/openconfig/gnoi/system" - sdc "github.com/Azure/sonic-telemetry/sonic_data_client" - gnmipb "github.com/openconfig/gnmi/proto/gnmi" - gnmi_extpb "github.com/openconfig/gnmi/proto/gnmi_ext" - spb_gnoi "github.com/Azure/sonic-telemetry/proto/gnoi" - spb "github.com/Azure/sonic-telemetry/proto" - "github.com/Azure/sonic-mgmt-common/translib" - spb_jwt_gnoi "github.com/Azure/sonic-telemetry/proto/gnoi/jwt" - "bytes" + "net" + "strings" + "sync" ) var ( @@ -41,81 +41,81 @@ type Server struct { clients map[string]*Client } type AuthTypes map[string]bool + // Config is a collection of values for Server type Config struct { // Port for the Server to listen on. If 0 or unset the Server will pick a port // for this Server. - Port int64 + Port int64 UserAuth AuthTypes } var AuthLock sync.Mutex func (i AuthTypes) String() string { - if i["none"] { - return "" - } - b := new(bytes.Buffer) - for key, value := range i { - if value { - fmt.Fprintf(b, "%s ", key) - } - } - return b.String() + if i["none"] { + return "" + } + b := new(bytes.Buffer) + for key, value := range i { + if value { + fmt.Fprintf(b, "%s ", key) + } + } + return b.String() } func (i AuthTypes) Any() bool { - if i["none"] { - return false - } - for _, value := range i { - if value { - return true - } - } - return false + if i["none"] { + return false + } + for _, value := range i { + if value { + return true + } + } + return false } func (i AuthTypes) Enabled(mode string) bool { - if i["none"] { - return false - } - if value, exist := i[mode]; exist && value { - return true - } - return false + if i["none"] { + return false + } + if value, exist := i[mode]; exist && value { + return true + } + return false } func (i AuthTypes) Set(mode string) error { - modes := strings.Split(mode, ",") - for _, m := range modes { - m = strings.Trim(m, " ") - if m == "none" || m == "" { - i["none"] = true - return nil - } - - if _, exist := i[m]; !exist { - return fmt.Errorf("Expecting one or more of 'cert', 'password' or 'jwt'") - } - i[m] = true - } - return nil + modes := strings.Split(mode, ",") + for _, m := range modes { + m = strings.Trim(m, " ") + if m == "none" || m == "" { + i["none"] = true + return nil + } + + if _, exist := i[m]; !exist { + return fmt.Errorf("Expecting one or more of 'cert', 'password' or 'jwt'") + } + i[m] = true + } + return nil } func (i AuthTypes) Unset(mode string) error { - modes := strings.Split(mode, ",") - for _, m := range modes { - m = strings.Trim(m, " ") - if _, exist := i[m]; !exist { - return fmt.Errorf("Expecting one or more of 'cert', 'password' or 'jwt'") - } - i[m] = false - } - return nil + modes := strings.Split(mode, ",") + for _, m := range modes { + m = strings.Trim(m, " ") + if _, exist := i[m]; !exist { + return fmt.Errorf("Expecting one or more of 'cert', 'password' or 'jwt'") + } + i[m] = false + } + return nil } - // New returns an initialized Server. func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) { if config == nil { @@ -168,7 +168,7 @@ func (srv *Server) Port() int64 { return srv.config.Port } -func authenticate(UserAuth AuthTypes, ctx context.Context) (context.Context,error) { +func authenticate(UserAuth AuthTypes, ctx context.Context) (context.Context, error) { var err error success := false rc, ctx := common_utils.GetContext(ctx) @@ -185,13 +185,13 @@ func authenticate(UserAuth AuthTypes, ctx context.Context) (context.Context,erro } } if !success && UserAuth.Enabled("jwt") { - _,ctx,err = JwtAuthenAndAuthor(ctx) + _, ctx, err = JwtAuthenAndAuthor(ctx) if err == nil { success = true } } if !success && UserAuth.Enabled("cert") { - ctx,err = ClientCertAuthenAndAuthor(ctx) + ctx, err = ClientCertAuthenAndAuthor(ctx) if err == nil { success = true } @@ -200,20 +200,19 @@ func authenticate(UserAuth AuthTypes, ctx context.Context) (context.Context,erro //Allow for future authentication mechanisms here... if !success { - return ctx,status.Error(codes.Unauthenticated, "Unauthenticated") - } + return ctx, status.Error(codes.Unauthenticated, "Unauthenticated") + } - return ctx,nil + return ctx, nil } // Subscribe implements the gNMI Subscribe RPC. func (s *Server) Subscribe(stream gnmipb.GNMI_SubscribeServer) error { ctx := stream.Context() - ctx, err := authenticate(s.config.UserAuth, ctx) - if err != nil { - return err - } - + ctx, err := authenticate(s.config.UserAuth, ctx) + if err != nil { + return err + } pr, ok := peer.FromContext(ctx) if !ok { @@ -270,10 +269,10 @@ 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) { - ctx, err := authenticate(s.config.UserAuth, ctx) - if err != nil { - return nil, err - } + ctx, err := authenticate(s.config.UserAuth, ctx) + if err != nil { + return nil, err + } if req.GetType() != gnmipb.GetRequest_ALL { return nil, status.Errorf(codes.Unimplemented, "unsupported request type: %s", gnmipb.GetRequest_DataType_name[int32(req.GetType())]) @@ -286,24 +285,24 @@ func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetRe var target string prefix := req.GetPrefix() if prefix == nil { - return nil, status.Error(codes.Unimplemented, "No target specified in prefix") + return nil, status.Error(codes.Unimplemented, "No target specified in prefix") } else { - target = prefix.GetTarget() - if target == "" { - return nil, status.Error(codes.Unimplemented, "Empty target data not supported yet") - } + target = prefix.GetTarget() + if target == "" { + return nil, status.Error(codes.Unimplemented, "Empty target data not supported yet") + } } paths := req.GetPath() extensions := req.GetExtension() - target = prefix.GetTarget() + target = prefix.GetTarget() log.V(5).Infof("GetRequest paths: %v", paths) var dc sdc.Client if target == "OTHERS" { dc, err = sdc.NewNonDbClient(paths, prefix) - } else if isTargetDb(target) == true { + } else if _, ok, _, _ := sdc.IsTargetDb(target); ok { dc, err = sdc.NewDbClient(paths, prefix) } else { /* If no prefix target is specified create new Transl Data Client . */ @@ -335,68 +334,65 @@ func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetRe return &gnmipb.GetResponse{Notification: notifications}, nil } -func (s *Server) Set(ctx context.Context,req *gnmipb.SetRequest) (*gnmipb.SetResponse, error) { - if !READ_WRITE_MODE { - return nil, grpc.Errorf(codes.Unimplemented, "Telemetry is in read-only mode") - } - ctx, err := authenticate(s.config.UserAuth, ctx) - if err != nil { - return nil, err - } - var results []*gnmipb.UpdateResult - - /* Fetch the prefix. */ - prefix := req.GetPrefix() - extensions := req.GetExtension() - /* Create Transl client. */ - dc, _ := sdc.NewTranslClient(prefix, nil, ctx, extensions) - - /* DELETE */ - for _, path := range req.GetDelete() { - log.V(2).Infof("Delete path: %v", path) - - res := gnmipb.UpdateResult{ - Path: path, - Op: gnmipb.UpdateResult_DELETE, - } - - /* Add to Set response results. */ - results = append(results, &res) - +func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetResponse, error) { + if !READ_WRITE_MODE { + return nil, grpc.Errorf(codes.Unimplemented, "Telemetry is in read-only mode") + } + ctx, err := authenticate(s.config.UserAuth, ctx) + if err != nil { + return nil, err + } + var results []*gnmipb.UpdateResult + + /* Fetch the prefix. */ + prefix := req.GetPrefix() + extensions := req.GetExtension() + /* Create Transl client. */ + dc, _ := sdc.NewTranslClient(prefix, nil, ctx, extensions) + + /* DELETE */ + for _, path := range req.GetDelete() { + log.V(2).Infof("Delete path: %v", path) + + res := gnmipb.UpdateResult{ + Path: path, + Op: gnmipb.UpdateResult_DELETE, } - - /* REPLACE */ - for _, path := range req.GetReplace(){ - log.V(2).Infof("Replace path: %v ", path) - - res := gnmipb.UpdateResult{ - Path: path.GetPath(), - Op: gnmipb.UpdateResult_REPLACE, - } - /* Add to Set response results. */ - results = append(results, &res) + + /* Add to Set response results. */ + results = append(results, &res) + + } + + /* REPLACE */ + for _, path := range req.GetReplace() { + log.V(2).Infof("Replace path: %v ", path) + + res := gnmipb.UpdateResult{ + Path: path.GetPath(), + Op: gnmipb.UpdateResult_REPLACE, } - - - /* UPDATE */ - for _, path := range req.GetUpdate(){ - log.V(2).Infof("Update path: %v ", path) - - res := gnmipb.UpdateResult{ - Path: path.GetPath(), - Op: gnmipb.UpdateResult_UPDATE, - } - /* Add to Set response results. */ - results = append(results, &res) + /* Add to Set response results. */ + results = append(results, &res) + } + + /* UPDATE */ + for _, path := range req.GetUpdate() { + log.V(2).Infof("Update path: %v ", path) + + res := gnmipb.UpdateResult{ + Path: path.GetPath(), + Op: gnmipb.UpdateResult_UPDATE, } - err = dc.Set(req.GetDelete(), req.GetReplace(), req.GetUpdate()) - - - - return &gnmipb.SetResponse{ - Prefix: req.GetPrefix(), - Response: results, - }, err + /* Add to Set response results. */ + results = append(results, &res) + } + err = dc.Set(req.GetDelete(), req.GetReplace(), req.GetUpdate()) + + return &gnmipb.SetResponse{ + Prefix: req.GetPrefix(), + Response: results, + }, err } @@ -406,7 +402,7 @@ func (s *Server) Capabilities(ctx context.Context, req *gnmipb.CapabilityRequest return nil, err } extensions := req.GetExtension() - dc, _ := sdc.NewTranslClient(nil , nil, ctx, extensions) + dc, _ := sdc.NewTranslClient(nil, nil, ctx, extensions) /* Fetch the client capabitlities. */ supportedModels := dc.Capabilities() @@ -414,41 +410,26 @@ func (s *Server) Capabilities(ctx context.Context, req *gnmipb.CapabilityRequest for index, model := range supportedModels { suppModels[index] = &gnmipb.ModelData{ - Name: model.Name, - Organization: model.Organization, - Version: model.Version, + Name: model.Name, + Organization: model.Organization, + Version: model.Version, } } sup_bver := spb.SupportedBundleVersions{ BundleVersion: translib.GetYangBundleVersion().String(), - BaseVersion: translib.GetYangBaseVersion().String(), + BaseVersion: translib.GetYangBaseVersion().String(), } sup_msg, _ := proto.Marshal(&sup_bver) ext := gnmi_extpb.Extension{} - ext.Ext = &gnmi_extpb.Extension_RegisteredExt { - RegisteredExt: &gnmi_extpb.RegisteredExtension { - Id: spb.SUPPORTED_VERSIONS_EXT, + ext.Ext = &gnmi_extpb.Extension_RegisteredExt{ + RegisteredExt: &gnmi_extpb.RegisteredExtension{ + Id: spb.SUPPORTED_VERSIONS_EXT, Msg: sup_msg}} exts := []*gnmi_extpb.Extension{&ext} - return &gnmipb.CapabilityResponse{SupportedModels: suppModels, - SupportedEncodings: supportedEncodings, - GNMIVersion: "0.7.0", - Extension: exts}, nil + return &gnmipb.CapabilityResponse{SupportedModels: suppModels, + SupportedEncodings: supportedEncodings, + GNMIVersion: "0.7.0", + Extension: exts}, nil } - -func isTargetDb ( target string) (bool) { - isDbClient := false - dbTargetSupported := []string { "APPL_DB", "ASIC_DB" , "COUNTERS_DB", "LOGLEVEL_DB", "CONFIG_DB", "PFC_WD_DB", "FLEX_COUNTER_DB", "STATE_DB"} - - for _, name := range dbTargetSupported { - if target == name { - isDbClient = true - return isDbClient - } - } - - return isDbClient -} - diff --git a/gnmi_server/server_test.go b/gnmi_server/server_test.go index 07a6c880a..7a7f58d49 100644 --- a/gnmi_server/server_test.go +++ b/gnmi_server/server_test.go @@ -2,7 +2,6 @@ package gnmi // server_test covers gNMI get, subscribe (stream and poll) test // Prerequisite: redis-server should be running. - import ( "crypto/tls" "encoding/json" @@ -39,6 +38,7 @@ import ( sgpb "github.com/Azure/sonic-telemetry/proto/gnoi" sdc "github.com/Azure/sonic-telemetry/sonic_data_client" sdcfg "github.com/Azure/sonic-telemetry/sonic_db_config" + "github.com/Azure/sonic-telemetry/test_utils" gclient "github.com/jipanyang/gnmi/client/gnmi" "github.com/jipanyang/gnxi/utils/xpath" gnoi_system_pb "github.com/openconfig/gnoi/system" @@ -86,22 +86,22 @@ func loadDBNotStrict(t *testing.T, rclient *redis.Client, mpi map[string]interfa } func createServer(t *testing.T, port int64) *Server { - certificate, err := testcert.NewCert() - if err != nil { - t.Errorf("could not load server key pair: %s", err) - } - tlsCfg := &tls.Config{ - ClientAuth: tls.RequestClientCert, - Certificates: []tls.Certificate{certificate}, - } - - opts := []grpc.ServerOption{grpc.Creds(credentials.NewTLS(tlsCfg))} - cfg := &Config{Port: port} - s, err := NewServer(cfg, opts) - if err != nil { - t.Errorf("Failed to create gNMI server: %v", err) - } - return s + certificate, err := testcert.NewCert() + if err != nil { + t.Errorf("could not load server key pair: %s", err) + } + tlsCfg := &tls.Config{ + ClientAuth: tls.RequestClientCert, + Certificates: []tls.Certificate{certificate}, + } + + opts := []grpc.ServerOption{grpc.Creds(credentials.NewTLS(tlsCfg))} + cfg := &Config{Port: port} + s, err := NewServer(cfg, opts) + if err != nil { + t.Errorf("Failed to create gNMI server: %v", err) + } + return s } // runTestGet requests a path from the server by Get grpc call, and compares if @@ -230,10 +230,10 @@ func runServer(t *testing.T, s *Server) { //t.Log("Exiting RPC server on address", s.Address()) } -func getRedisClientN(t *testing.T, n int) *redis.Client { +func getRedisClientN(t *testing.T, n int, namespace string) *redis.Client { rclient := redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr("COUNTERS_DB"), + Addr: sdcfg.GetDbTcpAddr("COUNTERS_DB", namespace), Password: "", // no password set DB: n, DialTimeout: 0, @@ -245,12 +245,13 @@ func getRedisClientN(t *testing.T, n int) *redis.Client { return rclient } -func getRedisClient(t *testing.T) *redis.Client { +func getRedisClient(t *testing.T, namespace string) *redis.Client { + rclient := redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr("COUNTERS_DB"), + Addr: sdcfg.GetDbTcpAddr("COUNTERS_DB", namespace), Password: "", // no password set - DB: sdcfg.GetDbId("COUNTERS_DB"), + DB: sdcfg.GetDbId("COUNTERS_DB", namespace), DialTimeout: 0, }) _, err := rclient.Ping().Result() @@ -260,12 +261,13 @@ func getRedisClient(t *testing.T) *redis.Client { return rclient } -func getConfigDbClient(t *testing.T) *redis.Client { +func getConfigDbClient(t *testing.T, namespace string) *redis.Client { + rclient := redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr("CONFIG_DB"), + Addr: sdcfg.GetDbTcpAddr("CONFIG_DB", namespace), Password: "", // no password set - DB: sdcfg.GetDbId("CONFIG_DB"), + DB: sdcfg.GetDbId("CONFIG_DB", namespace), DialTimeout: 0, }) _, err := rclient.Ping().Result() @@ -289,8 +291,8 @@ func loadConfigDB(t *testing.T, rclient *redis.Client, mpi map[string]interface{ } } -func prepareConfigDb(t *testing.T) { - rclient := getConfigDbClient(t) +func prepareConfigDb(t *testing.T, namespace string) { + rclient := getConfigDbClient(t, namespace) defer rclient.Close() rclient.FlushDB() @@ -310,9 +312,15 @@ func prepareConfigDb(t *testing.T) { mpi_pfcwd_map := loadConfig(t, "", configPfcwdByte) loadConfigDB(t, rclient, mpi_pfcwd_map) } +func prepareStateDb(t *testing.T, namespace string) { + rclient := getRedisClientN(t, 6, namespace) + defer rclient.Close() + rclient.FlushDB() + rclient.HSet("SWITCH_CAPABILITY|switch", "test_field", "test_value") +} -func prepareDb(t *testing.T) { - rclient := getRedisClient(t) +func prepareDb(t *testing.T, namespace string) { + rclient := getRedisClient(t, namespace) defer rclient.Close() rclient.FlushDB() //Enable keysapce notification @@ -394,11 +402,14 @@ func prepareDb(t *testing.T) { loadDB(t, rclient, mpi_counter) // Load CONFIG_DB for alias translation - prepareConfigDb(t) + prepareConfigDb(t, namespace) + + //Load STATE_DB to test non V2R dataset + prepareStateDb(t, namespace) } func prepareDbTranslib(t *testing.T) { - rclient := getRedisClient(t) + rclient := getRedisClient(t, sdcfg.GetDbDefaultNamespace()) rclient.FlushDB() rclient.Close() @@ -418,7 +429,7 @@ func prepareDbTranslib(t *testing.T) { t.Fatalf("read file %v err: %v", fileName, err) } for n, v := range rj { - rclient := getRedisClientN(t, n) + rclient := getRedisClientN(t, n, sdcfg.GetDbDefaultNamespace()) loadDBNotStrict(t, rclient, v) rclient.Close() } @@ -685,13 +696,7 @@ func TestGnmiSet(t *testing.T) { s.s.Stop() } -func TestGnmiGet(t *testing.T) { - //t.Log("Start server") - s := createServer(t, 8081) - go runServer(t, s) - - prepareDb(t) - +func runGnmiTestGet(t *testing.T, namespace string) { //t.Log("Start gNMI client") tlsConfig := &tls.Config{InsecureSkipVerify: true} opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} @@ -749,6 +754,12 @@ func TestGnmiGet(t *testing.T) { t.Fatalf("read file %v err: %v", fileName, err) } + stateDBPath := "STATE_DB" + + if namespace != sdcfg.GetDbDefaultNamespace() { + stateDBPath = "STATE_DB" + "/" + namespace + } + type testCase struct { desc string pathTarget string @@ -804,108 +815,138 @@ func TestGnmiGet(t *testing.T) { `, wantRetCode: codes.Unimplemented, }, { - desc: "Get valid but non-existing node", - pathTarget: "COUNTERS_DB", + desc: "Test passing asic in path for V2R Dataset Target", + pathTarget: "COUNTER_DB" + "/" + namespace, textPbPath: ` + elem: + elem: + `, + wantRetCode: codes.NotFound, + }, + { + desc: "Get valid but non-existing node", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: `, - wantRetCode: codes.NotFound, - }, { - desc: "Get COUNTERS_PORT_NAME_MAP", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.NotFound, + }, { + desc: "Get COUNTERS_PORT_NAME_MAP", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: `, - wantRetCode: codes.OK, - wantRespVal: countersPortNameMapByte, - }, { - desc: "get COUNTERS:Ethernet68", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: countersPortNameMapByte, + valTest: true, + }, { + desc: "get COUNTERS:Ethernet68", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: countersEthernet68Byte, - }, { - desc: "get COUNTERS:Ethernet68 SAI_PORT_STAT_PFC_7_RX_PKTS", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: countersEthernet68Byte, + valTest: true, + }, { + desc: "get COUNTERS:Ethernet68 SAI_PORT_STAT_PFC_7_RX_PKTS", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: "2", - }, { - desc: "get COUNTERS:Ethernet68 Pfcwd", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: "2", + valTest: true, + }, { + desc: "get COUNTERS:Ethernet68 Pfcwd", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: countersEthernet68PfcwdByte, - }, { - desc: "get COUNTERS (use vendor alias):Ethernet68/1", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: countersEthernet68PfcwdByte, + valTest: true, + }, { + desc: "get COUNTERS (use vendor alias):Ethernet68/1", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: countersEthernet68Byte, - }, { - desc: "get COUNTERS (use vendor alias):Ethernet68/1 SAI_PORT_STAT_PFC_7_RX_PKTS", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: countersEthernet68Byte, + valTest: true, + }, { + desc: "get COUNTERS (use vendor alias):Ethernet68/1 SAI_PORT_STAT_PFC_7_RX_PKTS", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: "2", - }, { - desc: "get COUNTERS (use vendor alias):Ethernet68/1 Pfcwd", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: "2", + valTest: true, + }, { + desc: "get COUNTERS (use vendor alias):Ethernet68/1 Pfcwd", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: countersEthernet68PfcwdAliasByte, - }, { - desc: "get COUNTERS:Ethernet*", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: countersEthernet68PfcwdAliasByte, + valTest: true, + }, { + desc: "get COUNTERS:Ethernet*", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: countersEthernetWildcardByte, - }, { - desc: "get COUNTERS:Ethernet* SAI_PORT_STAT_PFC_7_RX_PKTS", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: countersEthernetWildcardByte, + valTest: true, + }, { + desc: "get COUNTERS:Ethernet* SAI_PORT_STAT_PFC_7_RX_PKTS", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: countersEthernetWildcardPfcByte, - }, { - desc: "get COUNTERS:Ethernet* Pfcwd", - pathTarget: "COUNTERS_DB", - textPbPath: ` + wantRetCode: codes.OK, + wantRespVal: countersEthernetWildcardPfcByte, + valTest: true, + }, { + desc: "get COUNTERS:Ethernet* Pfcwd", + pathTarget: "COUNTERS_DB", + textPbPath: ` elem: elem: elem: `, - wantRetCode: codes.OK, - wantRespVal: countersEthernetWildcardPfcwdByte, - }, + wantRetCode: codes.OK, + wantRespVal: countersEthernetWildcardPfcwdByte, + valTest: true, + }, { + desc: "get State DB Data for SWITCH_CAPABILITY switch", + pathTarget: stateDBPath, + textPbPath: ` + elem: + elem: + `, + valTest: true, + wantRetCode: codes.OK, + wantRespVal: []byte(`{"test_field": "test_value"}`), + }, + // Happy path createBuildVersionTestCase( "get osversion/build", // query path @@ -944,9 +985,45 @@ func TestGnmiGet(t *testing.T) { runTestGet(t, ctx, gClient, td.pathTarget, td.textPbPath, td.wantRetCode, td.wantRespVal, td.valTest) }) } + +} + +func TestGnmiGet(t *testing.T) { + //t.Log("Start server") + s := createServer(t, 8081) + go runServer(t, s) + + prepareDb(t, sdcfg.GetDbDefaultNamespace()) + + runGnmiTestGet(t, sdcfg.GetDbDefaultNamespace()) + s.s.Stop() } +func TestGnmiGetMultiNs(t *testing.T) { + sdcfg.Init() + err := test_utils.SetupMultiNamespace() + if err != nil { + t.Fatalf("error Setting up MultiNamespace files with err %T", err) + } + + /* https://www.gopherguides.com/articles/test-cleanup-in-go-1-14*/ + t.Cleanup(func() { + if err := test_utils.CleanUpMultiNamespace(); err != nil { + t.Fatalf("error Cleaning up MultiNamespace files with err %T", err) + + } + }) + + //t.Log("Start server") + s := createServer(t, 8081) + go runServer(t, s) + prepareDb(t, test_utils.GetMultiNsNamespace()) + + runGnmiTestGet(t, test_utils.GetMultiNsNamespace()) + + s.s.Stop() +} func TestGnmiGetTranslib(t *testing.T) { //t.Log("Start server") s := createServer(t, 8081) @@ -1102,7 +1179,7 @@ type tablePathValue struct { // runTestSubscribe subscribe DB path in stream mode or poll mode. // The return code and response value are compared with expected code and value. -func runTestSubscribe(t *testing.T) { +func runTestSubscribe(t *testing.T, namespace string) { fileName := "../testdata/COUNTERS_PORT_NAME_MAP.txt" countersPortNameMapByte, err := ioutil.ReadFile(fileName) if err != nil { @@ -2069,10 +2146,10 @@ func runTestSubscribe(t *testing.T) { }, } - rclient := getRedisClient(t) + rclient := getRedisClient(t, namespace) defer rclient.Close() for _, tt := range tests { - prepareDb(t) + prepareDb(t, namespace) // Extra db preparation for this test case for _, prepare := range tt.prepares { switch prepare.op { @@ -2083,6 +2160,7 @@ func runTestSubscribe(t *testing.T) { } } + sdcIntervalTicker := sdc.IntervalTicker intervalTickerChan := make(chan time.Time) if tt.generateIntervals { sdc.IntervalTicker = func(interval time.Duration) <-chan time.Time { @@ -2165,6 +2243,9 @@ func runTestSubscribe(t *testing.T) { t.Errorf("unexpected updates:\n%s", diff) } }) + if tt.generateIntervals { + sdc.IntervalTicker = sdcIntervalTicker + } } } @@ -2172,7 +2253,29 @@ func TestGnmiSubscribe(t *testing.T) { s := createServer(t, 8081) go runServer(t, s) - runTestSubscribe(t) + runTestSubscribe(t, sdcfg.GetDbDefaultNamespace()) + + s.s.Stop() +} +func TestGnmiSubscribeMultiNs(t *testing.T) { + sdcfg.Init() + err := test_utils.SetupMultiNamespace() + if err != nil { + t.Fatalf("error Setting up MultiNamespace files with err %T", err) + } + + /* https://www.gopherguides.com/articles/test-cleanup-in-go-1-14*/ + t.Cleanup(func() { + if err := test_utils.CleanUpMultiNamespace(); err != nil { + t.Fatalf("error Cleaning up MultiNamespace files with err %T", err) + + } + }) + + s := createServer(t, 8081) + go runServer(t, s) + + runTestSubscribe(t, test_utils.GetMultiNsNamespace()) s.s.Stop() } @@ -2212,206 +2315,202 @@ func TestCapabilities(t *testing.T) { } func TestGNOI(t *testing.T) { - s := createServer(t, 8086) - go runServer(t, s) - defer s.s.Stop() - - // prepareDb(t) - - //t.Log("Start gNMI client") - tlsConfig := &tls.Config{InsecureSkipVerify: true} - opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} - - //targetAddr := "30.57.185.38:8080" - targetAddr := "127.0.0.1:8086" - conn, err := grpc.Dial(targetAddr, opts...) - if err != nil { - t.Fatalf("Dialing to %q failed: %v", targetAddr, err) - } - defer conn.Close() - - - 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) { - sc := sgpb.NewSonicServiceClient(conn) - rtime := time.Now().AddDate(0,-1,0) - req := &sgpb.TechsupportRequest { - Input: &sgpb.TechsupportRequest_Input{ - Date: rtime.Format("20060102_150405"), - }, - } - resp,err := sc.ShowTechsupport(ctx, req) - if err != nil { - t.Fatal(err.Error()) - } - - if len(resp.Output.OutputFilename) == 0 { - t.Fatalf("Invalid Output Filename: %s", resp.Output.OutputFilename) - } - }) - - type configData struct { - source string - destination string - overwrite bool - status int32 - } - - var cfg_data = []configData { - configData{"running-configuration", "startup-configuration", false, 0}, - configData{"running-configuration", "file://etc/sonic/config_db_test.json", false, 0}, - configData{"file://etc/sonic/config_db_test.json", "running-configuration", false, 0}, - configData{"startup-configuration", "running-configuration", false, 0}, - configData{"file://etc/sonic/config_db_3.json", "running-configuration", false, 1}} - - for _,v := range cfg_data { - - t.Run("SonicCopyConfig", func(t *testing.T) { - sc := sgpb.NewSonicServiceClient(conn) - req := &sgpb.CopyConfigRequest { - Input: &sgpb.CopyConfigRequest_Input{ - Source: v.source, - Destination: v.destination, - Overwrite: v.overwrite, - }, - } - t.Logf("source: %s dest: %s overwrite: %t", v.source, v.destination, v.overwrite) - resp, err := sc.CopyConfig(ctx, req) + s := createServer(t, 8086) + go runServer(t, s) + defer s.s.Stop() + + // prepareDb(t) + + //t.Log("Start gNMI client") + tlsConfig := &tls.Config{InsecureSkipVerify: true} + opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} + + //targetAddr := "30.57.185.38:8080" + targetAddr := "127.0.0.1:8086" + conn, err := grpc.Dial(targetAddr, opts...) if err != nil { - t.Fatal(err.Error()) + t.Fatalf("Dialing to %q failed: %v", targetAddr, err) } - if resp.Output.Status != v.status { - t.Fatalf("Copy Failed: status %d, %s", resp.Output.Status, resp.Output.StatusDetail) + defer conn.Close() + + 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) { + sc := sgpb.NewSonicServiceClient(conn) + rtime := time.Now().AddDate(0, -1, 0) + req := &sgpb.TechsupportRequest{ + Input: &sgpb.TechsupportRequest_Input{ + Date: rtime.Format("20060102_150405"), + }, + } + resp, err := sc.ShowTechsupport(ctx, req) + if err != nil { + t.Fatal(err.Error()) + } + + if len(resp.Output.OutputFilename) == 0 { + t.Fatalf("Invalid Output Filename: %s", resp.Output.OutputFilename) + } + }) + + type configData struct { + source string + destination string + overwrite bool + status int32 + } + + var cfg_data = []configData{ + configData{"running-configuration", "startup-configuration", false, 0}, + configData{"running-configuration", "file://etc/sonic/config_db_test.json", false, 0}, + configData{"file://etc/sonic/config_db_test.json", "running-configuration", false, 0}, + configData{"startup-configuration", "running-configuration", false, 0}, + configData{"file://etc/sonic/config_db_3.json", "running-configuration", false, 1}} + + for _, v := range cfg_data { + + t.Run("SonicCopyConfig", func(t *testing.T) { + sc := sgpb.NewSonicServiceClient(conn) + req := &sgpb.CopyConfigRequest{ + Input: &sgpb.CopyConfigRequest_Input{ + Source: v.source, + Destination: v.destination, + Overwrite: v.overwrite, + }, + } + t.Logf("source: %s dest: %s overwrite: %t", v.source, v.destination, v.overwrite) + resp, err := sc.CopyConfig(ctx, req) + if err != nil { + t.Fatal(err.Error()) + } + if resp.Output.Status != v.status { + t.Fatalf("Copy Failed: status %d, %s", resp.Output.Status, resp.Output.StatusDetail) + } + }) } - }) - } } func TestBundleVersion(t *testing.T) { - s := createServer(t, 8087) - go runServer(t, s) - defer s.s.Stop() - - // prepareDb(t) - - //t.Log("Start gNMI client") - tlsConfig := &tls.Config{InsecureSkipVerify: true} - opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} - - //targetAddr := "30.57.185.38:8080" - targetAddr := "127.0.0.1:8087" - 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() - t.Run("Invalid Bundle Version Format", func(t *testing.T) { - var pbPath *pb.Path - pbPath, err := xpath.ToGNMIPath("openconfig-interfaces:interfaces/interface[name=Ethernet0]/config") - prefix := pb.Path{Target: "OC-YANG"} - if err != nil { - t.Fatalf("error in unmarshaling path: %v", err) - } - bundleVersion := "50.0.0" - bv, err := proto.Marshal(&spb.BundleVersion{ - Version: bundleVersion, - }) - if err != nil { - t.Fatalf("%v", err) - } - req := &pb.GetRequest{ - Path: []*pb.Path{pbPath}, - Prefix: &prefix, - Encoding: pb.Encoding_JSON_IETF, - } - req.Extension = append(req.Extension, &ext_pb.Extension{ - Ext: &ext_pb.Extension_RegisteredExt { - RegisteredExt: &ext_pb.RegisteredExtension { - Id: spb.BUNDLE_VERSION_EXT, - Msg: bv, - }}}) - - - - _, err = gClient.Get(ctx, req) - gotRetStatus, ok := status.FromError(err) - if !ok { - t.Fatal("got a non-grpc error from grpc call") - } - if gotRetStatus.Code() != codes.NotFound { - t.Log("err: ", err) - t.Fatalf("got return code %v, want %v", gotRetStatus.Code(), codes.OK) - } - }) + s := createServer(t, 8087) + go runServer(t, s) + defer s.s.Stop() + + // prepareDb(t) + + //t.Log("Start gNMI client") + tlsConfig := &tls.Config{InsecureSkipVerify: true} + opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} + + //targetAddr := "30.57.185.38:8080" + targetAddr := "127.0.0.1:8087" + 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() + t.Run("Invalid Bundle Version Format", func(t *testing.T) { + var pbPath *pb.Path + pbPath, err := xpath.ToGNMIPath("openconfig-interfaces:interfaces/interface[name=Ethernet0]/config") + prefix := pb.Path{Target: "OC-YANG"} + if err != nil { + t.Fatalf("error in unmarshaling path: %v", err) + } + bundleVersion := "50.0.0" + bv, err := proto.Marshal(&spb.BundleVersion{ + Version: bundleVersion, + }) + if err != nil { + t.Fatalf("%v", err) + } + req := &pb.GetRequest{ + Path: []*pb.Path{pbPath}, + Prefix: &prefix, + Encoding: pb.Encoding_JSON_IETF, + } + req.Extension = append(req.Extension, &ext_pb.Extension{ + Ext: &ext_pb.Extension_RegisteredExt{ + RegisteredExt: &ext_pb.RegisteredExtension{ + Id: spb.BUNDLE_VERSION_EXT, + Msg: bv, + }}}) + + _, err = gClient.Get(ctx, req) + gotRetStatus, ok := status.FromError(err) + if !ok { + t.Fatal("got a non-grpc error from grpc call") + } + if gotRetStatus.Code() != codes.NotFound { + t.Log("err: ", err) + t.Fatalf("got return code %v, want %v", gotRetStatus.Code(), codes.OK) + } + }) } func TestBulkSet(t *testing.T) { - s := createServer(t, 8088) - go runServer(t, s) - defer s.s.Stop() - - // prepareDb(t) - - //t.Log("Start gNMI client") - tlsConfig := &tls.Config{InsecureSkipVerify: true} - opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} - - //targetAddr := "30.57.185.38:8080" - targetAddr := "127.0.0.1:8088" - 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() - t.Run("Set Multiple mtu", func(t *testing.T) { - pbPath1, _ := xpath.ToGNMIPath("openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/mtu") - v := &pb.TypedValue{ - Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("{\"mtu\": 9104}")}} - update1 := &pb.Update { - Path: pbPath1, - Val: v, - } - pbPath2, _ := xpath.ToGNMIPath("openconfig-interfaces:interfaces/interface[name=Ethernet4]/config/mtu") - v2 := &pb.TypedValue{ - Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("{\"mtu\": 9105}")}} - update2 := &pb.Update { - Path: pbPath2, - Val: v2, - } - - - req := &pb.SetRequest{ - Update: []*pb.Update{update1, update2}, - } - - _, err = gClient.Set(ctx, req) - _, ok := status.FromError(err) - if !ok { - t.Fatal("got a non-grpc error from grpc call") - } - - }) + s := createServer(t, 8088) + go runServer(t, s) + defer s.s.Stop() + + // prepareDb(t) + + //t.Log("Start gNMI client") + tlsConfig := &tls.Config{InsecureSkipVerify: true} + opts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))} + + //targetAddr := "30.57.185.38:8080" + targetAddr := "127.0.0.1:8088" + 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() + t.Run("Set Multiple mtu", func(t *testing.T) { + pbPath1, _ := xpath.ToGNMIPath("openconfig-interfaces:interfaces/interface[name=Ethernet0]/config/mtu") + v := &pb.TypedValue{ + Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("{\"mtu\": 9104}")}} + update1 := &pb.Update{ + Path: pbPath1, + Val: v, + } + pbPath2, _ := xpath.ToGNMIPath("openconfig-interfaces:interfaces/interface[name=Ethernet4]/config/mtu") + v2 := &pb.TypedValue{ + Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("{\"mtu\": 9105}")}} + update2 := &pb.Update{ + Path: pbPath2, + Val: v2, + } + + req := &pb.SetRequest{ + Update: []*pb.Update{update1, update2}, + } + + _, err = gClient.Set(ctx, req) + _, ok := status.FromError(err) + if !ok { + t.Fatal("got a non-grpc error from grpc call") + } + + }) } diff --git a/sonic_data_client/db_client.go b/sonic_data_client/db_client.go index 68fab01e3..ab3840316 100644 --- a/sonic_data_client/db_client.go +++ b/sonic_data_client/db_client.go @@ -62,7 +62,7 @@ type Stream interface { var UseRedisLocalTcpPort bool = false // redis client connected to each DB -var Target2RedisDb = make(map[string]*redis.Client) +var Target2RedisDb = make(map[string]map[string]*redis.Client) // MinSampleInterval is the lowest sampling interval for streaming subscriptions. // Any non-zero value that less than this threshold is considered invalid argument. @@ -75,11 +75,12 @@ var IntervalTicker = func(interval time.Duration) <-chan time.Time { } type tablePath struct { - dbName string - tableName string - tableKey string - delimitor string - field string + dbNamespace string + dbName string + tableName string + tableKey string + delimitor string + field string // path name to be used in json data which may be different // from the real data path. Ex. in Counters table, real tableKey // is oid:0x####, while key name like Ethernet## may be put @@ -128,24 +129,6 @@ func NewDbClient(paths []*gnmipb.Path, prefix *gnmipb.Path) (Client, error) { if UseRedisLocalTcpPort { useRedisTcpClient() } - if prefix.GetTarget() == "COUNTERS_DB" { - err = initCountersPortNameMap() - if err != nil { - return nil, err - } - err = initCountersQueueNameMap() - if err != nil { - return nil, err - } - err = initAliasMap() - if err != nil { - return nil, err - } - err = initCountersPfcwdNameMap() - if err != nil { - return nil, err - } - } client.prefix = prefix client.pathG2S = make(map[*gnmipb.Path][]tablePath) @@ -367,52 +350,97 @@ func ValToResp(val Value) (*gnmipb.SubscribeResponse, error) { } } -func GetTableKeySeparator(target string) (string, error) { +func GetTableKeySeparator(target string, ns string) (string, error) { _, ok := spb.Target_value[target] if !ok { log.V(1).Infof(" %v not a valid path target", target) return "", fmt.Errorf("%v not a valid path target", target) } - var separator string = sdcfg.GetDbSeparator(target) + var separator string = sdcfg.GetDbSeparator(target, ns) return separator, nil } +func GetRedisClientsForDb(target string) map[string]*redis.Client { + redis_client_map := make(map[string]*redis.Client) + if sdcfg.CheckDbMultiNamespace() { + ns_list := sdcfg.GetDbNonDefaultNamespaces() + for _, ns := range ns_list { + redis_client_map[ns] = Target2RedisDb[ns][target] + } + } else { + ns := sdcfg.GetDbDefaultNamespace() + redis_client_map[ns] = Target2RedisDb[ns][target] + } + return redis_client_map +} + +// This function get target present in GNMI Request and +// returns: 1. DbName (string) 2. Is DbName valid (bool) +// 3. DbNamespace (string) 4. Is DbNamespace present in Target (bool) +func IsTargetDb(target string) (string, bool, string, bool) { + targetname := strings.Split(target, "/") + dbName := targetname[0] + dbNameSpaceExist := false + dbNamespace := sdcfg.GetDbDefaultNamespace() + + if len(targetname) > 2 { + log.V(1).Infof("target format is not correct") + return dbName, false, dbNamespace, dbNameSpaceExist + } + + if len(targetname) > 1 { + dbNamespace = targetname[1] + dbNameSpaceExist = true + } + for name, _ := range spb.Target_value { + if name == dbName { + return dbName, true, dbNamespace, dbNameSpaceExist + } + } + + return dbName, false, dbNamespace, dbNameSpaceExist +} + // For testing only func useRedisTcpClient() { - for dbName, dbn := range spb.Target_value { - if dbName != "OTHERS" { - // DB connector for direct redis operation - var redisDb *redis.Client - if UseRedisLocalTcpPort { - redisDb = redis.NewClient(&redis.Options{ + if !UseRedisLocalTcpPort { + return + } + for _, dbNamespace := range sdcfg.GetDbAllNamespaces() { + Target2RedisDb[dbNamespace] = make(map[string]*redis.Client) + for dbName, dbn := range spb.Target_value { + if dbName != "OTHERS" { + // DB connector for direct redis operation + redisDb := redis.NewClient(&redis.Options{ Network: "tcp", - Addr: sdcfg.GetDbTcpAddr(dbName), + Addr: sdcfg.GetDbTcpAddr(dbName, dbNamespace), Password: "", // no password set DB: int(dbn), DialTimeout: 0, }) + Target2RedisDb[dbNamespace][dbName] = redisDb } - Target2RedisDb[dbName] = redisDb } } } // Client package prepare redis clients to all DBs automatically func init() { - for dbName, dbn := range spb.Target_value { - if dbName != "OTHERS" { - // DB connector for direct redis operation - var redisDb *redis.Client - - redisDb = redis.NewClient(&redis.Options{ - Network: "unix", - Addr: sdcfg.GetDbSock(dbName), - Password: "", // no password set - DB: int(dbn), - DialTimeout: 0, - }) - Target2RedisDb[dbName] = redisDb + for _, dbNamespace := range sdcfg.GetDbAllNamespaces() { + Target2RedisDb[dbNamespace] = make(map[string]*redis.Client) + for dbName, dbn := range spb.Target_value { + if dbName != "OTHERS" { + // DB connector for direct redis operation + redisDb := redis.NewClient(&redis.Options{ + Network: "unix", + Addr: sdcfg.GetDbSock(dbName, dbNamespace), + Password: "", // no password set + DB: int(dbn), + DialTimeout: 0, + }) + Target2RedisDb[dbNamespace][dbName] = redisDb + } } } } @@ -447,19 +475,45 @@ func populateDbtablePath(prefix, path *gnmipb.Path, pathG2S *map[*gnmipb.Path][] var tblPath tablePath target := prefix.GetTarget() + targetDbName, targetDbNameValid, targetDbNameSpace, targetDbNameSpaceExist := IsTargetDb(target) // Verify it is a valid db name - redisDb, ok := Target2RedisDb[target] + if !targetDbNameValid { + return fmt.Errorf("Invalid target dbName %v", targetDbName) + } + + // Verify Namespace is valid + dbNamespace, ok := sdcfg.GetDbNamespaceFromTarget(targetDbNameSpace) if !ok { - return fmt.Errorf("Invalid target name %v", target) + return fmt.Errorf("Invalid target dbNameSpace %v", targetDbNameSpace) } + if targetDbName == "COUNTERS_DB" { + err := initCountersPortNameMap() + if err != nil { + return err + } + err = initCountersQueueNameMap() + if err != nil { + return err + } + err = initAliasMap() + if err != nil { + return err + } + err = initCountersPfcwdNameMap() + if err != nil { + return err + } + } + + fullPath := path if prefix != nil { fullPath = gnmiFullPath(prefix, path) } - stringSlice := []string{target} - separator, _ := GetTableKeySeparator(target) + stringSlice := []string{targetDbName} + separator, _ := GetTableKeySeparator(targetDbName, dbNamespace) elems := fullPath.GetElem() if elems != nil { for i, elem := range elems { @@ -477,14 +531,17 @@ func populateDbtablePath(prefix, path *gnmipb.Path, pathG2S *map[*gnmipb.Path][] // First lookup the Virtual path to Real path mapping tree // The path from gNMI might not be real db path if tblPaths, err := lookupV2R(stringSlice); err == nil { + if targetDbNameSpaceExist { + return fmt.Errorf("Target having %v namespace is not supported for V2R Dataset", dbNamespace) + } (*pathG2S)[path] = tblPaths log.V(5).Infof("v2r from %v to %+v ", stringSlice, tblPaths) return nil } else { log.V(5).Infof("v2r lookup failed for %v %v", stringSlice, err) } - - tblPath.dbName = target + tblPath.dbNamespace = dbNamespace + tblPath.dbName = targetDbName tblPath.tableName = stringSlice[1] tblPath.delimitor = separator @@ -493,6 +550,11 @@ func populateDbtablePath(prefix, path *gnmipb.Path, pathG2S *map[*gnmipb.Path][] mappedKey = stringSlice[2] } + redisDb, ok := Target2RedisDb[tblPath.dbNamespace][tblPath.dbName] + if !ok { + return fmt.Errorf("Redis Client not present for dbName %v dbNamespace %v", targetDbName, dbNamespace) + } + // The expect real db path could be in one of the formats: // <1> DB Table // <2> DB Table Key @@ -596,7 +658,7 @@ func emitJSON(v *map[string]interface{}) ([]byte, error) { // If only table name provided in the tablePath, find all keys in the table, otherwise // Use tableName + tableKey as key to get all field value paires func tableData2Msi(tblPath *tablePath, useKey bool, op *string, msi *map[string]interface{}) error { - redisDb := Target2RedisDb[tblPath.dbName] + redisDb := Target2RedisDb[tblPath.dbNamespace][tblPath.dbName] var pattern string var dbkeys []string @@ -678,7 +740,7 @@ func tableData2TypedValue(tblPaths []tablePath, op *string) (*gnmipb.TypedValue, var useKey bool msi := make(map[string]interface{}) for _, tblPath := range tblPaths { - redisDb := Target2RedisDb[tblPath.dbName] + redisDb := Target2RedisDb[tblPath.dbNamespace][tblPath.dbName] if tblPath.jsonField == "" { // Not asked to include field in json value, which means not wildcard query // table path includes table, key and field @@ -750,7 +812,7 @@ func dbFieldMultiSubscribe(c *DbClient, gnmiPath *gnmipb.Path, onChange bool, in key = tblPath.tableName } // run redis get directly for field value - redisDb := Target2RedisDb[tblPath.dbName] + redisDb := Target2RedisDb[tblPath.dbNamespace][tblPath.dbName] val, err := redisDb.HGet(key, tblPath.field).Result() if err == redis.Nil { if tblPath.jsonField != "" { @@ -836,7 +898,7 @@ func dbFieldSubscribe(c *DbClient, gnmiPath *gnmipb.Path, onChange bool, interva tblPaths := c.pathG2S[gnmiPath] tblPath := tblPaths[0] // run redis get directly for field value - redisDb := Target2RedisDb[tblPath.dbName] + redisDb := Target2RedisDb[tblPath.dbNamespace][tblPath.dbName] var key string if tblPath.tableKey != "" { @@ -1066,7 +1128,7 @@ func dbTableKeySubscribe(c *DbClient, gnmiPath *gnmipb.Path, interval time.Durat prefixLen = len(pattern) pattern += "*" } - redisDb := Target2RedisDb[tblPath.dbName] + redisDb := Target2RedisDb[tblPath.dbNamespace][tblPath.dbName] pubsub := redisDb.PSubscribe(pattern) defer pubsub.Close() @@ -1160,9 +1222,10 @@ func dbTableKeySubscribe(c *DbClient, gnmiPath *gnmipb.Path, interval time.Durat } } -func (c *DbClient) Set(delete []*gnmipb.Path, replace []*gnmipb.Update, update []*gnmipb.Update) error { +func (c *DbClient) Set(delete []*gnmipb.Path, replace []*gnmipb.Update, update []*gnmipb.Update) error { return nil } + func (c *DbClient) Capabilities() []gnmipb.ModelData { return nil } diff --git a/sonic_data_client/virtual_db.go b/sonic_data_client/virtual_db.go index 7b1592cf7..b92020454 100644 --- a/sonic_data_client/virtual_db.go +++ b/sonic_data_client/virtual_db.go @@ -38,6 +38,8 @@ var ( alias2nameMap = make(map[string]string) // Alias translation: from sonic interface name to vendor port name name2aliasMap = make(map[string]string) + // Map of sonic interface name to namespace + port2namespaceMap = make(map[string]string) // SONiC interface name to their PFC-WD enabled queues, then to oid map countersPfcwdNameMap = make(map[string]map[string]string) @@ -98,14 +100,13 @@ func initCountersPortNameMap() error { func initAliasMap() error { var err error if len(alias2nameMap) == 0 { - alias2nameMap, name2aliasMap, err = getAliasMap() + alias2nameMap, name2aliasMap, port2namespaceMap, err = getAliasMap() if err != nil { return err } } return nil } - func initCountersPfcwdNameMap() error { var err error if len(countersPfcwdNameMap) == 0 { @@ -122,84 +123,85 @@ func getPfcwdMap() (map[string]map[string]string, error) { var pfcwdName_map = make(map[string]map[string]string) dbName := "CONFIG_DB" - separator, _ := GetTableKeySeparator(dbName) - redisDb, _ := Target2RedisDb[dbName] - _, err := redisDb.Ping().Result() - if err != nil { - log.V(1).Infof("Can not connect to %v, err: %v", dbName, err) - return nil, err - } + for namespace, redisDb := range GetRedisClientsForDb(dbName) { + separator, _ := GetTableKeySeparator(dbName, namespace) + _, err := redisDb.Ping().Result() + if err != nil { + log.V(1).Infof("Can not connect to %v in namsespace %v, err: %v", dbName, namespace, err) + return nil, err + } - keyName := fmt.Sprintf("PFC_WD_TABLE%v*", separator) - resp, err := redisDb.Keys(keyName).Result() - if err != nil { - log.V(1).Infof("redis get keys failed for %v, key = %v, err: %v", dbName, keyName, err) - return nil, err - } + keyName := fmt.Sprintf("PFC_WD_TABLE%v*", separator) + resp, err := redisDb.Keys(keyName).Result() + if err != nil { + log.V(1).Infof("redis get keys failed for %v in namsepace %v, key = %v, err: %v", dbName, namespace, keyName, err) + return nil, err + } - if len(resp) == 0 { - // PFC WD service not enabled on device - log.V(1).Infof("PFC WD not enabled on device") - return nil, nil - } + if len(resp) == 0 { + // PFC WD service not enabled on device + log.V(1).Infof("PFC WD not enabled on device") + return nil, nil + } - for _, key := range resp { - name := key[13:] - pfcwdName_map[name] = make(map[string]string) - } + for _, key := range resp { + name := key[13:] + pfcwdName_map[name] = make(map[string]string) + } - // Get Queue indexes that are enabled with PFC-WD - keyName = "PORT_QOS_MAP*" - resp, err = redisDb.Keys(keyName).Result() - if err != nil { - log.V(1).Infof("redis get keys failed for %v, key = %v, err: %v", dbName, keyName, err) - return nil, err - } - if len(resp) == 0 { - log.V(1).Infof("PFC WD not enabled on device") - return nil, nil - } - qos_key := resp[0] + // Get Queue indexes that are enabled with PFC-WD + keyName = "PORT_QOS_MAP*" + resp, err = redisDb.Keys(keyName).Result() + if err != nil { + log.V(1).Infof("redis get keys failed for %v in namespace %v, key = %v, err: %v", dbName, namespace, keyName, err) + return nil, err + } + if len(resp) == 0 { + log.V(1).Infof("PFC WD not enabled on device") + return nil, nil + } + qos_key := resp[0] - fieldName := "pfc_enable" - priorities, err := redisDb.HGet(qos_key, fieldName).Result() - if err != nil { - log.V(1).Infof("redis get field failed for %v, key = %v, field = %v, err: %v", dbName, qos_key, fieldName, err) - return nil, err - } + fieldName := "pfc_enable" + priorities, err := redisDb.HGet(qos_key, fieldName).Result() + if err != nil { + log.V(1).Infof("redis get field failed for %v in namsepace %v, key = %v, field = %v, err: %v", dbName, namespace, qos_key, fieldName, err) + return nil, err + } - keyName = fmt.Sprintf("MAP_PFC_PRIORITY_TO_QUEUE%vAZURE", separator) - pfc_queue_map, err := redisDb.HGetAll(keyName).Result() - if err != nil { - log.V(1).Infof("redis get fields failed for %v, key = %v, err: %v", dbName, keyName, err) - return nil, err - } + keyName = fmt.Sprintf("MAP_PFC_PRIORITY_TO_QUEUE%vAZURE", separator) + pfc_queue_map, err := redisDb.HGetAll(keyName).Result() + if err != nil { + log.V(1).Infof("redis get fields failed for %v in namsepace %v, key = %v, err: %v", dbName, namespace, keyName, err) + return nil, err + } - var indices []string - for _, p := range strings.Split(priorities, ",") { - _, ok := pfc_queue_map[p] - if !ok { - log.V(1).Infof("Missing mapping between PFC priority %v to queue", p) - } else { - indices = append(indices, pfc_queue_map[p]) + var indices []string + for _, p := range strings.Split(priorities, ",") { + _, ok := pfc_queue_map[p] + if !ok { + log.V(1).Infof("Missing mapping between PFC priority %v to queue", p) + } else { + indices = append(indices, pfc_queue_map[p]) + } } - } - if len(countersQueueNameMap) == 0 { - log.V(1).Infof("COUNTERS_QUEUE_NAME_MAP is empty") - return nil, nil - } + if len(countersQueueNameMap) == 0 { + log.V(1).Infof("COUNTERS_QUEUE_NAME_MAP is empty") + return nil, nil + } - var queue_key string - queue_separator, _ := GetTableKeySeparator("COUNTERS_DB") - for port, _ := range pfcwdName_map { - for _, indice := range indices { - queue_key = port + queue_separator + indice - oid, ok := countersQueueNameMap[queue_key] - if !ok { - return nil, fmt.Errorf("key %v not exists in COUNTERS_QUEUE_NAME_MAP", queue_key) + var queue_key string + queue_separator, _ := GetTableKeySeparator("COUNTERS_DB", namespace) + for port, _ := range pfcwdName_map { + for _, indice := range indices { + queue_key = port + queue_separator + indice + oid, ok := countersQueueNameMap[queue_key] + if !ok { + return nil, fmt.Errorf("key %v not exists in COUNTERS_QUEUE_NAME_MAP", queue_key) + } + pfcwdName_map[port][queue_key] = oid } - pfcwdName_map[port][queue_key] = oid } } @@ -207,60 +209,75 @@ func getPfcwdMap() (map[string]map[string]string, error) { return pfcwdName_map, nil } -// Get the mapping between sonic interface name and vendor alias -func getAliasMap() (map[string]string, map[string]string, error) { +// Get the mapping between sonic interface name and vendor alias and sonic-interface to namespace map +func getAliasMap() (map[string]string, map[string]string, map[string]string, error) { var alias2name_map = make(map[string]string) var name2alias_map = make(map[string]string) + var port2namespace_map = make(map[string]string) dbName := "CONFIG_DB" - separator, _ := GetTableKeySeparator(dbName) - redisDb, _ := Target2RedisDb[dbName] - _, err := redisDb.Ping().Result() - if err != nil { - log.V(1).Infof("Can not connect to %v, err: %v", dbName, err) - return nil, nil, err - } + for namespace, redisDb := range GetRedisClientsForDb(dbName) { + separator, _ := GetTableKeySeparator(dbName, namespace) + _, err := redisDb.Ping().Result() + if err != nil { + log.V(1).Infof("Can not connect to %v, in namsepace %v, err: %v", dbName, namespace, err) + return nil, nil, nil, err + } - keyName := fmt.Sprintf("PORT%v*", separator) - resp, err := redisDb.Keys(keyName).Result() - if err != nil { - log.V(1).Infof("redis get keys failed for %v, key = %v, err: %v", dbName, keyName, err) - return nil, nil, err - } - for _, key := range resp { - alias, err := redisDb.HGet(key, "alias").Result() + keyName := fmt.Sprintf("PORT%v*", separator) + resp, err := redisDb.Keys(keyName).Result() if err != nil { - log.V(1).Infof("redis get field alias failed for %v, key = %v, err: %v", dbName, key, err) - // clear aliasMap - alias2name_map = make(map[string]string) - name2alias_map = make(map[string]string) - return nil, nil, err - } - alias2name_map[alias] = key[5:] - name2alias_map[key[5:]] = alias + log.V(1).Infof("redis get keys failed for %v in namsepace %v, key = %v, err: %v", dbName, namespace, keyName, err) + return nil, nil, nil, err + } + for _, key := range resp { + alias, err := redisDb.HGet(key, "alias").Result() + if err != nil { + log.V(1).Infof("redis get field alias failed for %v in namsepace %v, key = %v, err: %v", dbName, namespace, key, err) + // clear aliasMap + alias2name_map = make(map[string]string) + name2alias_map = make(map[string]string) + port2namespace_map = make(map[string]string) + return nil, nil, nil, err + } + alias2name_map[alias] = key[5:] + name2alias_map[key[5:]] = alias + port2namespace_map[key[5:]] = namespace + } } log.V(6).Infof("alias2nameMap: %v", alias2name_map) log.V(6).Infof("name2aliasMap: %v", name2alias_map) - return alias2name_map, name2alias_map, nil + log.V(6).Infof("port2namespaceMap: %v", port2namespace_map) + return alias2name_map, name2alias_map, port2namespace_map, nil +} + +// Ref: https://stackoverflow.com/questions/12172215/merging-maps-in-go +func addmap(a map[string]string, b map[string]string) { + for k, v := range b { + a[k] = v + } } // Get the mapping between objects in counters DB, Ex. port name to oid in "COUNTERS_PORT_NAME_MAP" table. // Aussuming static port name to oid map in COUNTERS table func getCountersMap(tableName string) (map[string]string, error) { - redisDb, _ := Target2RedisDb["COUNTERS_DB"] - fv, err := redisDb.HGetAll(tableName).Result() - if err != nil { - log.V(2).Infof("redis HGetAll failed for COUNTERS_DB, tableName: %s", tableName) - return nil, err + counter_map := make(map[string]string) + dbName := "COUNTERS_DB" + for namespace, redisDb := range GetRedisClientsForDb(dbName) { + fv, err := redisDb.HGetAll(tableName).Result() + if err != nil { + log.V(2).Infof("redis HGetAll failed for COUNTERS_DB in namespace %v, tableName: %s", namespace, tableName) + return nil, err + } + addmap(counter_map, fv) + log.V(6).Infof("tableName: %s in namespace %v, map %v", tableName, namespace, fv) } - log.V(6).Infof("tableName: %s, map %v", tableName, fv) - return fv, nil + return counter_map, nil } // Populate real data paths from paths like // [COUNTER_DB COUNTERS Ethernet*] or [COUNTER_DB COUNTERS Ethernet68] func v2rEthPortStats(paths []string) ([]tablePath, error) { - separator, _ := GetTableKeySeparator(paths[DbIdx]) var tblPaths []tablePath if strings.HasSuffix(paths[KeyIdx], "*") { // All Ethernet ports for port, oid := range countersPortNameMap { @@ -271,8 +288,13 @@ func v2rEthPortStats(paths []string) ([]tablePath, error) { log.V(2).Infof("%v does not have a vendor alias", port) oport = port } - + namespace, ok := port2namespaceMap[port] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", port) + } + separator, _ := GetTableKeySeparator(paths[DbIdx], namespace) tblPath := tablePath{ + dbNamespace: namespace, dbName: paths[DbIdx], tableName: paths[TblIdx], tableKey: oid, @@ -292,11 +314,17 @@ func v2rEthPortStats(paths []string) ([]tablePath, error) { if !ok { return nil, fmt.Errorf("%v not a valid sonic interface. Vendor alias is %v", name, alias) } + namespace, ok := port2namespaceMap[name] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", name) + } + separator, _ := GetTableKeySeparator(paths[DbIdx], namespace) tblPaths = []tablePath{{ - dbName: paths[DbIdx], - tableName: paths[TblIdx], - tableKey: oid, - delimitor: separator, + dbNamespace: namespace, + dbName: paths[DbIdx], + tableName: paths[TblIdx], + tableKey: oid, + delimitor: separator, }} } log.V(6).Infof("v2rEthPortStats: %v", tblPaths) @@ -310,7 +338,6 @@ func v2rEthPortStats(paths []string) ([]tablePath, error) { // Ex. [COUNTER_DB COUNTERS Ethernet68 SAI_PORT_STAT_PFC_0_RX_PKTS] // case of "*" field could be covered in v2rEthPortStats() func v2rEthPortFieldStats(paths []string) ([]tablePath, error) { - separator, _ := GetTableKeySeparator(paths[DbIdx]) var tblPaths []tablePath if strings.HasSuffix(paths[KeyIdx], "*") { for port, oid := range countersPortNameMap { @@ -321,8 +348,13 @@ func v2rEthPortFieldStats(paths []string) ([]tablePath, error) { log.V(2).Infof("%v dose not have a vendor alias", port) oport = port } - + namespace, ok := port2namespaceMap[port] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", port) + } + separator, _ := GetTableKeySeparator(paths[DbIdx], namespace) tblPath := tablePath{ + dbNamespace: namespace, dbName: paths[DbIdx], tableName: paths[TblIdx], tableKey: oid, @@ -344,12 +376,18 @@ func v2rEthPortFieldStats(paths []string) ([]tablePath, error) { if !ok { return nil, fmt.Errorf(" %v not a valid sonic interface. Vendor alias is %v ", name, alias) } + namespace, ok := port2namespaceMap[name] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", name) + } + separator, _ := GetTableKeySeparator(paths[DbIdx], namespace) tblPaths = []tablePath{{ - dbName: paths[DbIdx], - tableName: paths[TblIdx], - tableKey: oid, - field: paths[FieldIdx], - delimitor: separator, + dbNamespace: namespace, + dbName: paths[DbIdx], + tableName: paths[TblIdx], + tableKey: oid, + field: paths[FieldIdx], + delimitor: separator, }} } log.V(6).Infof("v2rEthPortFieldStats: %+v", tblPaths) @@ -359,10 +397,14 @@ func v2rEthPortFieldStats(paths []string) ([]tablePath, error) { // Populate real data paths from paths like // [COUNTER_DB COUNTERS Ethernet* Pfcwd] or [COUNTER_DB COUNTERS Ethernet68 Pfcwd] func v2rEthPortPfcwdStats(paths []string) ([]tablePath, error) { - separator, _ := GetTableKeySeparator(paths[DbIdx]) var tblPaths []tablePath if strings.HasSuffix(paths[KeyIdx], "*") { // Pfcwd on all Ethernet ports - for _, pfcqueues := range countersPfcwdNameMap { + for port, pfcqueues := range countersPfcwdNameMap { + namespace, ok := port2namespaceMap[port] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", port) + } + separator, _ := GetTableKeySeparator(paths[DbIdx], namespace) for pfcque, oid := range pfcqueues { // pfcque is in format of "Interface:12" names := strings.Split(pfcque, separator) @@ -375,6 +417,7 @@ func v2rEthPortPfcwdStats(paths []string) ([]tablePath, error) { } que := strings.Join([]string{oname, names[1]}, separator) tblPath := tablePath{ + dbNamespace: namespace, dbName: paths[DbIdx], tableName: paths[TblIdx], tableKey: oid, @@ -390,10 +433,15 @@ func v2rEthPortPfcwdStats(paths []string) ([]tablePath, error) { if val, ok := alias2nameMap[alias]; ok { name = val } - _, ok := countersPortNameMap[name] + namespace, ok := port2namespaceMap[name] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", name) + } + _, ok = countersPortNameMap[name] if !ok { return nil, fmt.Errorf("%v not a valid SONiC interface. Vendor alias is %v", name, alias) } + separator, _ := GetTableKeySeparator(paths[DbIdx], namespace) pfcqueues, ok := countersPfcwdNameMap[name] if ok { @@ -402,6 +450,7 @@ func v2rEthPortPfcwdStats(paths []string) ([]tablePath, error) { names := strings.Split(pfcque, separator) que := strings.Join([]string{alias, names[1]}, separator) tblPath := tablePath{ + dbNamespace: namespace, dbName: paths[DbIdx], tableName: paths[TblIdx], tableKey: oid, @@ -419,7 +468,7 @@ func v2rEthPortPfcwdStats(paths []string) ([]tablePath, error) { // Populate real data paths from paths like // [COUNTER_DB COUNTERS Ethernet* Queues] or [COUNTER_DB COUNTERS Ethernet68 Queues] func v2rEthPortQueStats(paths []string) ([]tablePath, error) { - separator, _ := GetTableKeySeparator(paths[DbIdx]) + separator, _ := GetTableKeySeparator(paths[DbIdx], "") var tblPaths []tablePath if strings.HasSuffix(paths[KeyIdx], "*") { // queues on all Ethernet ports for que, oid := range countersQueueNameMap { @@ -432,8 +481,13 @@ func v2rEthPortQueStats(paths []string) ([]tablePath, error) { log.V(2).Infof(" %v dose not have a vendor alias", names[0]) oname = names[0] } + namespace, ok := port2namespaceMap[names[0]] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", names[0]) + } que = strings.Join([]string{oname, names[1]}, separator) tblPath := tablePath{ + dbNamespace: namespace, dbName: paths[DbIdx], tableName: paths[TblIdx], tableKey: oid, @@ -448,6 +502,10 @@ func v2rEthPortQueStats(paths []string) ([]tablePath, error) { if val, ok := alias2nameMap[alias]; ok { name = val } + namespace, ok := port2namespaceMap[name] + if !ok { + return nil, fmt.Errorf("%v does not have namespace associated", name) + } for que, oid := range countersQueueNameMap { //que is in format of "Ethernet64:12" names := strings.Split(que, separator) @@ -456,6 +514,7 @@ func v2rEthPortQueStats(paths []string) ([]tablePath, error) { } que = strings.Join([]string{alias, names[1]}, separator) tblPath := tablePath{ + dbNamespace: namespace, dbName: paths[DbIdx], tableName: paths[TblIdx], tableKey: oid, diff --git a/sonic_db_config/db_config.go b/sonic_db_config/db_config.go index c097359bc..2e37d9f6c 100644 --- a/sonic_db_config/db_config.go +++ b/sonic_db_config/db_config.go @@ -1,135 +1,236 @@ -// Package dbconfig provides a generic functions for parsing sonic database config file in system +//Package dbconfig provides a generic functions for parsing sonic database config file in system +//package main package dbconfig import ( - "encoding/json" - "fmt" - "strconv" - io "io/ioutil" + "encoding/json" + "errors" + "fmt" + io "io/ioutil" + "os" + "path/filepath" + "strconv" ) const ( - SONIC_DB_CONFIG_FILE string = "/var/run/redis/sonic-db/database_config.json" + SONIC_DB_GLOBAL_CONFIG_FILE string = "/var/run/redis/sonic-db/database_global.json" + SONIC_DB_CONFIG_FILE string = "/var/run/redis/sonic-db/database_config.json" + SONIC_DEFAULT_NAMESPACE string = "" ) -var sonic_db_config = make(map[string]interface{}) +var sonic_db_config = make(map[string]map[string]interface{}) var sonic_db_init bool +var sonic_db_multi_namespace bool -func GetDbList()(map[string]interface{}) { - if !sonic_db_init { - DbInit() - } - db_list, ok := sonic_db_config["DATABASES"].(map[string]interface{}) - if !ok { - panic(fmt.Errorf("DATABASES' is not valid key in database_config.json file!")) - } - return db_list -} - -func GetDbInst(db_name string)(map[string]interface{}) { - if !sonic_db_init { - DbInit() - } - db, ok := sonic_db_config["DATABASES"].(map[string]interface{})[db_name] - if !ok { - panic(fmt.Errorf("database name '%v' is not valid in database_config.json file!", db_name)) - } - inst_name, ok := db.(map[string]interface{})["instance"] - if !ok { - panic(fmt.Errorf("'instance' is not a valid field in database_config.json file!")) - } - inst, ok := sonic_db_config["INSTANCES"].(map[string]interface{})[inst_name.(string)] - if !ok { - panic(fmt.Errorf("instance name '%v' is not valid in database_config.json file!", inst_name)) - } - return inst.(map[string]interface{}) -} - -func GetDbSeparator(db_name string)(string) { - if !sonic_db_init { - DbInit() - } - db_list := GetDbList() - separator, ok := db_list[db_name].(map[string]interface{})["separator"] - if !ok { - panic(fmt.Errorf("'separator' is not a valid field in database_config.json file!")) - } - return separator.(string) -} - -func GetDbId(db_name string)(int) { - if !sonic_db_init { - DbInit() - } - db_list := GetDbList() - id, ok := db_list[db_name].(map[string]interface{})["id"] - if !ok { - panic(fmt.Errorf("'id' is not a valid field in database_config.json file!")) - } - return int(id.(float64)) -} - -func GetDbSock(db_name string)(string) { - if !sonic_db_init { - DbInit() - } - inst := GetDbInst(db_name) - unix_socket_path, ok := inst["unix_socket_path"] - if !ok { - panic(fmt.Errorf("'unix_socket_path' is not a valid field in database_config.json file!")) - } - return unix_socket_path.(string) -} - -func GetDbHostName(db_name string)(string) { - if !sonic_db_init { - DbInit() - } - inst := GetDbInst(db_name) - hostname, ok := inst["hostname"] - if !ok { - panic(fmt.Errorf("'hostname' is not a valid field in database_config.json file!")) - } - return hostname.(string) -} - -func GetDbPort(db_name string)(int) { - if !sonic_db_init { - DbInit() - } - inst := GetDbInst(db_name) - port, ok := inst["port"] - if !ok { - panic(fmt.Errorf("'port' is not a valid field in database_config.json file!")) - } - return int(port.(float64)) -} - -func GetDbTcpAddr(db_name string)(string) { - if !sonic_db_init { - DbInit() - } - hostname := GetDbHostName(db_name) - port := GetDbPort(db_name) - return hostname + ":" + strconv.Itoa(port) +func GetDbDefaultNamespace() string { + return SONIC_DEFAULT_NAMESPACE +} +func CheckDbMultiNamespace() bool { + if !sonic_db_init { + DbInit() + } + return sonic_db_multi_namespace +} +func GetDbNonDefaultNamespaces() []string { + if !sonic_db_init { + DbInit() + } + ns_list := make([]string, 0, len(sonic_db_config)) + for ns := range sonic_db_config { + if ns == SONIC_DEFAULT_NAMESPACE { + continue + } + ns_list = append(ns_list, ns) + } + return ns_list +} +func GetDbAllNamespaces() []string { + if !sonic_db_init { + DbInit() + } + ns_list := make([]string, len(sonic_db_config)) + i := 0 + for ns := range sonic_db_config { + ns_list[i] = ns + i++ + } + return ns_list +} + +func GetDbNamespaceFromTarget(target string) (string, bool) { + if target == GetDbDefaultNamespace() { + return target, true + } + ns_list := GetDbNonDefaultNamespaces() + for _, ns := range ns_list { + if target == ns { + return target, true + } + } + return "", false +} +func GetDbList(ns string) map[string]interface{} { + if !sonic_db_init { + DbInit() + } + db_list, ok := sonic_db_config[ns]["DATABASES"].(map[string]interface{}) + if !ok { + panic(fmt.Errorf("DATABASES' is not valid key in database_config.json file for namespace `%v` !", ns)) + } + return db_list +} + +func GetDbInst(db_name string, ns string) map[string]interface{} { + if !sonic_db_init { + DbInit() + } + db, ok := sonic_db_config[ns]["DATABASES"].(map[string]interface{})[db_name] + if !ok { + panic(fmt.Errorf("database name '%v' is not valid in database_config.json file for namespace `%v`!", db_name, ns)) + } + inst_name, ok := db.(map[string]interface{})["instance"] + if !ok { + panic(fmt.Errorf("'instance' is not a valid field in database_config.json file for namespace `%v`!", ns)) + } + inst, ok := sonic_db_config[ns]["INSTANCES"].(map[string]interface{})[inst_name.(string)] + if !ok { + panic(fmt.Errorf("instance name '%v' is not valid in database_config.json file for namespace `%v`!", inst_name, ns)) + } + return inst.(map[string]interface{}) +} + +func GetDbSeparator(db_name string, ns string) string { + if !sonic_db_init { + DbInit() + } + db_list := GetDbList(ns) + separator, ok := db_list[db_name].(map[string]interface{})["separator"] + if !ok { + panic(fmt.Errorf("'separator' is not a valid field in database_config.json file!")) + } + return separator.(string) +} + +func GetDbId(db_name string, ns string) int { + if !sonic_db_init { + DbInit() + } + db_list := GetDbList(ns) + id, ok := db_list[db_name].(map[string]interface{})["id"] + if !ok { + panic(fmt.Errorf("'id' is not a valid field in database_config.json file!")) + } + return int(id.(float64)) +} + +func GetDbSock(db_name string, ns string) string { + if !sonic_db_init { + DbInit() + } + inst := GetDbInst(db_name, ns) + unix_socket_path, ok := inst["unix_socket_path"] + if !ok { + panic(fmt.Errorf("'unix_socket_path' is not a valid field in database_config.json file!")) + } + return unix_socket_path.(string) +} + +func GetDbHostName(db_name string, ns string) string { + if !sonic_db_init { + DbInit() + } + inst := GetDbInst(db_name, ns) + hostname, ok := inst["hostname"] + if !ok { + panic(fmt.Errorf("'hostname' is not a valid field in database_config.json file!")) + } + return hostname.(string) +} + +func GetDbPort(db_name string, ns string) int { + if !sonic_db_init { + DbInit() + } + inst := GetDbInst(db_name, ns) + port, ok := inst["port"] + if !ok { + panic(fmt.Errorf("'port' is not a valid field in database_config.json file!")) + } + return int(port.(float64)) +} + +func GetDbTcpAddr(db_name string, ns string) string { + if !sonic_db_init { + DbInit() + } + hostname := GetDbHostName(db_name, ns) + port := GetDbPort(db_name, ns) + return hostname + ":" + strconv.Itoa(port) +} + +func DbGetNamespaceAndConfigFile(ns_to_cfgfile_map map[string]string) { + data, err := io.ReadFile(SONIC_DB_GLOBAL_CONFIG_FILE) + if err == nil { + //Ref:https://stackoverflow.com/questions/18537257/how-to-get-the-directory-of-the-currently-running-file + dir, err := filepath.Abs(filepath.Dir(SONIC_DB_GLOBAL_CONFIG_FILE)) + if err != nil { + panic(err) + } + sonic_db_global_config := make(map[string]interface{}) + err = json.Unmarshal([]byte(data), &sonic_db_global_config) + if err != nil { + panic(err) + } + for _, entry := range sonic_db_global_config["INCLUDES"].([]interface{}) { + ns, ok := entry.(map[string]interface{})["namespace"] + if !ok { + ns = SONIC_DEFAULT_NAMESPACE + } + _, ok = ns_to_cfgfile_map[ns.(string)] + if ok { + panic(fmt.Errorf("Global Database config file is not valid(multiple include for same namespace!")) + } + //Ref:https://www.geeksforgeeks.org/filepath-join-function-in-golang-with-examples/ + db_include_file := filepath.Join(dir, entry.(map[string]interface{})["include"].(string)) + ns_to_cfgfile_map[ns.(string)] = db_include_file + } + if len(ns_to_cfgfile_map) > 1 { + sonic_db_multi_namespace = true + } else { + sonic_db_multi_namespace = false + } + + } else if errors.Is(err, os.ErrNotExist) { + // Ref: https://stackoverflow.com/questions/23452157/how-do-i-check-for-specific-types-of-error-among-those-returned-by-ioutil-readfi + ns_to_cfgfile_map[SONIC_DEFAULT_NAMESPACE] = SONIC_DB_CONFIG_FILE + sonic_db_multi_namespace = false + } else { + panic(err) + } } func DbInit() { - if sonic_db_init { - return - } - data, err := io.ReadFile(SONIC_DB_CONFIG_FILE) - if err != nil { - panic(err) - } else { - err = json.Unmarshal([]byte(data), &sonic_db_config) - if err != nil { - panic(err) - } - sonic_db_init = true - } -} - -func init() { - sonic_db_init = false + if sonic_db_init { + return + } + ns_to_cfgfile_map := make(map[string]string) + // Ref: https://stackoverflow.com/questions/14928826/passing-pointers-to-maps-in-golang + DbGetNamespaceAndConfigFile(ns_to_cfgfile_map) + for ns, db_cfg_file := range ns_to_cfgfile_map { + data, err := io.ReadFile(db_cfg_file) + if err != nil { + panic(err) + } + db_config := make(map[string]interface{}) + err = json.Unmarshal([]byte(data), &db_config) + if err != nil { + panic(err) + } + sonic_db_config[ns] = db_config + } + sonic_db_init = true +} + +func Init() { + sonic_db_init = false } diff --git a/sonic_db_config/db_config_test.go b/sonic_db_config/db_config_test.go new file mode 100644 index 000000000..ae2e31af5 --- /dev/null +++ b/sonic_db_config/db_config_test.go @@ -0,0 +1,78 @@ +package dbconfig + +import ( + "github.com/Azure/sonic-telemetry/test_utils" + "testing" +) + +func TestGetDb(t *testing.T) { + t.Run("Id", func(t *testing.T) { + db_id := GetDbId("CONFIG_DB", GetDbDefaultNamespace()) + if db_id != 4 { + t.Fatalf(`Id("") = %d, want 4, error`, db_id) + } + }) + t.Run("Sock", func(t *testing.T) { + sock_path := GetDbSock("CONFIG_DB", GetDbDefaultNamespace()) + if sock_path != "/var/run/redis/redis.sock" { + t.Fatalf(`Sock("") = %q, want "/var/run/redis/redis.sock", error`, sock_path) + } + }) + t.Run("AllNamespaces", func(t *testing.T) { + ns_list := GetDbAllNamespaces() + if len(ns_list) != 1 { + t.Fatalf(`AllNamespaces("") = %q, want "1", error`, len(ns_list)) + } + if ns_list[0] != GetDbDefaultNamespace() { + t.Fatalf(`AllNamespaces("") = %q, want default, error`, ns_list[0]) + } + }) + t.Run("TcpAddr", func(t *testing.T) { + tcp_addr := GetDbTcpAddr("CONFIG_DB", GetDbDefaultNamespace()) + if tcp_addr != "127.0.0.1:6379" { + t.Fatalf(`TcpAddr("") = %q, want 127.0.0.1:6379, error`, tcp_addr) + } + }) +} +func TestGetDbMultiNs(t *testing.T) { + Init() + err := test_utils.SetupMultiNamespace() + if err != nil { + t.Fatalf("error Setting up MultiNamespace files with err %T", err) + } + + /* https://www.gopherguides.com/articles/test-cleanup-in-go-1-14*/ + t.Cleanup(func() { + if err := test_utils.CleanUpMultiNamespace(); err != nil { + t.Fatalf("error Cleaning up MultiNamespace files with err %T", err) + + } + }) + t.Run("Id", func(t *testing.T) { + db_id := GetDbId("CONFIG_DB", "asic0") + if db_id != 4 { + t.Fatalf(`Id("") = %d, want 4, error`, db_id) + } + }) + t.Run("Sock", func(t *testing.T) { + sock_path := GetDbSock("CONFIG_DB", "asic0") + if sock_path != "/var/run/redis0/redis.sock" { + t.Fatalf(`Sock("") = %q, want "/var/run/redis0/redis.sock", error`, sock_path) + } + }) + t.Run("AllNamespaces", func(t *testing.T) { + ns_list := GetDbAllNamespaces() + if len(ns_list) != 2 { + t.Fatalf(`AllNamespaces("") = %q, want "2", error`, len(ns_list)) + } + if !((ns_list[0] == GetDbDefaultNamespace() && ns_list[1] == "asic0") || (ns_list[0] == "asic0" && ns_list[1] == GetDbDefaultNamespace())) { + t.Fatalf(`AllNamespaces("") = %q %q, want default and asic0, error`, ns_list[0], ns_list[1]) + } + }) + t.Run("TcpAddr", func(t *testing.T) { + tcp_addr := GetDbTcpAddr("CONFIG_DB", "asic0") + if tcp_addr != "127.0.0.1:6379" { + t.Fatalf(`TcpAddr("") = %q, want 127.0.0.1:6379, error`, tcp_addr) + } + }) +} diff --git a/test_utils/test_utils.go b/test_utils/test_utils.go new file mode 100644 index 000000000..1a83ad9c9 --- /dev/null +++ b/test_utils/test_utils.go @@ -0,0 +1,57 @@ +package test_utils + +import ( + "io" + "os" +) + +func SetupMultiNamespace() error { + err := os.MkdirAll("/var/run/redis0/sonic-db/", 0755) + if err != nil { + return err + } + srcFileName := [2]string{"../testdata/database_global.json", "../testdata/database_config_asic0.json"} + dstFileName := [2]string{"/var/run/redis/sonic-db/database_global.json", "/var/run/redis0/sonic-db/database_config_asic0.json"} + for i := 0; i < len(srcFileName); i++ { + sourceFileStat, err := os.Stat(srcFileName[i]) + if err != nil { + return err + } + + if !sourceFileStat.Mode().IsRegular() { + return err + } + + source, err := os.Open(srcFileName[i]) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dstFileName[i]) + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + if err != nil { + return err + } + } + return nil +} + +func CleanUpMultiNamespace() error { + err := os.Remove("/var/run/redis/sonic-db/database_global.json") + if err != nil { + return err + } + err = os.RemoveAll("/var/run/redis0") + if err != nil { + return err + } + return nil +} +func GetMultiNsNamespace() string { + return "asic0" +} diff --git a/testdata/database_config_asic0.json b/testdata/database_config_asic0.json new file mode 100644 index 000000000..3d9823e33 --- /dev/null +++ b/testdata/database_config_asic0.json @@ -0,0 +1,57 @@ +{ + "INSTANCES": { + "redis":{ + "hostname" : "127.0.0.1", + "port" : 6379, + "unix_socket_path" : "/var/run/redis0/redis.sock" + } + }, + "DATABASES" : { + "APPL_DB" : { + "id" : 0, + "separator": ":", + "instance" : "redis" + }, + "ASIC_DB" : { + "id" : 1, + "separator": ":", + "instance" : "redis" + }, + "COUNTERS_DB" : { + "id" : 2, + "separator": ":", + "instance" : "redis" + }, + "LOGLEVEL_DB" : { + "id" : 3, + "separator": ":", + "instance" : "redis" + }, + "CONFIG_DB" : { + "id" : 4, + "separator": "|", + "instance" : "redis" + }, + "PFC_WD_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "FLEX_COUNTER_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "STATE_DB" : { + "id" : 6, + "separator": "|", + "instance" : "redis" + }, + "SNMP_OVERLAY_DB" : { + "id" : 7, + "separator": "|", + "instance" : "redis" + } + }, + "VERSION" : "1.0" +} diff --git a/testdata/database_global.json b/testdata/database_global.json new file mode 100644 index 000000000..5cb179782 --- /dev/null +++ b/testdata/database_global.json @@ -0,0 +1,12 @@ +{ + "INCLUDES" : [ + { + "include" : "../../redis/sonic-db/database_config.json" + }, + { + "namespace" : "asic0", + "include" : "../../redis0/sonic-db/database_config_asic0.json" + } + ], + "VERSION" : "1.0" +}