// Copyright (c) 2016 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 client provides helper functions for OpenConfig CLI tools. package client import ( "io" "strings" "sync" "github.com/aristanetworks/glog" "github.com/golang/protobuf/proto" "github.com/openconfig/reference/rpc/openconfig" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) const defaultPort = "6030" // PublishFunc is the method to publish responses type PublishFunc func(addr string, message proto.Message) // Client is a connected gRPC client type Client struct { client openconfig.OpenConfigClient ctx context.Context device string } // New creates a new gRPC client and connects it func New(username, password, addr string, opts []grpc.DialOption) *Client { device := addr if !strings.ContainsRune(addr, ':') { addr += ":" + defaultPort } // Make sure we don't move past the grpc.Dial() call until we actually // established an HTTP/2 connection successfully. opts = append(opts, grpc.WithBlock(), grpc.WithWaitForHandshake()) conn, err := grpc.Dial(addr, opts...) if err != nil { glog.Fatalf("Failed to dial: %s", err) } glog.Infof("Connected to %s", addr) client := openconfig.NewOpenConfigClient(conn) ctx := context.Background() if username != "" { ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs( "username", username, "password", password)) } return &Client{ client: client, device: device, ctx: ctx, } } // Get sends a get request and returns the responses func (c *Client) Get(path string) []*openconfig.Notification { req := &openconfig.GetRequest{ Path: []*openconfig.Path{ { Element: strings.Split(path, "/"), }, }, } response, err := c.client.Get(c.ctx, req) if err != nil { glog.Fatalf("Get failed: %s", err) } return response.Notification } // Subscribe sends subscriptions, and consumes responses. // The given publish function is used to publish SubscribeResponses received // for the given subscriptions, when connected to the given host, with the // given user/pass pair, or the client-side cert specified in the gRPC opts. // This function does not normally return so it should probably be run in its // own goroutine. When this function returns, the given WaitGroup is marked // as done. func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string, publish PublishFunc) { defer wg.Done() stream, err := c.client.Subscribe(c.ctx) if err != nil { glog.Fatalf("Subscribe failed: %s", err) } defer stream.CloseSend() for _, path := range subscriptions { sub := &openconfig.SubscribeRequest{ Request: &openconfig.SubscribeRequest_Subscribe{ Subscribe: &openconfig.SubscriptionList{ Subscription: []*openconfig.Subscription{ { Path: &openconfig.Path{Element: strings.Split(path, "/")}, }, }, }, }, } glog.Infof("Sending subscribe request: %s", sub) err = stream.Send(sub) if err != nil { glog.Fatalf("Failed to subscribe: %s", err) } } for { resp, err := stream.Recv() if err != nil { if err != io.EOF { glog.Fatalf("Error received from the server: %s", err) } return } switch resp := resp.Response.(type) { case *openconfig.SubscribeResponse_SyncResponse: if !resp.SyncResponse { panic("initial sync failed," + " check that you're using a client compatible with the server") } } glog.V(3).Info(resp) publish(c.device, resp) } }