github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/lanz/client_test.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 lanz_test
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"errors"
    11  	"io"
    12  	"net"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/aristanetworks/goarista/lanz"
    17  	pb "github.com/aristanetworks/goarista/lanz/proto"
    18  
    19  	"google.golang.org/protobuf/proto"
    20  )
    21  
    22  var testProtoBuf = &pb.LanzRecord{
    23  	ErrorRecord: &pb.ErrorRecord{
    24  		Timestamp:    proto.Uint64(146591697107549),
    25  		ErrorMessage: proto.String("Error"),
    26  	},
    27  }
    28  
    29  type testConnector struct {
    30  	reader   *bytes.Reader
    31  	open     bool
    32  	refusing bool
    33  	connect  chan bool
    34  	block    chan bool
    35  }
    36  
    37  func (c *testConnector) Read(p []byte) (int, error) {
    38  	if !c.open {
    39  		return 0, errors.New("closed")
    40  	}
    41  
    42  	if c.reader.Len() == 0 {
    43  		<-c.block
    44  		return 0, io.EOF
    45  	}
    46  
    47  	return c.reader.Read(p)
    48  }
    49  
    50  func (c *testConnector) Close() error {
    51  	if !c.open {
    52  		return nil
    53  	}
    54  
    55  	c.open = false
    56  	close(c.block)
    57  	return nil
    58  }
    59  
    60  func (c *testConnector) Connect() error {
    61  	var err error
    62  
    63  	if c.refusing {
    64  		err = errors.New("refused")
    65  	} else {
    66  		c.block = make(chan bool)
    67  		c.open = true
    68  	}
    69  	if c.connect != nil {
    70  		c.connect <- true
    71  	}
    72  	return err
    73  }
    74  
    75  func launchClient(ch chan<- *pb.LanzRecord, conn lanz.ConnectReadCloser) (lanz.Client, chan bool) {
    76  	done := make(chan bool)
    77  
    78  	c := lanz.New(lanz.WithConnector(conn), lanz.WithBackoff(1*time.Millisecond))
    79  	go func() {
    80  		c.Run(ch)
    81  		done <- true
    82  	}()
    83  
    84  	return c, done
    85  }
    86  
    87  func pbsToStream(t *testing.T, pbs []*pb.LanzRecord) []byte {
    88  	var r []byte
    89  
    90  	for _, p := range pbs {
    91  		b, err := proto.Marshal(p)
    92  		if err != nil {
    93  			t.Fatalf("Can't marshal pb: %v", err)
    94  		}
    95  
    96  		bLen := uint64(len(b))
    97  		s := make([]byte, binary.MaxVarintLen64)
    98  		sLen := binary.PutUvarint(s, bLen)
    99  
   100  		r = append(r, s[:sLen]...)
   101  		r = append(r, b...)
   102  	}
   103  
   104  	return r
   105  }
   106  
   107  func testStream(t *testing.T) []byte {
   108  	return pbsToStream(t, []*pb.LanzRecord{testProtoBuf})
   109  }
   110  
   111  // This function tests that basic workflow works.
   112  func TestSuccessPath(t *testing.T) {
   113  	pbs := []*pb.LanzRecord{
   114  		{
   115  			ConfigRecord: &pb.ConfigRecord{
   116  				Timestamp:    proto.Uint64(146591697107544),
   117  				LanzVersion:  proto.Uint32(1),
   118  				NumOfPorts:   proto.Uint32(146),
   119  				SegmentSize:  proto.Uint32(512),
   120  				MaxQueueSize: proto.Uint32(524288000),
   121  				PortConfigRecord: []*pb.ConfigRecord_PortConfigRecord{
   122  					{
   123  						IntfName:      proto.String("Cpu"),
   124  						SwitchId:      proto.Uint32(2048),
   125  						PortId:        proto.Uint32(4096),
   126  						InternalPort:  proto.Bool(false),
   127  						HighThreshold: proto.Uint32(50000),
   128  						LowThreshold:  proto.Uint32(25000),
   129  					},
   130  				},
   131  				GlobalUsageReportingEnabled: proto.Bool(true),
   132  			},
   133  		},
   134  		{
   135  			CongestionRecord: &pb.CongestionRecord{
   136  				Timestamp: proto.Uint64(146591697107546),
   137  				IntfName:  proto.String("Cpu"),
   138  				SwitchId:  proto.Uint32(2048),
   139  				PortId:    proto.Uint32(4096),
   140  				QueueSize: proto.Uint32(30000),
   141  			},
   142  		},
   143  		{
   144  			ErrorRecord: &pb.ErrorRecord{
   145  				Timestamp:    proto.Uint64(146591697107549),
   146  				ErrorMessage: proto.String("Error"),
   147  			},
   148  		},
   149  	}
   150  
   151  	conn := &testConnector{reader: bytes.NewReader(pbsToStream(t, pbs))}
   152  	ch := make(chan *pb.LanzRecord)
   153  	c, done := launchClient(ch, conn)
   154  	for i, p := range pbs {
   155  		r, ok := <-ch
   156  		if !ok {
   157  			t.Fatalf("Unexpected closed channel")
   158  		}
   159  		if !proto.Equal(p, r) {
   160  			t.Fatalf("Test case %d: expected %v, but got %v", i, p, r)
   161  		}
   162  	}
   163  	c.Stop()
   164  	<-done
   165  	if conn.open {
   166  		t.Fatalf("Connection still open after stopping")
   167  	}
   168  }
   169  
   170  // This function tests that the client keeps retrying on connection error.
   171  func TestRetryOnConnectionError(t *testing.T) {
   172  	conn := &testConnector{
   173  		refusing: true,
   174  		connect:  make(chan bool),
   175  	}
   176  
   177  	ch := make(chan *pb.LanzRecord)
   178  	c, done := launchClient(ch, conn)
   179  
   180  	connects := 3
   181  	stopped := false
   182  	for !stopped {
   183  		select {
   184  		case <-conn.connect:
   185  			connects--
   186  			if connects == 0 {
   187  				c.Stop()
   188  			}
   189  		case <-done:
   190  			stopped = true
   191  		}
   192  	}
   193  }
   194  
   195  // This function tests that the client will reconnect if the connection gets closed.
   196  func TestRetryOnClose(t *testing.T) {
   197  	conn := &testConnector{
   198  		reader:  bytes.NewReader(testStream(t)),
   199  		connect: make(chan bool),
   200  	}
   201  
   202  	ch := make(chan *pb.LanzRecord)
   203  	c, done := launchClient(ch, conn)
   204  	<-conn.connect
   205  	<-ch
   206  	conn.Close()
   207  	<-conn.connect
   208  	conn.Close()
   209  	<-conn.connect
   210  	c.Stop()
   211  	<-done
   212  
   213  	if conn.open {
   214  		t.Fatalf("Connection still open after stopping")
   215  	}
   216  }
   217  
   218  // This function tests that the client will reconnect if it receives truncated input.
   219  func TestRetryOnTrucatedInput(t *testing.T) {
   220  	conn := &testConnector{
   221  		reader:  bytes.NewReader(testStream(t)[:5]),
   222  		connect: make(chan bool),
   223  	}
   224  
   225  	ch := make(chan *pb.LanzRecord)
   226  	c, done := launchClient(ch, conn)
   227  	<-conn.connect
   228  	conn.block <- false
   229  	<-conn.connect
   230  	c.Stop()
   231  	<-done
   232  	if conn.open {
   233  		t.Fatalf("Connection still open after stopping")
   234  	}
   235  }
   236  
   237  // This function tests that the client will reconnect if it receives malformed input.
   238  func TestRetryOnMalformedInput(t *testing.T) {
   239  	stream := testStream(t)
   240  	uLen := binary.PutUvarint(stream, 3)
   241  	conn := &testConnector{
   242  		reader:  bytes.NewReader(stream[:uLen+3]),
   243  		connect: make(chan bool),
   244  	}
   245  
   246  	ch := make(chan *pb.LanzRecord)
   247  	c, done := launchClient(ch, conn)
   248  	<-conn.connect
   249  	<-conn.connect
   250  	c.Stop()
   251  	<-done
   252  	if conn.open {
   253  		t.Fatalf("Connection still open after stopping")
   254  	}
   255  }
   256  
   257  // This function tests the default connector.
   258  func TestDefaultConnector(t *testing.T) {
   259  	stream := testStream(t)
   260  	l, err := net.Listen("tcp", "127.0.0.1:0")
   261  	if err != nil {
   262  		t.Fatalf("Can't listen: %v", err)
   263  	}
   264  	go func() {
   265  		conn, err := l.Accept()
   266  		if err != nil {
   267  			t.Errorf("Can't accept: %v", err)
   268  		}
   269  		conn.Write(stream)
   270  		conn.Close()
   271  	}()
   272  
   273  	ch := make(chan *pb.LanzRecord)
   274  	done := make(chan bool)
   275  	c := lanz.New(lanz.WithAddr(l.Addr().String()), lanz.WithBackoff(1*time.Millisecond))
   276  	go func() {
   277  		c.Run(ch)
   278  		done <- true
   279  	}()
   280  	p := <-ch
   281  	c.Stop()
   282  	<-done
   283  
   284  	if !proto.Equal(p, testProtoBuf) {
   285  		t.Fatalf("Expected protobuf %v, but got %v", testProtoBuf, p)
   286  	}
   287  }