Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/querier/queryrange/queryrangebase/limits_test.go

217 lines
6.6 KiB

package queryrangebase
import (
"context"
"testing"
"time"
"github.com/cortexproject/cortex/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/weaveworks/common/user"
)
func TestLimitsMiddleware_MaxQueryLookback(t *testing.T) {
const (
thirtyDays = 30 * 24 * time.Hour
)
now := time.Now()
tests := map[string]struct {
maxQueryLookback time.Duration
reqStartTime time.Time
reqEndTime time.Time
expectedSkipped bool
expectedStartTime time.Time
expectedEndTime time.Time
}{
"should not manipulate time range if max lookback is disabled": {
maxQueryLookback: 0,
reqStartTime: time.Unix(0, 0),
reqEndTime: now,
expectedStartTime: time.Unix(0, 0),
expectedEndTime: now,
},
"should not manipulate time range for a query on short time range": {
maxQueryLookback: thirtyDays,
reqStartTime: now.Add(-time.Hour),
reqEndTime: now,
expectedStartTime: now.Add(-time.Hour),
expectedEndTime: now,
},
"should not manipulate a query on large time range close to the limit": {
maxQueryLookback: thirtyDays,
reqStartTime: now.Add(-thirtyDays).Add(time.Hour),
reqEndTime: now,
expectedStartTime: now.Add(-thirtyDays).Add(time.Hour),
expectedEndTime: now,
},
"should manipulate a query on large time range over the limit": {
maxQueryLookback: thirtyDays,
reqStartTime: now.Add(-thirtyDays).Add(-100 * time.Hour),
reqEndTime: now,
expectedStartTime: now.Add(-thirtyDays),
expectedEndTime: now,
},
"should skip executing a query outside the allowed time range": {
maxQueryLookback: thirtyDays,
reqStartTime: now.Add(-thirtyDays).Add(-100 * time.Hour),
reqEndTime: now.Add(-thirtyDays).Add(-90 * time.Hour),
expectedSkipped: true,
},
}
for testName, testData := range tests {
t.Run(testName, func(t *testing.T) {
req := &PrometheusRequest{
Start: util.TimeToMillis(testData.reqStartTime),
End: util.TimeToMillis(testData.reqEndTime),
}
limits := mockLimits{maxQueryLookback: testData.maxQueryLookback}
middleware := NewLimitsMiddleware(limits)
innerRes := NewEmptyPrometheusResponse()
inner := &mockHandler{}
inner.On("Do", mock.Anything, mock.Anything).Return(innerRes, nil)
ctx := user.InjectOrgID(context.Background(), "test")
outer := middleware.Wrap(inner)
res, err := outer.Do(ctx, req)
require.NoError(t, err)
if testData.expectedSkipped {
// We expect an empty response, but not the one returned by the inner handler
// which we expect has been skipped.
assert.NotSame(t, innerRes, res)
assert.Len(t, inner.Calls, 0)
} else {
// We expect the response returned by the inner handler.
assert.Same(t, innerRes, res)
// Assert on the time range of the request passed to the inner handler (5s delta).
delta := float64(5000)
require.Len(t, inner.Calls, 1)
assert.InDelta(t, util.TimeToMillis(testData.expectedStartTime), inner.Calls[0].Arguments.Get(1).(Request).GetStart(), delta)
assert.InDelta(t, util.TimeToMillis(testData.expectedEndTime), inner.Calls[0].Arguments.Get(1).(Request).GetEnd(), delta)
}
})
}
}
func TestLimitsMiddleware_MaxQueryLength(t *testing.T) {
const (
thirtyDays = 30 * 24 * time.Hour
)
now := time.Now()
tests := map[string]struct {
maxQueryLength time.Duration
reqStartTime time.Time
reqEndTime time.Time
expectedErr string
}{
"should skip validation if max length is disabled": {
maxQueryLength: 0,
reqStartTime: time.Unix(0, 0),
reqEndTime: now,
},
"should succeed on a query on short time range, ending now": {
maxQueryLength: thirtyDays,
reqStartTime: now.Add(-time.Hour),
reqEndTime: now,
},
"should succeed on a query on short time range, ending in the past": {
maxQueryLength: thirtyDays,
reqStartTime: now.Add(-2 * thirtyDays).Add(-time.Hour),
reqEndTime: now.Add(-2 * thirtyDays),
},
"should succeed on a query on large time range close to the limit, ending now": {
maxQueryLength: thirtyDays,
reqStartTime: now.Add(-thirtyDays).Add(time.Hour),
reqEndTime: now,
},
"should fail on a query on large time range over the limit, ending now": {
maxQueryLength: thirtyDays,
reqStartTime: now.Add(-thirtyDays).Add(-100 * time.Hour),
reqEndTime: now,
expectedErr: "the query time range exceeds the limit",
},
"should fail on a query on large time range over the limit, ending in the past": {
maxQueryLength: thirtyDays,
reqStartTime: now.Add(-4 * thirtyDays),
reqEndTime: now.Add(-2 * thirtyDays),
expectedErr: "the query time range exceeds the limit",
},
}
for testName, testData := range tests {
t.Run(testName, func(t *testing.T) {
req := &PrometheusRequest{
Start: util.TimeToMillis(testData.reqStartTime),
End: util.TimeToMillis(testData.reqEndTime),
}
limits := mockLimits{maxQueryLength: testData.maxQueryLength}
middleware := NewLimitsMiddleware(limits)
innerRes := NewEmptyPrometheusResponse()
inner := &mockHandler{}
inner.On("Do", mock.Anything, mock.Anything).Return(innerRes, nil)
ctx := user.InjectOrgID(context.Background(), "test")
outer := middleware.Wrap(inner)
res, err := outer.Do(ctx, req)
if testData.expectedErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), testData.expectedErr)
assert.Nil(t, res)
assert.Len(t, inner.Calls, 0)
} else {
// We expect the response returned by the inner handler.
require.NoError(t, err)
assert.Same(t, innerRes, res)
// The time range of the request passed to the inner handler should have not been manipulated.
require.Len(t, inner.Calls, 1)
assert.Equal(t, util.TimeToMillis(testData.reqStartTime), inner.Calls[0].Arguments.Get(1).(Request).GetStart())
assert.Equal(t, util.TimeToMillis(testData.reqEndTime), inner.Calls[0].Arguments.Get(1).(Request).GetEnd())
}
})
}
}
type mockLimits struct {
maxQueryLookback time.Duration
maxQueryLength time.Duration
maxCacheFreshness time.Duration
}
func (m mockLimits) MaxQueryLookback(string) time.Duration {
return m.maxQueryLookback
}
func (m mockLimits) MaxQueryLength(string) time.Duration {
return m.maxQueryLength
}
func (mockLimits) MaxQueryParallelism(string) int {
return 14 // Flag default.
}
func (m mockLimits) MaxCacheFreshness(string) time.Duration {
return m.maxCacheFreshness
}
type mockHandler struct {
mock.Mock
}
func (m *mockHandler) Do(ctx context.Context, req Request) (Response, error) {
args := m.Called(ctx, req)
return args.Get(0).(Response), args.Error(1)
}