github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/pusher/v2/queue_test.go (about) 1 package v2 2 3 import ( 4 "context" 5 "errors" 6 "net/http" 7 "net/url" 8 "testing" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 dto "github.com/prometheus/client_model/go" 13 "github.com/stretchr/testify/require" 14 "golang.org/x/sync/errgroup" 15 16 "github.com/grafana/synthetic-monitoring-agent/internal/pusher" 17 sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring" 18 ) 19 20 func TestQueue(t *testing.T) { 21 const timeout = time.Second * 5 22 23 defaultOptions := pusherOptions{ 24 maxPushBytes: 1024 * 1024, 25 maxQueuedBytes: 0, 26 maxQueuedTime: 0, 27 } 28 29 for title, tc := range map[string]struct { 30 actions []testAction 31 options *pusherOptions 32 countDropped int 33 }{ 34 "single": { 35 actions: []testAction{ 36 insert(30), 37 expect(timeout, []int{30}), 38 }, 39 }, 40 "get all": { 41 actions: []testAction{ 42 insert(10), 43 insert(20), 44 insert(30), 45 expect(timeout, []int{10, 20, 30}), 46 }, 47 }, 48 "multiple operations": { 49 actions: []testAction{ 50 insert(10), 51 insert(20), 52 insert(30), 53 expect(timeout, []int{10, 20, 30}), 54 insert(40), 55 insert(50), 56 expect(timeout, []int{40, 50}), 57 }, 58 }, 59 "max bytes": { 60 options: &pusherOptions{ 61 maxPushBytes: 30, 62 }, 63 actions: []testAction{ 64 insert(30), 65 insert(10), 66 expect(timeout, []int{30}), 67 insert(20), 68 insert(30), 69 expect(timeout, []int{10, 20}), 70 insert(30), 71 expect(timeout, []int{30}), 72 expect(timeout, []int{30}), 73 }, 74 }, 75 "take at least one": { 76 options: &pusherOptions{ 77 maxPushBytes: 1, 78 }, 79 actions: []testAction{ 80 insert(100), 81 insert(5), 82 insert(5), 83 expect(timeout, []int{100}), 84 expect(timeout, []int{5}), 85 expect(timeout, []int{5}), 86 }, 87 }, 88 "empty": { 89 actions: []testAction{ 90 expectEmpty(), 91 }, 92 }, 93 "requeue": { 94 actions: []testAction{ 95 insert(10), 96 insert(20), 97 expect(timeout, []int{10, 20}), 98 insert(30), 99 returnLast(), 100 expect(timeout, []int{10, 20, 30}), 101 expectEmpty(), 102 }, 103 }, 104 "max queued bytes": { 105 options: &pusherOptions{ 106 maxPushBytes: 1024, 107 maxQueuedBytes: 20, 108 }, 109 actions: []testAction{ 110 insert(9), 111 insert(11), 112 expect(timeout, []int{9, 11}), 113 insert(10), 114 insert(15), 115 insert(5), 116 expect(timeout, []int{15, 5}), 117 expectEmpty(), 118 }, 119 countDropped: 1, 120 }, 121 "max queued bytes return last": { 122 options: &pusherOptions{ 123 maxPushBytes: 1024, 124 maxQueuedBytes: 30, 125 }, 126 actions: []testAction{ 127 insert(2), 128 insert(9), 129 insert(11), 130 expect(timeout, []int{2, 9, 11}), 131 insert(10), 132 insert(5), 133 returnLast(), 134 expect(timeout, []int{11, 10, 5}), 135 expectEmpty(), 136 }, 137 countDropped: 2, 138 }, 139 "max queued time": { 140 options: &pusherOptions{ 141 maxQueuedTime: 100 * time.Millisecond, 142 }, 143 actions: []testAction{ 144 insert(1), 145 insert(2), 146 sleep(150 * time.Millisecond), 147 insert(3), 148 expect(timeout, []int{3}), 149 expectEmpty(), 150 }, 151 countDropped: 1, 152 }, 153 } { 154 t.Run(title, func(t *testing.T) { 155 dropped := prometheus.NewCounterVec(prometheus.CounterOpts{ 156 Name: "dropped", 157 }, nil) 158 159 if tc.options == nil { 160 tc.options = new(pusherOptions) 161 *tc.options = defaultOptions 162 } 163 164 tc.options.metrics.DroppedCounter = dropped 165 166 var st testSavedState 167 168 q := newQueue(tc.options) 169 170 for _, action := range tc.actions { 171 action(t, &q, &st) 172 } 173 174 m := dto.Metric{} 175 require.NoError(t, dropped.WithLabelValues().Write(&m)) 176 require.NotNil(t, m.Counter) 177 require.NotNil(t, m.Counter.Value) 178 require.Equal(t, float64(tc.countDropped), *m.Counter.Value) 179 }) 180 } 181 } 182 183 func TestQueuePush(t *testing.T) { 184 if testing.Short() { 185 t.Skip("skipping in short mode") 186 } 187 188 body := "HELLO WORLD!" 189 records := makeRecords([][]byte{ 190 snap(body[:6]), 191 snap(body[6:]), 192 }) 193 respond := func(code int, body string) func(w http.ResponseWriter, r *http.Request) { 194 return func(w http.ResponseWriter, _ *http.Request) { 195 w.WriteHeader(code) 196 _, _ = w.Write([]byte(body)) 197 } 198 } 199 200 testOptions := defaultPusherOptions 201 testOptions.minBackoff = 10 * time.Millisecond 202 testOptions.maxBackoff = 50 * time.Millisecond 203 204 for title, tc := range map[string]struct { 205 options *pusherOptions 206 responses []http.HandlerFunc 207 expectedErr error 208 }{ 209 "200": { 210 responses: []http.HandlerFunc{ 211 respond(http.StatusOK, "OK"), 212 }, 213 }, 214 "500, 200": { 215 responses: []http.HandlerFunc{ 216 respond(http.StatusInternalServerError, "error"), 217 respond(http.StatusOK, "OK"), 218 }, 219 }, 220 "500x2, 200": { 221 responses: []http.HandlerFunc{ 222 respond(http.StatusInternalServerError, "error"), 223 respond(http.StatusInternalServerError, "error"), 224 respond(http.StatusOK, "OK"), 225 }, 226 }, 227 "more 500": { 228 responses: []http.HandlerFunc{ 229 respond(http.StatusInternalServerError, "error"), 230 respond(http.StatusInternalServerError, "error"), 231 respond(http.StatusInternalServerError, "error"), 232 respond(http.StatusInternalServerError, "error"), 233 respond(http.StatusInternalServerError, "error"), 234 respond(http.StatusInternalServerError, "error"), 235 respond(http.StatusInternalServerError, "error"), 236 respond(http.StatusInternalServerError, "error"), 237 respond(http.StatusInternalServerError, "error"), 238 respond(http.StatusInternalServerError, "error"), 239 respond(http.StatusOK, "OK"), 240 }, 241 expectedErr: nil, 242 }, 243 "tenant error": { 244 responses: []http.HandlerFunc{ 245 respond(http.StatusUnauthorized, "invalid token"), 246 }, 247 expectedErr: pushError{ 248 kind: errKindTenant, 249 inner: errors.New("server returned HTTP status 401 Unauthorized: invalid token"), 250 }, 251 }, 252 "max retries": { 253 options: func() *pusherOptions { 254 opt := testOptions 255 opt.maxRetries = 3 256 257 return &opt 258 }(), 259 responses: []http.HandlerFunc{ 260 respond(http.StatusInternalServerError, "error"), 261 respond(http.StatusInternalServerError, "error"), 262 respond(http.StatusInternalServerError, "error"), 263 respond(http.StatusInternalServerError, "error"), 264 }, 265 }, 266 "discard": { 267 responses: []http.HandlerFunc{ 268 respond(http.StatusBadRequest, "some data is bad"), 269 }, 270 }, 271 "rate limit": { 272 responses: []http.HandlerFunc{ 273 respond(http.StatusTooManyRequests, "Too many requests"), 274 }, 275 expectedErr: pushError{ 276 kind: errKindWait, 277 inner: errors.New(`server returned HTTP status 429 Too Many Requests: Too many requests`), 278 }, 279 }, 280 "fatal error": { 281 responses: []http.HandlerFunc{ 282 respond(http.StatusTooManyRequests, "limit: 0 "), 283 }, 284 expectedErr: pushError{ 285 kind: errKindFatal, 286 inner: errors.New(`server returned HTTP status 429 Too Many Requests: limit: 0 `), 287 }, 288 }, 289 "limit error": { 290 responses: []http.HandlerFunc{ 291 respond(http.StatusTooManyRequests, "Maximum active stream limit exceeded"), 292 }, 293 }, 294 } { 295 t.Run(title, func(t *testing.T) { 296 srv := testServer{ 297 responses: tc.responses, 298 } 299 300 srv.start() 301 defer srv.stop() 302 303 url, err := url.Parse(srv.server.URL) 304 require.NoError(t, err) 305 306 registry := prometheus.NewRegistry() 307 308 ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) 309 defer cancel() 310 311 opt := testOptions 312 if tc.options != nil { 313 opt = *tc.options 314 } 315 316 opt.metrics = pusher.NewMetrics(registry) 317 opt = opt.withTenant(1).withType("test") 318 319 q := newQueue(&opt) 320 for _, r := range records { 321 q.insert(r.data) 322 } 323 324 errGroup, gCtx := errgroup.WithContext(ctx) 325 errGroup.Go(func() error { 326 return q.push(gCtx, &sm.RemoteInfo{ 327 Name: "test", 328 Url: url.String(), 329 }) 330 }) 331 errGroup.Go(func() error { 332 defer cancel() 333 334 for { 335 if err := sleepCtx(gCtx, time.Second); err != nil { 336 return nil 337 } 338 339 if srv.done() { 340 return nil 341 } 342 } 343 }) 344 345 if err = errGroup.Wait(); err == context.Canceled { 346 err = nil 347 } 348 349 if tc.expectedErr != nil { 350 require.Error(t, err) 351 require.Equal(t, err.Error(), tc.expectedErr.Error()) 352 } else { 353 require.NoError(t, err) 354 } 355 356 require.True(t, srv.done()) 357 require.Equal(t, []byte(body), srv.receivedBody) 358 require.Empty(t, q.get()) 359 }) 360 } 361 } 362 363 type testSavedState struct { 364 lastGet []queueEntry 365 } 366 367 type testAction func(*testing.T, *queue, *testSavedState) 368 369 func insert(numBytes int) testAction { 370 data := make([]byte, numBytes) 371 372 return func(t *testing.T, q *queue, _ *testSavedState) { 373 q.insert(&data) 374 } 375 } 376 377 func expect(timeout time.Duration, expectedBlocks []int) testAction { 378 return func(t *testing.T, q *queue, st *testSavedState) { 379 ctx, cancel := context.WithTimeout(context.Background(), timeout) 380 defer cancel() 381 382 select { 383 case <-ctx.Done(): 384 t.Fatal(ctx.Err()) 385 case <-q.WaitC(): 386 } 387 388 data := q.get() 389 st.lastGet = data 390 391 blocks := make([]int, len(data)) 392 for idx, d := range data { 393 blocks[idx] = len(*d.data) 394 } 395 396 require.Equal(t, expectedBlocks, blocks) 397 } 398 } 399 400 func expectEmpty() testAction { 401 return func(t *testing.T, q *queue, st *testSavedState) { 402 select { 403 case <-q.WaitC(): 404 t.Fatal("pending") 405 default: 406 } 407 408 require.Nil(t, q.get()) 409 410 select { 411 case <-q.WaitC(): 412 t.Fatal("pending") 413 default: 414 } 415 } 416 } 417 418 func returnLast() testAction { 419 return func(t *testing.T, q *queue, st *testSavedState) { 420 if st.lastGet == nil { 421 t.Fatal(st.lastGet) 422 } 423 424 q.requeue(st.lastGet) 425 } 426 } 427 428 func sleep(interval time.Duration) testAction { 429 return func(t *testing.T, q *queue, st *testSavedState) { 430 time.Sleep(interval) 431 } 432 }