github.com/thanos-io/thanos@v0.32.5/internal/cortex/querier/queryrange/limits_test.go (about)

     1  // Copyright (c) The Cortex Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package queryrange
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/weaveworks/common/user"
    15  
    16  	"github.com/thanos-io/thanos/internal/cortex/util"
    17  )
    18  
    19  func TestLimitsMiddleware_MaxQueryLookback(t *testing.T) {
    20  	const (
    21  		thirtyDays = 30 * 24 * time.Hour
    22  	)
    23  
    24  	now := time.Now()
    25  
    26  	tests := map[string]struct {
    27  		maxQueryLookback  time.Duration
    28  		reqStartTime      time.Time
    29  		reqEndTime        time.Time
    30  		expectedSkipped   bool
    31  		expectedStartTime time.Time
    32  		expectedEndTime   time.Time
    33  	}{
    34  		"should not manipulate time range if max lookback is disabled": {
    35  			maxQueryLookback:  0,
    36  			reqStartTime:      time.Unix(0, 0),
    37  			reqEndTime:        now,
    38  			expectedStartTime: time.Unix(0, 0),
    39  			expectedEndTime:   now,
    40  		},
    41  		"should not manipulate time range for a query on short time range": {
    42  			maxQueryLookback:  thirtyDays,
    43  			reqStartTime:      now.Add(-time.Hour),
    44  			reqEndTime:        now,
    45  			expectedStartTime: now.Add(-time.Hour),
    46  			expectedEndTime:   now,
    47  		},
    48  		"should not manipulate a query on large time range close to the limit": {
    49  			maxQueryLookback:  thirtyDays,
    50  			reqStartTime:      now.Add(-thirtyDays).Add(time.Hour),
    51  			reqEndTime:        now,
    52  			expectedStartTime: now.Add(-thirtyDays).Add(time.Hour),
    53  			expectedEndTime:   now,
    54  		},
    55  		"should manipulate a query on large time range over the limit": {
    56  			maxQueryLookback:  thirtyDays,
    57  			reqStartTime:      now.Add(-thirtyDays).Add(-100 * time.Hour),
    58  			reqEndTime:        now,
    59  			expectedStartTime: now.Add(-thirtyDays),
    60  			expectedEndTime:   now,
    61  		},
    62  		"should skip executing a query outside the allowed time range": {
    63  			maxQueryLookback: thirtyDays,
    64  			reqStartTime:     now.Add(-thirtyDays).Add(-100 * time.Hour),
    65  			reqEndTime:       now.Add(-thirtyDays).Add(-90 * time.Hour),
    66  			expectedSkipped:  true,
    67  		},
    68  	}
    69  
    70  	for testName, testData := range tests {
    71  		t.Run(testName, func(t *testing.T) {
    72  			req := &PrometheusRequest{
    73  				Start: util.TimeToMillis(testData.reqStartTime),
    74  				End:   util.TimeToMillis(testData.reqEndTime),
    75  			}
    76  
    77  			limits := mockLimits{maxQueryLookback: testData.maxQueryLookback}
    78  			middleware := NewLimitsMiddleware(limits)
    79  
    80  			innerRes := NewEmptyPrometheusResponse()
    81  			inner := &mockHandler{}
    82  			inner.On("Do", mock.Anything, mock.Anything).Return(innerRes, nil)
    83  
    84  			ctx := user.InjectOrgID(context.Background(), "test")
    85  			outer := middleware.Wrap(inner)
    86  			res, err := outer.Do(ctx, req)
    87  			require.NoError(t, err)
    88  
    89  			if testData.expectedSkipped {
    90  				// We expect an empty response, but not the one returned by the inner handler
    91  				// which we expect has been skipped.
    92  				assert.NotSame(t, innerRes, res)
    93  				assert.Len(t, inner.Calls, 0)
    94  			} else {
    95  				// We expect the response returned by the inner handler.
    96  				assert.Same(t, innerRes, res)
    97  
    98  				// Assert on the time range of the request passed to the inner handler (5s delta).
    99  				delta := float64(5000)
   100  				require.Len(t, inner.Calls, 1)
   101  				assert.InDelta(t, util.TimeToMillis(testData.expectedStartTime), inner.Calls[0].Arguments.Get(1).(Request).GetStart(), delta)
   102  				assert.InDelta(t, util.TimeToMillis(testData.expectedEndTime), inner.Calls[0].Arguments.Get(1).(Request).GetEnd(), delta)
   103  			}
   104  		})
   105  	}
   106  }
   107  
   108  func TestLimitsMiddleware_MaxQueryLength(t *testing.T) {
   109  	const (
   110  		thirtyDays = 30 * 24 * time.Hour
   111  	)
   112  
   113  	now := time.Now()
   114  
   115  	tests := map[string]struct {
   116  		maxQueryLength time.Duration
   117  		reqStartTime   time.Time
   118  		reqEndTime     time.Time
   119  		expectedErr    string
   120  	}{
   121  		"should skip validation if max length is disabled": {
   122  			maxQueryLength: 0,
   123  			reqStartTime:   time.Unix(0, 0),
   124  			reqEndTime:     now,
   125  		},
   126  		"should succeed on a query on short time range, ending now": {
   127  			maxQueryLength: thirtyDays,
   128  			reqStartTime:   now.Add(-time.Hour),
   129  			reqEndTime:     now,
   130  		},
   131  		"should succeed on a query on short time range, ending in the past": {
   132  			maxQueryLength: thirtyDays,
   133  			reqStartTime:   now.Add(-2 * thirtyDays).Add(-time.Hour),
   134  			reqEndTime:     now.Add(-2 * thirtyDays),
   135  		},
   136  		"should succeed on a query on large time range close to the limit, ending now": {
   137  			maxQueryLength: thirtyDays,
   138  			reqStartTime:   now.Add(-thirtyDays).Add(time.Hour),
   139  			reqEndTime:     now,
   140  		},
   141  		"should fail on a query on large time range over the limit, ending now": {
   142  			maxQueryLength: thirtyDays,
   143  			reqStartTime:   now.Add(-thirtyDays).Add(-100 * time.Hour),
   144  			reqEndTime:     now,
   145  			expectedErr:    "the query time range exceeds the limit",
   146  		},
   147  		"should fail on a query on large time range over the limit, ending in the past": {
   148  			maxQueryLength: thirtyDays,
   149  			reqStartTime:   now.Add(-4 * thirtyDays),
   150  			reqEndTime:     now.Add(-2 * thirtyDays),
   151  			expectedErr:    "the query time range exceeds the limit",
   152  		},
   153  	}
   154  
   155  	for testName, testData := range tests {
   156  		t.Run(testName, func(t *testing.T) {
   157  			req := &PrometheusRequest{
   158  				Start: util.TimeToMillis(testData.reqStartTime),
   159  				End:   util.TimeToMillis(testData.reqEndTime),
   160  			}
   161  
   162  			limits := mockLimits{maxQueryLength: testData.maxQueryLength}
   163  			middleware := NewLimitsMiddleware(limits)
   164  
   165  			innerRes := NewEmptyPrometheusResponse()
   166  			inner := &mockHandler{}
   167  			inner.On("Do", mock.Anything, mock.Anything).Return(innerRes, nil)
   168  
   169  			ctx := user.InjectOrgID(context.Background(), "test")
   170  			outer := middleware.Wrap(inner)
   171  			res, err := outer.Do(ctx, req)
   172  
   173  			if testData.expectedErr != "" {
   174  				require.Error(t, err)
   175  				assert.Contains(t, err.Error(), testData.expectedErr)
   176  				assert.Nil(t, res)
   177  				assert.Len(t, inner.Calls, 0)
   178  			} else {
   179  				// We expect the response returned by the inner handler.
   180  				require.NoError(t, err)
   181  				assert.Same(t, innerRes, res)
   182  
   183  				// The time range of the request passed to the inner handler should have not been manipulated.
   184  				require.Len(t, inner.Calls, 1)
   185  				assert.Equal(t, util.TimeToMillis(testData.reqStartTime), inner.Calls[0].Arguments.Get(1).(Request).GetStart())
   186  				assert.Equal(t, util.TimeToMillis(testData.reqEndTime), inner.Calls[0].Arguments.Get(1).(Request).GetEnd())
   187  			}
   188  		})
   189  	}
   190  }
   191  
   192  type mockLimits struct {
   193  	maxQueryLookback  time.Duration
   194  	maxQueryLength    time.Duration
   195  	maxCacheFreshness time.Duration
   196  }
   197  
   198  func (m mockLimits) MaxQueryLookback(string) time.Duration {
   199  	return m.maxQueryLookback
   200  }
   201  
   202  func (m mockLimits) MaxQueryLength(string) time.Duration {
   203  	return m.maxQueryLength
   204  }
   205  
   206  func (mockLimits) MaxQueryParallelism(string) int {
   207  	return 14 // Flag default.
   208  }
   209  
   210  func (m mockLimits) MaxCacheFreshness(string) time.Duration {
   211  	return m.maxCacheFreshness
   212  }
   213  
   214  type mockHandler struct {
   215  	mock.Mock
   216  }
   217  
   218  func (m *mockHandler) Do(ctx context.Context, req Request) (Response, error) {
   219  	args := m.Called(ctx, req)
   220  	return args.Get(0).(Response), args.Error(1)
   221  }