From 5675b4492e8f89b0a135b001927b0de158b2c279 Mon Sep 17 00:00:00 2001 From: ganglyu Date: Thu, 16 Jun 2022 14:56:05 +0800 Subject: [PATCH] Update coverage --- common_utils/context.go | 4 +- dialout/dialout_client/dialout_client.go | 727 ------------------ .../dialout_client_cli/dialout_client_cli.go | 47 -- dialout/dialout_server/dialout_server.go | 233 ------ .../dialout_server_cli/dialout_server_cli.go | 93 --- gnmi_server/server.go | 38 +- gnmi_server/server_test.go | 2 + patches/ygot/ygot.patch | 610 --------------- sonic_data_client/db_client.go | 147 +--- test/test_gnmi_appldb.py | 40 + test/test_gnmi_configdb.py | 41 + 11 files changed, 117 insertions(+), 1865 deletions(-) delete mode 100644 dialout/dialout_client/dialout_client.go delete mode 100644 dialout/dialout_client_cli/dialout_client_cli.go delete mode 100644 dialout/dialout_server/dialout_server.go delete mode 100644 dialout/dialout_server_cli/dialout_server_cli.go diff --git a/common_utils/context.go b/common_utils/context.go index 054972e1..3940a2ef 100644 --- a/common_utils/context.go +++ b/common_utils/context.go @@ -37,8 +37,8 @@ const requestContextKey contextkey = 0 var requestCounter uint64 var CountersName = [...]string{ - "GMNI get", - "GMNI get fail", + "GNMI get", + "GNMI get fail", "GNMI set", "GNMI set fail", "GNOI reboot", diff --git a/dialout/dialout_client/dialout_client.go b/dialout/dialout_client/dialout_client.go deleted file mode 100644 index e665a588..00000000 --- a/dialout/dialout_client/dialout_client.go +++ /dev/null @@ -1,727 +0,0 @@ -package telemetry_dialout - -import ( - // "encoding/json" - "crypto/tls" - "errors" - "fmt" - spb "github.com/sonic-net/sonic-gnmi/proto" - sdc "github.com/sonic-net/sonic-gnmi/sonic_data_client" - sdcfg "github.com/sonic-net/sonic-gnmi/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" - "golang.org/x/net/context" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "net" - //"reflect" - "strconv" - "strings" - "sync" - "time" -) - -const ( - // Unknown is an unknown report and should always be treated as an error. - Unknown reportType = iota - // Once will perform a Once report against the agent. - Once - // Poll will perform a Periodic report against the agent. - Periodic - // Stream will perform a Streaming report against the agent. - Stream -) - -// Type defines the type of report. -type reportType int - -// NewType returns a new reportType based on the provided string. -func NewReportType(s string) reportType { - v, ok := typeConst[s] - if !ok { - return Unknown - } - return v -} - -// String returns the string representation of the reportType. -func (r reportType) String() string { - return typeString[r] -} - -var ( - typeString = map[reportType]string{ - Unknown: "unknown", - Once: "once", - Periodic: "periodic", - Stream: "stream", - } - - typeConst = map[string]reportType{ - "unknown": Unknown, - "once": Once, - "periodic": Periodic, - "stream": Stream, - } - clientCfg *ClientConfig - // Global mutex for protecting the config data - configMu sync.Mutex - - // Each Destination group may have more than one Destinations - // Only one destination will be used at one time - destGrpNameMap = make(map[string][]Destination) - - // For finding clientSubscription quickly - ClientSubscriptionNameMap = make(map[string]*clientSubscription) - - // map for storing name of clientSubscription which are users of the destination group - DestGrp2ClientSubMap = make(map[string][]string) -) - -type Destination struct { - Addrs string -} - -func (d Destination) Validate() error { - if len(d.Addrs) == 0 { - return errors.New("Destination.Addrs is empty") - } - // TODO: validate Addrs is in format IP:PORT - return nil -} - -// Global config for all clients -type ClientConfig struct { - SrcIp string - RetryInterval time.Duration - Encoding gpb.Encoding - Unidirectional bool // by default, no reponse from remote server - TLS *tls.Config // TLS config to use when connecting to target. Optional. - RedisConType string // "unix" or "tcp" -} - -// clientSubscription is the container for config data, -// it also keeps mapping from destination to running publish Client instance -type clientSubscription struct { - // Config Data - name string - destGroupName string - prefix *gpb.Path - paths []*gpb.Path - reportType reportType - interval time.Duration // report interval - - // Running time data - cMu sync.Mutex - client *Client // GNMIDialOutClient - dc sdc.Client // SONiC data client - stop chan struct{} // Inform publishRun routine to stop - q *queue.PriorityQueue // for data passing among go routine - w sync.WaitGroup // Wait for all sub go routine to finish - opened bool // whether there is opened instance for this client subscription - cancel context.CancelFunc - - conTryCnt uint64 //Number of time trying to connect - sendMsg uint64 - recvMsg uint64 - errors uint64 -} - -// Client handles execution of the telemetry publish service. -type Client struct { - conn *grpc.ClientConn - - mu sync.Mutex - client spb.GNMIDialOutClient - publish spb.GNMIDialOut_PublishClient - - // dataChan chan struct{} //to pass data struct pointer - // - // synced sync.WaitGroup - sendMsg uint64 - recvMsg uint64 -} - -func (cs *clientSubscription) Close() { - cs.cMu.Lock() - defer cs.cMu.Unlock() - if cs.opened == false { - log.V(5).Infof("Opened is false: %v", cs) - return - } - if cs.stop != nil { - close(cs.stop) //Inform the clientSubscription publish service routine to stop - } - - if cs.q != nil { - if !cs.q.Disposed() { - cs.q.Dispose() - } - } - if cs.client != nil { - - cs.client.Close() // Close GNMIDialOutClient - } - cs.opened = false - log.V(2).Infof("Closed %v", cs) -} - -func (cs *clientSubscription) NewInstance(ctx context.Context) error { - cs.cMu.Lock() - defer cs.cMu.Unlock() - - if cs.destGroupName == "" { - log.V(2).Infof("Destination group is not set for %v", cs) - return fmt.Errorf("Destination group is not set for %v", cs) - } - - dests, ok := destGrpNameMap[cs.destGroupName] - if !ok { - log.V(2).Infof("Destination group %v doesn't exist", cs.destGroupName) - return fmt.Errorf("Destination group %v doesn't exist", cs.destGroupName) - } - - target := cs.prefix.GetTarget() - if target == "" { - return fmt.Errorf("Empty target data not supported yet") - } - - // Connection to system data source - var dc sdc.Client - var err error - if target == "OTHERS" { - dc, err = sdc.NewNonDbClient(cs.paths, cs.prefix) - } else { - dc, err = sdc.NewDbClient(cs.paths, cs.prefix) - } - if err != nil { - log.V(1).Infof("Connection to DB for %v failed: %v", *cs, err) - return fmt.Errorf("Connection to DB for %v failed: %v", *cs, err) - } - cs.dc = dc - go publishRun(ctx, cs, dests) - log.V(2).Infof("publishRun for %v with destination %v", cs, dests) - return nil -} - -// send runs until process Queue returns an error. -func (cs *clientSubscription) send(stream spb.GNMIDialOut_PublishClient) error { - for { - items, err := cs.q.Get(1) - - if items == nil { - log.V(1).Infof("%v", err) - return err - } - if err != nil { - cs.errors++ - log.V(1).Infof("%v", err) - return fmt.Errorf("unexpected queue Gext(1): %v", err) - } - - var resp *gpb.SubscribeResponse - switch v := items[0].(type) { - case sdc.Value: - if resp, err = sdc.ValToResp(v); err != nil { - cs.errors++ - return err - } - default: - log.V(1).Infof("Unknown data type %v for %s in queue", items[0], cs) - cs.errors++ - } - - cs.sendMsg++ - err = stream.Send(resp) - if err != nil { - log.V(1).Infof("Client %s sending error:%v", cs, err) - cs.errors++ - return err - } - log.V(5).Infof("Client %s done sending, msg count %d, msg %v", cs, cs.sendMsg, resp) - } -} - -// String returns the target the client is querying. -func (cs *clientSubscription) String() string { - return fmt.Sprintf(" %s:%s:%s prefix %v paths %v interval %v, sendMsg %v, recvMsg %v", - cs.name, cs.destGroupName, cs.reportType, cs.prefix.GetTarget(), cs.paths, cs.interval, cs.sendMsg, cs.recvMsg) -} - -// newClient returns a new initialized GNMIDialout client. -// it connects to destination and publish service -// TODO: TLS credential support -func newClient(ctx context.Context, dest Destination) (*Client, error) { - timeout := clientCfg.RetryInterval - - var cancel func() - ctx, cancel = context.WithTimeout(ctx, timeout) - defer cancel() - - opts := []grpc.DialOption{ - grpc.WithBlock(), - } - if clientCfg.TLS != nil { - opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(clientCfg.TLS))) - } - conn, err := grpc.DialContext(ctx, dest.Addrs, opts...) - if err != nil { - return nil, fmt.Errorf("Dial to (%s, timeout %v): %v", dest, timeout, err) - } - cl := spb.NewGNMIDialOutClient(conn) - return &Client{ - conn: conn, - client: cl, - }, nil -} - -// Closing of client queue is triggered upon end of stream receive or stream error -// or fatal error of any client go routine . -// it will cause cancle of client context and exit of the send goroutines. -func (c *Client) Close() error { - return c.conn.Close() -} - -func publishRun(ctx context.Context, cs *clientSubscription, dests []Destination) { - var err error - var c *Client - var destNum, destIdx int - destNum = len(dests) - destIdx = 0 - -restart: //Remote server might go down, in that case we restart with next destination in the group - cs.cMu.Lock() - cs.stop = make(chan struct{}, 1) - cs.q = queue.NewPriorityQueue(1, false) - cs.opened = true - cs.client = nil - cs.cMu.Unlock() - - cs.conTryCnt++ - dest := dests[destIdx] - destIdx = (destIdx + 1) % destNum - c, err = newClient(ctx, dest) - select { - case <-ctx.Done(): - cs.Close() - log.V(1).Infof("%v: %v, cs.conTryCnt %v", cs, err, cs.conTryCnt) - return - default: - } - if err != nil { - log.V(1).Infof("Dialout connection for %v failed for %v, %v cs.conTryCnt %v", dest, cs.name, err, cs.conTryCnt) - goto restart - } - - log.V(1).Infof("Dialout service connected to %v successfully for %v", dest, cs.name) - pub, err := c.client.Publish(ctx) - if err != nil { - log.V(1).Infof("Publish to %v for %v failed: %v, retrying", dest, cs.name, err) - c.Close() - cs.Close() - goto restart - } - - cs.cMu.Lock() - if cs.client == nil { - cs.client = c - } else { - log.V(1).Infof("connection to %v already exists for %v, exiting publishRun", dest, cs) - c.Close() - cs.cMu.Unlock() - return - } - cs.cMu.Unlock() - - switch cs.reportType { - case Periodic: - for { - select { - default: - spbValues, err := cs.dc.Get(nil) - if err != nil { - // TODO: need to inform - log.V(2).Infof("Data read error %v for %v", err, cs) - continue - //return nil, status.Error(codes.NotFound, err.Error()) - } - var updates []*gpb.Update - var spbValue *spb.Value - for _, spbValue = range spbValues { - update := &gpb.Update{ - Path: spbValue.GetPath(), - Val: spbValue.GetVal(), - } - updates = append(updates, update) - } - rs := &gpb.SubscribeResponse_Update{ - Update: &gpb.Notification{ - Timestamp: spbValue.GetTimestamp(), - Prefix: cs.prefix, - Update: updates, - }, - } - response := &gpb.SubscribeResponse{Response: rs} - - log.V(6).Infof("cs %s sending \n\t%v \n To %s", cs.name, response, dest) - err = pub.Send(response) - if err != nil { - log.V(1).Infof("Client %v pub Send error:%v, cs.conTryCnt %v", cs.name, err, cs.conTryCnt) - cs.Close() - // Retry - goto restart - } - log.V(6).Infof("cs %s to %s done", cs.name, dest) - cs.sendMsg++ - c.sendMsg++ - - time.Sleep(cs.interval) - case <-cs.stop: - log.V(1).Infof("%v exiting publishRun routine for destination %s", cs, dest) - return - } - } - case Stream: - select { - default: - cs.w.Add(1) - go cs.dc.StreamRun(cs.q, cs.stop, &cs.w, nil) - time.Sleep(100 * time.Millisecond) - err = cs.send(pub) - if err != nil { - log.V(1).Infof("Client %v pub Send error:%v, cs.conTryCnt %v", cs.name, err, cs.conTryCnt) - } - cs.Close() - cs.w.Wait() - // Don't restart immediatly - time.Sleep(clientCfg.RetryInterval) - goto restart - - case <-cs.stop: - log.V(1).Infof("%v exiting publishRun routine for destination %s", cs, dest) - return - } - default: - log.V(1).Infof("Unsupported report type %s in %v ", cs.reportType, cs) - } -} - -/* - // telemetry client global configuration - Key = TELEMETRY_CLIENT|Global - src_ip = IP - retry_interval = 1*4DIGIT ; In second - encoding = "JSON_IETF" / "ASCII" / "BYTES" / "PROTO" - unidirectional = "true" / "false" ; true by default - - // Destination group - Key = TELEMETRY_CLIENT|DestinationGroup_ - dst_addr = IP1:PORT2,IP2:PORT2 ;IP addresses separated by "," - - PORT = 1*5DIGIT - IP = dec-octet "." dec-octet "." dec-octet "." dec-octet - - // Subscription group - Key = TELEMETRY_CLIENT|Subscription_ - path_target = DbName - paths = PATH1,PATH2 ;PATH separated by "," - dst_group = ; // name of DestinationGroup - report_type = "periodic" / "stream" / "once" - report_interval = 1*8DIGIT ; In millisecond, -*/ - -// closeDestGroupClient close client instances for all clientSubscription using -// this Destination Group -func closeDestGroupClient(destGroupName string) { - if names, ok := DestGrp2ClientSubMap[destGroupName]; ok { - for _, name := range names { - cs := ClientSubscriptionNameMap[name] - cs.Close() - cs.cancel() - } - } -} - -// setupDestGroupClients create client instances for all clientSubscription using -// this Destination Group -func setupDestGroupClients(ctx context.Context, destGroupName string) { - if names, ok := DestGrp2ClientSubMap[destGroupName]; ok { - for _, name := range names { - // Create a copy of Client subscription, existing one might be closing, don't interfere with it. - cs := *ClientSubscriptionNameMap[name] - log.V(2).Infof("NewInstance with destGroup change for %s to %s", name, destGroupName) - cs.NewInstance(ctx) - ClientSubscriptionNameMap[name] = &cs - } - } -} - -// 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", sdcfg.GetDbDefaultNamespace()) - tableKey := "TELEMETRY_CLIENT" + separator + key - fv, err := redisDb.HGetAll(tableKey).Result() - if err != nil { - log.V(2).Infof("redis HGetAll failed for %s with error %v", tableKey, err) - return fmt.Errorf("redis HGetAll failed for %s with error %v", tableKey, err) - } - - log.V(2).Infof("Processing %v %v", tableKey, fv) - configMu.Lock() - defer configMu.Unlock() - - ctx, cancel := context.WithCancel(ctx) - - if key == "Global" { - if op == "hdel" { - log.V(2).Infof("Invalid delete operation for %v", tableKey) - return fmt.Errorf("Invalid delete operation for %v", tableKey) - } else { - for field, value := range fv { - switch field { - case "src_ip": - clientCfg.SrcIp = value - case "retry_interval": - //TODO: check validity of the interval - itvl, err := strconv.ParseUint(value, 10, 64) - if err != nil { - log.V(2).Infof("Invalid retry_interval %v %v", value, err) - continue - } - clientCfg.RetryInterval = time.Second * time.Duration(itvl) - case "encoding": - //Flexible encoding Not supported yet - clientCfg.Encoding = gpb.Encoding_JSON_IETF - case "unidirectional": - // No PublishResponse supported yet - clientCfg.Unidirectional = true - } - } - // Apply changes to all running instances - for grpName := range destGrpNameMap { - closeDestGroupClient(grpName) - setupDestGroupClients(ctx, grpName) - } - } - } else if strings.HasPrefix(key, "DestinationGroup_") { - destGroupName := strings.TrimPrefix(key, "DestinationGroup_") - if destGroupName == "" { - return fmt.Errorf("Empty Destination Group name %v", key) - } - // Close any client intances targeting this Destination group - closeDestGroupClient(destGroupName) - //DestGrp2ClientSubMap - if op == "hdel" { - if _, ok := DestGrp2ClientSubMap[destGroupName]; ok { - log.V(1).Infof("%v is being used: %v", destGroupName, DestGrp2ClientSubMap) - return fmt.Errorf("%v is being used: %v", destGroupName, DestGrp2ClientSubMap) - } - delete(destGrpNameMap, destGroupName) - log.V(3).Infof("Deleted DestinationGroup %v", destGroupName) - return nil - } else { - var dests []Destination - for field, value := range fv { - switch field { - case "dst_addr": - addrs := strings.Split(value, ",") - for _, addr := range addrs { - dst := Destination{Addrs: addr} - if err = dst.Validate(); err != nil { - log.V(2).Infof("Invalid destination address %v", addrs) - return fmt.Errorf("Invalid destination address %v", addrs) - } - dests = append(dests, Destination{Addrs: addr}) - } - default: - log.V(2).Infof("Invalid DestinationGroup value %v", value) - return fmt.Errorf("Invalid DestinationGroup value %v", value) - } - } - destGrpNameMap[destGroupName] = dests - setupDestGroupClients(ctx, destGroupName) - } - } else if strings.HasPrefix(key, "Subscription_") { - name := strings.TrimPrefix(key, "Subscription_") - if name == "" { - return fmt.Errorf("Empty Subscription_ name %v", key) - } - csub, ok := ClientSubscriptionNameMap[name] - if ok { - csub.Close() - csub.cancel() - } - - if op == "hdel" { - destGrpName := csub.destGroupName - // Remove this ClientSubscrition from the list of the Destination group users - csNames := DestGrp2ClientSubMap[destGrpName] - for i, csName := range csNames { - if name == csName { - csNames = append(csNames[:i], csNames[i+1:]...) - break - } - } - DestGrp2ClientSubMap[destGrpName] = csNames - // Delete clientSubscription from name map - delete(ClientSubscriptionNameMap, name) - log.V(3).Infof("Deleted Client Subscription %v", name) - return nil - } else { - // TODO: start one subscription publish routine for this request - // Only start routine when DestGrp2ClientSubMap is not empty, or ...? - cs := clientSubscription{ - interval: 5000, // default to 5000 milliseconds - name: name, - cancel: cancel, - } - for field, value := range fv { - switch field { - case "dst_group": - cs.destGroupName = value - case "report_type": - cs.reportType = NewReportType(value) - case "report_interval": - intvl, err := strconv.ParseUint(value, 10, 64) - if err != nil { - log.V(2).Infof("Invalid report_interval %v %v", value, err) - continue - } - cs.interval = time.Duration(intvl) * time.Millisecond - case "path_target": - cs.prefix = &gpb.Path{ - Target: value, - } - case "paths": - ps := strings.Split(value, ",") - newPaths := []*gpb.Path{} - for _, p := range ps { - pp, err := ygot.StringToPath(p, ygot.StructuredPath) - if err != nil { - log.V(2).Infof("Invalid paths %v", value) - return fmt.Errorf("Invalid paths %v", value) - } - // append *gpb.Path - newPaths = append(newPaths, pp) - } - cs.paths = newPaths - default: - log.V(2).Infof("Invalid field %v value %v", field, value) - return fmt.Errorf("Invalid field %v value %v", field, value) - } - } - log.V(2).Infof("New clientSubscription %v", cs) - if cs.destGroupName == "" { - // not destination configured, just return - return nil - } - - var found bool - for _, na := range DestGrp2ClientSubMap[cs.destGroupName] { - if na == cs.name { - found = true - break - } - } - if !found { - // Add this clientSubscription to the user list of Destination group - DestGrp2ClientSubMap[cs.destGroupName] = append(DestGrp2ClientSubMap[cs.destGroupName], cs.name) - } - ClientSubscriptionNameMap[cs.name] = &cs - log.V(2).Infof("NewInstance with Subscription change for %s to %s", cs.name, cs.destGroupName) - cs.NewInstance(ctx) - } - } - return nil -} - -// 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", sdcfg.GetDbDefaultNamespace()) - - var redisDb *redis.Client - if sdc.UseRedisLocalTcpPort == false { - redisDb = redis.NewClient(&redis.Options{ - Network: "unix", - Addr: sdcfg.GetDbSock("CONFIG_DB", sdcfg.GetDbDefaultNamespace()), - Password: "", // no password set - DB: dbn, - DialTimeout: 0, - }) - } else { - redisDb = redis.NewClient(&redis.Options{ - Network: "tcp", - Addr: sdcfg.GetDbTcpAddr("CONFIG_DB", sdcfg.GetDbDefaultNamespace()), - Password: "", // no password set - DB: dbn, - DialTimeout: 0, - }) - } - - separator, _ := sdc.GetTableKeySeparator("CONFIG_DB", sdcfg.GetDbDefaultNamespace()) - pattern := "__keyspace@" + strconv.Itoa(int(dbn)) + "__:TELEMETRY_CLIENT" + separator - prefixLen := len(pattern) - pattern += "*" - - pubsub := redisDb.PSubscribe(pattern) - defer pubsub.Close() - - msgi, err := pubsub.ReceiveTimeout(time.Second) - if err != nil { - log.V(1).Infof("psubscribe to %s failed %v", pattern, err) - return fmt.Errorf("psubscribe to %s failed %v", pattern, err) - } - subscr := msgi.(*redis.Subscription) - if subscr.Channel != pattern { - log.V(1).Infof("psubscribe to %s failed", pattern) - return fmt.Errorf("psubscribe to %s", pattern) - } - log.V(2).Infof("Psubscribe succeeded: %v", subscr) - - var dbkeys []string - dbkey_prefix := "TELEMETRY_CLIENT" + separator - dbkeys, err = redisDb.Keys(dbkey_prefix + "*").Result() - if err != nil { - log.V(2).Infof("redis Keys failed for %v with err %v", pattern, err) - return err - } - for _, dbkey := range dbkeys { - dbkey = dbkey[len(dbkey_prefix):] - processTelemetryClientConfig(ctx, redisDb, dbkey, "hset") - } - - for { - msgi, err := pubsub.ReceiveTimeout(time.Millisecond * 1000) - if err != nil { - neterr, ok := err.(net.Error) - if ok { - if neterr.Timeout() == true { - continue - } - } - log.V(2).Infof("pubsub.ReceiveTimeout err %v", err) - continue - } - subscr := msgi.(*redis.Message) - dbkey := subscr.Channel[prefixLen:] - if subscr.Payload == "del" || subscr.Payload == "hdel" { - processTelemetryClientConfig(ctx, redisDb, dbkey, "hdel") - } else if subscr.Payload == "hset" { - processTelemetryClientConfig(ctx, redisDb, dbkey, "hset") - } else { - log.V(2).Infof("Invalid psubscribe payload notification: %v", subscr) - continue - } - // Check if ctx was canceled. - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - } -} diff --git a/dialout/dialout_client_cli/dialout_client_cli.go b/dialout/dialout_client_cli/dialout_client_cli.go deleted file mode 100644 index de8b5033..00000000 --- a/dialout/dialout_client_cli/dialout_client_cli.go +++ /dev/null @@ -1,47 +0,0 @@ -// The dialout_client_cli program implements the telemetry publish client. -package main - -import ( - "crypto/tls" - "flag" - dc "github.com/sonic-net/sonic-gnmi/dialout/dialout_client" - log "github.com/golang/glog" - gpb "github.com/openconfig/gnmi/proto/gnmi" - "golang.org/x/net/context" - "os" - "os/signal" - "time" -) - -var ( - clientCfg = dc.ClientConfig{ - SrcIp: "", - RetryInterval: 30 * time.Second, - Encoding: gpb.Encoding_JSON_IETF, - Unidirectional: true, - TLS: &tls.Config{}, - } -) - -func init() { - flag.StringVar(&clientCfg.TLS.ServerName, "server_name", "", "When set, use this hostname to verify server certificate during TLS handshake.") - flag.BoolVar(&clientCfg.TLS.InsecureSkipVerify, "insecure", false, "When set, client will not verify the server certificate during TLS handshake.") - flag.DurationVar(&clientCfg.RetryInterval, "retry_interval", 30*time.Second, "Interval at which client tries to reconnect to destination servers") - flag.BoolVar(&clientCfg.Unidirectional, "unidirectional", true, "No repesponse from server is expected") -} - -func main() { - flag.Parse() - ctx, cancel := context.WithCancel(context.Background()) - // Terminate on Ctrl+C - go func() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c - cancel() - }() - log.V(1).Infof("Starting telemetry publish client") - err := dc.DialOutRun(ctx, &clientCfg) - log.V(1).Infof("Exiting telemetry publish client: %v", err) - log.Flush() -} diff --git a/dialout/dialout_server/dialout_server.go b/dialout/dialout_server/dialout_server.go deleted file mode 100644 index 4fbde056..00000000 --- a/dialout/dialout_server/dialout_server.go +++ /dev/null @@ -1,233 +0,0 @@ -package dialout_server - -import ( - "errors" - "fmt" - spb "github.com/sonic-net/sonic-gnmi/proto" - log "github.com/golang/glog" - "github.com/google/gnxi/utils" - gpb "github.com/openconfig/gnmi/proto/gnmi" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/reflection" - "io" - "net" - "strings" - "sync" -) - -var ( - supportedEncodings = []gpb.Encoding{gpb.Encoding_JSON, gpb.Encoding_JSON_IETF} -) - -// Server manages a single GNMIDialOut_PublishServer implementation. Each client that connects -// via PublistRequest sends subscribeResponse to the server. -type Server struct { - s *grpc.Server - lis net.Listener - config *Config - cMu sync.Mutex - clients map[string]*Client - sRWMu sync.RWMutex //for protection of appending data to data store - dataStore interface{} //For storing the data received -} - -// 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 -} - -// New returns an initialized Server. -func NewServer(config *Config, opts []grpc.ServerOption) (*Server, error) { - if config == nil { - return nil, errors.New("config not provided") - } - - s := grpc.NewServer(opts...) - reflection.Register(s) - - srv := &Server{ - s: s, - config: config, - clients: map[string]*Client{}, - } - var err error - if srv.config.Port < 0 { - srv.config.Port = 0 - } - srv.lis, err = net.Listen("tcp", fmt.Sprintf(":%d", srv.config.Port)) - if err != nil { - return nil, fmt.Errorf("failed to open listener port %d: %v", srv.config.Port, err) - } - spb.RegisterGNMIDialOutServer(srv.s, srv) - log.V(1).Infof("Created Server on %s", srv.Address()) - return srv, nil -} - -// Serve will start the Server serving and block until closed. -func (srv *Server) Serve() error { - s := srv.s - if s == nil { - return fmt.Errorf("Serve() failed: not initialized") - } - return srv.s.Serve(srv.lis) -} - -func (srv *Server) Stop() error { - s := srv.s - if s == nil { - return fmt.Errorf("Serve() failed: not initialized") - } - srv.s.Stop() - log.V(1).Infof("Server stopped on %s", srv.Address()) - return nil -} - -// Address returns the port the Server is listening to. -func (srv *Server) Address() string { - addr := srv.lis.Addr().String() - return strings.Replace(addr, "[::]", "localhost", 1) -} - -// Port returns the port the Server is listening to. -func (srv *Server) Port() int64 { - return srv.config.Port -} - -// Port returns the port the Server is listening to. -func (srv *Server) SetDataStore(dataStore interface{}) { - srv.dataStore = dataStore -} - -// Publish implements the GNMI DialOut Publish RPC. -func (srv *Server) Publish(stream spb.GNMIDialOut_PublishServer) error { - ctx := stream.Context() - - pr, ok := peer.FromContext(ctx) - if !ok { - return grpc.Errorf(codes.InvalidArgument, "failed to get peer from ctx") - //return fmt.Errorf("failed to get peer from ctx") - } - if pr.Addr == net.Addr(nil) { - return grpc.Errorf(codes.InvalidArgument, "failed to get peer address") - } - - /* TODO: authorize the user - msg, ok := credentials.AuthorizeUser(ctx) - if !ok { - log.Infof("denied a Set request: %v", msg) - return nil, status.Error(codes.PermissionDenied, msg) - } - */ - - c := NewClient(pr.Addr) - - srv.cMu.Lock() - if oc, ok := srv.clients[c.String()]; ok { - log.V(2).Infof("Delete duplicate client %s", oc) - oc.Close() - delete(srv.clients, c.String()) - } - srv.clients[c.String()] = c - srv.cMu.Unlock() - - err := c.Run(srv, stream) - c.Close() - - srv.cMu.Lock() - delete(srv.clients, c.String()) - srv.cMu.Unlock() - - log.Flush() - return err -} - -// Client contains information about a subscribe client that has connected to the server. -type Client struct { - addr net.Addr - sendMsg int64 - recvMsg int64 - errors int64 - polled chan struct{} - stop chan struct{} - mu sync.RWMutex -} - -// NewClient returns a new initialized client. -func NewClient(addr net.Addr) *Client { - return &Client{ - addr: addr, - } -} - -// String returns the target the client is querying. -func (c *Client) String() string { - return c.addr.String() -} - -// Run process streaming from publish client. The first message received must be a -// SubscriptionList. Once the client is started, it will run until the stream -// is closed or the schedule completes. For Poll queries the Run will block -// internally after sync until a Poll request is made to the server. -func (c *Client) Run(srv *Server, stream spb.GNMIDialOut_PublishServer) (err error) { - defer log.V(1).Infof("Client %s shutdown", c) - - if stream == nil { - return grpc.Errorf(codes.FailedPrecondition, "cannot start client: stream is nil") - } - - defer func() { - if err != nil { - c.errors++ - } - }() - - for { - subscribeResponse, err := stream.Recv() - c.recvMsg++ - if err != nil { - if err == io.EOF { - return grpc.Errorf(codes.Aborted, "stream EOF received") - } - return grpc.Errorf(grpc.Code(err), "received error from client") - } - - srv.sRWMu.Lock() - if srv.dataStore != nil { - switch ds := srv.dataStore.(type) { - default: - log.V(1).Infof("unexpected type %T\n", srv.dataStore) - case *[]*gpb.SubscribeResponse: - *ds = append(*ds, subscribeResponse) - } - } - srv.sRWMu.Unlock() - - if srv.dataStore == nil { - fmt.Println("== subscribeResponse:") - utils.PrintProto(subscribeResponse) - } - - // TODO: send back (m *PublishResponse)) - } - return grpc.Errorf(codes.InvalidArgument, "Exiting") -} - -// Closing of client queue is triggered upon end of stream receive or stream error -// or fatal error of any client go routine . -// it will cause cancle of client context and exit of the send goroutines. -func (c *Client) Close() { - c.mu.Lock() - defer c.mu.Unlock() - log.V(1).Infof("Client %s Close, sendMsg %v recvMsg %v errors %v", c, c.sendMsg, c.recvMsg, c.errors) - - if c.stop != nil { - close(c.stop) - } - if c.polled != nil { - close(c.polled) - } -} diff --git a/dialout/dialout_server_cli/dialout_server_cli.go b/dialout/dialout_server_cli/dialout_server_cli.go deleted file mode 100644 index f3ad5ba6..00000000 --- a/dialout/dialout_server_cli/dialout_server_cli.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "crypto/tls" - "crypto/x509" - "flag" - "io/ioutil" - - log "github.com/golang/glog" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - - ds "github.com/sonic-net/sonic-gnmi/dialout/dialout_server" - testcert "github.com/sonic-net/sonic-gnmi/testdata/tls" -) - -var ( - port = flag.Int("port", -1, "port to listen on") - // Certificate files. - caCert = flag.String("ca_crt", "", "CA certificate for client certificate validation. Optional.") - serverCert = flag.String("server_crt", "", "TLS server certificate") - serverKey = flag.String("server_key", "", "TLS server private key") - insecure = flag.Bool("insecure", false, "Skip providing TLS cert and key, for testing only!") - allowNoClientCert = flag.Bool("allow_no_client_auth", false, "When set, telemetry server will request but not require a client certificate.") -) - -func main() { - flag.Parse() - - switch { - case *port <= 0: - log.Errorf("port must be > 0.") - return - } - var certificate tls.Certificate - var err error - - if *insecure { - certificate, err = testcert.NewCert() - if err != nil { - log.Exitf("could not load server key pair: %s", err) - } - } else { - switch { - case *serverCert == "": - log.Errorf("serverCert must be set.") - return - case *serverKey == "": - log.Errorf("serverKey must be set.") - return - } - certificate, err = tls.LoadX509KeyPair(*serverCert, *serverKey) - if err != nil { - log.Exitf("could not load server key pair: %s", err) - } - } - - tlsCfg := &tls.Config{ - ClientAuth: tls.RequireAndVerifyClientCert, - Certificates: []tls.Certificate{certificate}, - } - if *allowNoClientCert { - // RequestClientCert will ask client for a certificate but won't - // require it to proceed. If certificate is provided, it will be - // verified. - tlsCfg.ClientAuth = tls.RequestClientCert - } - - if *caCert != "" { - ca, err := ioutil.ReadFile(*caCert) - if err != nil { - log.Exitf("could not read CA certificate: %s", err) - } - certPool := x509.NewCertPool() - if ok := certPool.AppendCertsFromPEM(ca); !ok { - log.Exit("failed to append CA certificate") - } - tlsCfg.ClientCAs = certPool - } - - opts := []grpc.ServerOption{grpc.Creds(credentials.NewTLS(tlsCfg))} - cfg := &ds.Config{} - cfg.Port = int64(*port) - s, err := ds.NewServer(cfg, opts) - if err != nil { - log.Errorf("Failed to create gNMI server: %v", err) - return - } - - log.V(1).Infof("Starting RPC server on address: %s", s.Address()) - s.Serve() // blocks until close - log.Flush() -} diff --git a/gnmi_server/server.go b/gnmi_server/server.go index 503c3303..ce0ab6f7 100644 --- a/gnmi_server/server.go +++ b/gnmi_server/server.go @@ -305,15 +305,15 @@ func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetRe if err != nil { return nil, err } - common_utils.IncCounter("GMNI get") + common_utils.IncCounter("GNMI get") if req.GetType() != gnmipb.GetRequest_ALL { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, status.Errorf(codes.Unimplemented, "unsupported request type: %s", gnmipb.GetRequest_DataType_name[int32(req.GetType())]) } if err = s.checkEncodingAndModel(req.GetEncoding(), req.GetUseModels()); err != nil { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, status.Error(codes.Unimplemented, err.Error()) } @@ -332,23 +332,23 @@ func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetRe if target == "" { target, err = ParseTarget(target, paths) if err != nil { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, err } } if origin == "" { origin, err = ParseOrigin(origin, paths) if err != nil { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, err } } if check := IsSupportedOrigin(origin); !check { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, status.Errorf(codes.Unimplemented, "Invalid origin: %s", origin) } if origin == "sonic-yang" { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, status.Errorf(codes.Unimplemented, "SONiC Yang Schema is not implemented yet") } log.V(5).Infof("GetRequest paths: %v", paths) @@ -358,18 +358,19 @@ func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetRe if _, ok, _, _ := sdc.IsTargetDb(target); ok { dc, err = sdc.NewDbClient(paths, prefix, target, origin, s.config.TestMode) } else { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, status.Errorf(codes.Unimplemented, "Invalid target: %s", target) } if err != nil { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, status.Error(codes.NotFound, err.Error()) } notifications := make([]*gnmipb.Notification, len(paths)) spbValues, err := dc.Get(nil) + dc.Close() if err != nil { - common_utils.IncCounter("GMNI get fail") + common_utils.IncCounter("GNMI get fail") return nil, status.Error(codes.NotFound, err.Error()) } @@ -395,7 +396,7 @@ func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetRe } var results []*gnmipb.UpdateResult - common_utils.IncCounter("GMNI set") + common_utils.IncCounter("GNMI set") target := "" origin := "" prefix := req.GetPrefix() @@ -417,23 +418,23 @@ func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetRe if target == "" { target, err = ParseTarget(target, paths) if err != nil { - common_utils.IncCounter("GMNI set fail") + common_utils.IncCounter("GNMI set fail") return nil, err } } if origin == "" { origin, err = ParseOrigin(origin, paths) if err != nil { - common_utils.IncCounter("GMNI set fail") + common_utils.IncCounter("GNMI set fail") return nil, err } } if check := IsSupportedOrigin(origin); !check { - common_utils.IncCounter("GMNI set fail") + common_utils.IncCounter("GNMI set fail") return nil, status.Errorf(codes.Unimplemented, "Invalid origin: %s", origin) } if origin == "sonic-yang" { - common_utils.IncCounter("GMNI set fail") + common_utils.IncCounter("GNMI set fail") return nil, status.Errorf(codes.Unimplemented, "SONiC Yang Schema is not implemented yet") } @@ -442,12 +443,12 @@ func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetRe if _, ok, _, _ := sdc.IsTargetDb(target); ok { dc, err = sdc.NewDbClient(nil, prefix, target, origin, s.config.TestMode) } else { - common_utils.IncCounter("GMNI set fail") + common_utils.IncCounter("GNMI set fail") return nil, status.Errorf(codes.Unimplemented, "Invalid target: %s", target) } if err != nil { - common_utils.IncCounter("GMNI set fail") + common_utils.IncCounter("GNMI set fail") return nil, status.Error(codes.NotFound, err.Error()) } @@ -489,8 +490,9 @@ func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetRe results = append(results, &res) } err = dc.Set(req.GetDelete(), req.GetReplace(), req.GetUpdate()) + dc.Close() if err != nil { - common_utils.IncCounter("GMNI set fail") + common_utils.IncCounter("GNMI set fail") } return &gnmipb.SetResponse{ diff --git a/gnmi_server/server_test.go b/gnmi_server/server_test.go index b1898fbd..14823503 100644 --- a/gnmi_server/server_test.go +++ b/gnmi_server/server_test.go @@ -15,6 +15,8 @@ import ( "testing" _ "github.com/openconfig/gnmi/client" + _ "github.com/openconfig/ygot/ygot" + _ "github.com/google/gnxi/utils" _ "github.com/jipanyang/gnxi/utils/xpath" "google.golang.org/grpc" "google.golang.org/grpc/credentials" diff --git a/patches/ygot/ygot.patch b/patches/ygot/ygot.patch index b6d8072d..5fea472d 100644 --- a/patches/ygot/ygot.patch +++ b/patches/ygot/ygot.patch @@ -34,252 +34,6 @@ diff -ruN ygot-dir-orig/ygot/genutil/common.go ygot-dir/ygot/genutil/common.go if len(s) != 0 { b.WriteString(s) } -diff -ruN ygot-dir-orig/ygot/util/debug.go ygot-dir/ygot/util/debug.go ---- ygot-dir-orig/ygot/util/debug.go 2020-10-07 14:33:58.180210000 -0700 -+++ ygot-dir/ygot/util/debug.go 2020-10-27 16:02:47.497624000 -0700 -@@ -12,6 +12,9 @@ - // See the License for the specific language governing permissions and - // limitations under the License. - -+// This file is changed by Broadcom. -+// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. -+ - package util - - import ( -@@ -53,6 +56,14 @@ - fmt.Println(globalIndent + out) - } - -+func IsDebugLibraryEnabled () bool { -+ return debugLibrary -+} -+ -+func IsDebugSchemaEnabled () bool { -+ return debugSchema -+} -+ - // DbgSchema prints v if the package global variable debugSchema is set. - // v has the same format as Printf. - func DbgSchema(v ...interface{}) { -@@ -177,6 +188,9 @@ - - // YangTypeToDebugString returns a debug string representation of a YangType. - func YangTypeToDebugString(yt *yang.YangType) string { -+ if !debugLibrary { -+ return "" -+ } - out := fmt.Sprintf("(TypeKind: %s", yang.TypeKindToName[yt.Kind]) - if len(yt.Pattern) != 0 { - out += fmt.Sprintf(", Pattern: %s", strings.Join(yt.Pattern, " or ")) -diff -ruN ygot-dir-orig/ygot/util/path.go ygot-dir/ygot/util/path.go ---- ygot-dir-orig/ygot/util/path.go 2020-10-07 14:33:58.191131000 -0700 -+++ ygot-dir/ygot/util/path.go 2020-10-27 16:02:47.508799000 -0700 -@@ -12,6 +12,9 @@ - // See the License for the specific language governing permissions and - // limitations under the License. - -+// This file is changed by Broadcom. -+// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. -+ - package util - - import ( -@@ -19,10 +22,13 @@ - "fmt" - "reflect" - "strings" -- -+ "sync" - "github.com/openconfig/goyang/pkg/yang" - ) - -+var pathToSchemaCache map[reflect.StructTag][]string = make(map[reflect.StructTag][]string) -+var pathToSchemaMutex sync.RWMutex -+ - // SchemaPaths returns all the paths in the path tag. - func SchemaPaths(f reflect.StructField) ([][]string, error) { - var out [][]string -@@ -49,25 +55,39 @@ - // leafref; the schema *yang.Entry for the field is given by - // schema.Dir["config"].Dir["a"]. - func RelativeSchemaPath(f reflect.StructField) ([]string, error) { -- pathTag, ok := f.Tag.Lookup("path") -- if !ok || pathTag == "" { -- return nil, fmt.Errorf("field %s did not specify a path", f.Name) -- } -+ pathToSchemaMutex.RLock() -+ if pe, ok := pathToSchemaCache[f.Tag]; ok { -+ pathToSchemaMutex.RUnlock() -+ return pe, nil -+ } else { -+ pathToSchemaMutex.RUnlock() -+ pathTag, ok := f.Tag.Lookup("path") -+ if !ok || pathTag == "" { -+ return nil, fmt.Errorf("field %s did not specify a path", f.Name) -+ } - -- paths := strings.Split(pathTag, "|") -- if len(paths) == 1 { -- pathTag = strings.TrimPrefix(pathTag, "/") -- return strings.Split(pathTag, "/"), nil -- } -- for _, pv := range paths { -- pv = strings.TrimPrefix(pv, "/") -- pe := strings.Split(pv, "/") -- if len(pe) > 1 { -- return pe, nil -+ paths := strings.Split(pathTag, "|") -+ if len(paths) == 1 { -+ pathTag = strings.TrimPrefix(pathTag, "/") -+ retPath := strings.Split(pathTag, "/") -+ pathToSchemaMutex.Lock() -+ pathToSchemaCache[f.Tag] = retPath -+ pathToSchemaMutex.Unlock() -+ return retPath, nil -+ } -+ for _, pv := range paths { -+ pv = strings.TrimPrefix(pv, "/") -+ pe := strings.Split(pv, "/") -+ if len(pe) > 1 { -+ pathToSchemaMutex.Lock() -+ pathToSchemaCache[f.Tag] = pe -+ pathToSchemaMutex.Unlock() -+ return pe, nil -+ } - } -- } - -- return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathTag) -+ return nil, fmt.Errorf("field %s had path tag %s with |, but no elements of form a/b", f.Name, pathTag) -+ } - } - - // SchemaTreePath returns the schema tree path of the supplied yang.Entry -@@ -215,6 +235,10 @@ - refSchema = refSchema.Dir[pe] - } - -+ if refSchema.Type.Kind == yang.Yleafref { -+ return FindLeafRefSchema(refSchema, refSchema.Type.Path) -+ } -+ - return refSchema, nil - } - -diff -ruN ygot-dir-orig/ygot/util/reflect.go ygot-dir/ygot/util/reflect.go ---- ygot-dir-orig/ygot/util/reflect.go 2020-10-07 14:33:58.196912000 -0700 -+++ ygot-dir/ygot/util/reflect.go 2020-10-27 16:02:47.512819000 -0700 -@@ -12,6 +12,9 @@ - // See the License for the specific language governing permissions and - // limitations under the License. - -+// This file is changed by Broadcom. -+// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. -+ - package util - - import ( -@@ -191,8 +194,10 @@ - - // InsertIntoMap inserts value with key into parent which must be a map. - func InsertIntoMap(parentMap interface{}, key interface{}, value interface{}) error { -- DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", -- parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) -+ if debugLibrary { -+ DbgPrint("InsertIntoMap into parent type %T with key %v(%T) value \n%s\n (%T)", -+ parentMap, ValueStrDebug(key), key, pretty.Sprint(value), value) -+ } - - v := reflect.ValueOf(parentMap) - t := reflect.TypeOf(parentMap) -@@ -283,7 +288,7 @@ - n = reflect.Zero(ft.Type) - } - -- if !isFieldTypeCompatible(ft, n) { -+ if !isFieldTypeCompatible(ft, n) && !IsValueTypeCompatible(ft.Type, v) { - return fmt.Errorf("cannot assign value %v (type %T) to struct field %s (type %v) in struct %T", fieldValue, fieldValue, fieldName, ft.Type, parentStruct) - } - -@@ -452,14 +457,36 @@ - return cmp.Equal(aa, bb) - } - -+func updateChildSchemaCache (schema *yang.Entry, tagStr reflect.StructTag, ygEntry *yang.Entry) { -+ schema.ChildSchemaMutex.Lock() -+ schema.ChildSchemaCache[tagStr] = ygEntry -+ schema.ChildSchemaMutex.Unlock() -+} -+ - // ChildSchema returns the schema for the struct field f, if f contains a valid - // path tag and the schema path is found in the schema tree. It returns an error - // if the struct tag is invalid, or nil if tag is valid but the schema is not - // found in the tree at the specified path. - // TODO(wenbli): need unit test - func ChildSchema(schema *yang.Entry, f reflect.StructField) (*yang.Entry, error) { -- pathTag, _ := f.Tag.Lookup("path") -- DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) -+ schema.ChildSchemaMutex.Lock() -+ if (schema.ChildSchemaCache == nil) { -+ schema.ChildSchemaCache = make(map[reflect.StructTag]*yang.Entry) -+ } -+ schema.ChildSchemaMutex.Unlock() -+ -+ schema.ChildSchemaMutex.RLock() -+ if cschema, ok := schema.ChildSchemaCache[f.Tag]; ok { -+ schema.ChildSchemaMutex.RUnlock() -+ return cschema, nil -+ } -+ schema.ChildSchemaMutex.RUnlock() -+ -+ if IsDebugSchemaEnabled() { -+ pathTag, _ := f.Tag.Lookup("path") -+ DbgSchema("childSchema for schema %s, field %s, tag %s\n", schema.Name, f.Name, pathTag) -+ } -+ - p, err := RelativeSchemaPath(f) - if err != nil { - return nil, err -@@ -490,6 +517,7 @@ - } - if foundSchema { - DbgSchema(" - found\n") -+ updateChildSchemaCache (schema, f.Tag, childSchema) - return childSchema, nil - } - DbgSchema(" - not found\n") -@@ -505,11 +533,15 @@ - // path element i.e. choice1/case1/leaf1 path in the schema will have - // struct tag `path:"leaf1"`. This implies that only paths with length - // 1 are eligible for this matching. -+ updateChildSchemaCache (schema, f.Tag, nil) - return nil, nil - } - entries := FindFirstNonChoiceOrCase(schema) - -- DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) -+ if IsDebugSchemaEnabled() { -+ DbgSchema("checking for %s against non choice/case entries: %v\n", p[0], stringMapKeys(entries)) -+ } -+ - for path, entry := range entries { - splitPath := SplitPath(path) - name := splitPath[len(splitPath)-1] -@@ -517,11 +549,13 @@ - - if StripModulePrefix(name) == p[0] { - DbgSchema(" - match\n") -+ updateChildSchemaCache (schema, f.Tag, entry) - return entry, nil - } - } - - DbgSchema(" - no matches\n") -+ updateChildSchemaCache (schema, f.Tag, nil) - return nil, nil - } - diff -ruN ygot-dir-orig/ygot/ygen/codegen.go ygot-dir/ygot/ygen/codegen.go --- ygot-dir-orig/ygot/ygen/codegen.go 2020-10-07 14:33:58.356199000 -0700 +++ ygot-dir/ygot/ygen/codegen.go 2020-10-27 16:02:47.723601000 -0700 @@ -372,368 +126,4 @@ diff -ruN ygot-dir-orig/ygot/ygen/genstate.go ygot-dir/ygot/ygen/genstate.go es = append(es, en) } -diff -ruN ygot-dir-orig/ygot/ygot/render.go ygot-dir/ygot/ygot/render.go ---- ygot-dir-orig/ygot/ygot/render.go 2020-10-07 14:33:58.224638000 -0700 -+++ ygot-dir/ygot/ygot/render.go 2020-10-27 16:02:47.544072000 -0700 -@@ -45,6 +45,11 @@ - p *gnmiPath - } - -+type structTagInfo struct { -+ gPathList []*gnmiPath -+ chModName string -+} -+ - func (p *path) String() string { - if p.p.isPathElemPath() { - return proto.MarshalTextString(&gnmipb.Path{Elem: p.p.pathElemPath}) -@@ -870,19 +875,21 @@ - // the module name should be appended to entities that are defined in a different - // module to their parent. - func ConstructIETFJSON(s GoStruct, args *RFC7951JSONConfig) (map[string]interface{}, error) { -+ tagPaths := map[reflect.StructTag]*structTagInfo{} - return structJSON(s, "", jsonOutputConfig{ - jType: RFC7951, - rfc7951Config: args, -- }) -+ }, tagPaths) - } - - // ConstructInternalJSON marshals a supplied GoStruct to a map, suitable for handing --// to json.Marshal. It uses the loosely specified JSON format document in -+// to json.Marshal. It usstres the loosely specified JSON format document in - // go/yang-internal-json. - func ConstructInternalJSON(s GoStruct) (map[string]interface{}, error) { -+ tagPaths := map[reflect.StructTag]*structTagInfo{} - return structJSON(s, "", jsonOutputConfig{ - jType: Internal, -- }) -+ }, tagPaths) - } - - // jsonOutputConfig is used to determine how constructJSON should generate -@@ -902,7 +909,7 @@ - // be produced and whether such module names are appended is controlled through the - // supplied jsonOutputConfig. Returns an error if the GoStruct cannot be rendered - // to JSON. --func structJSON(s GoStruct, parentMod string, args jsonOutputConfig) (map[string]interface{}, error) { -+func structJSON(s GoStruct, parentMod string, args jsonOutputConfig, tagPaths map[reflect.StructTag]*structTagInfo) (map[string]interface{}, error) { - var errs errlist.List - - sval := reflect.ValueOf(s).Elem() -@@ -916,20 +923,51 @@ - field := sval.Field(i) - fType := stype.Field(i) - -+ switch field.Kind() { -+ case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Interface: -+ if field.IsNil() { -+ continue -+ } -+ } -+ - // Determine whether we should append a module name to the path in RFC7951 - // output mode. - var appmod string -+ - pmod := parentMod -- if chMod, ok := fType.Tag.Lookup("module"); ok { -- // If the child module isn't the same as the parent module, -- // then appmod stores the name of the module to prefix to paths -- // within this context. -- if chMod != parentMod { -- appmod = chMod -- } -- // Update the parent module name to be used for subsequent -- // children. -- pmod = chMod -+ -+ var mapPaths []*gnmiPath -+ var err error -+ -+ if tagInfo, ok := tagPaths[fType.Tag]; !ok { -+ tagInfoObj := new (structTagInfo) -+ tagInfoObj.gPathList, err = structTagToLibPaths(fType, newStringSliceGNMIPath([]string{})) -+ if err != nil { -+ errs.Add(fmt.Errorf("%s: %v", fType.Name, err)) -+ continue -+ } -+ if chMod, modOk := fType.Tag.Lookup("module"); modOk { -+ // If the child module isn't the same as the parent module, -+ // then appmod stores the name of the module to prefix to pathsmakejson -+ // within this context. -+ if chMod != parentMod { -+ appmod = chMod -+ } -+ // Update the parent module name to be used for subsequent -+ // children. -+ pmod = chMod -+ tagInfoObj.chModName = chMod -+ } else { -+ tagInfoObj.chModName = pmod -+ } -+ tagPaths[fType.Tag] = tagInfoObj -+ mapPaths = tagInfoObj.gPathList -+ } else { -+ mapPaths = tagInfo.gPathList -+ pmod = tagInfo.chModName -+ if tagInfo.chModName != parentMod { -+ appmod = tagInfo.chModName -+ } - } - - var appendModName bool -@@ -937,19 +975,8 @@ - appendModName = true - } - -- mapPaths, err := structTagToLibPaths(fType, newStringSliceGNMIPath([]string{})) -- if err != nil { -- errs.Add(fmt.Errorf("%s: %v", fType.Name, err)) -- continue -- } -- - var value interface{} -- -- if util.IsYgotAnnotation(fType) { -- value, err = jsonAnnotationSlice(field) -- } else { -- value, err = jsonValue(field, pmod, args) -- } -+ value, err = jsonValue(field, pmod, args, tagPaths) - - if err != nil { - errs.Add(err) -@@ -1086,7 +1113,7 @@ - // constructs the representation for JSON marshalling that corresponds to it. - // The module within which the map is defined is specified by the parentMod - // argument. --func mapJSON(field reflect.Value, parentMod string, args jsonOutputConfig) (interface{}, error) { -+func mapJSON(field reflect.Value, parentMod string, args jsonOutputConfig, tagPaths map[reflect.StructTag]*structTagInfo) (interface{}, error) { - var errs errlist.List - mapKeyMap := map[string]reflect.Value{} - // Order of elements determines the order in which keys will be processed. -@@ -1155,6 +1182,7 @@ - default: - return nil, fmt.Errorf("invalid JSON format specified: %v", args.jType) - } -+ - for _, kn := range mapKeys { - k := mapKeyMap[kn] - goStruct, ok := field.MapIndex(k).Interface().(GoStruct) -@@ -1163,7 +1191,7 @@ - continue - } - -- val, err := structJSON(goStruct, parentMod, args) -+ val, err := structJSON(goStruct, parentMod, args, tagPaths) - if err != nil { - errs.Add(err) - continue -@@ -1191,7 +1219,7 @@ - // The module within which the value is defined is specified by the parentMod string, - // and the type of JSON to be rendered controlled by the value of the jsonOutputConfig - // provided. Returns an error if one occurs during the mapping process. --func jsonValue(field reflect.Value, parentMod string, args jsonOutputConfig) (interface{}, error) { -+func jsonValue(field reflect.Value, parentMod string, args jsonOutputConfig, tagPaths map[reflect.StructTag]*structTagInfo) (interface{}, error) { - var value interface{} - var errs errlist.List - -@@ -1210,7 +1238,7 @@ - switch field.Kind() { - case reflect.Map: - var err error -- value, err = mapJSON(field, parentMod, args) -+ value, err = mapJSON(field, parentMod, args, tagPaths) - if err != nil { - errs.Add(err) - } -@@ -1223,7 +1251,7 @@ - } - - var err error -- value, err = structJSON(goStruct, parentMod, args) -+ value, err = structJSON(goStruct, parentMod, args, tagPaths) - if err != nil { - errs.Add(err) - } -@@ -1235,7 +1263,7 @@ - } - case reflect.Slice: - var err error -- value, err = jsonSlice(field, parentMod, args) -+ value, err = jsonSlice(field, parentMod, args, tagPaths) - if err != nil { - return nil, err - } -@@ -1289,7 +1317,7 @@ - // GoStruct, a slice may be a binary field, leaf-list or an unkeyed list. The - // parentMod is used to track the name of the parent module in the case that - // module names should be appended. --func jsonSlice(field reflect.Value, parentMod string, args jsonOutputConfig) (interface{}, error) { -+func jsonSlice(field reflect.Value, parentMod string, args jsonOutputConfig, tagPaths map[reflect.StructTag]*structTagInfo) (interface{}, error) { - if field.Type().Name() == BinaryTypeName { - // Handle the case that that we have a Binary ([]byte) value, - // which must be returned as a JSON string. -@@ -1305,7 +1333,7 @@ - if !ok { - return nil, fmt.Errorf("invalid member of a slice, %s was not a valid GoStruct", c.Name()) - } -- j, err := structJSON(gs, parentMod, args) -+ j, err := structJSON(gs, parentMod, args, tagPaths) - if err != nil { - return nil, err - } -diff -ruN ygot-dir-orig/ygot/ygot/struct_validation_map.go ygot-dir/ygot/ygot/struct_validation_map.go ---- ygot-dir-orig/ygot/ygot/struct_validation_map.go 2020-10-07 14:33:58.231818000 -0700 -+++ ygot-dir/ygot/ygot/struct_validation_map.go 2020-10-27 16:02:47.553165000 -0700 -@@ -19,6 +19,10 @@ - // to return pointers to a type. - // - Renders structs to other output formats such as JSON, or gNMI - // notifications. -+ -+// This file is changed by Broadcom. -+// Modifications - Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. -+ - package ygot - - import ( -@@ -336,31 +340,12 @@ - // EmitJSON takes an input ValidatedGoStruct (produced by ygen with validation enabled) - // and serialises it to a JSON string. By default, produces the Internal format JSON. - func EmitJSON(s ValidatedGoStruct, opts *EmitJSONConfig) (string, error) { -- var ( -- vopts []ValidationOption -- skipValidation bool -- ) -- -- if opts != nil { -- vopts = opts.ValidationOpts -- skipValidation = opts.SkipValidation -- } -- -- if err := s.Validate(vopts...); !skipValidation && err != nil { -- return "", fmt.Errorf("validation err: %v", err) -- } -- - v, err := makeJSON(s, opts) - if err != nil { - return "", err - } - -- indent := indentString -- if opts != nil && opts.Indent != "" { -- indent = opts.Indent -- } -- -- j, err := json.MarshalIndent(v, "", indent) -+ j, err := json.Marshal(v) - if err != nil { - return "", fmt.Errorf("JSON marshalling error: %v", err) - } -@@ -525,7 +510,6 @@ - for i := 0; i < srcVal.NumField(); i++ { - srcField := srcVal.Field(i) - dstField := dstVal.Field(i) -- - switch srcField.Kind() { - case reflect.Ptr: - if err := copyPtrField(dstField, srcField); err != nil { -@@ -555,7 +539,13 @@ - dstField.Set(srcField) - } - default: -- dstField.Set(srcField) -+ if srcField.Type().Implements(reflect.TypeOf((*GoEnum)(nil)).Elem()) == true { -+ if srcField.Int() != 0 { -+ dstField.Set(srcField) -+ } -+ } else { -+ dstField.Set(srcField) -+ } - } - } - return nil -@@ -595,6 +585,7 @@ - return err - } - dstField.Set(d) -+ - return nil - } - -@@ -608,6 +599,7 @@ - p := reflect.New(srcField.Type().Elem()) - p.Elem().Set(srcField.Elem()) - dstField.Set(p) -+ - return nil - } - -@@ -636,6 +628,7 @@ - return err - } - dstField.Set(d) -+ - return nil - } - -@@ -645,6 +638,7 @@ - // key is populated in srcField and dstField, their contents are merged if they - // do not overlap, otherwise an error is returned. - func copyMapField(dstField, srcField reflect.Value) error { -+ - if !util.IsValueMap(srcField) { - return fmt.Errorf("received a non-map type in src map field: %v", srcField.Kind()) - } -@@ -664,8 +658,6 @@ - } - - srcKeys := srcField.MapKeys() -- dstKeys := dstField.MapKeys() -- - nm := reflect.MakeMapWithSize(reflect.MapOf(m.key, m.value), srcField.Len()) - - mapsToMap := []struct { -@@ -673,20 +665,24 @@ - field reflect.Value - }{ - {srcKeys, srcField}, -- {dstKeys, dstField}, - } - existingKeys := map[interface{}]reflect.Value{} - -+ for _, dstKey := range dstField.MapKeys() { -+ existingKeys[dstKey.Interface()] = dstField.MapIndex(dstKey) -+ } -+ - for _, m := range mapsToMap { - for _, k := range m.keys { - // If the key already exists, then determine the existing item to merge - // into. - v := m.field.MapIndex(k) - var d reflect.Value -- var ok bool -- if d, ok = existingKeys[k.Interface()]; !ok { -+ if tmpVal, keyErr := compareMapKeys(existingKeys, k.Interface()); keyErr != nil { - d = reflect.New(v.Elem().Type()) - existingKeys[k.Interface()] = v -+ } else { -+ d = *tmpVal - } - - if err := copyStruct(d.Elem(), v.Elem()); err != nil { -@@ -804,3 +800,12 @@ - } - return true, nil - } -+ -+func compareMapKeys(existingKeys map[interface{}]reflect.Value, searchKey interface{}) (*reflect.Value, error) { -+ for tmpKey, tmpVal := range existingKeys { -+ if cmp.Equal(tmpKey, searchKey) { -+ return &tmpVal, nil -+ } -+ } -+ return nil, fmt.Errorf("No match found in the existingKeys map.") -+} diff --git a/sonic_data_client/db_client.go b/sonic_data_client/db_client.go index e3c903cf..2fff6146 100644 --- a/sonic_data_client/db_client.go +++ b/sonic_data_client/db_client.go @@ -43,32 +43,15 @@ const ( // This package provides one implmentation for now: the DbClient // type Client interface { - // StreamRun will start watching service on data source - // and enqueue data change to the priority queue. - // It stops all activities upon receiving signal on stop channel - // It should run as a go routine - StreamRun(q *queue.PriorityQueue, stop chan struct{}, w *sync.WaitGroup, subscribe *gnmipb.SubscriptionList) - // Poll will start service to respond poll signal received on poll channel. - // data read from data source will be enqueued on to the priority queue - // The service will stop upon detection of poll channel closing. - // It should run as a go routine - PollRun(q *queue.PriorityQueue, poll chan struct{}, w *sync.WaitGroup, subscribe *gnmipb.SubscriptionList) - OnceRun(q *queue.PriorityQueue, once chan struct{}, w *sync.WaitGroup, subscribe *gnmipb.SubscriptionList) // Get return data from the data source in format of *spb.Value Get(w *sync.WaitGroup) ([]*spb.Value, error) // Set data based on path and value Set(delete []*gnmipb.Path, replace []*gnmipb.Update, update []*gnmipb.Update) error - // Capabilities of the switch - Capabilities() []gnmipb.ModelData // Close provides implemenation for explicit cleanup of Client Close() error } -type Stream interface { - Send(m *gnmipb.SubscribeResponse) error -} - // Let it be variable visible to other packages for now. // May add an interface function for it. var UseRedisLocalTcpPort bool = false @@ -76,16 +59,6 @@ var UseRedisLocalTcpPort bool = false // redis client connected to each DB 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. -var MinSampleInterval = time.Second - -// IntervalTicker is a factory method to implement interval ticking. -// Exposed for UT purposes. -var IntervalTicker = func(interval time.Duration) <-chan time.Time { - return time.After(interval) -} - type tablePath struct { dbNamespace string dbName string @@ -110,17 +83,6 @@ type Value struct { *spb.Value } -// Implement Compare method for priority queue -func (val Value) Compare(other queue.Item) int { - oval := other.(Value) - if val.GetTimestamp() > oval.GetTimestamp() { - return 1 - } else if val.GetTimestamp() == oval.GetTimestamp() { - return 0 - } - return -1 -} - type DbClient struct { prefix *gnmipb.Path paths []*gnmipb.Path @@ -159,18 +121,6 @@ func NewDbClient(paths []*gnmipb.Path, prefix *gnmipb.Path, target string, origi return &client, nil } -func (c *DbClient) StreamRun(q *queue.PriorityQueue, stop chan struct{}, w *sync.WaitGroup, subscribe *gnmipb.SubscriptionList) { - return -} - -func (c *DbClient) PollRun(q *queue.PriorityQueue, poll chan struct{}, w *sync.WaitGroup, subscribe *gnmipb.SubscriptionList) { - return -} - -func (c *DbClient) OnceRun(q *queue.PriorityQueue, once chan struct{}, w *sync.WaitGroup, subscribe *gnmipb.SubscriptionList) { - return -} - func PathExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { @@ -430,39 +380,6 @@ func (c *DbClient) Close() error { return nil } -// Convert from SONiC Value to its corresponding gNMI proto stream -// response type. -func ValToResp(val Value) (*gnmipb.SubscribeResponse, error) { - switch val.GetSyncResponse() { - case true: - return &gnmipb.SubscribeResponse{ - Response: &gnmipb.SubscribeResponse_SyncResponse{ - SyncResponse: true, - }, - }, nil - default: - // In case the subscribe/poll routines encountered fatal error - if fatal := val.GetFatal(); fatal != "" { - return nil, fmt.Errorf("%s", fatal) - } - - return &gnmipb.SubscribeResponse{ - Response: &gnmipb.SubscribeResponse_Update{ - Update: &gnmipb.Notification{ - Timestamp: val.GetTimestamp(), - Prefix: val.GetPrefix(), - Update: []*gnmipb.Update{ - { - Path: val.GetPath(), - Val: val.GetVal(), - }, - }, - }, - }, - }, nil - } -} - func GetTableKeySeparator(target string, ns string) (string, error) { _, ok := spb.Target_value[target] if !ok { @@ -474,20 +391,6 @@ func GetTableKeySeparator(target string, ns string) (string, error) { 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) @@ -497,15 +400,6 @@ func IsTargetDb(target string) (string, bool, string, bool) { 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 @@ -1162,19 +1056,6 @@ func handleTableData(tblPaths []tablePath) error { return nil } -func enqueueFatalMsg(c *DbClient, msg string) { - putFatalMsg(c.q, msg) -} - -func putFatalMsg(q *queue.PriorityQueue, msg string) { - q.Put(Value{ - &spb.Value{ - Timestamp: time.Now().UnixNano(), - Fatal: msg, - }, - }) -} - /* Populate the JsonPatch corresponding each GNMI operation. */ func ConvertToJsonPatch(prefix *gnmipb.Path, path *gnmipb.Path, t *gnmipb.TypedValue, output *string) error { if t != nil { @@ -1296,8 +1177,6 @@ func (c *DbClient) SetIncrementalConfig(delete []*gnmipb.Path, replace []*gnmipb err = sc.ApplyPatchDb(patchFile) } else if c.origin == "sonic-yang" { err = sc.ApplyPatchYang(patchFile) - } else { - return fmt.Errorf("Invalid schema %s", c.origin) } if err == nil { @@ -1319,9 +1198,13 @@ func (c *DbClient) SetFullConfig(delete []*gnmipb.Path, replace []*gnmipb.Update return err } - if c.testMode == false { - // TODO: Add Yang validation - PyCodeInGo := + PyCodeInGo := +` +print('No Yang validation for test mode...') +print('%s') +` + if c.testMode != true { + PyCodeInGo = ` import sonic_yang import json @@ -1337,12 +1220,12 @@ except sonic_yang.SonicYangException as e: print("Yang validation error: {}".format(str(e))) raise ` + } - PyCodeInGo = fmt.Sprintf(PyCodeInGo, ietf_json_val) - err = RunPyCode(PyCodeInGo) - if err != nil { - return fmt.Errorf("Yang validation failed!") - } + PyCodeInGo = fmt.Sprintf(PyCodeInGo, ietf_json_val) + err = RunPyCode(PyCodeInGo) + if err != nil { + return fmt.Errorf("Yang validation failed!") } return nil @@ -1417,13 +1300,7 @@ func (c *DbClient) Set(delete []*gnmipb.Path, replace []*gnmipb.Update, update [ return c.SetConfigDB(delete, replace, update) } else if c.target == "APPL_DB" { return c.SetDB(delete, replace, update) - } else if c.target == "DASH_APP_DB" { - return c.SetDB(delete, replace, update) } return fmt.Errorf("Set RPC does not support %v", c.target) } -func (c *DbClient) Capabilities() []gnmipb.ModelData { - return nil -} - diff --git a/test/test_gnmi_appldb.py b/test/test_gnmi_appldb.py index 9999b3e0..87370fc3 100644 --- a/test/test_gnmi_appldb.py +++ b/test/test_gnmi_appldb.py @@ -257,3 +257,43 @@ def test_gnmi_list_normal_01(self): get_list = [path] ret, msg_list = gnmi_get(get_list) assert ret != 0, 'Invalid return code' + + def test_gnmi_set_empty_01(self): + ret, msg = gnmi_set([], [], []) + assert ret != 0, msg + + def test_gnmi_invalid_origin_01(self): + path = '/sonic-invalid:APPL_DB/DASH_QOS' + value = { + 'qos_01': {'bw': '54321', 'cps': '1000', 'flows': '300'}, + 'qos_02': {'bw': '6000', 'cps': '200', 'flows': '101'} + } + update_list = [] + text = json.dumps(value) + file_name = 'update.txt' + file_object = open(file_name, 'w') + file_object.write(text) + file_object.close() + update_list = [path + ':@./' + file_name] + + ret, msg = gnmi_set([], update_list, []) + assert ret != 0, 'Origin is invalid' + assert 'Invalid origin' in msg + + def test_gnmi_invalid_target_01(self): + path = '/sonic-db:INVALID_DB/DASH_QOS' + value = { + 'qos_01': {'bw': '54321', 'cps': '1000', 'flows': '300'}, + 'qos_02': {'bw': '6000', 'cps': '200', 'flows': '101'} + } + update_list = [] + text = json.dumps(value) + file_name = 'update.txt' + file_object = open(file_name, 'w') + file_object.write(text) + file_object.close() + update_list = [path + ':@./' + file_name] + + ret, msg = gnmi_set([], update_list, []) + assert ret != 0, 'Target is invalid' + assert 'Invalid target' in msg diff --git a/test/test_gnmi_configdb.py b/test/test_gnmi_configdb.py index f1c104fc..aaed98ce 100644 --- a/test/test_gnmi_configdb.py +++ b/test/test_gnmi_configdb.py @@ -246,6 +246,14 @@ def test_gnmi_full(self): config_json = json.load(cf) assert test_data == config_json, "Wrong config file" + def test_gnmi_full_negative(self): + delete_list = ['/sonic-db:CONFIG_DB/'] + update_list = ['/sonic-db:CONFIG_DB/' + ':abc'] + + ret, msg = gnmi_set(delete_list, update_list, []) + assert ret != 0, 'Invalid ietf_json_val' + assert 'IETF JSON' in msg + @pytest.mark.parametrize("test_data", test_data_checkpoint) def test_gnmi_get_checkpoint(self, test_data): if os.path.isfile(checkpoint_file): @@ -284,3 +292,36 @@ def test_gnmi_get_checkpoint(self, test_data): break assert hit == True, 'No match for %s'%str(data['value']) + def test_gnmi_get_checkpoint_negative_01(self): + value = json.dumps(test_json_checkpoint) + file_object = open(checkpoint_file, 'w') + file_object.write(value) + file_object.close() + + get_list = ['/sonic-db:CONFIG_DB/DASH_VNET/vnet_3721/address_spaces/0/abc'] + + ret, msg_list = gnmi_get(get_list) + assert ret != 0, 'Invalid path' + + def test_gnmi_get_checkpoint_negative_02(self): + value = json.dumps(test_json_checkpoint) + file_object = open(checkpoint_file, 'w') + file_object.write(value) + file_object.close() + + get_list = ['/sonic-db:CONFIG_DB/DASH_VNET/vnet_3721/address_spaces/abc'] + + ret, msg_list = gnmi_get(get_list) + assert ret != 0, 'Invalid path' + + def test_gnmi_get_checkpoint_negative_03(self): + value = json.dumps(test_json_checkpoint) + file_object = open(checkpoint_file, 'w') + file_object.write(value) + file_object.close() + + get_list = ['/sonic-db:CONFIG_DB/DASH_VNET/vnet_3721/address_spaces/1000'] + + ret, msg_list = gnmi_get(get_list) + assert ret != 0, 'Invalid path' +