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 }