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/distributor/validator_test.go

244 lines
6.1 KiB

package distributor
import (
"errors"
"fmt"
"testing"
"time"
"github.com/grafana/dskit/flagext"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"github.com/stretchr/testify/assert"
"github.com/grafana/loki/pkg/push"
"github.com/grafana/loki/v3/pkg/logproto"
"github.com/grafana/loki/v3/pkg/logql/syntax"
"github.com/grafana/loki/v3/pkg/validation"
)
var (
testStreamLabels = labels.Labels{{Name: "my", Value: "label"}}
testStreamLabelsString = testStreamLabels.String()
testTime = time.Now()
)
type fakeLimits struct {
limits *validation.Limits
}
func (f fakeLimits) TenantLimits(_ string) *validation.Limits {
return f.limits
}
// unused, but satisfies interface
func (f fakeLimits) AllByUserID() map[string]*validation.Limits {
return nil
}
func TestValidator_ValidateEntry(t *testing.T) {
tests := []struct {
name string
userID string
overrides validation.TenantLimits
entry logproto.Entry
expected error
}{
{
"test valid",
"test",
nil,
logproto.Entry{Timestamp: testTime, Line: "test"},
nil,
},
{
"test too old",
"test",
fakeLimits{
&validation.Limits{
RejectOldSamples: true,
RejectOldSamplesMaxAge: model.Duration(1 * time.Hour),
},
},
logproto.Entry{Timestamp: testTime.Add(-time.Hour * 5), Line: "test"},
fmt.Errorf(validation.GreaterThanMaxSampleAgeErrorMsg,
testStreamLabelsString,
testTime.Add(-time.Hour*5).Format(timeFormat),
testTime.Add(-1*time.Hour).Format(timeFormat), // same as RejectOldSamplesMaxAge
),
},
{
"test too new",
"test",
nil,
logproto.Entry{Timestamp: testTime.Add(time.Hour * 5), Line: "test"},
fmt.Errorf(validation.TooFarInFutureErrorMsg, testStreamLabelsString, testTime.Add(time.Hour*5).Format(timeFormat)),
},
{
"line too long",
"test",
fakeLimits{
&validation.Limits{
MaxLineSize: 10,
},
},
logproto.Entry{Timestamp: testTime, Line: "12345678901"},
fmt.Errorf(validation.LineTooLongErrorMsg, 10, testStreamLabelsString, 11),
},
{
"disallowed structured metadata",
"test",
fakeLimits{
&validation.Limits{
AllowStructuredMetadata: false,
},
},
logproto.Entry{Timestamp: testTime, Line: "12345678901", StructuredMetadata: push.LabelsAdapter{{Name: "foo", Value: "bar"}}},
fmt.Errorf(validation.DisallowedStructuredMetadataErrorMsg, testStreamLabelsString),
},
{
"structured metadata too big",
"test",
fakeLimits{
&validation.Limits{
AllowStructuredMetadata: true,
MaxStructuredMetadataSize: 4,
},
},
logproto.Entry{Timestamp: testTime, Line: "12345678901", StructuredMetadata: push.LabelsAdapter{{Name: "foo", Value: "bar"}}},
fmt.Errorf(validation.StructuredMetadataTooLargeErrorMsg, testStreamLabelsString, 6, 4),
},
{
"structured metadata too many",
"test",
fakeLimits{
&validation.Limits{
AllowStructuredMetadata: true,
MaxStructuredMetadataEntriesCount: 1,
},
},
logproto.Entry{Timestamp: testTime, Line: "12345678901", StructuredMetadata: push.LabelsAdapter{{Name: "foo", Value: "bar"}, {Name: "too", Value: "many"}}},
fmt.Errorf(validation.StructuredMetadataTooManyErrorMsg, testStreamLabelsString, 2, 1),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &validation.Limits{}
flagext.DefaultValues(l)
o, err := validation.NewOverrides(*l, tt.overrides)
assert.NoError(t, err)
v, err := NewValidator(o, nil)
assert.NoError(t, err)
err = v.ValidateEntry(ctx, v.getValidationContextForTime(testTime, tt.userID), testStreamLabels, tt.entry)
assert.Equal(t, tt.expected, err)
})
}
}
func TestValidator_ValidateLabels(t *testing.T) {
tests := []struct {
name string
userID string
overrides validation.TenantLimits
labels string
expected error
}{
{
"test valid",
"test",
nil,
"{foo=\"bar\"}",
nil,
},
{
"empty",
"test",
nil,
"{}",
fmt.Errorf(validation.MissingLabelsErrorMsg),
},
{
"test too many labels",
"test",
fakeLimits{
&validation.Limits{MaxLabelNamesPerSeries: 2},
},
"{foo=\"bar\",food=\"bars\",fed=\"bears\"}",
fmt.Errorf(validation.MaxLabelNamesPerSeriesErrorMsg, "{foo=\"bar\",food=\"bars\",fed=\"bears\"}", 3, 2),
},
{
"label name too long",
"test",
fakeLimits{
&validation.Limits{
MaxLabelNamesPerSeries: 2,
MaxLabelNameLength: 5,
},
},
"{fooooo=\"bar\"}",
fmt.Errorf(validation.LabelNameTooLongErrorMsg, "{fooooo=\"bar\"}", "fooooo"),
},
{
"label value too long",
"test",
fakeLimits{
&validation.Limits{
MaxLabelNamesPerSeries: 2,
MaxLabelNameLength: 5,
MaxLabelValueLength: 5,
},
},
"{foo=\"barrrrrr\"}",
fmt.Errorf(validation.LabelValueTooLongErrorMsg, "{foo=\"barrrrrr\"}", "barrrrrr"),
},
{
"duplicate label",
"test",
fakeLimits{
&validation.Limits{
MaxLabelNamesPerSeries: 2,
MaxLabelNameLength: 5,
MaxLabelValueLength: 5,
},
},
"{foo=\"bar\", foo=\"barf\"}",
fmt.Errorf(validation.DuplicateLabelNamesErrorMsg, "{foo=\"bar\", foo=\"barf\"}", "foo"),
},
{
"label value contains %",
"test",
fakeLimits{
&validation.Limits{
MaxLabelNamesPerSeries: 2,
MaxLabelNameLength: 5,
MaxLabelValueLength: 5,
},
},
"{foo=\"bar\", foo=\"barf%s\"}",
errors.New("stream '{foo=\"bar\", foo=\"barf%s\"}' has label value too long: 'barf%s'"), // Intentionally construct the string to make sure %s isn't substituted as (MISSING)
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &validation.Limits{}
flagext.DefaultValues(l)
o, err := validation.NewOverrides(*l, tt.overrides)
assert.NoError(t, err)
v, err := NewValidator(o, nil)
assert.NoError(t, err)
err = v.ValidateLabels(v.getValidationContextForTime(testTime, tt.userID), mustParseLabels(tt.labels), logproto.Stream{Labels: tt.labels})
assert.Equal(t, tt.expected, err)
})
}
}
func mustParseLabels(s string) labels.Labels {
ls, err := syntax.ParseLabels(s)
if err != nil {
panic(err)
}
return ls
}