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/common/model"
|
||||
"github.com/prometheus/common/route"
|
||||
"github.com/prometheus/tsdb"
|
||||
tsdbLabels "github.com/prometheus/tsdb/labels"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/pkg/gate"
|
||||
|
@ -49,7 +49,6 @@ import (
|
|||
"github.com/prometheus/prometheus/storage/remote"
|
||||
"github.com/prometheus/prometheus/util/httputil"
|
||||
"github.com/prometheus/prometheus/util/stats"
|
||||
tsdbLabels "github.com/prometheus/tsdb/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -131,6 +130,14 @@ func setCORS(w http.ResponseWriter) {
|
|||
|
||||
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
|
||||
// them using the provided storage and query engine.
|
||||
type API struct {
|
||||
|
@ -145,7 +152,7 @@ type API struct {
|
|||
flagsMap map[string]string
|
||||
ready func(http.HandlerFunc) http.HandlerFunc
|
||||
|
||||
db func() *tsdb.DB
|
||||
db func() TSDBAdmin
|
||||
enableAdmin bool
|
||||
logger log.Logger
|
||||
remoteReadSampleLimit int
|
||||
|
@ -166,7 +173,7 @@ func NewAPI(
|
|||
configFunc func() config.Config,
|
||||
flagsMap map[string]string,
|
||||
readyFunc func(http.HandlerFunc) http.HandlerFunc,
|
||||
db func() *tsdb.DB,
|
||||
db func() TSDBAdmin,
|
||||
enableAdmin bool,
|
||||
logger log.Logger,
|
||||
rr rulesRetriever,
|
||||
|
@ -950,7 +957,7 @@ func (api *API) snapshot(r *http.Request) (interface{}, *apiError, func()) {
|
|||
if r.FormValue("skip_head") != "" {
|
||||
skipHead, err = strconv.ParseBool(r.FormValue("skip_head"))
|
||||
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/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/snappy"
|
||||
config_util "github.com/prometheus/common/config"
|
||||
|
@ -49,6 +49,7 @@ import (
|
|||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/remote"
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
tsdbLabels "github.com/prometheus/tsdb/labels"
|
||||
)
|
||||
|
||||
type testTargetRetriever struct{}
|
||||
|
@ -809,39 +810,49 @@ func testEndpoints(t *testing.T, api *API, testLabelAPI bool) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
resp, apiErr, _ := test.endpoint(req.WithContext(ctx))
|
||||
if apiErr != nil {
|
||||
if test.errType == errorNone {
|
||||
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),
|
||||
)
|
||||
}
|
||||
assertAPIError(t, apiErr, test.errType)
|
||||
assertAPIResponse(t, resp, test.response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
suite, err := promql.NewTest(t, `
|
||||
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) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
api := API{}
|
||||
|
|
|
@ -225,7 +225,12 @@ func New(logger log.Logger, o *Options) *Handler {
|
|||
},
|
||||
o.Flags,
|
||||
h.testReady,
|
||||
h.options.TSDB,
|
||||
func() api_v1.TSDBAdmin {
|
||||
if db := h.options.TSDB(); db != nil {
|
||||
return db
|
||||
}
|
||||
return nil
|
||||
},
|
||||
h.options.EnableAdminAPI,
|
||||
logger,
|
||||
h.ruleManager,
|
||||
|
|
|
@ -287,6 +287,7 @@ func TestRoutePrefix(t *testing.T) {
|
|||
testutil.Ok(t, err)
|
||||
testutil.Equals(t, http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestDebugHandler(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
prefix, url string
|
||||
|
|
Loading…
Reference in a new issue