290 lines
5.9 KiB
Go
290 lines
5.9 KiB
Go
|
// 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 lanz_test
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"net"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/aristanetworks/goarista/lanz"
|
||
|
pb "github.com/aristanetworks/goarista/lanz/proto"
|
||
|
"github.com/aristanetworks/goarista/test"
|
||
|
|
||
|
"github.com/aristanetworks/glog"
|
||
|
"github.com/golang/protobuf/proto"
|
||
|
)
|
||
|
|
||
|
var testProtoBuf = &pb.LanzRecord{
|
||
|
ErrorRecord: &pb.ErrorRecord{
|
||
|
Timestamp: proto.Uint64(146591697107549),
|
||
|
ErrorMessage: proto.String("Error"),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
type testConnector struct {
|
||
|
reader *bytes.Reader
|
||
|
open bool
|
||
|
refusing bool
|
||
|
connect chan bool
|
||
|
block chan bool
|
||
|
}
|
||
|
|
||
|
func (c *testConnector) Read(p []byte) (int, error) {
|
||
|
if !c.open {
|
||
|
return 0, errors.New("closed")
|
||
|
}
|
||
|
|
||
|
if c.reader.Len() == 0 {
|
||
|
<-c.block
|
||
|
return 0, io.EOF
|
||
|
}
|
||
|
|
||
|
return c.reader.Read(p)
|
||
|
}
|
||
|
|
||
|
func (c *testConnector) Close() error {
|
||
|
if !c.open {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
c.open = false
|
||
|
close(c.block)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *testConnector) Connect() error {
|
||
|
var err error
|
||
|
|
||
|
if c.refusing {
|
||
|
err = errors.New("refused")
|
||
|
} else {
|
||
|
c.block = make(chan bool)
|
||
|
c.open = true
|
||
|
}
|
||
|
if c.connect != nil {
|
||
|
c.connect <- true
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func launchClient(ch chan<- *pb.LanzRecord, conn lanz.ConnectReadCloser) (lanz.Client, chan bool) {
|
||
|
done := make(chan bool)
|
||
|
|
||
|
c := lanz.New(lanz.WithConnector(conn), lanz.WithBackoff(1*time.Millisecond))
|
||
|
go func() {
|
||
|
c.Run(ch)
|
||
|
done <- true
|
||
|
}()
|
||
|
|
||
|
return c, done
|
||
|
}
|
||
|
|
||
|
func pbsToStream(pbs []*pb.LanzRecord) []byte {
|
||
|
var r []byte
|
||
|
|
||
|
for _, p := range pbs {
|
||
|
b, err := proto.Marshal(p)
|
||
|
if err != nil {
|
||
|
glog.Fatalf("Can't marshal pb: %v", err)
|
||
|
}
|
||
|
|
||
|
bLen := uint64(len(b))
|
||
|
s := make([]byte, binary.MaxVarintLen64)
|
||
|
sLen := binary.PutUvarint(s, bLen)
|
||
|
|
||
|
r = append(r, s[:sLen]...)
|
||
|
r = append(r, b...)
|
||
|
}
|
||
|
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
func testStream() []byte {
|
||
|
return pbsToStream([]*pb.LanzRecord{testProtoBuf})
|
||
|
}
|
||
|
|
||
|
// This function tests that basic workflow works.
|
||
|
func TestSuccessPath(t *testing.T) {
|
||
|
pbs := []*pb.LanzRecord{
|
||
|
{
|
||
|
ConfigRecord: &pb.ConfigRecord{
|
||
|
Timestamp: proto.Uint64(146591697107544),
|
||
|
LanzVersion: proto.Uint32(1),
|
||
|
NumOfPorts: proto.Uint32(146),
|
||
|
SegmentSize: proto.Uint32(512),
|
||
|
MaxQueueSize: proto.Uint32(524288000),
|
||
|
PortConfigRecord: []*pb.ConfigRecord_PortConfigRecord{
|
||
|
{
|
||
|
IntfName: proto.String("Cpu"),
|
||
|
SwitchId: proto.Uint32(2048),
|
||
|
PortId: proto.Uint32(4096),
|
||
|
InternalPort: proto.Bool(false),
|
||
|
HighThreshold: proto.Uint32(50000),
|
||
|
LowThreshold: proto.Uint32(25000),
|
||
|
},
|
||
|
},
|
||
|
GlobalUsageReportingEnabled: proto.Bool(true),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
CongestionRecord: &pb.CongestionRecord{
|
||
|
Timestamp: proto.Uint64(146591697107546),
|
||
|
IntfName: proto.String("Cpu"),
|
||
|
SwitchId: proto.Uint32(2048),
|
||
|
PortId: proto.Uint32(4096),
|
||
|
QueueSize: proto.Uint32(30000),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
ErrorRecord: &pb.ErrorRecord{
|
||
|
Timestamp: proto.Uint64(146591697107549),
|
||
|
ErrorMessage: proto.String("Error"),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
conn := &testConnector{reader: bytes.NewReader(pbsToStream(pbs))}
|
||
|
ch := make(chan *pb.LanzRecord)
|
||
|
c, done := launchClient(ch, conn)
|
||
|
for i, p := range pbs {
|
||
|
r, ok := <-ch
|
||
|
if !ok {
|
||
|
t.Fatalf("Unexpected closed channel")
|
||
|
}
|
||
|
if !test.DeepEqual(p, r) {
|
||
|
t.Fatalf("Test case %d: expected %v, but got %v", i, p, r)
|
||
|
}
|
||
|
}
|
||
|
c.Stop()
|
||
|
<-done
|
||
|
if conn.open {
|
||
|
t.Fatalf("Connection still open after stopping")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function tests that the client keeps retrying on connection error.
|
||
|
func TestRetryOnConnectionError(t *testing.T) {
|
||
|
conn := &testConnector{
|
||
|
refusing: true,
|
||
|
connect: make(chan bool),
|
||
|
}
|
||
|
|
||
|
ch := make(chan *pb.LanzRecord)
|
||
|
c, done := launchClient(ch, conn)
|
||
|
|
||
|
connects := 3
|
||
|
stopped := false
|
||
|
for !stopped {
|
||
|
select {
|
||
|
case <-conn.connect:
|
||
|
connects--
|
||
|
if connects == 0 {
|
||
|
c.Stop()
|
||
|
}
|
||
|
case <-done:
|
||
|
stopped = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function tests that the client will reconnect if the connection gets closed.
|
||
|
func TestRetryOnClose(t *testing.T) {
|
||
|
conn := &testConnector{
|
||
|
reader: bytes.NewReader(testStream()),
|
||
|
connect: make(chan bool),
|
||
|
}
|
||
|
|
||
|
ch := make(chan *pb.LanzRecord)
|
||
|
c, done := launchClient(ch, conn)
|
||
|
<-conn.connect
|
||
|
<-ch
|
||
|
conn.Close()
|
||
|
<-conn.connect
|
||
|
conn.Close()
|
||
|
<-conn.connect
|
||
|
c.Stop()
|
||
|
<-done
|
||
|
|
||
|
if conn.open {
|
||
|
t.Fatalf("Connection still open after stopping")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function tests that the client will reconnect if it receives truncated input.
|
||
|
func TestRetryOnTrucatedInput(t *testing.T) {
|
||
|
conn := &testConnector{
|
||
|
reader: bytes.NewReader(testStream()[:5]),
|
||
|
connect: make(chan bool),
|
||
|
}
|
||
|
|
||
|
ch := make(chan *pb.LanzRecord)
|
||
|
c, done := launchClient(ch, conn)
|
||
|
<-conn.connect
|
||
|
conn.block <- false
|
||
|
<-conn.connect
|
||
|
c.Stop()
|
||
|
<-done
|
||
|
if conn.open {
|
||
|
t.Fatalf("Connection still open after stopping")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function tests that the client will reconnect if it receives malformed input.
|
||
|
func TestRetryOnMalformedInput(t *testing.T) {
|
||
|
stream := testStream()
|
||
|
uLen := binary.PutUvarint(stream, 3)
|
||
|
conn := &testConnector{
|
||
|
reader: bytes.NewReader(stream[:uLen+3]),
|
||
|
connect: make(chan bool),
|
||
|
}
|
||
|
|
||
|
ch := make(chan *pb.LanzRecord)
|
||
|
c, done := launchClient(ch, conn)
|
||
|
<-conn.connect
|
||
|
<-conn.connect
|
||
|
c.Stop()
|
||
|
<-done
|
||
|
if conn.open {
|
||
|
t.Fatalf("Connection still open after stopping")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function tests the default connector.
|
||
|
func TestDefaultConnector(t *testing.T) {
|
||
|
stream := testStream()
|
||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||
|
if err != nil {
|
||
|
t.Fatalf("Can't listen: %v", err)
|
||
|
}
|
||
|
go func() {
|
||
|
conn, err := l.Accept()
|
||
|
if err != nil {
|
||
|
t.Fatalf("Can't accept: %v", err)
|
||
|
}
|
||
|
conn.Write(stream)
|
||
|
conn.Close()
|
||
|
}()
|
||
|
|
||
|
ch := make(chan *pb.LanzRecord)
|
||
|
done := make(chan bool)
|
||
|
c := lanz.New(lanz.WithAddr(l.Addr().String()), lanz.WithBackoff(1*time.Millisecond))
|
||
|
go func() {
|
||
|
c.Run(ch)
|
||
|
done <- true
|
||
|
}()
|
||
|
p := <-ch
|
||
|
c.Stop()
|
||
|
<-done
|
||
|
|
||
|
if !test.DeepEqual(p, testProtoBuf) {
|
||
|
t.Fatalf("Expected protobuf %v, but got %v", testProtoBuf, p)
|
||
|
}
|
||
|
}
|