github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/openconfig/client/client.go (about)

     1  // Copyright (c) 2016 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  // Package client provides helper functions for OpenConfig CLI tools.
     6  package client
     7  
     8  import (
     9  	"io"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/aristanetworks/glog"
    14  	"github.com/openconfig/gnmi/proto/gnmi"
    15  
    16  	"golang.org/x/net/context"
    17  	"google.golang.org/grpc"
    18  	"google.golang.org/grpc/metadata"
    19  	"google.golang.org/protobuf/proto"
    20  )
    21  
    22  const defaultPort = "6030"
    23  
    24  // PublishFunc is the method to publish responses
    25  type PublishFunc func(addr string, message proto.Message)
    26  
    27  // Client is a connected gRPC client
    28  type Client struct {
    29  	client gnmi.GNMIClient
    30  	ctx    context.Context
    31  	device string
    32  }
    33  
    34  // New creates a new gRPC client and connects it
    35  func New(username, password, addr string, opts []grpc.DialOption) *Client {
    36  	device := addr
    37  	if !strings.ContainsRune(addr, ':') {
    38  		addr += ":" + defaultPort
    39  	}
    40  	// Make sure we don't move past the grpc.Dial() call until we actually
    41  	// established an HTTP/2 connection successfully.
    42  	opts = append(opts, grpc.WithBlock())
    43  	conn, err := grpc.Dial(addr, opts...)
    44  	if err != nil {
    45  		glog.Fatalf("Failed to dial: %s", err)
    46  	}
    47  	glog.Infof("Connected to %s", addr)
    48  	client := gnmi.NewGNMIClient(conn)
    49  
    50  	ctx := context.Background()
    51  	if username != "" {
    52  		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
    53  			"username", username,
    54  			"password", password))
    55  	}
    56  	return &Client{
    57  		client: client,
    58  		device: device,
    59  		ctx:    ctx,
    60  	}
    61  }
    62  
    63  // Get sends a get request and returns the responses
    64  func (c *Client) Get(path string) []*gnmi.Notification {
    65  	req := &gnmi.GetRequest{
    66  		Path: []*gnmi.Path{
    67  			{
    68  				Element: strings.Split(path, "/"),
    69  			},
    70  		},
    71  	}
    72  	response, err := c.client.Get(c.ctx, req)
    73  	if err != nil {
    74  		glog.Fatalf("Get failed: %s", err)
    75  	}
    76  	return response.Notification
    77  }
    78  
    79  // Subscribe sends subscriptions, and consumes responses.
    80  // The given publish function is used to publish SubscribeResponses received
    81  // for the given subscriptions, when connected to the given host, with the
    82  // given user/pass pair, or the client-side cert specified in the gRPC opts.
    83  // This function does not normally return so it should probably be run in its
    84  // own goroutine.  When this function returns, the given WaitGroup is marked
    85  // as done.
    86  func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string,
    87  	publish PublishFunc) {
    88  	defer wg.Done()
    89  	stream, err := c.client.Subscribe(c.ctx)
    90  	if err != nil {
    91  		glog.Fatalf("Subscribe failed: %s", err)
    92  	}
    93  	defer stream.CloseSend()
    94  
    95  	for _, path := range subscriptions {
    96  		sub := &gnmi.SubscribeRequest{
    97  			Request: &gnmi.SubscribeRequest_Subscribe{
    98  				Subscribe: &gnmi.SubscriptionList{
    99  					Subscription: []*gnmi.Subscription{
   100  						{
   101  							Path: &gnmi.Path{Element: strings.Split(path, "/")},
   102  						},
   103  					},
   104  				},
   105  			},
   106  		}
   107  
   108  		glog.Infof("Sending subscribe request: %s", sub)
   109  		err = stream.Send(sub)
   110  		if err != nil {
   111  			glog.Fatalf("Failed to subscribe: %s", err)
   112  		}
   113  	}
   114  
   115  	for {
   116  		resp, err := stream.Recv()
   117  		if err != nil {
   118  			if err != io.EOF {
   119  				glog.Fatalf("Error received from the server: %s", err)
   120  			}
   121  			return
   122  		}
   123  		switch resp := resp.Response.(type) {
   124  		case *gnmi.SubscribeResponse_SyncResponse:
   125  			if !resp.SyncResponse {
   126  				panic("initial sync failed," +
   127  					" check that you're using a client compatible with the server")
   128  			}
   129  		}
   130  		glog.V(3).Info(resp)
   131  		publish(c.device, resp)
   132  	}
   133  }