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 }