Skip to content

Commit

Permalink
Project import generated by Copybara.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 212521689
  • Loading branch information
Googler authored and g-yusufsn committed Sep 11, 2018
1 parent 3e90089 commit 084de23
Show file tree
Hide file tree
Showing 19 changed files with 226 additions and 1,496 deletions.
39 changes: 29 additions & 10 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/openconfig/gnmi/ctree"
"github.com/openconfig/gnmi/metadata"
"github.com/openconfig/gnmi/path"
"github.com/openconfig/gnmi/value"

gpb "github.com/openconfig/gnmi/proto/gnmi"
)
Expand Down Expand Up @@ -201,7 +202,7 @@ func (c *Cache) Remove(target string) {
// Notify clients that the target is removed.
switch Type {
case GnmiNoti:
c.client(ctree.DetachedLeaf(deleteNoti(target, []string{"*"})))
c.client(ctree.DetachedLeaf(deleteNoti(target, "", []string{"*"})))
case ClientLeaf:
c.client(ctree.DetachedLeaf(client.Delete{Path: []string{target}, TS: time.Now()}))
default:
Expand Down Expand Up @@ -279,7 +280,6 @@ func (c *Cache) Update(n client.Notification) error {
if target == nil {
return fmt.Errorf("target %q not found in cache", name)
}
target.meta.AddInt(metadata.UpdateCount, 1)
target.checkTimestamp(l.TS)
switch u := n.(type) {
case client.Update:
Expand Down Expand Up @@ -346,11 +346,11 @@ func (t *Target) GnmiUpdate(n *gpb.Notification) error {
for _, u := range updates {
noti := proto.Clone(n).(*gpb.Notification)
noti.Update = []*gpb.Update{u}
t.meta.AddInt(metadata.UpdateCount, 1)
nd, err := t.gnmiUpdate(noti)
if err != nil {
return err
}
t.meta.AddInt(metadata.UpdateCount, 1)
if nd != nil {
t.client(nd)
}
Expand Down Expand Up @@ -418,16 +418,21 @@ func (t *Target) update(u client.Update) (*ctree.Leaf, error) {
old := oldval.Value().(client.Update)
if !old.TS.Before(u.TS) {
// Update rejected. Timestamp <= previous recorded timestamp.
t.meta.AddInt(metadata.StaleCount, 1)
return nil, errors.New("update is stale")
}
oldval.Update(u)
if realData {
t.meta.AddInt(metadata.UpdateCount, 1)
}
return oldval, nil
}
// Add a new leaf.
if err := t.t.Add(path, u); err != nil {
return nil, err
}
if realData {
t.meta.AddInt(metadata.UpdateCount, 1)
t.meta.AddInt(metadata.LeafCount, 1)
t.meta.AddInt(metadata.AddCount, 1)
}
Expand Down Expand Up @@ -468,14 +473,23 @@ func (t *Target) gnmiUpdate(n *gpb.Notification) (*ctree.Leaf, error) {
}
// Update an existing leaf.
if oldval := t.t.GetLeaf(path); oldval != nil {
// Since we control what goes into the tree, oldval should always
// contain *gpb.Notification and there's no need to do a safe assertion.
old := oldval.Value().(*gpb.Notification)
// An update with corrupt data is possible to visit a node that does not
// contain *gpb.Notification. Thus, need type assertion here.
old, ok := oldval.Value().(*gpb.Notification)
if !ok {
return nil, fmt.Errorf("corrupt schema with collision for path %q, got %T", path, oldval.Value())
}
if !T(old.GetTimestamp()).Before(T(n.GetTimestamp())) {
// Update rejected. Timestamp <= previous recorded timestamp.
t.meta.AddInt(metadata.StaleCount, 1)
return nil, errors.New("update is stale")
}
oldval.Update(n)
// Simulate event-driven for all non-atomic updates.
if !n.Atomic && value.Equal(old.Update[0].Val, n.Update[0].Val) {
t.meta.AddInt(metadata.SuppressedCount, 1)
return nil, errors.New("suppressed duplicate value")
}
return oldval, nil
}
// Add a new leaf.
Expand Down Expand Up @@ -659,7 +673,7 @@ func (t *Target) Reset() {
case ClientLeaf:
t.client(ctree.DetachedLeaf(client.Delete{Path: []string{t.name, root}, TS: resetTime}))
case GnmiNoti:
t.client(ctree.DetachedLeaf(deleteNoti(t.name, []string{root})))
t.client(ctree.DetachedLeaf(deleteNoti(t.name, root, []string{"*"})))
default:
log.Errorf("cache type is invalid: %v", Type)
}
Expand Down Expand Up @@ -703,10 +717,15 @@ func IsTargetDelete(l *ctree.Leaf) bool {
return len(v.Path) == 1
case *gpb.Notification:
if len(v.Delete) == 1 {
var orig string
if v.Prefix != nil {
orig = v.Prefix.Origin
}
// Prefix path is indexed without target and origin
p := path.ToStrings(v.Prefix, false)
p = append(p, path.ToStrings(v.Delete[0], false)...)
return len(p) == 1 && p[0] == "*"
// When origin isn't set, intention must be to delete entire target.
return orig == "" && len(p) == 1 && p[0] == "*"
}
}
return false
Expand All @@ -722,14 +741,14 @@ func joinPrefixAndPath(pr, ph *gpb.Path) []string {
return p
}

func deleteNoti(t string, p []string) *gpb.Notification {
func deleteNoti(t, o string, p []string) *gpb.Notification {
pe := make([]*gpb.PathElem, 0, len(p))
for _, e := range p {
pe = append(pe, &gpb.PathElem{Name: e})
}
return &gpb.Notification{
Timestamp: time.Now().UnixNano(),
Prefix: &gpb.Path{Target: t},
Prefix: &gpb.Path{Target: t, Origin: o},
Delete: []*gpb.Path{&gpb.Path{Elem: pe}},
}
}
Expand Down
158 changes: 151 additions & 7 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"testing"
Expand All @@ -31,6 +32,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/openconfig/gnmi/client"
"github.com/openconfig/gnmi/ctree"
"github.com/openconfig/gnmi/errdiff"
"github.com/openconfig/gnmi/metadata"

gpb "github.com/openconfig/gnmi/proto/gnmi"
Expand Down Expand Up @@ -404,6 +406,93 @@ func TestUpdateMeta(t *testing.T) {
}
})
}
}

func TestMetadataStale(t *testing.T) {
c := New([]string{"dev1"})
for i := 0; i < 10; i++ {
c.Update(client.Update{Path: []string{"dev1", "a"}, Val: i, TS: T(int64(10 - i))})
c.GetTarget("dev1").updateMeta(nil)
path := metadata.Path(metadata.StaleCount)
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v interface{}) {
staleCount := v.(client.Update).Val.(int64)
if staleCount != int64(i) {
t.Errorf("got staleCount = %d, want %d", staleCount, i)
}
})
path = metadata.Path(metadata.UpdateCount)
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v interface{}) {
updates := v.(client.Update).Val.(int64)
if updates != 1 {
t.Errorf("got updates %d, want 1", updates)
}
})
}
}

