2024-11-26 19:03:56 +01:00
|
|
|
package tests
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
2025-07-25 20:28:50 +02:00
|
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
2024-11-26 19:03:56 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestClusterMultiTenantSelect(t *testing.T) {
|
2025-07-25 20:28:50 +02:00
|
|
|
fs.MustRemoveDir(t.Name())
|
2024-11-26 19:03:56 +01:00
|
|
|
|
|
|
|
|
cmpOpt := cmpopts.IgnoreFields(apptest.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType")
|
|
|
|
|
cmpSROpt := cmpopts.IgnoreFields(apptest.PrometheusAPIV1SeriesResponse{}, "Status", "IsPartial")
|
|
|
|
|
|
|
|
|
|
tc := apptest.NewTestCase(t)
|
|
|
|
|
defer tc.Stop()
|
|
|
|
|
vmstorage := tc.MustStartVmstorage("vmstorage", []string{
|
|
|
|
|
"-storageDataPath=" + tc.Dir() + "/vmstorage",
|
|
|
|
|
"-retentionPeriod=100y",
|
|
|
|
|
})
|
|
|
|
|
vminsert := tc.MustStartVminsert("vminsert", []string{
|
|
|
|
|
"-storageNode=" + vmstorage.VminsertAddr(),
|
|
|
|
|
})
|
|
|
|
|
vmselect := tc.MustStartVmselect("vmselect", []string{
|
|
|
|
|
"-storageNode=" + vmstorage.VmselectAddr(),
|
|
|
|
|
"-search.tenantCacheExpireDuration=0",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var commonSamples = []string{
|
|
|
|
|
`foo_bar 1.00 1652169600000`, // 2022-05-10T08:00:00Z
|
|
|
|
|
`foo_bar 2.00 1652169660000`, // 2022-05-10T08:01:00Z
|
|
|
|
|
`foo_bar 3.00 1652169720000`, // 2022-05-10T08:02:00Z
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// test for empty tenants request
|
|
|
|
|
got := vmselect.PrometheusAPIV1Query(t, "foo_bar", apptest.QueryOpts{
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
Step: "5m",
|
|
|
|
|
Time: "2022-05-10T08:03:00.000Z",
|
|
|
|
|
})
|
|
|
|
|
want := apptest.NewPrometheusAPIV1QueryResponse(t, `{"data":{"result":[]}}`)
|
|
|
|
|
if diff := cmp.Diff(want, got, cmpOpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ingest per tenant data and verify it with search
|
|
|
|
|
tenantIDs := []string{"1:1", "1:15"}
|
2025-07-07 11:41:33 +02:00
|
|
|
instantCT := "2022-05-10T08:05:00.000Z" // 1652169900 Unix seconds
|
2024-11-26 19:03:56 +01:00
|
|
|
for _, tenantID := range tenantIDs {
|
|
|
|
|
vminsert.PrometheusAPIV1ImportPrometheus(t, commonSamples, apptest.QueryOpts{Tenant: tenantID})
|
|
|
|
|
vmstorage.ForceFlush(t)
|
|
|
|
|
got := vmselect.PrometheusAPIV1Query(t, "foo_bar", apptest.QueryOpts{
|
|
|
|
|
Tenant: tenantID, Time: instantCT,
|
|
|
|
|
})
|
|
|
|
|
want := apptest.NewPrometheusAPIV1QueryResponse(t, `{"data":{"result":[{"metric":{"__name__":"foo_bar"},"value":[1652169900,"3"]}]}}`)
|
|
|
|
|
if diff := cmp.Diff(want, got, cmpOpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// verify all tenants searchable with multitenant APIs
|
|
|
|
|
|
|
|
|
|
// /api/v1/query
|
|
|
|
|
want = apptest.NewPrometheusAPIV1QueryResponse(t,
|
|
|
|
|
`{"data":
|
|
|
|
|
{"result":[
|
|
|
|
|
{"metric":{"__name__":"foo_bar","vm_account_id":"1","vm_project_id": "1"},"value":[1652169900,"3"]},
|
|
|
|
|
{"metric":{"__name__":"foo_bar","vm_account_id":"1","vm_project_id":"15"},"value":[1652169900,"3"]}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}`,
|
|
|
|
|
)
|
|
|
|
|
got = vmselect.PrometheusAPIV1Query(t, "foo_bar", apptest.QueryOpts{
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
Time: instantCT,
|
|
|
|
|
})
|
|
|
|
|
if diff := cmp.Diff(want, got, cmpOpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// /api/v1/query_range aggregated by tenant labels
|
|
|
|
|
query := "sum(foo_bar) by(vm_account_id,vm_project_id)"
|
|
|
|
|
got = vmselect.PrometheusAPIV1QueryRange(t, query, apptest.QueryOpts{
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
Start: "2022-05-10T07:59:00.000Z",
|
|
|
|
|
End: "2022-05-10T08:05:00.000Z",
|
|
|
|
|
Step: "1m",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
want = apptest.NewPrometheusAPIV1QueryResponse(t,
|
|
|
|
|
`{"data":
|
|
|
|
|
{"result": [
|
|
|
|
|
{"metric": {"vm_account_id": "1","vm_project_id":"1"}, "values": [[1652169600,"1"],[1652169660,"2"],[1652169720,"3"],[1652169780,"3"]]},
|
|
|
|
|
{"metric": {"vm_account_id": "1","vm_project_id":"15"}, "values": [[1652169600,"1"],[1652169660,"2"],[1652169720,"3"],[1652169780,"3"]]}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}`)
|
|
|
|
|
if diff := cmp.Diff(want, got, cmpOpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// verify /api/v1/series response
|
|
|
|
|
|
|
|
|
|
wantSR := apptest.NewPrometheusAPIV1SeriesResponse(t,
|
|
|
|
|
`{"data": [
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"1", "vm_project_id":"1"},
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"1", "vm_project_id":"15"}
|
|
|
|
|
]
|
|
|
|
|
}`)
|
|
|
|
|
wantSR.Sort()
|
|
|
|
|
|
|
|
|
|
gotSR := vmselect.PrometheusAPIV1Series(t, "foo_bar", apptest.QueryOpts{
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
Start: "2022-05-10T08:03:00.000Z",
|
|
|
|
|
})
|
|
|
|
|
gotSR.Sort()
|
|
|
|
|
if diff := cmp.Diff(wantSR, gotSR, cmpSROpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// test multitenant ingest path, tenants must be populated from labels
|
|
|
|
|
//
|
|
|
|
|
var tenantLabelsSamples = []string{
|
2025-07-07 11:41:33 +02:00
|
|
|
`foo_bar{vm_account_id="5"} 1.00 1652169720000`, // 2022-05-10T08:02:00Z'
|
2024-11-26 19:03:56 +01:00
|
|
|
`foo_bar{vm_project_id="10"} 2.00 1652169660000`, // 2022-05-10T08:01:00Z
|
|
|
|
|
`foo_bar{vm_account_id="5",vm_project_id="15"} 3.00 1652169720000`, // 2022-05-10T08:02:00Z
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vminsert.PrometheusAPIV1ImportPrometheus(t, tenantLabelsSamples, apptest.QueryOpts{Tenant: "multitenant"})
|
|
|
|
|
vmstorage.ForceFlush(t)
|
|
|
|
|
|
|
|
|
|
// /api/v1/query with query filters
|
|
|
|
|
want = apptest.NewPrometheusAPIV1QueryResponse(t,
|
|
|
|
|
`{"data":
|
|
|
|
|
{"result":[
|
|
|
|
|
{"metric":{"__name__":"foo_bar","vm_account_id":"5","vm_project_id": "0"},"value":[1652169900,"1"]},
|
|
|
|
|
{"metric":{"__name__":"foo_bar","vm_account_id":"5","vm_project_id":"15"},"value":[1652169900,"3"]}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}`,
|
|
|
|
|
)
|
|
|
|
|
got = vmselect.PrometheusAPIV1Query(t, `foo_bar{vm_account_id="5"}`, apptest.QueryOpts{
|
|
|
|
|
Time: instantCT,
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
})
|
|
|
|
|
if diff := cmp.Diff(want, got, cmpOpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// /api/v1/series with extra_filters
|
|
|
|
|
|
|
|
|
|
wantSR = apptest.NewPrometheusAPIV1SeriesResponse(t,
|
|
|
|
|
`{"data": [
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"5", "vm_project_id":"15"},
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"1", "vm_project_id":"15"}
|
|
|
|
|
]
|
|
|
|
|
}`)
|
|
|
|
|
wantSR.Sort()
|
|
|
|
|
gotSR = vmselect.PrometheusAPIV1Series(t, "foo_bar", apptest.QueryOpts{
|
|
|
|
|
Start: "2022-05-10T08:00:00.000Z",
|
|
|
|
|
End: "2022-05-10T08:30:00.000Z",
|
|
|
|
|
ExtraFilters: []string{`{vm_project_id="15"}`},
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
})
|
|
|
|
|
gotSR.Sort()
|
|
|
|
|
|
|
|
|
|
if diff := cmp.Diff(wantSR, gotSR, cmpSROpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 15:39:28 +01:00
|
|
|
// /api/v1/label/../value with extra_filters
|
|
|
|
|
|
|
|
|
|
wantVR := apptest.NewPrometheusAPIV1LabelValuesResponse(t,
|
|
|
|
|
`{"data": [
|
|
|
|
|
"5"
|
|
|
|
|
]
|
|
|
|
|
}`)
|
|
|
|
|
wantSR.Sort()
|
|
|
|
|
gotVR := vmselect.PrometheusAPIV1LabelValues(t, "vm_account_id", "foo", apptest.QueryOpts{
|
|
|
|
|
Start: "2022-05-10T08:00:00.000Z",
|
|
|
|
|
End: "2022-05-10T08:30:00.000Z",
|
|
|
|
|
ExtraFilters: []string{`{vm_account_id="5"}`},
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
})
|
|
|
|
|
gotSR.Sort()
|
|
|
|
|
|
|
|
|
|
if diff := cmp.Diff(wantVR, gotVR, cmpopts.IgnoreFields(apptest.PrometheusAPIV1LabelValuesResponse{}, "Status", "IsPartial")); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-24 11:32:50 +04:00
|
|
|
// Delete series from specific tenant
|
2026-05-12 16:24:01 +02:00
|
|
|
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(t, "foo_bar", apptest.QueryOpts{
|
2025-01-24 11:32:50 +04:00
|
|
|
Tenant: "5:15",
|
|
|
|
|
})
|
|
|
|
|
wantSR = apptest.NewPrometheusAPIV1SeriesResponse(t,
|
|
|
|
|
`{"data": [
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"0", "vm_project_id":"10"},
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"1", "vm_project_id":"1"},
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"1", "vm_project_id":"15"},
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"5", "vm_project_id":"0"}
|
|
|
|
|
]
|
|
|
|
|
}`)
|
|
|
|
|
wantSR.Sort()
|
|
|
|
|
|
|
|
|
|
gotSR = vmselect.PrometheusAPIV1Series(t, "foo_bar", apptest.QueryOpts{
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
Start: "2022-05-10T08:03:00.000Z",
|
|
|
|
|
})
|
|
|
|
|
gotSR.Sort()
|
|
|
|
|
if diff := cmp.Diff(wantSR, gotSR, cmpSROpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete series for multitenant with tenant filter
|
2026-05-12 16:24:01 +02:00
|
|
|
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(t, `foo_bar{vm_account_id="1"}`, apptest.QueryOpts{
|
2025-01-24 11:32:50 +04:00
|
|
|
Tenant: "multitenant",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
wantSR = apptest.NewPrometheusAPIV1SeriesResponse(t,
|
|
|
|
|
`{"data": [
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"0", "vm_project_id":"10"},
|
|
|
|
|
{"__name__":"foo_bar", "vm_account_id":"5", "vm_project_id":"0"}
|
|
|
|
|
]
|
|
|
|
|
}`)
|
|
|
|
|
wantSR.Sort()
|
|
|
|
|
|
|
|
|
|
gotSR = vmselect.PrometheusAPIV1Series(t, `foo_bar`, apptest.QueryOpts{
|
|
|
|
|
Tenant: "multitenant",
|
|
|
|
|
Start: "2022-05-10T08:03:00.000Z",
|
|
|
|
|
})
|
|
|
|
|
gotSR.Sort()
|
|
|
|
|
if diff := cmp.Diff(wantSR, gotSR, cmpSROpt); diff != "" {
|
|
|
|
|
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 17:00:03 +04:00
|
|
|
if got := vmselect.GetIntMetric(t, `vm_cache_requests_total{type="multitenancy/tenants"}`); got != 0 {
|
|
|
|
|
t.Errorf("unexpected multitenancy tenants cache requests; got %d; want 0", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got := vmselect.GetIntMetric(t, `vm_cache_misses_total{type="multitenancy/tenants"}`); got != 0 {
|
|
|
|
|
t.Errorf("unexpected multitenancy tenants cache misses; got %d; want 0", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got := vmselect.GetIntMetric(t, `vm_cache_entries{type="multitenancy/tenants"}`); got != 0 {
|
|
|
|
|
t.Errorf("unexpected multitenancy tenants cache entries; got %d; want 0", got)
|
|
|
|
|
}
|
2024-11-26 19:03:56 +01:00
|
|
|
}
|