Skip to content

Commit 91fd031

Browse files
authored
Merge pull request #188 from balajisundaravel/master
Project import generated by Copybara
2 parents 776b4d0 + 56523db commit 91fd031

31 files changed

+1283
-4796
lines changed

cache/cache.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type Target struct {
6767
ts time.Time // latest timestamp for an update
6868
excludedMeta stringset.Set // set of metadata not to generate update for
6969
futureThreshold time.Duration // how far in the future an update can be accepted
70+
eventDriven bool // whether to emulate event driven
7071
}
7172

7273
// Name returns the name of the target.
@@ -83,6 +84,7 @@ type options struct {
8384
serverName string
8485
excludedUpdateMeta stringset.Set
8586
futureThreshold time.Duration
87+
DisableEventDriven bool
8688
}
8789

8890
// Option defines the function prototype to set options for creating a Cache.
@@ -139,6 +141,16 @@ func WithFutureThreshold(futureThreshold time.Duration) Option {
139141
}
140142
}
141143

144+
// DisableEventDrivenEmulation returns an Option to disable event-driven
145+
// emulation. Note that it only disables event-driven emulation. If the
146+
// input data to Cache is already event-driven, this won't be able to
147+
// disable the event-driven nature of the original data.
148+
func DisableEventDrivenEmulation() Option {
149+
return func(o *options) {
150+
o.DisableEventDriven = true
151+
}
152+
}
153+
142154
// Cache is a structure holding state information for multiple targets.
143155
type Cache struct {
144156
opts options
@@ -287,6 +299,7 @@ func (c *Cache) Add(target string) *Target {
287299
lat: latency.New(c.opts.latencyWindows, latOpts),
288300
excludedMeta: c.opts.excludedUpdateMeta,
289301
futureThreshold: c.opts.futureThreshold,
302+
eventDriven: !c.opts.DisableEventDriven,
290303
}
291304
t.meta.SetStr(metadata.ServerName, c.opts.serverName)
292305
c.targets[target] = t
@@ -556,7 +569,7 @@ func (t *Target) gnmiUpdate(n *pb.Notification) (*ctree.Leaf, error) {
556569
}
557570
oldval.Update(n)
558571
// Simulate event-driven for all non-atomic updates.
559-
if !n.Atomic && value.Equal(old.Update[0].Val, n.Update[0].Val) {
572+
if !n.Atomic && value.Equal(old.Update[0].Val, n.Update[0].Val) && t.eventDriven {
560573
t.meta.AddInt(metadata.SuppressedCount, 1)
561574
return nil, nil
562575
}

cache/cache_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,54 @@ func TestMetadataSuppressed(t *testing.T) {
216216
}
217217
}
218218

219+
func TestMetadataSuppressedWithEventDrivenDisabled(t *testing.T) {
220+
c := New([]string{"dev1"}, DisableEventDrivenEmulation())
221+
// Unique values not suppressed.
222+
for i := 0; i < 10; i++ {
223+
c.GnmiUpdate(gnmiNotification("dev1", []string{"prefix", "path"}, []string{"update", "path"}, int64(i), strconv.Itoa(i), false))
224+
c.GetTarget("dev1").updateMeta(nil)
225+
path := metadata.Path(metadata.SuppressedCount)
226+
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v any) error {
227+
suppressedCount := v.(*pb.Notification).Update[0].Val.GetIntVal()
228+
if suppressedCount != 0 {
229+
t.Errorf("got suppressedCount = %d, want 0", suppressedCount)
230+
}
231+
return nil
232+
})
233+
path = metadata.Path(metadata.UpdateCount)
234+
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v any) error {
235+
updates := v.(*pb.Notification).Update[0].Val.GetIntVal()
236+
if updates != int64(i+1) {
237+
t.Errorf("got updates %d, want %d", updates, i+1)
238+
}
239+
return nil
240+
})
241+
}
242+
c.Reset("dev1")
243+
244+
// Duplicate values also not suppressed.
245+
for i := 0; i < 10; i++ {
246+
c.GnmiUpdate(gnmiNotification("dev1", []string{"prefix", "path"}, []string{"update", "path"}, int64(i), "same value", false))
247+
c.GetTarget("dev1").updateMeta(nil)
248+
path := metadata.Path(metadata.SuppressedCount)
249+
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v any) error {
250+
suppressedCount := v.(*pb.Notification).Update[0].Val.GetIntVal()
251+
if suppressedCount != 0 {
252+
t.Errorf("got suppressedCount = %d, want 0", suppressedCount)
253+
}
254+
return nil
255+
})
256+
path = metadata.Path(metadata.UpdateCount)
257+
c.Query("dev1", path, func(_ []string, _ *ctree.Leaf, v any) error {
258+
updates := v.(*pb.Notification).Update[0].Val.GetIntVal()
259+
if updates != int64(i+1) {
260+
t.Errorf("got updates %d, want %d", updates, i+1)
261+
}
262+
return nil
263+
})
264+
}
265+
}
266+
219267
func TestMetadataLatency(t *testing.T) {
220268
window := 2 * time.Second
221269
opt, _ := WithLatencyWindows([]string{"2s"}, 2*time.Second)

cli/cli.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ var (
4747
"p": "PROTO", "proto": "PROTO", "PROTO": "PROTO",
4848
"sp": "SHORTPROTO", "shortproto": "SHORTPROTO", "SHORTPROTO": "SHORTPROTO",
4949
}
50+
// Stub for testing.
51+
since = time.Since
5052
)
5153