func TestGNMIUpdateIntermediateUpdate(t *testing.T) {
c := New([]string{"dev1"})
// Initialize a cache tree for next GnmiUpdate test.
n := gnmiNotification("dev1", []string{"prefix", "path"}, []string{"update", "a", "b", "c"}, 0, "", true)
if err := c.GnmiUpdate(n); err != nil {
t.Fatalf("GnmiUpdate(%+v): got %v, want nil error", n, err)
}
// This is a negative test case for invalid path in an update.
// For a cache tree initialized above with path "a"/"b"/"c", "a" is a non-leaf node.
// Because non-leaf node "a" does not contain notification, error should be returned in following update.
n = gnmiNotification("dev1", []string{"prefix", "path"}, []string{"update", "a"}, 0, "", true)
err := c.GnmiUpdate(n)
if diff := errdiff.Substring(err, "corrupt schema with collision"); diff != "" {
t.Errorf("GnmiUpdate(%+v): %v", n, diff)
}
}

func TestMetadataSuppressed(t *testing.T) {
c := New([]string{"dev1"})
// Unique values not suppressed.
for i := 0; i < 10; i++ {
c.GnmiUpdate(gnmiNotification("dev1", []string{"prefix", "path"}, []string{"update", "path"}, int64(i), strconv.Itoa(i), true))
c.GetTarget("dev1").updateMeta(nil)
path := metadata.Path(metadata.SuppressedCount)
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v interface{}) {
suppressedCount := v.(client.Update).Val.(int64)
if suppressedCount != 0 {
t.Errorf("got suppressedCount = %d, want 0", suppressedCount)
}
})
path = metadata.Path(metadata.UpdateCount)
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v interface{}) {
updates := v.(client.Update).Val.(int64)
if updates != int64(i+1) {
t.Errorf("got updates %d, want %d", updates, i)
}
})
}
c.Reset("dev1")
// Duplicate values suppressed.
for i := 0; i < 10; i++ {
c.GnmiUpdate(gnmiNotification("dev1", []string{"prefix", "path"}, []string{"update", "path"}, int64(i), "same value", true))
c.GetTarget("dev1").updateMeta(nil)
path := metadata.Path(metadata.SuppressedCount)
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v interface{}) {
suppressedCount := v.(client.Update).Val.(int64)
if suppressedCount != int64(i) {
t.Errorf("got suppressedCount = %d, want %d", suppressedCount, i)
}
})
path = metadata.Path(metadata.UpdateCount)
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v interface{}) {
updates := v.(client.Update).Val.(int64)
if updates != 1 {
t.Errorf("got updates %d, want 1", updates)
}
})
}
}

