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 }