5254
// Config is a type to hold parameters that affect how the cli sends and
@@ -66,11 +68,14 @@ type Config struct {
6668
// on - human readable timestamp according to layout
6769
// raw - int64 nanos since epoch
6870
// <FORMAT> - human readable timestamp according to <FORMAT>
69-
Timestamp string // Formatting of timestamp in result output.
70-
DisplaySize bool
71-
Latency bool // Show latency to client
72-
ClientTypes []string // List of client types to try.
73-
Location *time.Location // Location that time formatting uses in lieu of the local time zone.
71+
Timestamp string // Formatting of timestamp in result output.
72+
DisplaySize bool
73+
Latency bool // Show latency to client. For single DisplayType only.
74+
ClientTypes []string // List of client types to try.
75+
Location *time.Location // Location that time formatting uses in lieu of the local time zone.
76+
FilterDeletes bool // Filter out delete results. For single DisplayType only.
77+
FilterUpdates bool // Filter out update results. For single DisplayType only.
78+
FilterMinLatency time.Duration // Filter out results not meeting minimum latency. For single DisplayType only.
7479
}
7580

7681
// QueryType returns a client query type for t after trying aliases for the
@@ -160,14 +165,21 @@ func genHandler(cfg *Config) client.NotificationHandler {
160165
var buf bytes.Buffer // Reuse the same buffer in either case.
161166
iDisplay := func(p client.Path, v interface{}, ts time.Time) {
162167
buf.Reset()
168+
var latency time.Duration
169+
if cfg.Latency || cfg.FilterMinLatency > 0 {
170+
latency = since(ts)
171+
}
172+
if cfg.FilterMinLatency > 0 && latency < cfg.FilterMinLatency {
173+
return
174+
}
163175
buf.WriteString(strings.Join(p, cfg.Delimiter))
164176
buf.WriteString(fmt.Sprintf(", %v", v))
165177
t := formatTime(ts, cfg)
166178
if t != nil {
167179
buf.WriteString(fmt.Sprintf(", %v", t))
168180
}
169181
if cfg.Latency {
170-
buf.WriteString(fmt.Sprintf(", %s", time.Since(ts)))
182+
buf.WriteString(fmt.Sprintf(", %s", latency))
171183
}
172184
cfg.Display(buf.Bytes())
173185
}
@@ -176,9 +188,13 @@ func genHandler(cfg *Config) client.NotificationHandler {
176188
default:
177189
return fmt.Errorf("invalid type: %#v", v)
178190
case client.Update:
179-
iDisplay(v.Path, v.Val, v.TS)
191+
if !cfg.FilterUpdates {
192+
iDisplay(v.Path, v.Val, v.TS)
193+
}
180194
case client.Delete:
181-
iDisplay(v.Path, v.Val, v.TS)
195+
if !cfg.FilterDeletes {
196+
iDisplay(v.Path, v.Val, v.TS)
197+
}
182198
case client.Sync, client.Connected:
183199
case client.Error:
184200
return v

cli/cli_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,15 @@ func TestSendQueryAndDisplay(t *testing.T) {
7171
display := func(b []byte) {
7272
displayOut += string(b) + "\n"
7373
}
74+
now := time.Unix(300, 0)
7475
tests := []struct {
7576
desc string
7677
updates []*fpb.Value
7778
query client.Query
7879
cfg Config
7980
want string
8081
sort bool
82+
since func(time.Time) time.Duration
8183
}{{
8284
desc: "single target single output with provided layout",
8385
updates: []*fpb.Value{
@@ -271,6 +273,84 @@ func TestSendQueryAndDisplay(t *testing.T) {
271273
want: `dev1/a/b, 5
272274
dev1/a/c, 5
273275
dev1/a/c, <nil>
276+
`,
277+
}, {
278+
desc: "single target single output with FilterMinLatency",
279+
updates: []*fpb.Value{
280+
{Path: []string{"a", "b"}, Value: &fpb.Value_IntValue{IntValue: &fpb.IntValue{Value: 5}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: now.Add(-62 * time.Second).UnixNano()}},
281+
{Path: []string{"a", "c"}, Value: &fpb.Value_IntValue{IntValue: &fpb.IntValue{Value: 5}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: now.Add(-59 * time.Second).UnixNano()}},
282+
{Path: []string{"a", "d"}, Value: &fpb.Value_IntValue{IntValue: &fpb.IntValue{Value: 7}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: now.Add(-61 * time.Second).UnixNano()}},
283+
{Path: []string{"a", "e"}, Value: &fpb.Value_Delete{Delete: &fpb.DeleteValue{}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: now.Add(-60 * time.Second).UnixNano()}},
284+
{Path: []string{"a", "f"}, Value: &fpb.Value_Delete{Delete: &fpb.DeleteValue{}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: now.Add(-59 * time.Second).UnixNano()}},
285+
{Value: &fpb.Value_Sync{Sync: 1}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: now.Add(-59 * time.Second).UnixNano()}},
286+
},
287+
query: client.Query{
288+
Target: "dev1",
289+
Queries: []client.Path{{"a"}},
290+
Type: client.Once,
291+
TLS: &tls.Config{InsecureSkipVerify: true},
292+
},
293+
cfg: Config{
294+
Delimiter: "/",
295+
Display: display,
296+
DisplayPrefix: "",
297+
DisplayIndent: " ",
298+
DisplayType: "single",
299+
FilterMinLatency: time.Minute,
300+
},
301+
since: func(ts time.Time) time.Duration { return now.Sub(ts) },
302+
want: `dev1/a/b, 5
303+
dev1/a/d, 7
304+
dev1/a/e, <nil>
305+
`,
306+
}, {
307+
desc: "single target single output with FilterDeletes",
308+
updates: []*fpb.Value{
309+
{Path: []string{"a", "b"}, Value: &fpb.Value_IntValue{IntValue: &fpb.IntValue{Value: 5}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 100}},
310+
{Path: []string{"a", "c"}, Value: &fpb.Value_Delete{Delete: &fpb.DeleteValue{}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 101}},
311+
{Path: []string{"a", "d"}, Value: &fpb.Value_IntValue{IntValue: &fpb.IntValue{Value: 7}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 102}},
312+
{Value: &fpb.Value_Sync{Sync: 1}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 103}},
313+
},
314+
query: client.Query{
315+
Target: "dev1",
316+
Queries: []client.Path{{"a"}},
317+
Type: client.Once,
318+
TLS: &tls.Config{InsecureSkipVerify: true},
319+
},
320+
cfg: Config{
321+
Delimiter: "/",
322+
Display: display,
323+
DisplayPrefix: "",
324+
DisplayIndent: " ",
325+
DisplayType: "single",
326+
FilterDeletes: true,
327+
},
328+
want: `dev1/a/b, 5
329+
dev1/a/d, 7
330+
`,
331+
}, {
332+
desc: "single target single output with FilterUpdates",
333+
updates: []*fpb.Value{
334+
{Path: []string{"a", "b"}, Value: &fpb.Value_IntValue{IntValue: &fpb.IntValue{Value: 5}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 100}},
335+
{Path: []string{"a", "c"}, Value: &fpb.Value_Delete{Delete: &fpb.DeleteValue{}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 101}},
336+
{Path: []string{"a", "d"}, Value: &fpb.Value_IntValue{IntValue: &fpb.IntValue{Value: 7}}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 102}},
337+
{Value: &fpb.Value_Sync{Sync: 1}, Repeat: 1, Timestamp: &fpb.Timestamp{Timestamp: 103}},
338+
},
339+
query: client.Query{
340+
Target: "dev1",
341+
Queries: []client.Path{{"a"}},
342+
Type: client.Once,
343+
TLS: &tls.Config{InsecureSkipVerify: true},
344+
},
345+
cfg: Config{
346+
Delimiter: "/",
347+
Display: display,
348+
DisplayPrefix: "",
349+
DisplayIndent: " ",
350+
DisplayType: "single",
351+
FilterUpdates: true,
352+
},
353+
want: `dev1/a/c, <nil>
274354
`,
275355
}, {
276356
desc: "single target multiple paths",
@@ -717,6 +797,10 @@ sync_response: true
717797
}
718798
for _, tt := range tests {
719799
t.Run(tt.desc, func(t *testing.T) {
800+
if tt.since != nil {
801+
since = tt.since
802+
defer func() { since = time.Since }()
803+
}
720804
displayOut = ""
721805
s, err := gnmi.New(
722806
&fpb.Config{

cmd/gnmi_cli/gnmi_cli.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ func init() {
101101
flag.StringVar(&cfg.Timestamp, "timestamp", "", "Specify timestamp formatting in output. One of (<empty string>, on, raw, <FORMAT>) where <empty string> is disabled, on is human readable, raw is int64 nanos since epoch, and <FORMAT> is according to golang time.Format(<FORMAT>)")
102102
flag.BoolVar(&cfg.DisplaySize, "display_size", false, "Display the total size of query response.")
103103
flag.BoolVar(&cfg.Latency, "latency", false, "Display the latency for receiving each update (Now - update timestamp).")
104+
flag.DurationVar(&cfg.FilterMinLatency, "filter_min_latency", 0, "Filter out results with latency < the specified minium latency. No filtering if 0. Works with single display type only.")
105+
flag.BoolVar(&cfg.FilterDeletes, "filter_deletes", false, "Filter out delete results. Works with single display type only.")
106+
flag.BoolVar(&cfg.FilterUpdates, "filter_updates", false, "Filter out update results. Works with single display type only.")
104107
flag.StringVar(&q.TLS.ServerName, "server_name", "", "When set, CLI will use this hostname to verify server certificate during TLS handshake.")
105108
flag.BoolVar(&q.TLS.InsecureSkipVerify, "tls_skip_verify", false, "When set, CLI will not verify the server certificate during TLS handshake.")
106109

@@ -118,6 +121,9 @@ func init() {
118121
flag.DurationVar(&cfg.PollingInterval, "pi", cfg.PollingInterval, "Short for polling_interval.")
119122
flag.BoolVar(&cfg.DisplaySize, "ds", cfg.DisplaySize, "Short for display_size.")
120123
flag.BoolVar(&cfg.Latency, "l", cfg.Latency, "Short for latency.")
124+
flag.DurationVar(&cfg.FilterMinLatency, "flml", 0, "Short for filter_min_latency.")
125+
flag.BoolVar(&cfg.FilterDeletes, "fld", false, "Short for filter_deletes.")
126+
flag.BoolVar(&cfg.FilterUpdates, "flu", false, "Short for filter_updates.")
121127
flag.StringVar(reqProto, "p", *reqProto, "Short for request proto.")
122128
}
123129

go.mod

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
11
module github.com/openconfig/gnmi
22

3-
go 1.21
4-
5-
toolchain go1.22.5
3+
go 1.22.0
64

75
require (
86
bitbucket.org/creachadair/stringset v0.0.14
9-
github.com/cenkalti/backoff/v4 v4.1.1
10-
github.com/golang/glog v1.2.1
7+
github.com/cenkalti/backoff/v4 v4.3.0
8+
github.com/golang/glog v1.2.2
119
github.com/google/go-cmp v0.6.0
1210
github.com/kylelemons/godebug v1.1.0
13-
github.com/openconfig/grpctunnel v0.0.0-20220819142823-6f5422b8ca70
14-
github.com/openconfig/ygot v0.6.0
15-
github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a
16-
golang.org/x/crypto v0.26.0
17-
golang.org/x/net v0.28.0
18-
google.golang.org/grpc v1.66.0
19-
google.golang.org/protobuf v1.34.2
11+
github.com/openconfig/grpctunnel v0.1.0
12+
github.com/openconfig/ygot v0.29.20
13+
github.com/protocolbuffers/txtpbfmt v0.0.0-20240823084532-8e6b51fa9bef
14+
golang.org/x/crypto v0.28.0
15+
golang.org/x/net v0.30.0
16+
google.golang.org/grpc v1.67.1
17+
google.golang.org/protobuf v1.35.1
2018
)
2119

2220
require (
23-
github.com/golang/protobuf v1.5.4 // indirect
2421
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
25-
github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea // indirect
26-
golang.org/x/sys v0.24.0 // indirect
27-
golang.org/x/term v0.23.0 // indirect
28-
golang.org/x/text v0.17.0 // indirect
29-
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect
22+
github.com/openconfig/goyang v1.6.0 // indirect
23+
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
24+
golang.org/x/sys v0.26.0 // indirect
25+
golang.org/x/term v0.25.0 // indirect
26+
golang.org/x/text v0.19.0 // indirect
27+
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
3028
)

0 commit comments

Comments
 (0)