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 }