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

     1  // Copyright (c) 2020 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 server
     6  
     7  import (
     8  	"context"
     9  	"crypto/tls"
    10  	"crypto/x509"
    11  	"flag"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"log"
    15  	"math"
    16  	"net"
    17  	"os"
    18  	"path"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/aristanetworks/glog"
    23  	gnmilib "github.com/aristanetworks/goarista/gnmi"
    24  	"github.com/aristanetworks/goarista/gnmireverse"
    25  	"github.com/openconfig/gnmi/proto/gnmi"
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/credentials"
    28  	_ "google.golang.org/grpc/encoding/gzip" // Enable gzip encoding for the server.
    29  	"google.golang.org/grpc/peer"
    30  	"google.golang.org/protobuf/proto"
    31  )
    32  
    33  // The debug flag is composed of these flag bits.
    34  // These flag bits are bitwise OR'ed to select what debug information is printed.
    35  const (
    36  	debugSilent             = 1 << iota // Do not print anything.
    37  	debugGetResponseSummary             // Print GetResponse summary.
    38  	debugResponseProto                  // Print response protobuf text format.
    39  	debugUpdates                        // Print update receive and notification times.
    40  	debugUpdatesTiming                  // Print debugUpdates and update timing calculations.
    41  	debugUpdatesPaths                   // Print debugUpdates and update paths.
    42  	debugUpdatesValues                  // Print debugUpdates and update values.
    43  	debugUpdatesAll         = debugUpdates | debugUpdatesTiming | debugUpdatesPaths |
    44  		debugUpdatesValues // Print all update information.
    45  )
    46  
    47  // Use log.Logger for thread-safe printing.
    48  var logger = log.New(os.Stdout, "", 0)
    49  
    50  func newTLSConfig(clientCertAuth bool, certFile, keyFile, clientCAFile string) (*tls.Config,
    51  	error) {
    52  	tlsConfig := tls.Config{}
    53  	if clientCertAuth {
    54  		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
    55  		if clientCAFile == "" {
    56  			return nil, fmt.Errorf("client_cert_auth enable, client_cafile must also be set")
    57  		}
    58  		b, err := ioutil.ReadFile(clientCAFile)
    59  		if err != nil {
    60  			return nil, err
    61  		}
    62  		cp := x509.NewCertPool()
    63  		if !cp.AppendCertsFromPEM(b) {
    64  			return nil, fmt.Errorf("credentials: failed to append certificates")
    65  		}
    66  		tlsConfig.ClientCAs = cp
    67  	}
    68  	if certFile != "" {
    69  		if keyFile == "" {
    70  			return nil, fmt.Errorf("please provide both -certfile and -keyfile")
    71  		}
    72  		cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		tlsConfig.Certificates = []tls.Certificate{cert}
    77  	}
    78  	return &tlsConfig, nil
    79  }
    80  
    81  // Main initializes the gNMIReverse server.
    82  func Main() {
    83  	addr := flag.String("addr", "127.0.0.1:6035", "address to listen on")
    84  	useTLS := flag.Bool("tls", true, "set to false to disable TLS in collector")
    85  	clientCertAuth := flag.Bool("client_cert_auth", false,
    86  		"require and verify a client certificate. -client_cafile must also be set.")
    87  	certFile := flag.String("certfile", "", "path to TLS certificate file")
    88  	keyFile := flag.String("keyfile", "", "path to TLS key file")
    89  	clientCAFile := flag.String("client_cafile", "",
    90  		"path to TLS CA file to verify client certificate")
    91  
    92  	debugFlagUsage := `Debug flags. Use bitwise OR to select what to print.
    93    -1  Enable all debug flags.
    94     1  Silent mode: do not print anything. Any other enabled flag overrides silent mode.
    95     2  Print GetResponse summary: receive time, last receive time, response size and
    96        number of notifications.
    97     4  Print GetResponse/SubscribeResponse protobuf text format.
    98     8  Print update receive time and notification time.
    99    16  Print update receive time, notification time and update timing calculations: difference
   100        between the last receive time of the same update path, difference between the last
   101        notification time of the same update path and latency (receive time - notification time).
   102    32  Print update receive time, notification time and update path.
   103    64  Print update receive time, notification time and update value.
   104  Example: Use 50 (= 2 | 16 | 32) to print the GetResponse summary and the receive time,
   105           notification time, timing calculations and path of updates.`
   106  	debugFlag := flag.Int("debug", 0, debugFlagUsage)
   107  
   108  	flag.Parse()
   109  
   110  	var config *tls.Config
   111  	if *useTLS {
   112  		var err error
   113  		config, err = newTLSConfig(*clientCertAuth, *certFile, *keyFile, *clientCAFile)
   114  		if err != nil {
   115  			glog.Fatal(err)
   116  		}
   117  	}
   118  
   119  	var serverOptions []grpc.ServerOption
   120  	if config != nil {
   121  		serverOptions = append(serverOptions, grpc.Creds(credentials.NewTLS(config)))
   122  	}
   123  
   124  	serverOptions = append(serverOptions,
   125  		grpc.MaxRecvMsgSize(math.MaxInt32),
   126  	)
   127  
   128  	grpcServer := grpc.NewServer(serverOptions...)
   129  	s := &server{
   130  		debugFlag: *debugFlag,
   131  	}
   132  	gnmireverse.RegisterGNMIReverseServer(grpcServer, s)
   133  
   134  	listener, err := net.Listen("tcp", *addr)
   135  	if err != nil {
   136  		glog.Fatal(err)
   137  	}
   138  	if err := grpcServer.Serve(listener); err != nil {
   139  		glog.Fatal(err)
   140  	}
   141  }
   142  
   143  type server struct {
   144  	debugFlag int
   145  	gnmireverse.UnimplementedGNMIReverseServer
   146  }
   147  
   148  func (s *server) Publish(stream gnmireverse.GNMIReverse_PublishServer) error {
   149  	debugger := newDebugger(stream.Context(), "subscribe", s.debugFlag)
   150  	for {
   151  		resp, err := stream.Recv()
   152  		if err != nil {
   153  			return err
   154  		}
   155  		if s.debugFlag != 0 {
   156  			debugger.logSubscribeResponse(resp)
   157  			continue
   158  		}
   159  
   160  		if err := gnmilib.LogSubscribeResponse(resp); err != nil {
   161  			glog.Error(err)
   162  		}
   163  	}
   164  }
   165  
   166  func (s *server) PublishGet(stream gnmireverse.GNMIReverse_PublishGetServer) error {
   167  	debugger := newDebugger(stream.Context(), "get", s.debugFlag)
   168  	for {
   169  		resp, err := stream.Recv()
   170  		if err != nil {
   171  			return err
   172  		}
   173  		if s.debugFlag != 0 {
   174  			debugger.logGetResponse(resp)
   175  			continue
   176  		}
   177  
   178  		for _, notif := range resp.GetNotification() {
   179  			notifTime := time.Unix(0, notif.GetTimestamp()).UTC().Format(time.RFC3339Nano)
   180  			var notifTarget string
   181  			if target := notif.GetPrefix().GetTarget(); target != "" {
   182  				notifTarget = " (" + target + ")"
   183  			}
   184  			prefix := gnmilib.StrPath(notif.GetPrefix())
   185  			for _, update := range notif.GetUpdate() {
   186  				pathValSeperator := " = "
   187  				// If the value is a subtree, print the subtree on a new line.
   188  				if _, ok := update.GetVal().GetValue().(*gnmi.TypedValue_JsonIetfVal); ok {
   189  					pathValSeperator = "\n"
   190  				}
   191  				pth := path.Join(prefix, gnmilib.StrPath(update.GetPath()))
   192  				val := gnmilib.StrUpdateVal(update)
   193  				logger.Printf("[%s]%s %s%s%s",
   194  					notifTime,
   195  					notifTarget,
   196  					pth,
   197  					pathValSeperator,
   198  					val,
   199  				)
   200  			}
   201  		}
   202  	}
   203  }
   204  
   205  // debugger stores debug information related to responses received from a client.
   206  type debugger struct {
   207  	clientAddr       string               // Address of the client sending the response.
   208  	responseName     string               // Name of the response received.
   209  	responsesCounter uint64               // Updates of the same response have the same counter.
   210  	lastResponseTime time.Time            // Time of the last response received.
   211  	lastUpdateTimes  map[string]time.Time // Map of update paths to the last notification time.
   212  	logBuffer        strings.Builder      // Resultant log text to be printed.
   213  	debugFlag        int                  // Debug flag to control what to print.
   214  }
   215  
   216  func newDebugger(ctx context.Context, responseName string, debugFlag int) *debugger {
   217  	var clientAddr string
   218  	if pr, ok := peer.FromContext(ctx); ok && pr.Addr != nil {
   219  		clientAddr = pr.Addr.String()
   220  	}
   221  	return &debugger{
   222  		clientAddr:      clientAddr,
   223  		responseName:    responseName,
   224  		lastUpdateTimes: make(map[string]time.Time),
   225  		debugFlag:       debugFlag,
   226  	}
   227  }
   228  
   229  // logGetResponse prints GetResponse debug information based on
   230  // the bits of the debug flag.
   231  func (d *debugger) logGetResponse(res *gnmi.GetResponse) {
   232  	if d.debugFlag^debugSilent == 0 { // Any other enabled flag overrides silence.
   233  		return
   234  	}
   235  	receiveTime := time.Now().UTC()
   236  	if d.debugFlag&debugGetResponseSummary != 0 {
   237  		d.logGetResponseSummary(res, receiveTime)
   238  	}
   239  	if d.debugFlag&debugResponseProto != 0 {
   240  		d.logResponseProto(res)
   241  	}
   242  	if d.debugFlag&debugUpdatesAll != 0 {
   243  		d.logGetResponseNotifs(res, receiveTime)
   244  	}
   245  	logger.Printf(d.logBuffer.String())
   246  	d.logBuffer.Reset()
   247  	d.lastResponseTime = receiveTime
   248  	d.responsesCounter++
   249  }
   250  
   251  func (d *debugger) logGetResponseSummary(res *gnmi.GetResponse, receiveTime time.Time) {
   252  	debugFields := append(
   253  		d.logPrefixFields(receiveTime),
   254  		d.logLastReceiveAgoField(receiveTime),
   255  		fmt.Sprintf("size_bytes=%d", proto.Size(res)),
   256  		fmt.Sprintf("num_notifs=%d", len(res.GetNotification())),
   257  	)
   258  	d.logBuffer.WriteString(strings.Join(debugFields, " "))
   259  	d.logBuffer.WriteByte('\n')
   260  }
   261  
   262  func (d *debugger) logResponseProto(res fmt.Stringer) {
   263  	d.logBuffer.WriteString(res.String())
   264  	d.logBuffer.WriteByte('\n')
   265  }
   266  
   267  func (d *debugger) logGetResponseNotifs(res *gnmi.GetResponse, receiveTime time.Time) {
   268  	for _, notif := range res.GetNotification() {
   269  		d.logNotif(notif, receiveTime)
   270  	}
   271  }
   272  
   273  func (d *debugger) logNotif(notif *gnmi.Notification, receiveTime time.Time) {
   274  	notifTime := time.Unix(0, notif.GetTimestamp()).UTC()
   275  	prefix := gnmilib.StrPath(notif.GetPrefix())
   276  	for _, update := range notif.GetUpdate() {
   277  		pth := path.Join(prefix, gnmilib.StrPath(update.GetPath()))
   278  		val := gnmilib.StrValCompactJSON(update.GetVal())
   279  		d.logUpdate(receiveTime, notifTime, pth, val)
   280  	}
   281  }
   282  
   283  func (d *debugger) logUpdate(receiveTime time.Time, notifTime time.Time, path, val string) {
   284  	debugFields := append(
   285  		d.logPrefixFields(receiveTime),
   286  		fmt.Sprintf("notif_time=%s", notifTime.Format(time.RFC3339Nano)),
   287  	)
   288  	if d.debugFlag&debugUpdatesTiming != 0 {
   289  		// Difference between the notification time and last notification time.
   290  		if _, ok := d.lastUpdateTimes[path]; !ok {
   291  			d.lastUpdateTimes[path] = time.Time{}
   292  		}
   293  		lastUpdateTime := d.lastUpdateTimes[path]
   294  		var lastNotifAgo time.Duration
   295  		if !lastUpdateTime.IsZero() {
   296  			lastNotifAgo = notifTime.Sub(lastUpdateTime)
   297  		}
   298  		d.lastUpdateTimes[path] = notifTime
   299  
   300  		// Difference between the response receive time and notification time.
   301  		var latency time.Duration
   302  		if !lastUpdateTime.IsZero() {
   303  			latency = receiveTime.Sub(lastUpdateTime)
   304  		}
   305  
   306  		debugFields = append(debugFields,
   307  			d.logLastReceiveAgoField(receiveTime),
   308  			fmt.Sprintf("last_notif_ago=%s", lastNotifAgo),
   309  			fmt.Sprintf("latency=%s", latency),
   310  		)
   311  	}
   312  	if d.debugFlag&debugUpdatesPaths != 0 {
   313  		debugFields = append(debugFields, fmt.Sprintf("path=%s", path))
   314  	}
   315  	if d.debugFlag&debugUpdatesValues != 0 {
   316  		debugFields = append(debugFields, fmt.Sprintf("val=%s", val))
   317  	}
   318  	d.logBuffer.WriteString(strings.Join(debugFields, " "))
   319  	d.logBuffer.WriteByte('\n')
   320  }
   321  
   322  // logSubscribeResponse prints SubscribeResponse debug information based on
   323  // the bits of the debug flag.
   324  func (d *debugger) logSubscribeResponse(res *gnmi.SubscribeResponse) {
   325  	if d.debugFlag^debugSilent == 0 { // Any other enabled flag overrides silence.
   326  		return
   327  	}
   328  	receiveTime := time.Now().UTC()
   329  	if d.debugFlag&debugResponseProto != 0 {
   330  		d.logResponseProto(res)
   331  	}
   332  	if d.debugFlag&debugUpdatesAll != 0 {
   333  		d.logSubscribeResponseNotif(res, receiveTime)
   334  	}
   335  	logger.Printf(d.logBuffer.String())
   336  	d.logBuffer.Reset()
   337  	d.lastResponseTime = receiveTime
   338  	d.responsesCounter++
   339  }
   340  
   341  func (d *debugger) logSubscribeResponseNotif(res *gnmi.SubscribeResponse, receiveTime time.Time) {
   342  	if res.GetSyncResponse() {
   343  		debugFields := d.logPrefixFields(receiveTime)
   344  		if d.debugFlag&debugUpdatesTiming != 0 {
   345  			debugFields = append(debugFields, d.logLastReceiveAgoField(receiveTime))
   346  		}
   347  		debugFields = append(debugFields, "sync_response=true")
   348  		d.logBuffer.WriteString(strings.Join(debugFields, " "))
   349  		d.logBuffer.WriteByte('\n')
   350  		return
   351  	}
   352  	d.logNotif(res.GetUpdate(), receiveTime)
   353  }
   354  
   355  // logPrefixFields returns the base debug prefix fields.
   356  func (d *debugger) logPrefixFields(receiveTime time.Time) []string {
   357  	return []string{
   358  		fmt.Sprintf("client=%s", d.clientAddr),
   359  		fmt.Sprintf("res=%s", d.responseName),
   360  		fmt.Sprintf("n=%d", d.responsesCounter),
   361  		fmt.Sprintf("rx_time=%s", receiveTime.Format(time.RFC3339Nano)),
   362  	}
   363  }
   364  
   365  // logLastReceiveAgoField returns the debug field of difference between the
   366  // the response receive time and last response receive time.
   367  func (d *debugger) logLastReceiveAgoField(receiveTime time.Time) string {
   368  	var lastReceiveAgo time.Duration
   369  	if !d.lastResponseTime.IsZero() {
   370  		lastReceiveAgo = receiveTime.Sub(d.lastResponseTime)
   371  	}
   372  	return fmt.Sprintf("last_rx_ago=%s", lastReceiveAgo)
   373  }