mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 00:26:36 +03:00
return empty results if tenant does not match
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
This commit is contained in:
@@ -37,6 +37,8 @@ var (
|
||||
futureRetention = flagutil.NewRetentionDuration("futureRetention", "2d", "Data with timestamps bigger than now+futureRetention is automatically deleted. "+
|
||||
"The minimum futureRetention is 2 days. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention")
|
||||
vmselectAddr = flag.String("vmselectAddr", ":8401", "TCP address to accept connections from vmselect services")
|
||||
accountID = flag.Uint64("accountID", 0, "The accountID of the stored data")
|
||||
projectID = flag.Uint64("projectID", 0, "The projectID of the stored data")
|
||||
snapshotAuthKey = flagutil.NewPassword("snapshotAuthKey", "authKey, which must be passed in query string to /snapshot* pages. It overrides -httpAuth.*")
|
||||
forceMergeAuthKey = flagutil.NewPassword("forceMergeAuthKey", "authKey, which must be passed in query string to /internal/force_merge pages. It overrides -httpAuth.*")
|
||||
forceFlushAuthKey = flagutil.NewPassword("forceFlushAuthKey", "authKey, which must be passed in query string to /internal/force_flush pages. It overrides -httpAuth.*")
|
||||
@@ -185,7 +187,13 @@ func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
|
||||
fs.RegisterPathFsMetrics(*DataPath)
|
||||
|
||||
var err error
|
||||
vmselectSrv, err = servers.NewVMSelectServer(*vmselectAddr, strg)
|
||||
if *accountID > math.MaxUint32 {
|
||||
logger.Fatalf("-accountID must to be in the range [0, %d], got %d", math.MaxUint32, *accountID)
|
||||
}
|
||||
if *projectID > math.MaxUint32 {
|
||||
logger.Fatalf("-projectID must to be in the range [0, %d], got %d", math.MaxUint32, *projectID)
|
||||
}
|
||||
vmselectSrv, err = servers.NewVMSelectServer(*vmselectAddr, strg, uint32(*accountID), uint32(*projectID))
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot create a server with -vmselectAddr=%s: %s", *vmselectAddr, err)
|
||||
}
|
||||
|
||||
@@ -48,9 +48,11 @@ var (
|
||||
)
|
||||
|
||||
// NewVMSelectServer starts new server at the given addr, which serves vmselect requests from the given s.
|
||||
func NewVMSelectServer(addr string, s *storage.Storage) (*vmselectapi.Server, error) {
|
||||
func NewVMSelectServer(addr string, s *storage.Storage, accountID, projectID uint32) (*vmselectapi.Server, error) {
|
||||
api := &vmstorageAPI{
|
||||
s: s,
|
||||
s: s,
|
||||
accountID: accountID,
|
||||
projectID: projectID,
|
||||
}
|
||||
limits := vmselectapi.Limits{
|
||||
MaxLabelNames: *maxTagKeys,
|
||||
@@ -66,17 +68,22 @@ func NewVMSelectServer(addr string, s *storage.Storage) (*vmselectapi.Server, er
|
||||
|
||||
// vmstorageAPI impelements vmselectapi.API
|
||||
type vmstorageAPI struct {
|
||||
s *storage.Storage
|
||||
s *storage.Storage
|
||||
accountID uint32
|
||||
projectID uint32
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) InitSearch(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline uint64) (vmselectapi.BlockIterator, error) {
|
||||
// TODO(rtm0): Return empty result if sq.AccountID, sq.ProjectID do not
|
||||
// match tenantID from flag and sq is not multitenant.
|
||||
// TODO(rtm0): Support multitenant queries.
|
||||
if sq.AccountID != api.accountID || sq.ProjectID != api.projectID {
|
||||
return emptyBI, nil
|
||||
}
|
||||
|
||||
tr := sq.GetTimeRange()
|
||||
if err := checkTimeRange(api.s, tr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxMetrics := getMaxMetrics(sq.MaxMetrics)
|
||||
tfss, err := api.setupTfss(qt, sq, tr, maxMetrics, deadline)
|
||||
if err != nil {
|
||||
@@ -100,8 +107,10 @@ func (api *vmstorageAPI) InitSearch(qt *querytracer.Tracer, sq *storage.SearchQu
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) SearchMetricNames(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline uint64) ([]string, error) {
|
||||
// TODO(rtm0): Return empty result if sq.AccountID, sq.ProjectID do not
|
||||
// match tenantID from flag and sq is not multitenant.
|
||||
// TODO(@rtm0): Support multitenant queries.
|
||||
if sq.AccountID != api.accountID || sq.ProjectID != api.projectID {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tr := sq.GetTimeRange()
|
||||
maxMetrics := sq.MaxMetrics
|
||||
@@ -138,8 +147,10 @@ func (api *vmstorageAPI) SearchMetricNames(qt *querytracer.Tracer, sq *storage.S
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) LabelValues(qt *querytracer.Tracer, sq *storage.SearchQuery, labelName string, maxLabelValues int, deadline uint64) ([]string, error) {
|
||||
// TODO(rtm0): Return empty result if sq.AccountID, sq.ProjectID do not
|
||||
// match tenantID from flag and sq is not multitenant.
|
||||
// TODO(@rtm0): Support multitenant queries.
|
||||
if sq.AccountID != api.accountID || sq.ProjectID != api.projectID {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tr := sq.GetTimeRange()
|
||||
maxMetrics := sq.MaxMetrics
|
||||
@@ -157,8 +168,10 @@ func (api *vmstorageAPI) LabelValues(qt *querytracer.Tracer, sq *storage.SearchQ
|
||||
|
||||
func (api *vmstorageAPI) TagValueSuffixes(qt *querytracer.Tracer, accountID, projectID uint32, tr storage.TimeRange, tagKey, tagValuePrefix string, delimiter byte,
|
||||
maxSuffixes int, deadline uint64) ([]string, error) {
|
||||
// TODO(rtm0): Return empty result if accountID, projectID do not match
|
||||
// tenantID from flag.
|
||||
// TODO(@rtm0): Support multitenant queries?
|
||||
if accountID != api.accountID || projectID != api.projectID {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
suffixes, err := api.s.SearchTagValueSuffixes(qt, tr, tagKey, tagValuePrefix, delimiter, maxSuffixes, deadline)
|
||||
if err != nil {
|
||||
@@ -172,8 +185,10 @@ func (api *vmstorageAPI) TagValueSuffixes(qt *querytracer.Tracer, accountID, pro
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) LabelNames(qt *querytracer.Tracer, sq *storage.SearchQuery, maxLabelNames int, deadline uint64) ([]string, error) {
|
||||
// TODO(rtm0): Return empty result if sq.AccountID, sq.ProjectID do not
|
||||
// match tenantID from flag and sq is not multitenant.
|
||||
// TODO(@rtm0): Support multitenant queries.
|
||||
if sq.AccountID != api.accountID || sq.ProjectID != api.projectID {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tr := sq.GetTimeRange()
|
||||
maxMetrics := sq.MaxMetrics
|
||||
@@ -190,20 +205,23 @@ func (api *vmstorageAPI) LabelNames(qt *querytracer.Tracer, sq *storage.SearchQu
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) SeriesCount(_ *querytracer.Tracer, accountID, projectID uint32, deadline uint64) (uint64, error) {
|
||||
// TODO(rtm0): Return 0 if accountID, projectID do not match tenantID from
|
||||
// flag.
|
||||
// TODO(@rtm0): Support multitenant queries?
|
||||
if accountID != api.accountID || projectID != api.projectID {
|
||||
return 0, nil
|
||||
}
|
||||
return api.s.GetSeriesCount(deadline)
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) Tenants(qt *querytracer.Tracer, tr storage.TimeRange, deadline uint64) ([]string, error) {
|
||||
// TODO(rtm0): Return the tenantID from flag.
|
||||
return []string{"0:0"}, nil
|
||||
// return api.s.SearchTenants(qt, tr, deadline)
|
||||
func (api *vmstorageAPI) Tenants(_ *querytracer.Tracer, _ storage.TimeRange, _ uint64) ([]string, error) {
|
||||
tenantID := fmt.Sprintf("%d:%d", api.accountID, api.projectID)
|
||||
return []string{tenantID}, nil
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) TSDBStatus(qt *querytracer.Tracer, sq *storage.SearchQuery, focusLabel string, topN int, deadline uint64) (*storage.TSDBStatus, error) {
|
||||
// TODO(rtm0): Return empty result if sq.AccountID, sq.ProjectID do not
|
||||
// match tenantID from flag and sq is not multitenant.
|
||||
// TODO(@rtm0): Support multitenant queries.
|
||||
if sq.AccountID != api.accountID || sq.ProjectID != api.projectID {
|
||||
return &storage.TSDBStatus{}, nil
|
||||
}
|
||||
|
||||
tr := sq.GetTimeRange()
|
||||
maxMetrics := sq.MaxMetrics
|
||||
@@ -221,8 +239,10 @@ func (api *vmstorageAPI) TSDBStatus(qt *querytracer.Tracer, sq *storage.SearchQu
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) DeleteSeries(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline uint64) (int, error) {
|
||||
// TODO(rtm0): Return empty result if sq.AccountID, sq.ProjectID do not
|
||||
// match tenantID from flag and sq is not multitenant.
|
||||
// TODO(@rtm0): Support multitenant queries.
|
||||
if sq.AccountID != api.accountID || sq.ProjectID != api.projectID {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
tr := sq.GetTimeRange()
|
||||
maxMetrics := sq.MaxMetrics
|
||||
@@ -246,7 +266,10 @@ func (api *vmstorageAPI) RegisterMetricNames(qt *querytracer.Tracer, mrs []stora
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) GetMetricNamesUsageStats(qt *querytracer.Tracer, tt *storage.TenantToken, limit, le int, matchPattern string, _ uint64) (metricnamestats.StatsResult, error) {
|
||||
// TODO(rtm0): Return empty result if tt do not match tenantID from flag.
|
||||
// TODO(@rtm0): Support multitenant queries?
|
||||
if tt.AccountID != api.accountID || tt.ProjectID != api.projectID {
|
||||
return metricnamestats.StatsResult{}, nil
|
||||
}
|
||||
return api.s.GetMetricNamesStats(qt, limit, le, matchPattern), nil
|
||||
}
|
||||
|
||||
@@ -287,7 +310,10 @@ func (api *vmstorageAPI) setupTfss(qt *querytracer.Tracer, sq *storage.SearchQue
|
||||
}
|
||||
|
||||
func (api *vmstorageAPI) GetMetadataRecords(qt *querytracer.Tracer, tt *storage.TenantToken, limit int, metricName string, deadline uint64) ([]*metricsmetadata.Row, error) {
|
||||
// TODO(rtm0): Return empty result if tt do not match tenantID from flag.
|
||||
// TODO(@rtm0): Support multitenant queries?
|
||||
if tt.AccountID != api.accountID || tt.ProjectID != api.projectID {
|
||||
return nil, nil
|
||||
}
|
||||
return api.s.GetMetadataRows(qt, limit, metricName), nil
|
||||
}
|
||||
|
||||
@@ -338,6 +364,20 @@ func (bi *blockIterator) Error() error {
|
||||
return bi.sr.Error()
|
||||
}
|
||||
|
||||
var emptyBI = &emptyBlockIterator{}
|
||||
|
||||
type emptyBlockIterator struct{}
|
||||
|
||||
func (bi *emptyBlockIterator) MustClose() {}
|
||||
|
||||
func (bi *emptyBlockIterator) NextBlock(dst []byte) ([]byte, bool) {
|
||||
return dst, false
|
||||
}
|
||||
|
||||
func (bi *emptyBlockIterator) Error() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkTimeRange returns true if the given tr is denied for querying.
|
||||
func checkTimeRange(s *storage.Storage, tr storage.TimeRange) error {
|
||||
if !*denyQueriesOutsideRetention {
|
||||
|
||||
@@ -1,32 +1,51 @@
|
||||
package apptest
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type TestData struct {
|
||||
Samples []string
|
||||
Step int64
|
||||
WantSeries []map[string]string
|
||||
WantQueryResults []*QueryResult
|
||||
Samples []string
|
||||
Step int64
|
||||
WantSeries []map[string]string
|
||||
WantLabels []string
|
||||
WantLabelValues []string
|
||||
WantQueryResults []*QueryResult
|
||||
WantMetadata map[string][]MetadataEntry
|
||||
WantMetricNamesStats []MetricNamesStatsRecord
|
||||
}
|
||||
|
||||
func GenerateTestData(prefix string, numMetrics, start, end int64) TestData {
|
||||
samples := make([]string, numMetrics)
|
||||
step := (end - start) / numMetrics
|
||||
wantSeries := make([]map[string]string, numMetrics)
|
||||
wantQueryResults := make([]*QueryResult, numMetrics)
|
||||
d := TestData{
|
||||
Samples: []string{},
|
||||
Step: (end - start) / numMetrics,
|
||||
WantSeries: make([]map[string]string, numMetrics),
|
||||
WantLabels: make([]string, numMetrics),
|
||||
WantLabelValues: make([]string, numMetrics),
|
||||
WantQueryResults: make([]*QueryResult, numMetrics),
|
||||
WantMetadata: make(map[string][]MetadataEntry),
|
||||
WantMetricNamesStats: make([]MetricNamesStatsRecord, numMetrics),
|
||||
}
|
||||
for i := range numMetrics {
|
||||
metricName := fmt.Sprintf("%s_%04d", prefix, i)
|
||||
metricHelp := fmt.Sprintf("# HELP %s some help message", metricName)
|
||||
metricType := fmt.Sprintf("# TYPE %s gauge", metricName)
|
||||
labelName := fmt.Sprintf("label_%04d", i)
|
||||
labelValue := fmt.Sprintf("value_%04d", i)
|
||||
value := i
|
||||
timestamp := start + i*step
|
||||
samples[i] = fmt.Sprintf(`%s{%s="value", label="%s"} %d %d`, metricName, labelName, labelValue, value, timestamp)
|
||||
wantSeries[i] = map[string]string{
|
||||
timestamp := start + i*d.Step
|
||||
sample := fmt.Sprintf(`%s{%s="value", label="%s"} %d %d`, metricName, labelName, labelValue, value, timestamp)
|
||||
|
||||
d.Samples = append(d.Samples, metricHelp, metricType, sample)
|
||||
d.WantSeries[i] = map[string]string{
|
||||
"__name__": metricName,
|
||||
labelName: "value",
|
||||
"label": labelValue,
|
||||
}
|
||||
wantQueryResults[i] = &QueryResult{
|
||||
d.WantLabels[i] = labelName
|
||||
d.WantLabelValues[i] = labelValue
|
||||
d.WantQueryResults[i] = &QueryResult{
|
||||
Metric: map[string]string{
|
||||
"__name__": metricName,
|
||||
labelName: "value",
|
||||
@@ -34,42 +53,121 @@ func GenerateTestData(prefix string, numMetrics, start, end int64) TestData {
|
||||
},
|
||||
Samples: []*Sample{{Timestamp: timestamp, Value: float64(value)}},
|
||||
}
|
||||
d.WantMetadata[metricName] = []MetadataEntry{{Help: "some help message", Type: "gauge"}}
|
||||
d.WantMetricNamesStats[i].MetricName = metricName
|
||||
}
|
||||
return TestData{samples, step, wantSeries, wantQueryResults}
|
||||
d.WantLabels = append(d.WantLabels, "__name__", "label")
|
||||
slices.Sort(d.WantLabels)
|
||||
return d
|
||||
}
|
||||
|
||||
// AssertSeries retrieves metric names from the storage and compares the result
|
||||
// with the expected one.
|
||||
func AssertSeries(tc *TestCase, app PrometheusQuerier, metricNameRE string, start, end int64, want []map[string]string) {
|
||||
func AssertSeries(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, start, end int64, want []map[string]string) {
|
||||
tc.T().Helper()
|
||||
|
||||
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /api/v1/series response",
|
||||
Msg: "unexpected /prometheus/api/v1/series response",
|
||||
Got: func() any {
|
||||
return app.PrometheusAPIV1Series(tc.T(), query, QueryOpts{
|
||||
Start: fmt.Sprintf("%d", start),
|
||||
End: fmt.Sprintf("%d", end),
|
||||
Tenant: tenantID,
|
||||
Start: fmt.Sprintf("%d", start),
|
||||
End: fmt.Sprintf("%d", end),
|
||||
}).Sort()
|
||||
},
|
||||
Want: &PrometheusAPIV1SeriesResponse{
|
||||
Status: "success",
|
||||
Data: want,
|
||||
},
|
||||
Retries: 1000,
|
||||
FailNow: true,
|
||||
})
|
||||
}
|
||||
|
||||
// AssertSeriesCount retrieves series count and compares it with expected one.
|
||||
func AssertSeriesCount(tc *TestCase, app PrometheusQuerier, tenantID string, start, end int64, want uint64) {
|
||||
tc.T().Helper()
|
||||
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /prometheus/api/v1/series/count response",
|
||||
Got: func() any {
|
||||
return app.PrometheusAPIV1SeriesCount(tc.T(), QueryOpts{
|
||||
Tenant: tenantID,
|
||||
Start: fmt.Sprintf("%d", start),
|
||||
End: fmt.Sprintf("%d", end),
|
||||
})
|
||||
},
|
||||
Want: &PrometheusAPIV1SeriesCountResponse{
|
||||
Status: "success",
|
||||
Data: []uint64{want},
|
||||
},
|
||||
FailNow: true,
|
||||
})
|
||||
}
|
||||
|
||||
// AssertLabels retrieves label names from the storage and compares the result
|
||||
// with the expected one.
|
||||
func AssertLabels(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, start, end int64, want []string) {
|
||||
tc.T().Helper()
|
||||
|
||||
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /prometheus/api/v1/labels response",
|
||||
Got: func() any {
|
||||
res := app.PrometheusAPIV1Labels(tc.T(), query, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
Start: fmt.Sprintf("%d", start),
|
||||
End: fmt.Sprintf("%d", end),
|
||||
})
|
||||
slices.Sort(res.Data)
|
||||
return res
|
||||
},
|
||||
Want: &PrometheusAPIV1LabelsResponse{
|
||||
Status: "success",
|
||||
Data: want,
|
||||
},
|
||||
FailNow: true,
|
||||
})
|
||||
}
|
||||
|
||||
// AssertLabelValues retrieves values for the label whose name is labelName for
|
||||
// the series whose name mathes metricNameRE, compares the result with the
|
||||
// expected one.
|
||||
func AssertLabelValues(tc *TestCase, app PrometheusQuerier, metricNameRE, labelName, tenantID string, start, end int64, want []string) {
|
||||
tc.T().Helper()
|
||||
|
||||
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /prometheus/api/v1/labels/.../values response",
|
||||
Got: func() any {
|
||||
res := app.PrometheusAPIV1LabelValues(tc.T(), labelName, query, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
Start: fmt.Sprintf("%d", start),
|
||||
End: fmt.Sprintf("%d", end),
|
||||
})
|
||||
slices.Sort(res.Data)
|
||||
return res
|
||||
},
|
||||
Want: &PrometheusAPIV1LabelValuesResponse{
|
||||
Status: "success",
|
||||
Data: want,
|
||||
},
|
||||
FailNow: true,
|
||||
})
|
||||
}
|
||||
|
||||
// AssertQueryResults sends a data query to storage and compares the query
|
||||
// result with the expected one.
|
||||
func AssertQueryResults(tc *TestCase, app PrometheusQuerier, metricNameRE string, start, end, step int64, want []*QueryResult) {
|
||||
func AssertQueryResults(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, start, end, step int64, want []*QueryResult) {
|
||||
tc.T().Helper()
|
||||
|
||||
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /api/v1/query_range response",
|
||||
Msg: "unexpected /prometheus/api/v1/query_range response",
|
||||
Got: func() any {
|
||||
return app.PrometheusAPIV1QueryRange(tc.T(), query, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
Start: fmt.Sprintf("%d", start),
|
||||
End: fmt.Sprintf("%d", end),
|
||||
Step: fmt.Sprintf("%dms", step),
|
||||
@@ -87,3 +185,163 @@ func AssertQueryResults(tc *TestCase, app PrometheusQuerier, metricNameRE string
|
||||
FailNow: true,
|
||||
})
|
||||
}
|
||||
|
||||
func AssertMetadata(tc *TestCase, app PrometheusQuerier, metricName, tenantID string, want map[string][]MetadataEntry) {
|
||||
tc.T().Helper()
|
||||
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /prometheus/api/v1/metadata response",
|
||||
Got: func() any {
|
||||
return app.PrometheusAPIV1Metadata(tc.T(), metricName, 0, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
})
|
||||
},
|
||||
Want: &PrometheusAPIV1Metadata{
|
||||
Status: "success",
|
||||
Data: want,
|
||||
},
|
||||
FailNow: true,
|
||||
})
|
||||
}
|
||||
|
||||
func AssertMetricNamesStats(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, want []MetricNamesStatsRecord) {
|
||||
tc.T().Helper()
|
||||
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /prometheus/api/v1/status/metric_names_stats response",
|
||||
Got: func() any {
|
||||
return app.PrometheusAPIV1StatusMetricNamesStats(tc.T(), "", "", metricNameRE, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
})
|
||||
},
|
||||
Want: MetricNamesStatsResponse{
|
||||
Records: want,
|
||||
},
|
||||
FailNow: true,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// GraphiteTestData holds the data samples in Graphite Pickle format, distance
|
||||
// between samples in milliseconds and expected responses for various Graphite
|
||||
// API endpoints.
|
||||
type GraphiteTestData struct {
|
||||
Samples []string
|
||||
Step int64
|
||||
WantMetricsIndex []string
|
||||
WantMetricsFind []GraphiteMetric
|
||||
WantMetricsExpand []string
|
||||
WantRenderedTargets []GraphiteRenderedTarget
|
||||
}
|
||||
|
||||
// GenerateGraphiteTestData generates Graphite test data.
|
||||
func GenerateGraphiteTestData(prefix string, numMetrics, start, end int64) GraphiteTestData {
|
||||
d := GraphiteTestData{
|
||||
Samples: make([]string, numMetrics),
|
||||
Step: (end - start) / numMetrics,
|
||||
WantMetricsIndex: make([]string, numMetrics),
|
||||
WantMetricsFind: make([]GraphiteMetric, numMetrics),
|
||||
WantMetricsExpand: make([]string, numMetrics),
|
||||
WantRenderedTargets: make([]GraphiteRenderedTarget, numMetrics),
|
||||
}
|
||||
|
||||
datapoints := make([][2]float64, numMetrics)
|
||||
for i := range numMetrics {
|
||||
timestamp := (start + i*d.Step) / 1000
|
||||
datapoints[i][1] = float64(timestamp)
|
||||
}
|
||||
|
||||
for i := range numMetrics {
|
||||
suffix := fmt.Sprintf("%04d", i)
|
||||
metricName := fmt.Sprintf("%s.%s", prefix, suffix)
|
||||
value := i
|
||||
timestamp := (start + i*d.Step) / 1000
|
||||
sample := fmt.Sprintf(`%s %d %d`, metricName, value, timestamp)
|
||||
|
||||
d.Samples[i] = sample
|
||||
d.WantMetricsIndex[i] = metricName
|
||||
d.WantMetricsFind[i].Id = metricName
|
||||
d.WantMetricsFind[i].Text = suffix
|
||||
d.WantMetricsFind[i].Leaf = 1
|
||||
d.WantMetricsExpand[i] = metricName
|
||||
d.WantRenderedTargets[i].Target = metricName
|
||||
d.WantRenderedTargets[i].Datapoints = slices.Clone(datapoints)
|
||||
d.WantRenderedTargets[i].Datapoints[i][0] = float64(value)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// AssertGraphiteMetricsIndex retrieves all metrics by sending a request to
|
||||
// /graphite/metrics/index.json and compares the result with the expected one.
|
||||
func AssertGraphiteMetricsIndex(tc *TestCase, app PrometheusQuerier, tenantID string, want []string) {
|
||||
tc.T().Helper()
|
||||
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /graphite/metrics/index.json response",
|
||||
Got: func() any {
|
||||
return app.GraphiteMetricsIndex(tc.T(), QueryOpts{
|
||||
Tenant: tenantID,
|
||||
})
|
||||
},
|
||||
Want: want,
|
||||
Retries: 30,
|
||||
FailNow: true,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// AssertGraphiteMetricsFind finds metric names by sending a request to
|
||||
// /graphite/metrics/find and compares the result with the expected one.
|
||||
func AssertGraphiteMetricsFind(tc *TestCase, app PrometheusQuerier, query, tenantID string, want []GraphiteMetric) {
|
||||
tc.T().Helper()
|
||||
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /graphite/metrics/find response",
|
||||
Got: func() any {
|
||||
return app.GraphiteMetricsFind(tc.T(), query, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
})
|
||||
},
|
||||
Want: want,
|
||||
FailNow: true,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// AssertGraphiteMetricsFind expands metric names by sending a request to
|
||||
// /graphite/metrics/expand and compares the result with the expected one.
|
||||
func AssertGraphiteMetricsExpand(tc *TestCase, app PrometheusQuerier, query, tenantID string, want []string) {
|
||||
tc.T().Helper()
|
||||
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /graphite/metrics/expand response",
|
||||
Got: func() any {
|
||||
return app.GraphiteMetricsExpand(tc.T(), query, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
})
|
||||
},
|
||||
Want: want,
|
||||
FailNow: true,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// AssertGraphiteRender retieves metric raw data by sending a request to
|
||||
// /graphite/render and compares the result with the expected one.
|
||||
func AssertGraphiteRender(tc *TestCase, app PrometheusQuerier, target, tenantID string, from, until, step int64, want []GraphiteRenderedTarget) {
|
||||
tc.T().Helper()
|
||||
|
||||
tc.Assert(&AssertOptions{
|
||||
Msg: "unexpected /graphite/render response",
|
||||
Got: func() any {
|
||||
return app.GraphiteRender(tc.T(), target, QueryOpts{
|
||||
Tenant: tenantID,
|
||||
From: fmt.Sprintf("%d", from/1000),
|
||||
Until: fmt.Sprintf("%d", until/1000),
|
||||
StorageStep: fmt.Sprintf("%dms", step),
|
||||
})
|
||||
},
|
||||
Want: want,
|
||||
FailNow: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,30 +1,49 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
var (
|
||||
vmselectPath = os.Getenv("VM_VMSELECT_PATH")
|
||||
)
|
||||
|
||||
func TestMixedDataRetrieval(t *testing.T) {
|
||||
func TestMixedPrometheusQueries(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
const numMetrics = 1000
|
||||
const (
|
||||
accountID1 = 12
|
||||
projectID1 = 34
|
||||
accountID2 = 56
|
||||
projectID2 = 78
|
||||
numMetrics = 10
|
||||
)
|
||||
tenantID1 := fmt.Sprintf("%d:%d", accountID1, projectID1)
|
||||
tenantID2 := fmt.Sprintf("%d:%d", accountID2, projectID2)
|
||||
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
end := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
data := apptest.GenerateTestData("metric", numMetrics, start, end)
|
||||
emptySeries := []map[string]string{}
|
||||
emptyLabels := []string{}
|
||||
emptyLabelValues := []string{}
|
||||
emptyQueryResults := []*apptest.QueryResult{}
|
||||
emptyMetadata := map[string][]apptest.MetadataEntry{}
|
||||
emptyMetricNamesStats := []apptest.MetricNamesStatsRecord{}
|
||||
|
||||
vmsingle := tc.MustStartVmsingle("vmsingle", []string{
|
||||
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmsingle"),
|
||||
"-retentionPeriod=100y",
|
||||
fmt.Sprintf("-accountID=%d", accountID1),
|
||||
fmt.Sprintf("-projectID=%d", projectID1),
|
||||
})
|
||||
vmselect := tc.MustStartVmselectAt("vmselect", vmselectPath, []string{
|
||||
"-storageNode=" + vmsingle.VmselectAddr(),
|
||||
@@ -32,9 +51,143 @@ func TestMixedDataRetrieval(t *testing.T) {
|
||||
|
||||
vmsingle.PrometheusAPIV1ImportPrometheus(tc.T(), data.Samples, apptest.QueryOpts{})
|
||||
vmsingle.ForceFlush(t)
|
||||
apptest.AssertSeries(tc, vmsingle, "metric.*", start, end, data.WantSeries)
|
||||
apptest.AssertQueryResults(tc, vmsingle, "metric.*", start, end, data.Step, data.WantQueryResults)
|
||||
|
||||
apptest.AssertSeries(tc, vmselect, "metric.*", start, end, data.WantSeries)
|
||||
apptest.AssertQueryResults(tc, vmselect, "metric.*", start, end, data.Step, data.WantQueryResults)
|
||||
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, data.WantSeries)
|
||||
apptest.AssertSeriesCount(tc, vmsingle, "", start, end, numMetrics)
|
||||
apptest.AssertLabels(tc, vmsingle, "metric.*", "", start, end, data.WantLabels)
|
||||
apptest.AssertLabelValues(tc, vmsingle, "metric.*", "label", "", start, end, data.WantLabelValues)
|
||||
apptest.AssertQueryResults(tc, vmsingle, "metric.*", "", start, end, data.Step, data.WantQueryResults)
|
||||
apptest.AssertMetadata(tc, vmsingle, "", "", data.WantMetadata)
|
||||
for i := range data.WantMetricNamesStats {
|
||||
data.WantMetricNamesStats[i].QueryRequestsCount = 1
|
||||
}
|
||||
apptest.AssertMetricNamesStats(tc, vmsingle, "", "", data.WantMetricNamesStats)
|
||||
|
||||
apptest.AssertSeries(tc, vmselect, "metric.*", tenantID1, start, end, data.WantSeries)
|
||||
apptest.AssertSeriesCount(tc, vmselect, tenantID1, start, end, numMetrics)
|
||||
apptest.AssertLabels(tc, vmselect, "metric.*", tenantID1, start, end, data.WantLabels)
|
||||
apptest.AssertLabelValues(tc, vmselect, "metric.*", "label", tenantID1, start, end, data.WantLabelValues)
|
||||
apptest.AssertQueryResults(tc, vmselect, "metric.*", tenantID1, start, end, data.Step, data.WantQueryResults)
|
||||
apptest.AssertMetadata(tc, vmselect, "", tenantID1, data.WantMetadata)
|
||||
for i := range data.WantMetricNamesStats {
|
||||
data.WantMetricNamesStats[i].QueryRequestsCount = 2
|
||||
}
|
||||
apptest.AssertMetricNamesStats(tc, vmselect, "", tenantID1, data.WantMetricNamesStats)
|
||||
|
||||
apptest.AssertSeries(tc, vmselect, "metric.*", tenantID2, start, end, emptySeries)
|
||||
apptest.AssertSeriesCount(tc, vmselect, tenantID2, start, end, 0)
|
||||
apptest.AssertLabels(tc, vmselect, "metric.*", tenantID2, start, end, emptyLabels)
|
||||
apptest.AssertLabelValues(tc, vmselect, "metric.*", "label", tenantID2, start, end, emptyLabelValues)
|
||||
apptest.AssertQueryResults(tc, vmselect, "metric.*", tenantID2, start, end, data.Step, emptyQueryResults)
|
||||
apptest.AssertMetadata(tc, vmselect, "", tenantID2, emptyMetadata)
|
||||
apptest.AssertMetricNamesStats(tc, vmselect, "", tenantID2, emptyMetricNamesStats)
|
||||
|
||||
gotAdminTenantsResponse := vmselect.APIV1AdminTenants(t, apptest.QueryOpts{})
|
||||
wantAdminTenantsResponse := &apptest.AdminTenantsResponse{
|
||||
Status: "success",
|
||||
Data: []string{tenantID1},
|
||||
}
|
||||
if diff := cmp.Diff(wantAdminTenantsResponse, gotAdminTenantsResponse); diff != "" {
|
||||
t.Fatalf("unexpected tenants (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMixedDeleteSeries(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
const (
|
||||
accountID1 = 12
|
||||
projectID1 = 34
|
||||
accountID2 = 56
|
||||
projectID2 = 78
|
||||
numMetrics = 10
|
||||
)
|
||||
tenantID1 := fmt.Sprintf("%d:%d", accountID1, projectID1)
|
||||
tenantID2 := fmt.Sprintf("%d:%d", accountID2, projectID2)
|
||||
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
end := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
data1 := apptest.GenerateTestData("metric1", numMetrics, start, end)
|
||||
data2 := apptest.GenerateTestData("metric2", numMetrics, start, end)
|
||||
emptySeries := []map[string]string{}
|
||||
|
||||
vmsingle := tc.MustStartVmsingle("vmsingle", []string{
|
||||
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmsingle"),
|
||||
"-retentionPeriod=100y",
|
||||
fmt.Sprintf("-accountID=%d", accountID1),
|
||||
fmt.Sprintf("-projectID=%d", projectID1),
|
||||
})
|
||||
vmselect := tc.MustStartVmselectAt("vmselect", vmselectPath, []string{
|
||||
"-storageNode=" + vmsingle.VmselectAddr(),
|
||||
})
|
||||
|
||||
vmsingle.PrometheusAPIV1ImportPrometheus(tc.T(), data1.Samples, apptest.QueryOpts{})
|
||||
vmsingle.PrometheusAPIV1ImportPrometheus(tc.T(), data2.Samples, apptest.QueryOpts{})
|
||||
vmsingle.ForceFlush(t)
|
||||
|
||||
wantSeries12 := slices.Concat(data1.WantSeries, data2.WantSeries)
|
||||
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, wantSeries12)
|
||||
|
||||
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(tc.T(), `{__name__=~"metric1.*"}`, apptest.QueryOpts{
|
||||
Tenant: tenantID1,
|
||||
})
|
||||
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, data2.WantSeries)
|
||||
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(tc.T(), `{__name__=~"metric2.*"}`, apptest.QueryOpts{
|
||||
Tenant: tenantID2,
|
||||
})
|
||||
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, data2.WantSeries)
|
||||
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(tc.T(), `{__name__=~"metric2.*"}`, apptest.QueryOpts{
|
||||
Tenant: tenantID1,
|
||||
})
|
||||
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, emptySeries)
|
||||
}
|
||||
|
||||
func TestMixedGraphiteQueries(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
const (
|
||||
accountID1 = 12
|
||||
projectID1 = 34
|
||||
accountID2 = 56
|
||||
projectID2 = 78
|
||||
numMetrics = 10
|
||||
)
|
||||
tenantID1 := fmt.Sprintf("%d:%d", accountID1, projectID1)
|
||||
tenantID2 := fmt.Sprintf("%d:%d", accountID2, projectID2)
|
||||
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
end := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli()
|
||||
data := apptest.GenerateGraphiteTestData("metric", numMetrics, start, end)
|
||||
emptyMetricsIndex := []string{}
|
||||
emptyMetricsFind := []apptest.GraphiteMetric{}
|
||||
emptyMetricsExpand := []string{}
|
||||
emptyRenderedTargets := []apptest.GraphiteRenderedTarget{}
|
||||
|
||||
vmsingle := tc.MustStartVmsingle("vmsingle", []string{
|
||||
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmsingle"),
|
||||
"-retentionPeriod=100y",
|
||||
fmt.Sprintf("-accountID=%d", accountID1),
|
||||
fmt.Sprintf("-projectID=%d", projectID1),
|
||||
})
|
||||
vmselect := tc.MustStartVmselectAt("vmselect", vmselectPath, []string{
|
||||
"-storageNode=" + vmsingle.VmselectAddr(),
|
||||
})
|
||||
|
||||
vmsingle.GraphiteWrite(tc.T(), data.Samples, apptest.QueryOpts{})
|
||||
vmsingle.ForceFlush(t)
|
||||
|
||||
apptest.AssertGraphiteMetricsIndex(tc, vmsingle, "", data.WantMetricsIndex)
|
||||
apptest.AssertGraphiteMetricsFind(tc, vmsingle, "metric.*", "", data.WantMetricsFind)
|
||||
apptest.AssertGraphiteMetricsExpand(tc, vmsingle, "metric.*", "", data.WantMetricsExpand)
|
||||
apptest.AssertGraphiteRender(tc, vmsingle, "metric.*", "", start, end, data.Step, data.WantRenderedTargets)
|
||||
|
||||
apptest.AssertGraphiteMetricsIndex(tc, vmselect, tenantID1, data.WantMetricsIndex)
|
||||
apptest.AssertGraphiteMetricsFind(tc, vmselect, "metric.*", tenantID1, data.WantMetricsFind)
|
||||
apptest.AssertGraphiteMetricsExpand(tc, vmselect, "metric.*", tenantID1, data.WantMetricsExpand)
|
||||
apptest.AssertGraphiteRender(tc, vmselect, "metric.*", tenantID1, start, end, data.Step, data.WantRenderedTargets)
|
||||
|
||||
apptest.AssertGraphiteMetricsIndex(tc, vmselect, tenantID2, emptyMetricsIndex)
|
||||
apptest.AssertGraphiteMetricsFind(tc, vmselect, "metric.*", tenantID2, emptyMetricsFind)
|
||||
apptest.AssertGraphiteMetricsExpand(tc, vmselect, "metric.*", tenantID2, emptyMetricsExpand)
|
||||
apptest.AssertGraphiteRender(tc, vmselect, "metric.*", tenantID2, start, end, data.Step, emptyRenderedTargets)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user