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