Skip to content

Commit b3e49cb

Browse files
authored
add query fuzz test with promqlsmith (#5200)
Signed-off-by: Ben Ye <[email protected]>
1 parent 41db460 commit b3e49cb

File tree

22 files changed

+1759
-3
lines changed

22 files changed

+1759
-3
lines changed

.github/workflows/test-build-deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ jobs:
113113
- integration_memberlist
114114
- integration_querier
115115
- integration_ruler
116+
- integration_query_fuzz
116117
steps:
117118
- name: Upgrade golang
118119
uses: actions/setup-go@v2

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ run:
3939
- integration_memberlist
4040
- integration_querier
4141
- integration_ruler
42+
- integration_query_fuzz

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ lint:
184184
golangci-lint run
185185

186186
# Ensure no blocklisted package is imported.
187-
GOFLAGS="-tags=requires_docker,integration,integration_alertmanager,integration_backward_compatibility,integration_memberlist,integration_querier,integration_ruler" faillint -paths "github.com/bmizerany/assert=github.com/stretchr/testify/assert,\
187+
GOFLAGS="-tags=requires_docker,integration,integration_alertmanager,integration_backward_compatibility,integration_memberlist,integration_querier,integration_ruler,integration_query_fuzz" faillint -paths "github.com/bmizerany/assert=github.com/stretchr/testify/assert,\
188188
golang.org/x/net/context=context,\
189189
sync/atomic=go.uber.org/atomic,\
190190
github.com/prometheus/client_golang/prometheus.{MultiError}=github.com/prometheus/prometheus/tsdb/errors.{NewMulti},\

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/aws/aws-sdk-go v1.44.189
1212
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
1313
github.com/cespare/xxhash v1.1.0
14+
github.com/cortexproject/promqlsmith v0.0.0-20230309031733-1c551fa10a5c
1415
github.com/dustin/go-humanize v1.0.1
1516
github.com/efficientgo/core v1.0.0-rc.2
1617
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
@@ -76,6 +77,8 @@ require (
7677
sigs.k8s.io/yaml v1.3.0
7778
)
7879

80+
require github.com/google/go-cmp v0.5.9
81+
7982
require (
8083
cloud.google.com/go v0.105.0 // indirect
8184
cloud.google.com/go/compute v1.14.0 // indirect
@@ -134,7 +137,6 @@ require (
134137
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
135138
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
136139
github.com/google/btree v1.0.1 // indirect
137-
github.com/google/go-cmp v0.5.9 // indirect
138140
github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b // indirect
139141
github.com/google/uuid v1.3.0 // indirect
140142
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/z
452452
github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
453453
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
454454
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
455+
github.com/cortexproject/promqlsmith v0.0.0-20230309031733-1c551fa10a5c h1:XdtEYH0mSzTnJhylFjHV5lt57x+MxgWZP7/mfTGHyCQ=
456+
github.com/cortexproject/promqlsmith v0.0.0-20230309031733-1c551fa10a5c/go.mod h1:ngsF8Fu5zfL7q0TufnEd+QvomTblziwTKBRvb9kQ5Ic=
455457
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
456458
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
457459
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=

integration/e2e/db/db.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,12 @@ func NewKES(port int, serverKeyFile, serverCertFile, rootCertFile string) *e2e.H
7171
}
7272

7373
func NewConsul() *e2e.HTTPService {
74+
return NewConsulWithName("consul")
75+
}
76+
77+
func NewConsulWithName(name string) *e2e.HTTPService {
7478
return e2e.NewHTTPService(
75-
"consul",
79+
name,
7680
images.Consul,
7781
// Run consul in "dev" mode so that the initial leader election is immediate
7882
e2e.NewCommand("agent", "-server", "-client=0.0.0.0", "-dev", "-log-level=err"),

integration/e2e/util.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,40 @@ func GenerateSeries(name string, ts time.Time, additionalLabels ...prompb.Label)
138138
return
139139
}
140140

141+
func GenerateSeriesWithSamples(
142+
name string,
143+
startTime time.Time,
144+
scrapeInterval time.Duration,
145+
startValue int,
146+
numSamples int,
147+
additionalLabels ...prompb.Label,
148+
) (series prompb.TimeSeries) {
149+
tsMillis := TimeToMilliseconds(startTime)
150+
durMillis := scrapeInterval.Milliseconds()
151+
152+
lbls := append(
153+
[]prompb.Label{
154+
{Name: labels.MetricName, Value: name},
155+
},
156+
additionalLabels...,
157+
)
158+
159+
startTMillis := tsMillis
160+
samples := make([]prompb.Sample, numSamples)
161+
for i := 0; i < numSamples; i++ {
162+
samples[i] = prompb.Sample{
163+
Timestamp: startTMillis,
164+
Value: float64(i + startValue),
165+
}
166+
startTMillis += durMillis
167+
}
168+
169+
return prompb.TimeSeries{
170+
Labels: lbls,
171+
Samples: samples,
172+
}
173+
}
174+
141175
// GetTempDirectory creates a temporary directory for shared integration
142176
// test files, either in the working directory or a directory referenced by
143177
// the E2E_TEMP_DIR environment variable

integration/query_fuzz_test.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
//go:build integration_query_fuzz
2+
// +build integration_query_fuzz
3+
4+
package integration
5+
6+
import (
7+
"math/rand"
8+
"path"
9+
"sort"
10+
"strconv"
11+
"testing"
12+
"time"
13+
14+
"github.com/cortexproject/promqlsmith"
15+
"github.com/google/go-cmp/cmp"
16+
"github.com/google/go-cmp/cmp/cmpopts"
17+
"github.com/prometheus/common/model"
18+
"github.com/prometheus/prometheus/model/labels"
19+
"github.com/prometheus/prometheus/prompb"
20+
"github.com/stretchr/testify/require"
21+
22+
"github.com/cortexproject/cortex/integration/e2e"
23+
e2edb "github.com/cortexproject/cortex/integration/e2e/db"
24+
"github.com/cortexproject/cortex/integration/e2ecortex"
25+
"github.com/cortexproject/cortex/pkg/storage/tsdb"
26+
)
27+
28+
func TestVerticalShardingFuzz(t *testing.T) {
29+
s, err := e2e.NewScenario(networkName)
30+
require.NoError(t, err)
31+
defer s.Close()
32+
33+
// Start dependencies.
34+
consul1 := e2edb.NewConsulWithName("consul1")
35+
consul2 := e2edb.NewConsulWithName("consul2")
36+
require.NoError(t, s.StartAndWaitReady(consul1, consul2))
37+
38+
flags := map[string]string{
39+
"-store.engine": blocksStorageEngine,
40+
"-blocks-storage.backend": "filesystem",
41+
"-blocks-storage.tsdb.head-compaction-interval": "4m",
42+
"-blocks-storage.tsdb.block-ranges-period": "2h",
43+
"-blocks-storage.tsdb.ship-interval": "1h",
44+
"-blocks-storage.bucket-store.sync-interval": "15m",
45+
"-blocks-storage.tsdb.retention-period": "2h",
46+
"-blocks-storage.bucket-store.index-cache.backend": tsdb.IndexCacheBackendInMemory,
47+
"-blocks-storage.bucket-store.bucket-index.enabled": "true",
48+
"-querier.ingester-streaming": "true",
49+
"-querier.query-store-for-labels-enabled": "true",
50+
// Ingester.
51+
"-ring.store": "consul",
52+
"-consul.hostname": consul1.NetworkHTTPEndpoint(),
53+
// Distributor.
54+
"-distributor.replication-factor": "1",
55+
// Store-gateway.
56+
"-store-gateway.sharding-enabled": "false",
57+
}
58+
59+
path1 := path.Join(s.SharedDir(), "cortex-1")
60+
path2 := path.Join(s.SharedDir(), "cortex-2")
61+
62+
flags1 := mergeFlags(flags, map[string]string{"-blocks-storage.filesystem.dir": path1})
63+
// Start Cortex replicas.
64+
cortex1 := e2ecortex.NewSingleBinary("cortex-1", flags1, "")
65+
// Enable vertical sharding for the second Cortex instance.
66+
flags2 := mergeFlags(flags, map[string]string{
67+
"-frontend.query-vertical-shard-size": "2",
68+
"-blocks-storage.filesystem.dir": path2,
69+
"-consul.hostname": consul2.NetworkHTTPEndpoint(),
70+
})
71+
cortex2 := e2ecortex.NewSingleBinary("cortex-2", flags2, "")
72+
require.NoError(t, s.StartAndWaitReady(cortex1, cortex2))
73+
74+
// Wait until Cortex replicas have updated the ring state.
75+
require.NoError(t, cortex1.WaitSumMetrics(e2e.Equals(float64(512)), "cortex_ring_tokens_total"))
76+
require.NoError(t, cortex2.WaitSumMetrics(e2e.Equals(float64(512)), "cortex_ring_tokens_total"))
77+
78+
c1, err := e2ecortex.NewClient(cortex1.HTTPEndpoint(), cortex1.HTTPEndpoint(), "", "", "user-1")
79+
require.NoError(t, err)
80+
c2, err := e2ecortex.NewClient(cortex2.HTTPEndpoint(), cortex2.HTTPEndpoint(), "", "", "user-1")
81+
require.NoError(t, err)
82+
83+
now := time.Now()
84+
// Push some series to Cortex.
85+
start := now.Add(-time.Minute * 10)
86+
end := now.Add(-time.Minute * 1)
87+
numSeries := 3
88+
numSamples := 20
89+
lbls := make([]labels.Labels, numSeries)
90+
serieses := make([]prompb.TimeSeries, numSeries)
91+
scrapeInterval := 30 * time.Second
92+
for i := 0; i < numSeries; i++ {
93+
series := e2e.GenerateSeriesWithSamples("test_series", start, scrapeInterval, i*numSamples, numSamples, prompb.Label{Name: "job", Value: "test"}, prompb.Label{Name: "series", Value: strconv.Itoa(i)})
94+
serieses[i] = series
95+
builder := labels.NewBuilder(labels.EmptyLabels())
96+
for _, lbl := range series.Labels {
97+
builder.Set(lbl.Name, lbl.Value)
98+
}
99+
lbls[i] = builder.Labels(labels.EmptyLabels())
100+
}
101+
res, err := c1.Push(serieses)
102+
require.NoError(t, err)
103+
require.Equal(t, 200, res.StatusCode)
104+
res, err = c2.Push(serieses)
105+
require.NoError(t, err)
106+
require.Equal(t, 200, res.StatusCode)
107+
108+
labelSet1, err := c1.Series([]string{`{job="test"}`}, start, end)
109+
require.NoError(t, err)
110+
labelSet2, err := c2.Series([]string{`{job="test"}`}, start, end)
111+
require.NoError(t, err)
112+
require.Equal(t, labelSet1, labelSet2)
113+
114+
rnd := rand.New(rand.NewSource(now.Unix()))
115+
ps := promqlsmith.New(rnd, lbls, false, false)
116+
117+
type testCase struct {
118+
query string
119+
res1, res2 model.Value
120+
err1, err2 error
121+
instantQuery bool
122+
}
123+
124+
now = time.Now()
125+
cases := make([]*testCase, 0, 200)
126+
for i := 0; i < 100; i++ {
127+
expr := ps.WalkInstantQuery()
128+
query := expr.Pretty(0)
129+
res1, err1 := c1.Query(query, now)
130+
res2, err2 := c2.Query(query, now)
131+
cases = append(cases, &testCase{
132+
query: query,
133+
res1: res1,
134+
res2: res2,
135+
err1: err1,
136+
err2: err2,
137+
instantQuery: true,
138+
})
139+
}
140+
141+
for i := 0; i < 100; i++ {
142+
expr := ps.WalkRangeQuery()
143+
query := expr.Pretty(0)
144+
res1, err1 := c1.QueryRange(query, start, end, scrapeInterval)
145+
res2, err2 := c2.QueryRange(query, start, end, scrapeInterval)
146+
cases = append(cases, &testCase{
147+
query: query,
148+
res1: res1,
149+
res2: res2,
150+
err1: err1,
151+
err2: err2,
152+
instantQuery: false,
153+
})
154+
}
155+
156+
for i, tc := range cases {
157+
qt := "instant query"
158+
if !tc.instantQuery {
159+
qt = "range query"
160+
}
161+
if tc.err1 != nil || tc.err2 != nil {
162+
if !cmp.Equal(tc.err1, tc.err2) {
163+
t.Logf("case %d error mismatch.\n%s: %s\nerr1: %v\nerr2: %v\n", i, qt, tc.query, tc.err1, tc.err2)
164+
}
165+
} else if !sameModelValue(tc.res1, tc.res2) {
166+
t.Logf("case %d results mismatch.\n%s: %s\nres1: %s\nres2: %s\n", i, qt, tc.query, tc.res1.String(), tc.res2.String())
167+
}
168+
}
169+
}
170+
171+
func sameModelValue(a model.Value, b model.Value) bool {
172+
if a.Type() != b.Type() {
173+
return false
174+
}
175+
// We allow a margin for comparing floats.
176+
opts := []cmp.Option{cmpopts.EquateNaNs(), cmpopts.EquateApprox(0, 1e-6)}
177+
switch a.Type() {
178+
case model.ValMatrix:
179+
s1, _ := a.(model.Matrix)
180+
s2, _ := b.(model.Matrix)
181+
// Sort to make sure we are not affected by series order.
182+
sort.Sort(s1)
183+
sort.Sort(s2)
184+
return cmp.Equal(s1, s2, opts...)
185+
case model.ValVector:
186+
s1, _ := a.(model.Vector)
187+
s2, _ := b.(model.Vector)
188+
// Sort to make sure we are not affected by series order.
189+
sort.Sort(s1)
190+
sort.Sort(s2)
191+
return cmp.Equal(s1, s2, opts...)
192+
case model.ValScalar:
193+
s1, _ := a.(*model.Scalar)
194+
s2, _ := b.(*model.Scalar)
195+
return cmp.Equal(s1, s2, opts...)
196+
case model.ValString:
197+
s1, _ := a.(*model.String)
198+
s2, _ := b.(*model.String)
199+
return cmp.Equal(s1, s2, opts...)
200+
default:
201+
// model.ValNone is impossible.
202+
return false
203+
}
204+
}

vendor/github.com/cortexproject/promqlsmith/.gitignore

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/cortexproject/promqlsmith/.go-version

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)