func TestMetadataLatency(t *testing.T) {
c := New([]string{"dev1"})

for _, path := range [][]string{
metadata.Path(metadata.LatencyAvg),
metadata.Path(metadata.LatencyMax),
Expand Down Expand Up @@ -444,6 +533,8 @@ func TestUpdateMetadata(t *testing.T) {
{metadata.Root, metadata.AddCount},
{metadata.Root, metadata.UpdateCount},
{metadata.Root, metadata.DelCount},
{metadata.Root, metadata.StaleCount},
{metadata.Root, metadata.SuppressedCount},
{metadata.Root, metadata.Connected},
{metadata.Root, metadata.Sync},
{metadata.Root, metadata.Size},
Expand Down Expand Up @@ -548,12 +639,65 @@ func TestIsDeleteTarget(t *testing.T) {
noti interface{}
want bool
}{
{"Update", client.Update{Path: client.Path{"a"}}, false},
{"Leaf delete", client.Delete{Path: client.Path{"a", "b"}}, false},
{"Target delete", client.Delete{Path: client.Path{"target"}}, true},
{"Gnmi Update", gnmiNotification("d", []string{}, []string{"a"}, 0, "", true), false},
{"Gnmi Delete", gnmiNotification("d", []string{}, []string{"a"}, 0, "", false), false},
{"Gnmi Target Delete", gnmiNotification("d", []string{}, []string{"*"}, 0, "", false), true},
{
name: "Update",
noti: client.Update{Path: client.Path{"a"}},
want: false,
},
{
name: "Leaf delete",
noti: client.Delete{Path: client.Path{"a", "b"}},
want: false,
},
{
name: "Target delete",
noti: client.Delete{Path: client.Path{"target"}},
want: true,
},
{
name: "GNMI Update",
noti: &gpb.Notification{
Prefix: &gpb.Path{Target: "d", Origin: "o"},
Update: []*gpb.Update{
{
Path: &gpb.Path{
Elem: []*gpb.PathElem{{Name: "p"}},
},
},
},
},
want: false,
},
{
name: "GNMI Leaf Delete",
noti: &gpb.Notification{
Prefix: &gpb.Path{Target: "d", Origin: "o"},
Delete: []*gpb.Path{{
Elem: []*gpb.PathElem{{Name: "p"}},
}},
},
want: false,
},
{
name: "GNMI Root Delete",
noti: &gpb.Notification{
Prefix: &gpb.Path{Target: "d", Origin: "o"},
Delete: []*gpb.Path{{
Elem: []*gpb.PathElem{{Name: "*"}},
}},
},
want: false,
},
{
name: "GNMI Target Delete",
noti: &gpb.Notification{
Prefix: &gpb.Path{Target: "d"},
Delete: []*gpb.Path{{
Elem: []*gpb.PathElem{{Name: "*"}},
}},
},
want: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down Expand Up @@ -1005,7 +1149,7 @@ func TestGNMIClient(t *testing.T) {
}
t.Run("remove target", func(t *testing.T) {
got = nil
want := deleteNoti("dev1", []string{"*"})
want := deleteNoti("dev1", "", []string{"*"})
want.Timestamp = 0
c.Remove("dev1")
if len(got) != 1 {
Expand Down
46 changes: 0 additions & 46 deletions client/gnmi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,52 +298,6 @@ func init() {
client.Register(Type, New)
}

// ProtoResponse converts client library Notification types into gNMI
// SubscribeResponse proto. An error is returned if any notifications have
// invalid paths or if update values can't be converted to gpb.TypedValue.
func ProtoResponse(notifs ...client.Notification) (*gpb.SubscribeResponse, error) {
n := new(gpb.Notification)

for _, nn := range notifs {
switch nn := nn.(type) {
case client.Update:
if n.Timestamp == 0 {
n.Timestamp = nn.TS.UnixNano()
}
pp, err := ygot.StringToPath(pathToString(nn.Path), ygot.StructuredPath, ygot.StringSlicePath)
if err != nil {
return nil, err
}
v, err := value.FromScalar(nn.Val)
if err != nil {
return nil, err
}

n.Update = append(n.Update, &gpb.Update{
Path: pp,
Val: v,
})

case client.Delete:
if n.Timestamp == 0 {
n.Timestamp = nn.TS.UnixNano()
}

pp, err := ygot.StringToPath(pathToString(nn.Path), ygot.StructuredPath, ygot.StringSlicePath)
if err != nil {
return nil, err
}
n.Delete = append(n.Delete, pp)

default:
return nil, fmt.Errorf("gnmi.ProtoResponse: unsupported type %T", nn)
}
}

resp := &gpb.SubscribeResponse{Response: &gpb.SubscribeResponse_Update{Update: n}}
return resp, nil
}

func pathToString(q client.Path) string {
qq := make(client.Path, len(q))
copy(qq, q)
Expand Down
Loading

0 comments on commit 084de23

Please sign in to comment.