From 6df988cce714ab7be3a914069ad24d30d153da13 Mon Sep 17 00:00:00 2001 From: abdosi <58047199+abdosi@users.noreply.github.com> Date: Mon, 14 Jun 2021 10:35:31 -0700 Subject: [PATCH] [multi-asic] Added Support for multi-asic for telemetry/gnmi server (#77) [multi-asic] support in telemetry. Summary of Changes:- Enhanced sonic_db_config package for multi-asic to read different namespace redis configuration. Added unit-test for same. Enhanced V2R Lookup for multi-asic by understanding namespace port belongs to Enhanced gNMI Server to initiate redis connection with all namespaces Enhanced gNMI Server Get and Subscribe tests for multi-namespace. Added test_utils package providing utility API's needed by test-cases. Also added multi-namespace specific json files into testdata Enhance parsing of Target in gNMI Request to understand namespace if present. Format the modified files using go fmt. Please ignore whitespaces diff as part of review. Added new test case to verify gNMI Get on non-counter DB (eg: STATE_DB) and fixed issue in existing GET of not verifying the return value. Fixed the gNMI Subscribe test issue where IntervalTimeTicker function pointer not re-assigned after test case is done. --- Makefile | 6 +- dialout/dialout_client/dialout_client.go | 12 +- dialout/dialout_client/dialout_client_test.go | 8 +- gnmi_server/client_subscribe.go | 2 +- gnmi_server/server.go | 325 ++++---- gnmi_server/server_test.go | 695 ++++++++++-------- sonic_data_client/db_client.go | 181 +++-- sonic_data_client/virtual_db.go | 297 +++++--- sonic_db_config/db_config.go | 343 ++++++--- sonic_db_config/db_config_test.go | 78 ++ test_utils/test_utils.go | 57 ++ testdata/database_config_asic0.json | 57 ++ testdata/database_global.json | 12 + 13 files changed, 1291 insertions(+), 782 deletions(-) create mode 100644 sonic_db_config/db_config_test.go create mode 100644 test_utils/test_utils.go create mode 100644 testdata/database_config_asic0.json create mode 100644 testdata/database_global.json 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" +}