Skip to content

Commit

Permalink
add query fuzz test with promqlsmith (#5200)
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Ye <[email protected]>
  • Loading branch information
yeya24 authored Mar 9, 2023
1 parent 41db460 commit b3e49cb
Show file tree
Hide file tree
Showing 22 changed files with 1,759 additions and 3 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test-build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ jobs:
- integration_memberlist
- integration_querier
- integration_ruler
- integration_query_fuzz
steps:
- name: Upgrade golang
uses: actions/setup-go@v2
Expand Down
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ run:
- integration_memberlist
- integration_querier
- integration_ruler
- integration_query_fuzz
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ lint:
golangci-lint run

# Ensure no blocklisted package is imported.
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,\
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,\
golang.org/x/net/context=context,\
sync/atomic=go.uber.org/atomic,\
github.com/prometheus/client_golang/prometheus.{MultiError}=github.com/prometheus/prometheus/tsdb/errors.{NewMulti},\
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/aws/aws-sdk-go v1.44.189
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/cespare/xxhash v1.1.0
github.com/cortexproject/promqlsmith v0.0.0-20230309031733-1c551fa10a5c
github.com/dustin/go-humanize v1.0.1
github.com/efficientgo/core v1.0.0-rc.2
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
Expand Down Expand Up @@ -76,6 +77,8 @@ require (
sigs.k8s.io/yaml v1.3.0
)

require github.com/google/go-cmp v0.5.9

require (
cloud.google.com/go v0.105.0 // indirect
cloud.google.com/go/compute v1.14.0 // indirect
Expand Down Expand Up @@ -134,7 +137,6 @@ require (
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,8 @@ github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/z
github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cortexproject/promqlsmith v0.0.0-20230309031733-1c551fa10a5c h1:XdtEYH0mSzTnJhylFjHV5lt57x+MxgWZP7/mfTGHyCQ=
github.com/cortexproject/promqlsmith v0.0.0-20230309031733-1c551fa10a5c/go.mod h1:ngsF8Fu5zfL7q0TufnEd+QvomTblziwTKBRvb9kQ5Ic=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
Expand Down
6 changes: 5 additions & 1 deletion integration/e2e/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ func NewKES(port int, serverKeyFile, serverCertFile, rootCertFile string) *e2e.H
}

func NewConsul() *e2e.HTTPService {
return NewConsulWithName("consul")
}

func NewConsulWithName(name string) *e2e.HTTPService {
return e2e.NewHTTPService(
"consul",
name,
images.Consul,
// Run consul in "dev" mode so that the initial leader election is immediate
e2e.NewCommand("agent", "-server", "-client=0.0.0.0", "-dev", "-log-level=err"),
Expand Down
34 changes: 34 additions & 0 deletions integration/e2e/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,40 @@ func GenerateSeries(name string, ts time.Time, additionalLabels ...prompb.Label)
return
}

func GenerateSeriesWithSamples(
name string,
startTime time.Time,
scrapeInterval time.Duration,
startValue int,
numSamples int,
additionalLabels ...prompb.Label,
) (series prompb.TimeSeries) {
tsMillis := TimeToMilliseconds(startTime)
durMillis := scrapeInterval.Milliseconds()

lbls := append(
[]prompb.Label{
{Name: labels.MetricName, Value: name},
},
additionalLabels...,
)

startTMillis := tsMillis
samples := make([]prompb.Sample, numSamples)
for i := 0; i < numSamples; i++ {
samples[i] = prompb.Sample{
Timestamp: startTMillis,
Value: float64(i + startValue),
}
startTMillis += durMillis
}

return prompb.TimeSeries{
Labels: lbls,
Samples: samples,
}
}

// GetTempDirectory creates a temporary directory for shared integration
// test files, either in the working directory or a directory referenced by
// the E2E_TEMP_DIR environment variable
Expand Down
204 changes: 204 additions & 0 deletions integration/query_fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//go:build integration_query_fuzz
// +build integration_query_fuzz

package integration

import (
"math/rand"
"path"
"sort"
"strconv"
"testing"
"time"

"github.com/cortexproject/promqlsmith"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/prompb"
"github.com/stretchr/testify/require"

"github.com/cortexproject/cortex/integration/e2e"
e2edb "github.com/cortexproject/cortex/integration/e2e/db"
"github.com/cortexproject/cortex/integration/e2ecortex"
"github.com/cortexproject/cortex/pkg/storage/tsdb"
)

func TestVerticalShardingFuzz(t *testing.T) {
s, err := e2e.NewScenario(networkName)
require.NoError(t, err)
defer s.Close()

// Start dependencies.
consul1 := e2edb.NewConsulWithName("consul1")
consul2 := e2edb.NewConsulWithName("consul2")
require.NoError(t, s.StartAndWaitReady(consul1, consul2))

flags := map[string]string{
"-store.engine": blocksStorageEngine,
"-blocks-storage.backend": "filesystem",
"-blocks-storage.tsdb.head-compaction-interval": "4m",
"-blocks-storage.tsdb.block-ranges-period": "2h",
"-blocks-storage.tsdb.ship-interval": "1h",
"-blocks-storage.bucket-store.sync-interval": "15m",
"-blocks-storage.tsdb.retention-period": "2h",
"-blocks-storage.bucket-store.index-cache.backend": tsdb.IndexCacheBackendInMemory,
"-blocks-storage.bucket-store.bucket-index.enabled": "true",
"-querier.ingester-streaming": "true",
"-querier.query-store-for-labels-enabled": "true",
// Ingester.
"-ring.store": "consul",
"-consul.hostname": consul1.NetworkHTTPEndpoint(),
// Distributor.
"-distributor.replication-factor": "1",
// Store-gateway.
"-store-gateway.sharding-enabled": "false",
}

path1 := path.Join(s.SharedDir(), "cortex-1")
path2 := path.Join(s.SharedDir(), "cortex-2")

flags1 := mergeFlags(flags, map[string]string{"-blocks-storage.filesystem.dir": path1})
// Start Cortex replicas.
cortex1 := e2ecortex.NewSingleBinary("cortex-1", flags1, "")
// Enable vertical sharding for the second Cortex instance.
flags2 := mergeFlags(flags, map[string]string{
"-frontend.query-vertical-shard-size": "2",
"-blocks-storage.filesystem.dir": path2,
"-consul.hostname": consul2.NetworkHTTPEndpoint(),
})
cortex2 := e2ecortex.NewSingleBinary("cortex-2", flags2, "")
require.NoError(t, s.StartAndWaitReady(cortex1, cortex2))

// Wait until Cortex replicas have updated the ring state.
require.NoError(t, cortex1.WaitSumMetrics(e2e.Equals(float64(512)), "cortex_ring_tokens_total"))
require.NoError(t, cortex2.WaitSumMetrics(e2e.Equals(float64(512)), "cortex_ring_tokens_total"))

c1, err := e2ecortex.NewClient(cortex1.HTTPEndpoint(), cortex1.HTTPEndpoint(), "", "", "user-1")
require.NoError(t, err)
c2, err := e2ecortex.NewClient(cortex2.HTTPEndpoint(), cortex2.HTTPEndpoint(), "", "", "user-1")
require.NoError(t, err)

now := time.Now()
// Push some series to Cortex.
start := now.Add(-time.Minute * 10)
end := now.Add(-time.Minute * 1)
numSeries := 3
numSamples := 20
lbls := make([]labels.Labels, numSeries)
serieses := make([]prompb.TimeSeries, numSeries)
scrapeInterval := 30 * time.Second
for i := 0; i < numSeries; i++ {
series := e2e.GenerateSeriesWithSamples("test_series", start, scrapeInterval, i*numSamples, numSamples, prompb.Label{Name: "job", Value: "test"}, prompb.Label{Name: "series", Value: strconv.Itoa(i)})
serieses[i] = series
builder := labels.NewBuilder(labels.EmptyLabels())
for _, lbl := range series.Labels {
builder.Set(lbl.Name, lbl.Value)
}
lbls[i] = builder.Labels(labels.EmptyLabels())
}
res, err := c1.Push(serieses)
require.NoError(t, err)
require.Equal(t, 200, res.StatusCode)
res, err = c2.Push(serieses)
require.NoError(t, err)
require.Equal(t, 200, res.StatusCode)

labelSet1, err := c1.Series([]string{`{job="test"}`}, start, end)
require.NoError(t, err)
labelSet2, err := c2.Series([]string{`{job="test"}`}, start, end)
require.NoError(t, err)
require.Equal(t, labelSet1, labelSet2)

rnd := rand.New(rand.NewSource(now.Unix()))
ps := promqlsmith.New(rnd, lbls, false, false)

type testCase struct {
query string
res1, res2 model.Value
err1, err2 error
instantQuery bool
}

now = time.Now()
cases := make([]*testCase, 0, 200)
for i := 0; i < 100; i++ {
expr := ps.WalkInstantQuery()
query := expr.Pretty(0)
res1, err1 := c1.Query(query, now)
res2, err2 := c2.Query(query, now)
cases = append(cases, &testCase{
query: query,
res1: res1,
res2: res2,
err1: err1,
err2: err2,
instantQuery: true,
})
}

for i := 0; i < 100; i++ {
expr := ps.WalkRangeQuery()
query := expr.Pretty(0)
res1, err1 := c1.QueryRange(query, start, end, scrapeInterval)
res2, err2 := c2.QueryRange(query, start, end, scrapeInterval)
cases = append(cases, &testCase{
query: query,
res1: res1,
res2: res2,
err1: err1,
err2: err2,
instantQuery: false,
})
}

for i, tc := range cases {
qt := "instant query"
if !tc.instantQuery {
qt = "range query"
}
if tc.err1 != nil || tc.err2 != nil {
if !cmp.Equal(tc.err1, tc.err2) {
t.Logf("case %d error mismatch.\n%s: %s\nerr1: %v\nerr2: %v\n", i, qt, tc.query, tc.err1, tc.err2)
}
} else if !sameModelValue(tc.res1, tc.res2) {
t.Logf("case %d results mismatch.\n%s: %s\nres1: %s\nres2: %s\n", i, qt, tc.query, tc.res1.String(), tc.res2.String())
}
}
}

func sameModelValue(a model.Value, b model.Value) bool {
if a.Type() != b.Type() {
return false
}
// We allow a margin for comparing floats.
opts := []cmp.Option{cmpopts.EquateNaNs(), cmpopts.EquateApprox(0, 1e-6)}
switch a.Type() {
case model.ValMatrix:
s1, _ := a.(model.Matrix)
s2, _ := b.(model.Matrix)
// Sort to make sure we are not affected by series order.
sort.Sort(s1)
sort.Sort(s2)
return cmp.Equal(s1, s2, opts...)
case model.ValVector:
s1, _ := a.(model.Vector)
s2, _ := b.(model.Vector)
// Sort to make sure we are not affected by series order.
sort.Sort(s1)
sort.Sort(s2)
return cmp.Equal(s1, s2, opts...)
case model.ValScalar:
s1, _ := a.(*model.Scalar)
s2, _ := b.(*model.Scalar)
return cmp.Equal(s1, s2, opts...)
case model.ValString:
s1, _ := a.(*model.String)
s2, _ := b.(*model.String)
return cmp.Equal(s1, s2, opts...)
default:
// model.ValNone is impossible.
return false
}
}
17 changes: 17 additions & 0 deletions vendor/github.com/cortexproject/promqlsmith/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/cortexproject/promqlsmith/.go-version

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions vendor/github.com/cortexproject/promqlsmith/.golangci.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b3e49cb

Please sign in to comment.