mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
web/v1/api: add tests for admin actions (#4767)
Signed-off-by: Simon Pasquier <spasquie@redhat.com>
This commit is contained in:
parent
5464c64853
commit
6fa8de132b
|
@ -34,7 +34,7 @@ import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
"github.com/prometheus/common/route"
|
"github.com/prometheus/common/route"
|
||||||
"github.com/prometheus/tsdb"
|
tsdbLabels "github.com/prometheus/tsdb/labels"
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/config"
|
"github.com/prometheus/prometheus/config"
|
||||||
"github.com/prometheus/prometheus/pkg/gate"
|
"github.com/prometheus/prometheus/pkg/gate"
|
||||||
|
@ -49,7 +49,6 @@ import (
|
||||||
"github.com/prometheus/prometheus/storage/remote"
|
"github.com/prometheus/prometheus/storage/remote"
|
||||||
"github.com/prometheus/prometheus/util/httputil"
|
"github.com/prometheus/prometheus/util/httputil"
|
||||||
"github.com/prometheus/prometheus/util/stats"
|
"github.com/prometheus/prometheus/util/stats"
|
||||||
tsdbLabels "github.com/prometheus/tsdb/labels"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -131,6 +130,14 @@ func setCORS(w http.ResponseWriter) {
|
||||||
|
|
||||||
type apiFunc func(r *http.Request) (interface{}, *apiError, func())
|
type apiFunc func(r *http.Request) (interface{}, *apiError, func())
|
||||||
|
|
||||||
|
// TSDBAdmin defines the tsdb interfaces used by the v1 API for admin operations.
|
||||||
|
type TSDBAdmin interface {
|
||||||
|
CleanTombstones() error
|
||||||
|
Delete(mint, maxt int64, ms ...tsdbLabels.Matcher) error
|
||||||
|
Dir() string
|
||||||
|
Snapshot(dir string, withHead bool) error
|
||||||
|
}
|
||||||
|
|
||||||
// API can register a set of endpoints in a router and handle
|
// API can register a set of endpoints in a router and handle
|
||||||
// them using the provided storage and query engine.
|
// them using the provided storage and query engine.
|
||||||
type API struct {
|
type API struct {
|
||||||
|
@ -145,7 +152,7 @@ type API struct {
|
||||||
flagsMap map[string]string
|
flagsMap map[string]string
|
||||||
ready func(http.HandlerFunc) http.HandlerFunc
|
ready func(http.HandlerFunc) http.HandlerFunc
|
||||||
|
|
||||||
db func() *tsdb.DB
|
db func() TSDBAdmin
|
||||||
enableAdmin bool
|
enableAdmin bool
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
remoteReadSampleLimit int
|
remoteReadSampleLimit int
|
||||||
|
@ -166,7 +173,7 @@ func NewAPI(
|
||||||
configFunc func() config.Config,
|
configFunc func() config.Config,
|
||||||
flagsMap map[string]string,
|
flagsMap map[string]string,
|
||||||
readyFunc func(http.HandlerFunc) http.HandlerFunc,
|
readyFunc func(http.HandlerFunc) http.HandlerFunc,
|
||||||
db func() *tsdb.DB,
|
db func() TSDBAdmin,
|
||||||
enableAdmin bool,
|
enableAdmin bool,
|
||||||
logger log.Logger,
|
logger log.Logger,
|
||||||
rr rulesRetriever,
|
rr rulesRetriever,
|
||||||
|
@ -950,7 +957,7 @@ func (api *API) snapshot(r *http.Request) (interface{}, *apiError, func()) {
|
||||||
if r.FormValue("skip_head") != "" {
|
if r.FormValue("skip_head") != "" {
|
||||||
skipHead, err = strconv.ParseBool(r.FormValue("skip_head"))
|
skipHead, err = strconv.ParseBool(r.FormValue("skip_head"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &apiError{errorUnavailable, fmt.Errorf("unable to parse boolean 'skip_head' argument: %v", err)}, nil
|
return nil, &apiError{errorBadData, fmt.Errorf("unable to parse boolean 'skip_head' argument: %v", err)}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-kit/kit/log"
|
"github.com/go-kit/kit/log"
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/golang/snappy"
|
"github.com/golang/snappy"
|
||||||
config_util "github.com/prometheus/common/config"
|
config_util "github.com/prometheus/common/config"
|
||||||
|
@ -49,6 +49,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/storage"
|
"github.com/prometheus/prometheus/storage"
|
||||||
"github.com/prometheus/prometheus/storage/remote"
|
"github.com/prometheus/prometheus/storage/remote"
|
||||||
"github.com/prometheus/prometheus/util/testutil"
|
"github.com/prometheus/prometheus/util/testutil"
|
||||||
|
tsdbLabels "github.com/prometheus/tsdb/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testTargetRetriever struct{}
|
type testTargetRetriever struct{}
|
||||||
|
@ -809,39 +810,49 @@ func testEndpoints(t *testing.T, api *API, testLabelAPI bool) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
resp, apiErr, _ := test.endpoint(req.WithContext(ctx))
|
resp, apiErr, _ := test.endpoint(req.WithContext(ctx))
|
||||||
if apiErr != nil {
|
assertAPIError(t, apiErr, test.errType)
|
||||||
if test.errType == errorNone {
|
assertAPIResponse(t, resp, test.response)
|
||||||
t.Fatalf("Unexpected error: %s", apiErr)
|
|
||||||
}
|
|
||||||
if test.errType != apiErr.typ {
|
|
||||||
t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.typ)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if apiErr == nil && test.errType != errorNone {
|
|
||||||
t.Fatalf("Expected error of type %q but got none", test.errType)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(resp, test.response) {
|
|
||||||
respJSON, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to marshal response as JSON: %v", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedRespJSON, err := json.Marshal(test.response)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to marshal expected response as JSON: %v", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Fatalf(
|
|
||||||
"Response does not match, expected:\n%+v\ngot:\n%+v",
|
|
||||||
string(expectedRespJSON),
|
|
||||||
string(respJSON),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertAPIError(t *testing.T, got *apiError, exp errorType) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if got != nil {
|
||||||
|
if exp == errorNone {
|
||||||
|
t.Fatalf("Unexpected error: %s", got)
|
||||||
|
}
|
||||||
|
if exp != got.typ {
|
||||||
|
t.Fatalf("Expected error of type %q but got type %q (%q)", exp, got.typ, got)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got == nil && exp != errorNone {
|
||||||
|
t.Fatalf("Expected error of type %q but got none", exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertAPIResponse(t *testing.T, got interface{}, exp interface{}) {
|
||||||
|
if !reflect.DeepEqual(exp, got) {
|
||||||
|
respJSON, err := json.Marshal(got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal response as JSON: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedRespJSON, err := json.Marshal(exp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal expected response as JSON: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf(
|
||||||
|
"Response does not match, expected:\n%+v\ngot:\n%+v",
|
||||||
|
string(expectedRespJSON),
|
||||||
|
string(respJSON),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadEndpoint(t *testing.T) {
|
func TestReadEndpoint(t *testing.T) {
|
||||||
suite, err := promql.NewTest(t, `
|
suite, err := promql.NewTest(t, `
|
||||||
load 1m
|
load 1m
|
||||||
|
@ -944,6 +955,211 @@ func TestReadEndpoint(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeDB struct {
|
||||||
|
err error
|
||||||
|
closer func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeDB) CleanTombstones() error { return f.err }
|
||||||
|
func (f *fakeDB) Delete(mint, maxt int64, ms ...tsdbLabels.Matcher) error { return f.err }
|
||||||
|
func (f *fakeDB) Dir() string {
|
||||||
|
dir, _ := ioutil.TempDir("", "fakeDB")
|
||||||
|
f.closer = func() {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
func (f *fakeDB) Snapshot(dir string, withHead bool) error { return f.err }
|
||||||
|
|
||||||
|
func TestAdminEndpoints(t *testing.T) {
|
||||||
|
tsdb, tsdbWithError := &fakeDB{}, &fakeDB{err: fmt.Errorf("some error")}
|
||||||
|
snapshotAPI := func(api *API) apiFunc { return api.snapshot }
|
||||||
|
cleanAPI := func(api *API) apiFunc { return api.cleanTombstones }
|
||||||
|
deleteAPI := func(api *API) apiFunc { return api.deleteSeries }
|
||||||
|
|
||||||
|
for i, tc := range []struct {
|
||||||
|
db *fakeDB
|
||||||
|
enableAdmin bool
|
||||||
|
endpoint func(api *API) apiFunc
|
||||||
|
method string
|
||||||
|
values url.Values
|
||||||
|
|
||||||
|
errType errorType
|
||||||
|
}{
|
||||||
|
// Tests for the snapshot endpoint.
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: false,
|
||||||
|
endpoint: snapshotAPI,
|
||||||
|
|
||||||
|
errType: errorUnavailable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: snapshotAPI,
|
||||||
|
|
||||||
|
errType: errorNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: snapshotAPI,
|
||||||
|
values: map[string][]string{"skip_head": []string{"true"}},
|
||||||
|
|
||||||
|
errType: errorNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: snapshotAPI,
|
||||||
|
values: map[string][]string{"skip_head": []string{"xxx"}},
|
||||||
|
|
||||||
|
errType: errorBadData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdbWithError,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: snapshotAPI,
|
||||||
|
|
||||||
|
errType: errorInternal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: nil,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: snapshotAPI,
|
||||||
|
|
||||||
|
errType: errorUnavailable,
|
||||||
|
},
|
||||||
|
// Tests for the cleanTombstones endpoint.
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: false,
|
||||||
|
endpoint: cleanAPI,
|
||||||
|
|
||||||
|
errType: errorUnavailable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: cleanAPI,
|
||||||
|
|
||||||
|
errType: errorNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdbWithError,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: cleanAPI,
|
||||||
|
|
||||||
|
errType: errorInternal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: nil,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: cleanAPI,
|
||||||
|
|
||||||
|
errType: errorUnavailable,
|
||||||
|
},
|
||||||
|
// Tests for the deleteSeries endpoint.
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: false,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
|
||||||
|
errType: errorUnavailable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
|
||||||
|
errType: errorBadData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
values: map[string][]string{"match[]": []string{"123"}},
|
||||||
|
|
||||||
|
errType: errorBadData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
values: map[string][]string{"match[]": []string{"up"}, "start": []string{"xxx"}},
|
||||||
|
|
||||||
|
errType: errorBadData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
values: map[string][]string{"match[]": []string{"up"}, "end": []string{"xxx"}},
|
||||||
|
|
||||||
|
errType: errorBadData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
values: map[string][]string{"match[]": []string{"up"}},
|
||||||
|
|
||||||
|
errType: errorNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdb,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
values: map[string][]string{"match[]": []string{"up{job!=\"foo\"}", "{job=~\"bar.+\"}", "up{instance!~\"fred.+\"}"}},
|
||||||
|
|
||||||
|
errType: errorNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: tsdbWithError,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
values: map[string][]string{"match[]": []string{"up"}},
|
||||||
|
|
||||||
|
errType: errorInternal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
db: nil,
|
||||||
|
enableAdmin: true,
|
||||||
|
endpoint: deleteAPI,
|
||||||
|
|
||||||
|
errType: errorUnavailable,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tc := tc
|
||||||
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||||
|
api := &API{
|
||||||
|
db: func() TSDBAdmin {
|
||||||
|
if tc.db != nil {
|
||||||
|
return tc.db
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ready: func(f http.HandlerFunc) http.HandlerFunc { return f },
|
||||||
|
enableAdmin: tc.enableAdmin,
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if tc.db != nil && tc.db.closer != nil {
|
||||||
|
tc.db.closer()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
endpoint := tc.endpoint(api)
|
||||||
|
req, err := http.NewRequest(tc.method, fmt.Sprintf("?%s", tc.values.Encode()), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error when creating test request: %s", err)
|
||||||
|
}
|
||||||
|
_, apiErr, _ := endpoint(req)
|
||||||
|
assertAPIError(t, apiErr, tc.errType)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRespondSuccess(t *testing.T) {
|
func TestRespondSuccess(t *testing.T) {
|
||||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
api := API{}
|
api := API{}
|
||||||
|
|
|
@ -225,7 +225,12 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
},
|
},
|
||||||
o.Flags,
|
o.Flags,
|
||||||
h.testReady,
|
h.testReady,
|
||||||
h.options.TSDB,
|
func() api_v1.TSDBAdmin {
|
||||||
|
if db := h.options.TSDB(); db != nil {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
h.options.EnableAdminAPI,
|
h.options.EnableAdminAPI,
|
||||||
logger,
|
logger,
|
||||||
h.ruleManager,
|
h.ruleManager,
|
||||||
|
|
|
@ -287,6 +287,7 @@ func TestRoutePrefix(t *testing.T) {
|
||||||
testutil.Ok(t, err)
|
testutil.Ok(t, err)
|
||||||
testutil.Equals(t, http.StatusOK, resp.StatusCode)
|
testutil.Equals(t, http.StatusOK, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebugHandler(t *testing.T) {
|
func TestDebugHandler(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
prefix, url string
|
prefix, url string
|
||||||
|
|
Loading…
Reference in a new issue