2018-01-29 19:44:18 +00:00
|
|
|
// Copyright (c) 2017 Arista Networks, Inc.
|
|
|
|
// Use of this source code is governed by the Apache License 2.0
|
|
|
|
// that can be found in the COPYING file.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2018-09-05 15:36:14 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2018-01-29 19:44:18 +00:00
|
|
|
|
|
|
|
"github.com/aristanetworks/goarista/gnmi"
|
|
|
|
|
|
|
|
"github.com/aristanetworks/glog"
|
|
|
|
pb "github.com/openconfig/gnmi/proto/gnmi"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO: Make this more clear
|
|
|
|
var help = `Usage of gnmi:
|
2018-09-05 15:36:14 +00:00
|
|
|
gnmi -addr [<VRF-NAME>/]ADDRESS:PORT [options...]
|
2018-01-29 19:44:18 +00:00
|
|
|
capabilities
|
|
|
|
get PATH+
|
|
|
|
subscribe PATH+
|
2018-09-05 15:36:14 +00:00
|
|
|
((update|replace (origin=ORIGIN) PATH JSON|FILE)|(delete (origin=ORIGIN) PATH))+
|
2018-01-29 19:44:18 +00:00
|
|
|
`
|
|
|
|
|
2018-09-05 15:36:14 +00:00
|
|
|
func usageAndExit(s string) {
|
2018-01-29 19:44:18 +00:00
|
|
|
flag.Usage()
|
2018-09-05 15:36:14 +00:00
|
|
|
if s != "" {
|
|
|
|
fmt.Fprintln(os.Stderr, s)
|
|
|
|
}
|
2018-01-29 19:44:18 +00:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
cfg := &gnmi.Config{}
|
2018-09-05 15:36:14 +00:00
|
|
|
flag.StringVar(&cfg.Addr, "addr", "", "Address of gNMI gRPC server with optional VRF name")
|
2018-01-29 19:44:18 +00:00
|
|
|
flag.StringVar(&cfg.CAFile, "cafile", "", "Path to server TLS certificate file")
|
|
|
|
flag.StringVar(&cfg.CertFile, "certfile", "", "Path to client TLS certificate file")
|
|
|
|
flag.StringVar(&cfg.KeyFile, "keyfile", "", "Path to client TLS private key file")
|
|
|
|
flag.StringVar(&cfg.Password, "password", "", "Password to authenticate with")
|
|
|
|
flag.StringVar(&cfg.Username, "username", "", "Username to authenticate with")
|
|
|
|
flag.BoolVar(&cfg.TLS, "tls", false, "Enable TLS")
|
|
|
|
|
2018-09-05 15:36:14 +00:00
|
|
|
subscribeOptions := &gnmi.SubscribeOptions{}
|
|
|
|
flag.StringVar(&subscribeOptions.Prefix, "prefix", "", "Subscribe prefix path")
|
|
|
|
flag.BoolVar(&subscribeOptions.UpdatesOnly, "updates_only", false,
|
|
|
|
"Subscribe to updates only (false | true)")
|
|
|
|
flag.StringVar(&subscribeOptions.Mode, "mode", "stream",
|
|
|
|
"Subscribe mode (stream | once | poll)")
|
|
|
|
flag.StringVar(&subscribeOptions.StreamMode, "stream_mode", "target_defined",
|
|
|
|
"Subscribe stream mode, only applies for stream subscriptions "+
|
|
|
|
"(target_defined | on_change | sample)")
|
|
|
|
sampleIntervalStr := flag.String("sample_interval", "0", "Subscribe sample interval, "+
|
|
|
|
"only applies for sample subscriptions (400ms, 2.5s, 1m, etc.)")
|
|
|
|
heartbeatIntervalStr := flag.String("heartbeat_interval", "0", "Subscribe heartbeat "+
|
|
|
|
"interval, only applies for on-change subscriptions (400ms, 2.5s, 1m, etc.)")
|
|
|
|
|
2018-01-29 19:44:18 +00:00
|
|
|
flag.Usage = func() {
|
|
|
|
fmt.Fprintln(os.Stderr, help)
|
|
|
|
flag.PrintDefaults()
|
|
|
|
}
|
|
|
|
flag.Parse()
|
|
|
|
if cfg.Addr == "" {
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit("error: address not specified")
|
|
|
|
}
|
|
|
|
|
|
|
|
var sampleInterval, heartbeatInterval time.Duration
|
|
|
|
var err error
|
|
|
|
if sampleInterval, err = time.ParseDuration(*sampleIntervalStr); err != nil {
|
|
|
|
usageAndExit(fmt.Sprintf("error: sample interval (%s) invalid", *sampleIntervalStr))
|
|
|
|
}
|
|
|
|
subscribeOptions.SampleInterval = uint64(sampleInterval)
|
|
|
|
if heartbeatInterval, err = time.ParseDuration(*heartbeatIntervalStr); err != nil {
|
|
|
|
usageAndExit(fmt.Sprintf("error: heartbeat interval (%s) invalid", *heartbeatIntervalStr))
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
2018-09-05 15:36:14 +00:00
|
|
|
subscribeOptions.HeartbeatInterval = uint64(heartbeatInterval)
|
2018-01-29 19:44:18 +00:00
|
|
|
|
|
|
|
args := flag.Args()
|
|
|
|
|
|
|
|
ctx := gnmi.NewContext(context.Background(), cfg)
|
2018-09-05 15:36:14 +00:00
|
|
|
client, err := gnmi.Dial(cfg)
|
|
|
|
if err != nil {
|
|
|
|
glog.Fatal(err)
|
|
|
|
}
|
2018-01-29 19:44:18 +00:00
|
|
|
|
|
|
|
var setOps []*gnmi.Operation
|
|
|
|
for i := 0; i < len(args); i++ {
|
|
|
|
switch args[i] {
|
|
|
|
case "capabilities":
|
|
|
|
if len(setOps) != 0 {
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit("error: 'capabilities' not allowed after 'merge|replace|delete'")
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
err := gnmi.Capabilities(ctx, client)
|
|
|
|
if err != nil {
|
|
|
|
glog.Fatal(err)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case "get":
|
|
|
|
if len(setOps) != 0 {
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit("error: 'get' not allowed after 'merge|replace|delete'")
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
err := gnmi.Get(ctx, client, gnmi.SplitPaths(args[i+1:]))
|
|
|
|
if err != nil {
|
|
|
|
glog.Fatal(err)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case "subscribe":
|
|
|
|
if len(setOps) != 0 {
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit("error: 'subscribe' not allowed after 'merge|replace|delete'")
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
respChan := make(chan *pb.SubscribeResponse)
|
|
|
|
errChan := make(chan error)
|
|
|
|
defer close(errChan)
|
2018-09-05 15:36:14 +00:00
|
|
|
subscribeOptions.Paths = gnmi.SplitPaths(args[i+1:])
|
|
|
|
go gnmi.Subscribe(ctx, client, subscribeOptions, respChan, errChan)
|
2018-01-29 19:44:18 +00:00
|
|
|
for {
|
|
|
|
select {
|
2018-09-05 15:36:14 +00:00
|
|
|
case resp, open := <-respChan:
|
|
|
|
if !open {
|
|
|
|
return
|
|
|
|
}
|
2018-01-29 19:44:18 +00:00
|
|
|
if err := gnmi.LogSubscribeResponse(resp); err != nil {
|
2018-09-05 15:36:14 +00:00
|
|
|
glog.Fatal(err)
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
case err := <-errChan:
|
2018-09-05 15:36:14 +00:00
|
|
|
glog.Fatal(err)
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case "update", "replace", "delete":
|
|
|
|
if len(args) == i+1 {
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit("error: missing path")
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
op := &gnmi.Operation{
|
|
|
|
Type: args[i],
|
|
|
|
}
|
|
|
|
i++
|
2018-09-05 15:36:14 +00:00
|
|
|
if strings.HasPrefix(args[i], "origin=") {
|
|
|
|
op.Origin = strings.TrimPrefix(args[i], "origin=")
|
|
|
|
i++
|
|
|
|
}
|
2018-01-29 19:44:18 +00:00
|
|
|
op.Path = gnmi.SplitPath(args[i])
|
|
|
|
if op.Type != "delete" {
|
|
|
|
if len(args) == i+1 {
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit("error: missing JSON or FILEPATH to data")
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
i++
|
|
|
|
op.Val = args[i]
|
|
|
|
}
|
|
|
|
setOps = append(setOps, op)
|
|
|
|
default:
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit(fmt.Sprintf("error: unknown operation %q", args[i]))
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(setOps) == 0 {
|
2018-09-05 15:36:14 +00:00
|
|
|
usageAndExit("")
|
2018-01-29 19:44:18 +00:00
|
|
|
}
|
2018-09-05 15:36:14 +00:00
|
|
|
err = gnmi.Set(ctx, client, setOps)
|
2018-01-29 19:44:18 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|