560305f601
- uses newer version of go-ethereum required for go1.11
194 lines
4.8 KiB
Go
194 lines
4.8 KiB
Go
// 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 gnmi
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"time"
|
|
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
"github.com/aristanetworks/goarista/netns"
|
|
pb "github.com/openconfig/gnmi/proto/gnmi"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
"google.golang.org/grpc/metadata"
|
|
)
|
|
|
|
const (
|
|
defaultPort = "6030"
|
|
)
|
|
|
|
// Config is the gnmi.Client config
|
|
type Config struct {
|
|
Addr string
|
|
CAFile string
|
|
CertFile string
|
|
KeyFile string
|
|
Password string
|
|
Username string
|
|
TLS bool
|
|
}
|
|
|
|
// SubscribeOptions is the gNMI subscription request options
|
|
type SubscribeOptions struct {
|
|
UpdatesOnly bool
|
|
Prefix string
|
|
Mode string
|
|
StreamMode string
|
|
SampleInterval uint64
|
|
HeartbeatInterval uint64
|
|
Paths [][]string
|
|
}
|
|
|
|
// Dial connects to a gnmi service and returns a client
|
|
func Dial(cfg *Config) (pb.GNMIClient, error) {
|
|
var opts []grpc.DialOption
|
|
if cfg.TLS || cfg.CAFile != "" || cfg.CertFile != "" {
|
|
tlsConfig := &tls.Config{}
|
|
if cfg.CAFile != "" {
|
|
b, err := ioutil.ReadFile(cfg.CAFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cp := x509.NewCertPool()
|
|
if !cp.AppendCertsFromPEM(b) {
|
|
return nil, fmt.Errorf("credentials: failed to append certificates")
|
|
}
|
|
tlsConfig.RootCAs = cp
|
|
} else {
|
|
tlsConfig.InsecureSkipVerify = true
|
|
}
|
|
if cfg.CertFile != "" {
|
|
if cfg.KeyFile == "" {
|
|
return nil, fmt.Errorf("please provide both -certfile and -keyfile")
|
|
}
|
|
cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
}
|
|
opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
|
|
} else {
|
|
opts = append(opts, grpc.WithInsecure())
|
|
}
|
|
|
|
if !strings.ContainsRune(cfg.Addr, ':') {
|
|
cfg.Addr += ":" + defaultPort
|
|
}
|
|
|
|
dial := func(addrIn string, time time.Duration) (net.Conn, error) {
|
|
var conn net.Conn
|
|
nsName, addr, err := netns.ParseAddress(addrIn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = netns.Do(nsName, func() error {
|
|
var err error
|
|
conn, err = net.Dial("tcp", addr)
|
|
return err
|
|
})
|
|
return conn, err
|
|
}
|
|
|
|
opts = append(opts,
|
|
grpc.WithDialer(dial),
|
|
|
|
// Allows received protobuf messages to be larger than 4MB
|
|
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(math.MaxInt32)),
|
|
)
|
|
grpcconn, err := grpc.Dial(cfg.Addr, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to dial: %s", err)
|
|
}
|
|
return pb.NewGNMIClient(grpcconn), nil
|
|
}
|
|
|
|
// NewContext returns a new context with username and password
|
|
// metadata if they are set in cfg.
|
|
func NewContext(ctx context.Context, cfg *Config) context.Context {
|
|
if cfg.Username != "" {
|
|
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
|
|
"username", cfg.Username,
|
|
"password", cfg.Password))
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
// NewGetRequest returns a GetRequest for the given paths
|
|
func NewGetRequest(paths [][]string) (*pb.GetRequest, error) {
|
|
req := &pb.GetRequest{
|
|
Path: make([]*pb.Path, len(paths)),
|
|
}
|
|
for i, p := range paths {
|
|
gnmiPath, err := ParseGNMIElements(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Path[i] = gnmiPath
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
// NewSubscribeRequest returns a SubscribeRequest for the given paths
|
|
func NewSubscribeRequest(subscribeOptions *SubscribeOptions) (*pb.SubscribeRequest, error) {
|
|
var mode pb.SubscriptionList_Mode
|
|
switch subscribeOptions.Mode {
|
|
case "once":
|
|
mode = pb.SubscriptionList_ONCE
|
|
case "poll":
|
|
mode = pb.SubscriptionList_POLL
|
|
case "stream":
|
|
mode = pb.SubscriptionList_STREAM
|
|
default:
|
|
return nil, fmt.Errorf("subscribe mode (%s) invalid", subscribeOptions.Mode)
|
|
}
|
|
|
|
var streamMode pb.SubscriptionMode
|
|
switch subscribeOptions.StreamMode {
|
|
case "on_change":
|
|
streamMode = pb.SubscriptionMode_ON_CHANGE
|
|
case "sample":
|
|
streamMode = pb.SubscriptionMode_SAMPLE
|
|
case "target_defined":
|
|
streamMode = pb.SubscriptionMode_TARGET_DEFINED
|
|
default:
|
|
return nil, fmt.Errorf("subscribe stream mode (%s) invalid", subscribeOptions.StreamMode)
|
|
}
|
|
|
|
prefixPath, err := ParseGNMIElements(SplitPath(subscribeOptions.Prefix))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subList := &pb.SubscriptionList{
|
|
Subscription: make([]*pb.Subscription, len(subscribeOptions.Paths)),
|
|
Mode: mode,
|
|
UpdatesOnly: subscribeOptions.UpdatesOnly,
|
|
Prefix: prefixPath,
|
|
}
|
|
for i, p := range subscribeOptions.Paths {
|
|
gnmiPath, err := ParseGNMIElements(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subList.Subscription[i] = &pb.Subscription{
|
|
Path: gnmiPath,
|
|
Mode: streamMode,
|
|
SampleInterval: subscribeOptions.SampleInterval,
|
|
HeartbeatInterval: subscribeOptions.HeartbeatInterval,
|
|
}
|
|
}
|
|
return &pb.SubscribeRequest{Request: &pb.SubscribeRequest_Subscribe{
|
|
Subscribe: subList}}, nil
|
|
}
|