Skip to content

Commit

Permalink
Improve readme and add more examples (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanG100 authored Jun 15, 2022
1 parent 4d93b87 commit 785bb4b
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# ygnmi
![Build Status](https://github.com/openconfig/ygnmi/workflows/Go/badge.svg?branch=main)
[![Coverage Status](https://coveralls.io/repos/github/openconfig/ygnmi/badge.svg?branch=main)](https://coveralls.io/github/openconfig/ygnmi?branch=main)
[![Go Reference](https://pkg.go.dev/badge/github.com/openconfig/ygnmi.svg)](https://pkg.go.dev/github.com/openconfig/ygnmi)
## Introduction

ygnmi is a A Go gNMI client library based on [ygot](github.com/openconfig/ygot)-generated code. It includes a generator whose input is a set of YANG modules and output is ygot Go structs and a path library that can be used for making gNMI queries.
Expand All @@ -22,3 +25,40 @@ Not all ygot generator flags are supported by ygnmi. Notably ygnmi makes two imp
2. PreferOperationState is selected.

Note: the supported flags may evolve over time to include these options.

### Output

Calling the generation with `--base_import_path=<somepath>/exampleoc` flag will output:

* exampleoc
* This package contains the structs, enums, unions, and schema.
* These correspond to **values** that can be returned or set.
* exampleoc/\<module\>
* For every YANG module (that defines at least one container) one Go package is generated.
* Each package contains PathStructs: structs that represent a gNMI **path** that can queried or set.
* Each PathStruct has a State() method that returns Query for path. It may also have a Config() method.
* exampleoc/root
* This package contains a special "fakeroot" struct.
* It is called the fakeroot because there is no YANG container that corresponds to this struct.
* The package also contains a batch struct.

## gNMI Client Library

The ygnmi client library uses the generated code to perform schema compliant subscriptions and set gNMI RPCs.

### Queries

The ygnmi library uses generic queries to represent a gNMI path, the value type, and schema. Queries should never be constructed directly.
Instead, they are returned by calling .Config() or .State() on the generated code. There are several query types that allow type safety when running an operation.
The relationship of the query types is:

![Query Diagram](doc/queries.svg)

* Singleton: Lookup, Get, Watch, Await, Collect
* Config: Update, Replace, Delete, BatchUpdate, BatchReplace, BatchDelete
* Wildcard: LookupAll, GetAll, WatchAll, CollectAll

## Additional Reference

* See [ygot](github.com/openconfig/ygot) for more information on how YANG is mapped to Go code.
* See [gNMI](github.com/openconfig/gnmi) and [gNMI Reference](https://github.com/openconfig/reference/tree/master/rpc/gnmi) for more information on the gNMI protocol and spec.
1 change: 1 addition & 0 deletions doc/queries.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
177 changes: 177 additions & 0 deletions ygnmi/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,183 @@ func ExampleCollect() {
fmt.Printf("Got %d values", len(vals))
}

func ExampleLookupAll() {
c := initClient()
// LookupAll on the keys of the SingleKey list
path := root.New().Model().SingleKeyAny().Value()
vals, err := ygnmi.LookupAll(context.Background(), c, path.State())
if err != nil {
log.Fatalf("Failed to Lookup: %v", err)
}

// Get the value of each list element.
for _, val := range vals {
if listVal, ok := val.Val(); ok {
fmt.Printf("Got List Val %v at path %v", listVal, val.Path)
}
}
}

func ExampleGetAll() {
c := initClient()
// Get on the keys of the SingleKey list
path := root.New().Model().SingleKeyAny().Value()
vals, err := ygnmi.GetAll(context.Background(), c, path.State())
if err != nil {
log.Fatalf("Failed to Lookup: %v", err)
}

// Get the value of each list element.
// With GetAll it is impossible to know the path associated with a value,
// so use LookupAll or Batch with with wildcard path instead.
for _, val := range vals {
fmt.Printf("Got List Val %v", val)
}
}

func ExampleWatchAll() {
c := initClient()
// Watch a path
path := root.New().Model().SingleKeyAny().Value()
// Use a context with a timeout, so that Watch doesn't continue indefinitely.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

var gotKeyFoo, gotKeyBar bool
// Watch list values and wait for 2 different list elements to be equal to 10.
// Set ExampleWatch_batch for another way to use Watch will a wildcard query.
watcher := ygnmi.WatchAll(ctx, c, path.State(), func(v *ygnmi.Value[int64]) error {
if val, ok := v.Val(); ok {
gotKeyFoo = gotKeyFoo || v.Path.GetElem()[2].Key["key"] == "foo" && val == 10
gotKeyBar = gotKeyBar || v.Path.GetElem()[2].Key["key"] == "bar" && val == 10
}

// Return nil to indicate success and stop evaluating predicate.
if gotKeyFoo && gotKeyBar {
return nil
}
// Return ygnmi.Continue to continue evaluating the func.
return ygnmi.Continue
})
// Child is the last value received.
child, err := watcher.Await()
if err != nil {
log.Fatalf("Failed to Watch: %v", err)
}
// Check if the value is present.
val, ok := child.Val()
if !ok {
log.Fatal("No value at path")
}
fmt.Printf("Last Value: %v\n", val)
}

func ExampleCollectAll() {
c := initClient()
path := root.New().Model().SingleKeyAny().Value()

// Use a context with a timeout, so that Collect doesn't continue indefinitely.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

// Collect values at path until context deadline is reached.
collector := ygnmi.CollectAll(ctx, c, path.State())
vals, err := collector.Await()
if err != nil {
log.Fatalf("Failed to Collect: %v", err)
}

fmt.Printf("Got %d values", len(vals))
}

func ExampleGet_batch() {
c := initClient()
// Create a new batch object and add paths to it.
b := new(root.Batch)
// Note: the AddPaths accepts paths not queries.
b.AddPaths(root.New().RemoteContainer().ALeaf(), root.New().Model().SingleKeyAny().Value())
// Get the values of the paths in the paths, a Batch Query always returns the root object.
val, err := ygnmi.Get(context.Background(), c, b.State())
if err != nil {
log.Fatalf("Get failed: %v", err)
}
fmt.Printf("Got aLeaf %v, SingleKey[foo]: %v", val.GetRemoteContainer().GetALeaf(), val.GetModel().GetSingleKey("foo").GetValue())

}

func ExampleWatch_batch() {
c := initClient()
// Create a new batch object and add paths to it.
b := new(root.Batch)
// Note: the AddPaths accepts paths not queries.
b.AddPaths(root.New().RemoteContainer().ALeaf(), root.New().Model().SingleKeyAny().Value())
// Watch all input path until they meet the desired condition.
_, err := ygnmi.Watch(context.Background(), c, b.State(), func(v *ygnmi.Value[*exampleoc.Root]) error {
val, ok := v.Val()
if !ok {
return ygnmi.Continue
}
if val.GetModel().GetSingleKey("foo").GetValue() == 1 && val.GetModel().GetSingleKey("bar").GetValue() == 2 &&
val.GetRemoteContainer().GetALeaf() == "string" {

return nil
}
return ygnmi.Continue
}).Await()
if err != nil {
log.Fatalf("Failed to Watch: %v", err)
}
}

func ExampleUpdate() {
c := initClient()
// Perform the Update request.
res, err := ygnmi.Update(context.Background(), c, root.New().Parent().Child().Three().Config(), exampleoc.Child_Three_TWO)
if err != nil {
log.Fatalf("Update failed: %v", err)
}
fmt.Printf("Timestamp: %v", res.Timestamp)
}

func ExampleReplace() {
c := initClient()
// Perform the Replace request.
res, err := ygnmi.Replace(context.Background(), c, root.New().RemoteContainer().Config(), &exampleoc.RemoteContainer{ALeaf: ygot.String("foo")})
if err != nil {
log.Fatalf("Update failed: %v", err)
}
fmt.Printf("Timestamp: %v", res.Timestamp)
}

func ExampleDelete() {
c := initClient()
// Perform the Update request.
res, err := ygnmi.Delete(context.Background(), c, root.New().Parent().Child().One().Config())
if err != nil {
log.Fatalf("Delete failed: %v", err)
}
fmt.Printf("Timestamp: %v", res.Timestamp)
}

func ExampleSetBatch_Set() {
c := initClient()
b := new(ygnmi.SetBatch)

// Add set operations to a batch request.
ygnmi.BatchUpdate(b, root.New().Parent().Child().Three().Config(), exampleoc.Child_Three_TWO)
ygnmi.BatchDelete(b, root.New().Parent().Child().One().Config())
ygnmi.BatchReplace(b, root.New().RemoteContainer().Config(), &exampleoc.RemoteContainer{ALeaf: ygot.String("foo")})

// Perform the gnmi Set request.
res, err := b.Set(context.Background(), c)
if err != nil {
log.Fatalf("Set failed: %v", err)
}
fmt.Printf("Timestamp: %v", res.Timestamp)

root.New().Model().SingleKeyAny().Config()
}

func initClient() *ygnmi.Client {
// Initialize the client here.
return nil
Expand Down

0 comments on commit 785bb4b

Please sign in to comment.