github.com/cilium/cilium@v1.16.2/pkg/hubble/container/ring_reader_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Hubble 3 4 package container 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "io" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/assert" 15 "go.uber.org/goleak" 16 "google.golang.org/protobuf/types/known/timestamppb" 17 18 flowpb "github.com/cilium/cilium/api/v1/flow" 19 v1 "github.com/cilium/cilium/pkg/hubble/api/v1" 20 ) 21 22 func TestRingReader_Previous(t *testing.T) { 23 ring := NewRing(Capacity15) 24 for i := 0; i < 15; i++ { 25 ring.Write(&v1.Event{Timestamp: ×tamppb.Timestamp{Seconds: int64(i)}}) 26 } 27 tests := []struct { 28 start uint64 29 count int 30 want []*v1.Event 31 wantErr error 32 }{ 33 { 34 start: 13, 35 count: 1, 36 want: []*v1.Event{ 37 {Timestamp: ×tamppb.Timestamp{Seconds: 13}}, 38 }, 39 }, { 40 start: 13, 41 count: 2, 42 want: []*v1.Event{ 43 {Timestamp: ×tamppb.Timestamp{Seconds: 13}}, 44 {Timestamp: ×tamppb.Timestamp{Seconds: 12}}, 45 }, 46 }, { 47 start: 5, 48 count: 5, 49 want: []*v1.Event{ 50 {Timestamp: ×tamppb.Timestamp{Seconds: 5}}, 51 {Timestamp: ×tamppb.Timestamp{Seconds: 4}}, 52 {Timestamp: ×tamppb.Timestamp{Seconds: 3}}, 53 {Timestamp: ×tamppb.Timestamp{Seconds: 2}}, 54 {Timestamp: ×tamppb.Timestamp{Seconds: 1}}, 55 }, 56 }, { 57 start: 0, 58 count: 1, 59 want: []*v1.Event{ 60 {Timestamp: ×tamppb.Timestamp{Seconds: 0}}, 61 }, 62 }, { 63 start: 0, 64 count: 1, 65 want: []*v1.Event{ 66 {Timestamp: ×tamppb.Timestamp{Seconds: 0}}, 67 }, 68 }, { 69 start: 14, 70 count: 1, 71 wantErr: io.EOF, 72 }, 73 } 74 for _, tt := range tests { 75 name := fmt.Sprintf("read %d, start at position %d", tt.count, tt.start) 76 t.Run(name, func(t *testing.T) { 77 reader := NewRingReader(ring, tt.start) 78 var got []*v1.Event 79 for i := 0; i < tt.count; i++ { 80 event, err := reader.Previous() 81 if !errors.Is(err, tt.wantErr) { 82 t.Errorf(`"%s" error = %v, wantErr %v`, name, err, tt.wantErr) 83 } 84 if err != nil { 85 return 86 } 87 got = append(got, event) 88 } 89 assert.Equal(t, tt.want, got) 90 assert.Nil(t, reader.Close()) 91 }) 92 } 93 } 94 95 func TestRingReader_PreviousLost(t *testing.T) { 96 ring := NewRing(Capacity15) 97 for i := 0; i < 15; i++ { 98 ring.Write(&v1.Event{Timestamp: ×tamppb.Timestamp{Seconds: int64(i)}}) 99 } 100 reader := NewRingReader(ring, ^uint64(0)) 101 expected := &v1.Event{ 102 Event: &flowpb.LostEvent{ 103 Source: flowpb.LostEventSource_HUBBLE_RING_BUFFER, 104 NumEventsLost: 1, 105 Cpu: nil, 106 }, 107 } 108 actual, err := reader.Previous() 109 assert.NoError(t, err) 110 assert.Equal(t, expected.GetLostEvent(), actual.GetLostEvent()) 111 assert.Nil(t, reader.Close()) 112 } 113 114 func TestRingReader_Next(t *testing.T) { 115 ring := NewRing(Capacity15) 116 for i := 0; i < 15; i++ { 117 ring.Write(&v1.Event{Timestamp: ×tamppb.Timestamp{Seconds: int64(i)}}) 118 } 119 120 tests := []struct { 121 start uint64 122 count int 123 want []*v1.Event 124 wantErr error 125 }{ 126 { 127 start: 0, 128 count: 1, 129 want: []*v1.Event{ 130 {Timestamp: ×tamppb.Timestamp{Seconds: 0}}, 131 }, 132 }, { 133 start: 0, 134 count: 2, 135 want: []*v1.Event{ 136 {Timestamp: ×tamppb.Timestamp{Seconds: 0}}, 137 {Timestamp: ×tamppb.Timestamp{Seconds: 1}}, 138 }, 139 }, { 140 start: 5, 141 count: 5, 142 want: []*v1.Event{ 143 {Timestamp: ×tamppb.Timestamp{Seconds: 5}}, 144 {Timestamp: ×tamppb.Timestamp{Seconds: 6}}, 145 {Timestamp: ×tamppb.Timestamp{Seconds: 7}}, 146 {Timestamp: ×tamppb.Timestamp{Seconds: 8}}, 147 {Timestamp: ×tamppb.Timestamp{Seconds: 9}}, 148 }, 149 }, { 150 start: 13, 151 count: 1, 152 want: []*v1.Event{ 153 {Timestamp: ×tamppb.Timestamp{Seconds: 13}}, 154 }, 155 }, { 156 start: 14, 157 count: 1, 158 wantErr: io.EOF, 159 }, 160 } 161 for _, tt := range tests { 162 name := fmt.Sprintf("read %d, start at position %d", tt.count, tt.start) 163 t.Run(name, func(t *testing.T) { 164 reader := NewRingReader(ring, tt.start) 165 var got []*v1.Event 166 for i := 0; i < tt.count; i++ { 167 event, err := reader.Next() 168 if !errors.Is(err, tt.wantErr) { 169 t.Errorf(`"%s" error = %v, wantErr %v`, name, err, tt.wantErr) 170 } 171 if err != nil { 172 return 173 } 174 got = append(got, event) 175 } 176 assert.Equal(t, tt.want, got) 177 assert.Nil(t, reader.Close()) 178 }) 179 } 180 } 181 182 func TestRingReader_NextLost(t *testing.T) { 183 ring := NewRing(Capacity15) 184 for i := 0; i < 15; i++ { 185 ring.Write(&v1.Event{Timestamp: ×tamppb.Timestamp{Seconds: int64(i)}}) 186 } 187 expected := &v1.Event{ 188 Event: &flowpb.LostEvent{ 189 Source: flowpb.LostEventSource_HUBBLE_RING_BUFFER, 190 NumEventsLost: 1, 191 Cpu: nil, 192 }, 193 } 194 reader := NewRingReader(ring, ^uint64(0)) 195 actual, err := reader.Next() 196 assert.NoError(t, err) 197 assert.Equal(t, expected.GetLostEvent(), actual.GetLostEvent()) 198 assert.Nil(t, reader.Close()) 199 } 200 201 func TestRingReader_NextFollow(t *testing.T) { 202 defer goleak.VerifyNone( 203 t, 204 // ignore goroutines started by the redirect we do from klog to logrus 205 goleak.IgnoreTopFunction("k8s.io/klog.(*loggingT).flushDaemon"), 206 goleak.IgnoreTopFunction("k8s.io/klog/v2.(*loggingT).flushDaemon"), 207 goleak.IgnoreTopFunction("io.(*pipe).read")) 208 ring := NewRing(Capacity15) 209 for i := 0; i < 15; i++ { 210 ring.Write(&v1.Event{Timestamp: ×tamppb.Timestamp{Seconds: int64(i)}}) 211 } 212 213 tests := []struct { 214 start uint64 215 count int 216 want []*v1.Event 217 wantTimeout bool 218 }{ 219 { 220 start: 0, 221 count: 1, 222 want: []*v1.Event{ 223 {Timestamp: ×tamppb.Timestamp{Seconds: 0}}, 224 }, 225 }, { 226 start: 0, 227 count: 2, 228 want: []*v1.Event{ 229 {Timestamp: ×tamppb.Timestamp{Seconds: 0}}, 230 {Timestamp: ×tamppb.Timestamp{Seconds: 1}}, 231 }, 232 }, { 233 start: 5, 234 count: 5, 235 want: []*v1.Event{ 236 {Timestamp: ×tamppb.Timestamp{Seconds: 5}}, 237 {Timestamp: ×tamppb.Timestamp{Seconds: 6}}, 238 {Timestamp: ×tamppb.Timestamp{Seconds: 7}}, 239 {Timestamp: ×tamppb.Timestamp{Seconds: 8}}, 240 {Timestamp: ×tamppb.Timestamp{Seconds: 9}}, 241 }, 242 }, { 243 start: 13, 244 count: 1, 245 want: []*v1.Event{ 246 {Timestamp: ×tamppb.Timestamp{Seconds: 13}}, 247 }, 248 }, { 249 start: 14, 250 count: 1, 251 want: []*v1.Event{nil}, 252 wantTimeout: true, 253 }, 254 } 255 for _, tt := range tests { 256 name := fmt.Sprintf("read %d, start at position %d, expect timeout=%t", tt.count, tt.start, tt.wantTimeout) 257 t.Run(name, func(t *testing.T) { 258 reader := NewRingReader(ring, tt.start) 259 var timedOut bool 260 var got []*v1.Event 261 for i := 0; i < tt.count; i++ { 262 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 263 got = append(got, reader.NextFollow(ctx)) 264 select { 265 case <-ctx.Done(): 266 timedOut = true 267 default: 268 assert.NotNil(t, got[i]) 269 } 270 cancel() 271 assert.Nil(t, reader.Close()) 272 } 273 assert.Equal(t, tt.want, got) 274 assert.Equal(t, tt.wantTimeout, timedOut) 275 }) 276 } 277 } 278 279 func TestRingReader_NextFollow_WithEmptyRing(t *testing.T) { 280 defer goleak.VerifyNone( 281 t, 282 // ignore goroutines started by the redirect we do from klog to logrus 283 goleak.IgnoreTopFunction("k8s.io/klog.(*loggingT).flushDaemon"), 284 goleak.IgnoreTopFunction("k8s.io/klog/v2.(*loggingT).flushDaemon"), 285 goleak.IgnoreTopFunction("io.(*pipe).read")) 286 ring := NewRing(Capacity15) 287 reader := NewRingReader(ring, ring.LastWriteParallel()) 288 ctx, cancel := context.WithCancel(context.Background()) 289 c := make(chan *v1.Event) 290 done := make(chan struct{}) 291 go func() { 292 select { 293 case <-ctx.Done(): 294 case c <- reader.NextFollow(ctx): 295 } 296 close(done) 297 }() 298 select { 299 case <-c: 300 t.Fail() 301 case <-time.After(100 * time.Millisecond): 302 // the call blocked, we're good 303 } 304 cancel() 305 <-done 306 assert.Nil(t, reader.Close()) 307 }