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  }