mirror of https://github.com/prometheus/prometheus
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.
490 lines
13 KiB
490 lines
13 KiB
// Copyright 2017 The Prometheus Authors |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
package remote |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"net/url" |
|
"sort" |
|
"testing" |
|
|
|
"github.com/prometheus/client_golang/prometheus" |
|
config_util "github.com/prometheus/common/config" |
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/prometheus/prometheus/config" |
|
"github.com/prometheus/prometheus/model/labels" |
|
"github.com/prometheus/prometheus/prompb" |
|
"github.com/prometheus/prometheus/storage" |
|
) |
|
|
|
func TestNoDuplicateReadConfigs(t *testing.T) { |
|
dir := t.TempDir() |
|
|
|
cfg1 := config.RemoteReadConfig{ |
|
Name: "write-1", |
|
URL: &config_util.URL{ |
|
URL: &url.URL{ |
|
Scheme: "http", |
|
Host: "localhost1", |
|
}, |
|
}, |
|
} |
|
cfg2 := config.RemoteReadConfig{ |
|
Name: "write-2", |
|
URL: &config_util.URL{ |
|
URL: &url.URL{ |
|
Scheme: "http", |
|
Host: "localhost2", |
|
}, |
|
}, |
|
} |
|
cfg3 := config.RemoteReadConfig{ |
|
URL: &config_util.URL{ |
|
URL: &url.URL{ |
|
Scheme: "http", |
|
Host: "localhost3", |
|
}, |
|
}, |
|
} |
|
|
|
type testcase struct { |
|
cfgs []*config.RemoteReadConfig |
|
err bool |
|
} |
|
|
|
cases := []testcase{ |
|
{ // Duplicates but with different names, we should not get an error. |
|
cfgs: []*config.RemoteReadConfig{ |
|
&cfg1, |
|
&cfg2, |
|
}, |
|
err: false, |
|
}, |
|
{ // Duplicates but one with no name, we should not get an error. |
|
cfgs: []*config.RemoteReadConfig{ |
|
&cfg1, |
|
&cfg3, |
|
}, |
|
err: false, |
|
}, |
|
{ // Duplicates both with no name, we should get an error. |
|
cfgs: []*config.RemoteReadConfig{ |
|
&cfg3, |
|
&cfg3, |
|
}, |
|
err: true, |
|
}, |
|
} |
|
|
|
for _, tc := range cases { |
|
t.Run("", func(t *testing.T) { |
|
s := NewStorage(nil, nil, nil, dir, defaultFlushDeadline, nil) |
|
conf := &config.Config{ |
|
GlobalConfig: config.DefaultGlobalConfig, |
|
RemoteReadConfigs: tc.cfgs, |
|
} |
|
err := s.ApplyConfig(conf) |
|
prometheus.Unregister(s.rws.highestTimestamp) |
|
gotError := err != nil |
|
require.Equal(t, tc.err, gotError) |
|
require.NoError(t, s.Close()) |
|
}) |
|
} |
|
} |
|
|
|
func TestExternalLabelsQuerierAddExternalLabels(t *testing.T) { |
|
tests := []struct { |
|
el labels.Labels |
|
inMatchers []*labels.Matcher |
|
outMatchers []*labels.Matcher |
|
added []string |
|
}{ |
|
{ |
|
inMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "job", "api-server"), |
|
}, |
|
outMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "job", "api-server"), |
|
}, |
|
added: []string{}, |
|
}, |
|
{ |
|
el: labels.FromStrings("dc", "berlin-01", "region", "europe"), |
|
inMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "job", "api-server"), |
|
}, |
|
outMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "job", "api-server"), |
|
labels.MustNewMatcher(labels.MatchEqual, "region", "europe"), |
|
labels.MustNewMatcher(labels.MatchEqual, "dc", "berlin-01"), |
|
}, |
|
added: []string{"dc", "region"}, |
|
}, |
|
{ |
|
el: labels.FromStrings("dc", "berlin-01", "region", "europe"), |
|
inMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "job", "api-server"), |
|
labels.MustNewMatcher(labels.MatchEqual, "dc", "munich-02"), |
|
}, |
|
outMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "job", "api-server"), |
|
labels.MustNewMatcher(labels.MatchEqual, "region", "europe"), |
|
labels.MustNewMatcher(labels.MatchEqual, "dc", "munich-02"), |
|
}, |
|
added: []string{"region"}, |
|
}, |
|
} |
|
|
|
for i, test := range tests { |
|
q := &querier{externalLabels: test.el} |
|
matchers, added := q.addExternalLabels(test.inMatchers) |
|
|
|
sort.Slice(test.outMatchers, func(i, j int) bool { return test.outMatchers[i].Name < test.outMatchers[j].Name }) |
|
sort.Slice(matchers, func(i, j int) bool { return matchers[i].Name < matchers[j].Name }) |
|
|
|
require.Equal(t, test.outMatchers, matchers, "%d", i) |
|
require.Equal(t, test.added, added, "%d", i) |
|
} |
|
} |
|
|
|
func TestSeriesSetFilter(t *testing.T) { |
|
tests := []struct { |
|
in *prompb.QueryResult |
|
toRemove []string |
|
|
|
expected *prompb.QueryResult |
|
}{ |
|
{ |
|
toRemove: []string{"foo"}, |
|
in: &prompb.QueryResult{ |
|
Timeseries: []*prompb.TimeSeries{ |
|
{Labels: labelsToLabelsProto(labels.FromStrings("foo", "bar", "a", "b"), nil)}, |
|
}, |
|
}, |
|
expected: &prompb.QueryResult{ |
|
Timeseries: []*prompb.TimeSeries{ |
|
{Labels: labelsToLabelsProto(labels.FromStrings("a", "b"), nil)}, |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
for _, tc := range tests { |
|
filtered := newSeriesSetFilter(FromQueryResult(true, tc.in), tc.toRemove) |
|
act, ws, err := ToQueryResult(filtered, 1e6) |
|
require.NoError(t, err) |
|
require.Equal(t, 0, len(ws)) |
|
require.Equal(t, tc.expected, act) |
|
} |
|
} |
|
|
|
type mockedRemoteClient struct { |
|
got *prompb.Query |
|
store []*prompb.TimeSeries |
|
} |
|
|
|
func (c *mockedRemoteClient) Read(_ context.Context, query *prompb.Query) (*prompb.QueryResult, error) { |
|
if c.got != nil { |
|
return nil, fmt.Errorf("expected only one call to remote client got: %v", query) |
|
} |
|
c.got = query |
|
|
|
matchers, err := FromLabelMatchers(query.Matchers) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
q := &prompb.QueryResult{} |
|
for _, s := range c.store { |
|
l := labelProtosToLabels(s.Labels) |
|
var notMatch bool |
|
|
|
for _, m := range matchers { |
|
if v := l.Get(m.Name); v != "" { |
|
if !m.Matches(v) { |
|
notMatch = true |
|
break |
|
} |
|
} |
|
} |
|
|
|
if !notMatch { |
|
q.Timeseries = append(q.Timeseries, &prompb.TimeSeries{Labels: s.Labels}) |
|
} |
|
} |
|
return q, nil |
|
} |
|
|
|
func (c *mockedRemoteClient) reset() { |
|
c.got = nil |
|
} |
|
|
|
// NOTE: We don't need to test ChunkQuerier as it's uses querier for all operations anyway. |
|
func TestSampleAndChunkQueryableClient(t *testing.T) { |
|
m := &mockedRemoteClient{ |
|
// Samples does not matter for below tests. |
|
store: []*prompb.TimeSeries{ |
|
{Labels: []prompb.Label{{Name: "a", Value: "b"}}}, |
|
{Labels: []prompb.Label{{Name: "a", Value: "b3"}, {Name: "region", Value: "us"}}}, |
|
{Labels: []prompb.Label{{Name: "a", Value: "b2"}, {Name: "region", Value: "europe"}}}, |
|
}, |
|
} |
|
|
|
for _, tc := range []struct { |
|
name string |
|
matchers []*labels.Matcher |
|
mint, maxt int64 |
|
externalLabels labels.Labels |
|
requiredMatchers []*labels.Matcher |
|
readRecent bool |
|
callback startTimeCallback |
|
|
|
expectedQuery *prompb.Query |
|
expectedSeries []labels.Labels |
|
}{ |
|
{ |
|
name: "empty", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchNotEqual, "a", "something"), |
|
}, |
|
readRecent: true, |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 1, |
|
EndTimestampMs: 2, |
|
Matchers: []*prompb.LabelMatcher{ |
|
{Type: prompb.LabelMatcher_NEQ, Name: "a", Value: "something"}, |
|
}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b"), |
|
labels.FromStrings("a", "b2", "region", "europe"), |
|
labels.FromStrings("a", "b3", "region", "us"), |
|
}, |
|
}, |
|
{ |
|
name: "external labels specified, not explicitly requested", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchNotEqual, "a", "something"), |
|
}, |
|
readRecent: true, |
|
externalLabels: labels.FromStrings("region", "europe"), |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 1, |
|
EndTimestampMs: 2, |
|
Matchers: []*prompb.LabelMatcher{ |
|
{Type: prompb.LabelMatcher_NEQ, Name: "a", Value: "something"}, |
|
{Type: prompb.LabelMatcher_EQ, Name: "region", Value: "europe"}, |
|
}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b"), |
|
labels.FromStrings("a", "b2"), |
|
}, |
|
}, |
|
{ |
|
name: "external labels specified, explicitly requested europe", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchNotEqual, "a", "something"), |
|
labels.MustNewMatcher(labels.MatchEqual, "region", "europe"), |
|
}, |
|
readRecent: true, |
|
externalLabels: labels.FromStrings("region", "europe"), |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 1, |
|
EndTimestampMs: 2, |
|
Matchers: []*prompb.LabelMatcher{ |
|
{Type: prompb.LabelMatcher_NEQ, Name: "a", Value: "something"}, |
|
{Type: prompb.LabelMatcher_EQ, Name: "region", Value: "europe"}, |
|
}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b"), |
|
labels.FromStrings("a", "b2", "region", "europe"), |
|
}, |
|
}, |
|
{ |
|
name: "external labels specified, explicitly requested not europe", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchNotEqual, "a", "something"), |
|
labels.MustNewMatcher(labels.MatchEqual, "region", "us"), |
|
}, |
|
readRecent: true, |
|
externalLabels: labels.FromStrings("region", "europe"), |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 1, |
|
EndTimestampMs: 2, |
|
Matchers: []*prompb.LabelMatcher{ |
|
{Type: prompb.LabelMatcher_NEQ, Name: "a", Value: "something"}, |
|
{Type: prompb.LabelMatcher_EQ, Name: "region", Value: "us"}, |
|
}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b"), |
|
labels.FromStrings("a", "b3", "region", "us"), |
|
}, |
|
}, |
|
{ |
|
name: "prefer local storage", |
|
mint: 0, maxt: 50, |
|
callback: func() (i int64, err error) { return 100, nil }, |
|
readRecent: false, |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 0, |
|
EndTimestampMs: 50, |
|
Matchers: []*prompb.LabelMatcher{}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b"), |
|
labels.FromStrings("a", "b2", "region", "europe"), |
|
labels.FromStrings("a", "b3", "region", "us"), |
|
}, |
|
}, |
|
{ |
|
name: "prefer local storage, limited time", |
|
mint: 0, maxt: 50, |
|
callback: func() (i int64, err error) { return 20, nil }, |
|
readRecent: false, |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 0, |
|
EndTimestampMs: 20, |
|
Matchers: []*prompb.LabelMatcher{}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b"), |
|
labels.FromStrings("a", "b2", "region", "europe"), |
|
labels.FromStrings("a", "b3", "region", "us"), |
|
}, |
|
}, |
|
{ |
|
name: "prefer local storage, skipped", |
|
mint: 30, maxt: 50, |
|
callback: func() (i int64, err error) { return 20, nil }, |
|
readRecent: false, |
|
|
|
expectedQuery: nil, |
|
expectedSeries: nil, // Noop should be used. |
|
}, |
|
{ |
|
name: "required matcher specified, user also specifies same", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "a", "b2"), |
|
}, |
|
readRecent: true, |
|
requiredMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "a", "b2"), |
|
}, |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 1, |
|
EndTimestampMs: 2, |
|
Matchers: []*prompb.LabelMatcher{ |
|
{Type: prompb.LabelMatcher_EQ, Name: "a", Value: "b2"}, |
|
}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b2", "region", "europe"), |
|
}, |
|
}, |
|
{ |
|
name: "required matcher specified", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "a", "b2"), |
|
}, |
|
readRecent: true, |
|
requiredMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "a", "b2"), |
|
}, |
|
|
|
expectedQuery: &prompb.Query{ |
|
StartTimestampMs: 1, |
|
EndTimestampMs: 2, |
|
Matchers: []*prompb.LabelMatcher{ |
|
{Type: prompb.LabelMatcher_EQ, Name: "a", Value: "b2"}, |
|
}, |
|
}, |
|
expectedSeries: []labels.Labels{ |
|
labels.FromStrings("a", "b2", "region", "europe"), |
|
}, |
|
}, |
|
{ |
|
name: "required matcher specified, given matcher does not match", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchNotEqual, "a", "something"), |
|
}, |
|
readRecent: true, |
|
requiredMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "a", "b2"), |
|
}, |
|
|
|
expectedQuery: nil, |
|
expectedSeries: nil, // Given matchers does not match with required ones, noop expected. |
|
}, |
|
{ |
|
name: "required matcher specified, given matcher does not match2", |
|
mint: 1, maxt: 2, |
|
matchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchNotEqual, "x", "something"), |
|
}, |
|
readRecent: true, |
|
requiredMatchers: []*labels.Matcher{ |
|
labels.MustNewMatcher(labels.MatchEqual, "a", "b2"), |
|
}, |
|
expectedQuery: nil, |
|
expectedSeries: nil, // Given matchers does not match with required ones, noop expected. |
|
}, |
|
} { |
|
t.Run(tc.name, func(t *testing.T) { |
|
m.reset() |
|
|
|
c := NewSampleAndChunkQueryableClient( |
|
m, |
|
tc.externalLabels, |
|
tc.requiredMatchers, |
|
tc.readRecent, |
|
tc.callback, |
|
) |
|
q, err := c.Querier(context.TODO(), tc.mint, tc.maxt) |
|
require.NoError(t, err) |
|
defer require.NoError(t, q.Close()) |
|
|
|
ss := q.Select(true, nil, tc.matchers...) |
|
require.NoError(t, err) |
|
require.Equal(t, storage.Warnings(nil), ss.Warnings()) |
|
|
|
require.Equal(t, tc.expectedQuery, m.got) |
|
|
|
var got []labels.Labels |
|
for ss.Next() { |
|
got = append(got, ss.At().Labels()) |
|
} |
|
require.NoError(t, ss.Err()) |
|
require.Equal(t, tc.expectedSeries, got) |
|
}) |
|
} |
|
}
|
|
|