Move Admin APIs to v1

Signed-off-by: Goutham Veeramachaneni <cs14btech11014@iith.ac.in>
This commit is contained in:
Goutham Veeramachaneni 2017-12-03 10:37:05 +05:30
parent 41b8f1f8fe
commit d8515b2580
4 changed files with 170 additions and 54 deletions

View file

@ -384,19 +384,19 @@ $ curl http://localhost:9090/api/v1/alertmanagers
## TSDB Admin APIs ## TSDB Admin APIs
In 2.0, there are new APIs that expose database functionalities for the advanced user. These APIs are not enabled unless the `--web.enable-admin-api` is set. All the APIs below are experimental and might change in the future. These are APIs that expose database functionalities for the advanced user. These APIs are not enabled unless the `--web.enable-admin-api` is set.
We also expose a gRPC API whose definition can be found [here](https://github.com/prometheus/prometheus/blob/master/prompb/rpc.proto). We also expose a gRPC API whose definition can be found [here](https://github.com/prometheus/prometheus/blob/master/prompb/rpc.proto). This is experimental and might change in the future.
### Snapshot ### Snapshot
Snapshot creates a snapshot of all current data into `snapshots/<datetime>-<rand>` under the TSDB's data directory and returns the directory as response. Snapshot creates a snapshot of all current data into `snapshots/<datetime>-<rand>` under the TSDB's data directory and returns the directory as response.
``` ```
POST /api/v2/admin/tsdb/snapshot POST /api/v1/admin/tsdb/snapshot
``` ```
```json ```json
$ curl -XPOST http://localhost:9090/api/v2/admin/tsdb/snapshot $ curl -XPOST http://localhost:9090/api/v1/admin/tsdb/snapshot
{ {
"name": "2017-11-30T15:31:59Z-2366f0a55106d6e1" "name": "2017-11-30T15:31:59Z-2366f0a55106d6e1"
} }
@ -409,45 +409,20 @@ The snapshot now exists at `<data-dir>/snapshots/2017-11-30T15:31:59Z-2366f0a551
DeleteSeries deletes data for a selection of series in a time range. The actual data still exists on disk and is cleaned up in future compactions or can be explicitly cleaned up by hitting the Clean Tombstones endpoint. DeleteSeries deletes data for a selection of series in a time range. The actual data still exists on disk and is cleaned up in future compactions or can be explicitly cleaned up by hitting the Clean Tombstones endpoint.
``` ```
POST /api/v2/admin/tsdb/delete_series DELETE /api/v1/admin/tsdb/delete_series
``` ```
Parameters (body): URL query parameters:
```json - `match[]=<series_selector>`: Repeated label matcher argument that selects the series to delete. At least one `match[]` argument must be provided.
{ - `start=<rfc3339 | unix_timestamp>`: Start timestamp.
"min_time": "date-time(iso-8601)" // optional, defaults to minmum-time. - `end=<rfc3339 | unix_timestamp>`: End timestamp.
"max_time": "date-time(iso-8601)" // optional, defaults to maximum-time
"matchers": [LabelMatcher]
}
```
```json
LabelMatchers: Matcher specifies a rule, which can match or set of labels or not.
{
"type": "string", // One of: EQ, NEQ, RE, NRE defaults to EQ.
"name": "label-name",
"value": "label-value"
}
```
Example: Example:
```json ```json
$ curl -X POST \ $ curl -X DELETE \
http://localhost:9090/api/v2/admin/tsdb/delete_series \ -g 'http://localhost:9090/api/v1/series?match[]=up&match[]=process_start_time_seconds{job="prometheus"}'
-H 'content-type: application/json' \
-d '{
"max_time": "'2017-11-30T20:18:30+05:30",
"matchers": [{
"type": "EQ",
"name": "__name__",
"value": "up"
}]
}'
{}
``` ```
@ -456,12 +431,11 @@ $ curl -X POST \
CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. This can be used after deleting series to free up space. CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. This can be used after deleting series to free up space.
``` ```
POST /api/v2/admin/tsdb/clean_tombstones POST /api/v1/admin/tsdb/clean_tombstones
``` ```
This takes no parameters or body. This takes no parameters or body.
```json ```json
$ curl -XPOST http://localhost:9090/api/v2/admin/tsdb/clean_tombstones $ curl -XPOST http://localhost:9090/api/v1/admin/tsdb/clean_tombstones
{}
``` ```

View file

@ -19,8 +19,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"math/rand"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"time" "time"
@ -28,6 +31,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"
"github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/pkg/labels"
@ -39,6 +43,7 @@ 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"
) )
type status string type status string
@ -51,12 +56,13 @@ const (
type errorType string type errorType string
const ( const (
errorNone errorType = "" errorNone errorType = ""
errorTimeout = "timeout" errorTimeout = "timeout"
errorCanceled = "canceled" errorCanceled = "canceled"
errorExec = "execution" errorExec = "execution"
errorBadData = "bad_data" errorBadData = "bad_data"
errorInternal = "internal" errorInternal = "internal"
errorUnavailable = "unavailable"
) )
var corsHeaders = map[string]string{ var corsHeaders = map[string]string{
@ -111,6 +117,9 @@ type API struct {
now func() time.Time now func() time.Time
config func() config.Config config func() config.Config
ready func(http.HandlerFunc) http.HandlerFunc ready func(http.HandlerFunc) http.HandlerFunc
db func() *tsdb.DB
enableAdmin bool
} }
// NewAPI returns an initialized API type. // NewAPI returns an initialized API type.
@ -121,15 +130,19 @@ func NewAPI(
ar alertmanagerRetriever, ar alertmanagerRetriever,
configFunc func() config.Config, configFunc func() config.Config,
readyFunc func(http.HandlerFunc) http.HandlerFunc, readyFunc func(http.HandlerFunc) http.HandlerFunc,
db func() *tsdb.DB,
enableAdmin bool,
) *API { ) *API {
return &API{ return &API{
QueryEngine: qe, QueryEngine: qe,
Queryable: q, Queryable: q,
targetRetriever: tr, targetRetriever: tr,
alertmanagerRetriever: ar, alertmanagerRetriever: ar,
now: time.Now, now: time.Now,
config: configFunc, config: configFunc,
ready: readyFunc, ready: readyFunc,
db: db,
enableAdmin: enableAdmin,
} }
} }
@ -168,6 +181,11 @@ func (api *API) Register(r *route.Router) {
r.Get("/status/config", instr("config", api.serveConfig)) r.Get("/status/config", instr("config", api.serveConfig))
r.Post("/read", api.ready(prometheus.InstrumentHandler("read", http.HandlerFunc(api.remoteRead)))) r.Post("/read", api.ready(prometheus.InstrumentHandler("read", http.HandlerFunc(api.remoteRead))))
// Admin APIs
r.Del("/admin/tsdb/delete_series", instr("delete_series", api.deleteSeries))
r.Post("/admin/tsdb/clean_tombstones", instr("clean_tombstones", api.cleanTombstones))
r.Post("/admin/tsdb/snapshot", instr("snapshot", api.snapshot))
} }
type queryData struct { type queryData struct {
@ -555,6 +573,128 @@ func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
} }
} }
func (api *API) deleteSeries(r *http.Request) (interface{}, *apiError) {
if !api.enableAdmin {
return nil, &apiError{errorUnavailable, errors.New("Admin APIs disabled")}
}
db := api.db()
if db == nil {
return nil, &apiError{errorUnavailable, errors.New("TSDB not ready")}
}
r.ParseForm()
if len(r.Form["match[]"]) == 0 {
return nil, &apiError{errorBadData, fmt.Errorf("no match[] parameter provided")}
}
var start time.Time
if t := r.FormValue("start"); t != "" {
var err error
start, err = parseTime(t)
if err != nil {
return nil, &apiError{errorBadData, err}
}
} else {
start = minTime
}
var end time.Time
if t := r.FormValue("end"); t != "" {
var err error
end, err = parseTime(t)
if err != nil {
return nil, &apiError{errorBadData, err}
}
} else {
end = maxTime
}
for _, s := range r.Form["match[]"] {
matchers, err := promql.ParseMetricSelector(s)
if err != nil {
return nil, &apiError{errorBadData, err}
}
var selector tsdbLabels.Selector
for _, m := range matchers {
selector = append(selector, convertMatcher(m))
}
if err := db.Delete(timestamp.FromTime(start), timestamp.FromTime(end), selector...); err != nil {
return nil, &apiError{errorInternal, err}
}
}
return nil, nil
}
func (api *API) snapshot(r *http.Request) (interface{}, *apiError) {
if !api.enableAdmin {
return nil, &apiError{errorUnavailable, errors.New("Admin APIs disabled")}
}
db := api.db()
if db == nil {
return nil, &apiError{errorUnavailable, errors.New("TSDB not ready")}
}
var (
snapdir = filepath.Join(db.Dir(), "snapshots")
name = fmt.Sprintf("%s-%x", time.Now().UTC().Format(time.RFC3339), rand.Int())
dir = filepath.Join(snapdir, name)
)
if err := os.MkdirAll(dir, 0777); err != nil {
return nil, &apiError{errorInternal, fmt.Errorf("create snapshot directory: %s", err)}
}
if err := db.Snapshot(dir); err != nil {
return nil, &apiError{errorInternal, fmt.Errorf("create snapshot: %s", err)}
}
return struct {
Name string `json:"name"`
}{name}, nil
}
func (api *API) cleanTombstones(r *http.Request) (interface{}, *apiError) {
if !api.enableAdmin {
return nil, &apiError{errorUnavailable, errors.New("Admin APIs disabled")}
}
db := api.db()
if db == nil {
return nil, &apiError{errorUnavailable, errors.New("TSDB not ready")}
}
if err := db.CleanTombstones(); err != nil {
return nil, &apiError{errorInternal, err}
}
return nil, nil
}
func convertMatcher(m *labels.Matcher) tsdbLabels.Matcher {
switch m.Type {
case labels.MatchEqual:
return tsdbLabels.NewEqualMatcher(m.Name, m.Value)
case labels.MatchNotEqual:
return tsdbLabels.Not(tsdbLabels.NewEqualMatcher(m.Name, m.Value))
case labels.MatchRegexp:
res, err := tsdbLabels.NewRegexpMatcher(m.Name, "^(?:"+m.Value+")$")
if err != nil {
panic(err)
}
return res
case labels.MatchNotRegexp:
res, err := tsdbLabels.NewRegexpMatcher(m.Name, "^(?:"+m.Value+")$")
if err != nil {
panic(err)
}
return tsdbLabels.Not(res)
}
panic("storage.convertMatcher: invalid matcher type")
}
// mergeLabels merges two sets of sorted proto labels, preferring those in // mergeLabels merges two sets of sorted proto labels, preferring those in
// primary to those in secondary when there is an overlap. // primary to those in secondary when there is an overlap.
func mergeLabels(primary, secondary []*prompb.Label) []*prompb.Label { func mergeLabels(primary, secondary []*prompb.Label) []*prompb.Label {

View file

@ -79,7 +79,7 @@ func (api *API) RegisterGRPC(srv *grpc.Server) {
if api.enableAdmin { if api.enableAdmin {
pb.RegisterAdminServer(srv, NewAdmin(api.db)) pb.RegisterAdminServer(srv, NewAdmin(api.db))
} else { } else {
pb.RegisterAdminServer(srv, &adminDisabled{}) pb.RegisterAdminServer(srv, &AdminDisabled{})
} }
} }
@ -134,23 +134,23 @@ func labelsToProto(lset labels.Labels) pb.Labels {
return r return r
} }
// adminDisabled implements the administration interface that informs // AdminDisabled implements the administration interface that informs
// that the API endpoints are disbaled. // that the API endpoints are disbaled.
type adminDisabled struct { type AdminDisabled struct {
} }
// TSDBSnapshot implements pb.AdminServer. // TSDBSnapshot implements pb.AdminServer.
func (s *adminDisabled) TSDBSnapshot(_ old_ctx.Context, _ *pb.TSDBSnapshotRequest) (*pb.TSDBSnapshotResponse, error) { func (s *AdminDisabled) TSDBSnapshot(_ old_ctx.Context, _ *pb.TSDBSnapshotRequest) (*pb.TSDBSnapshotResponse, error) {
return nil, status.Error(codes.Unavailable, "Admin APIs are disabled") return nil, status.Error(codes.Unavailable, "Admin APIs are disabled")
} }
// TSDBCleanTombstones implements pb.AdminServer. // TSDBCleanTombstones implements pb.AdminServer.
func (s *adminDisabled) TSDBCleanTombstones(_ old_ctx.Context, _ *pb.TSDBCleanTombstonesRequest) (*pb.TSDBCleanTombstonesResponse, error) { func (s *AdminDisabled) TSDBCleanTombstones(_ old_ctx.Context, _ *pb.TSDBCleanTombstonesRequest) (*pb.TSDBCleanTombstonesResponse, error) {
return nil, status.Error(codes.Unavailable, "Admin APIs are disabled") return nil, status.Error(codes.Unavailable, "Admin APIs are disabled")
} }
// DeleteSeries imeplements pb.AdminServer. // DeleteSeries imeplements pb.AdminServer.
func (s *adminDisabled) DeleteSeries(_ old_ctx.Context, r *pb.SeriesDeleteRequest) (*pb.SeriesDeleteResponse, error) { func (s *AdminDisabled) DeleteSeries(_ old_ctx.Context, r *pb.SeriesDeleteRequest) (*pb.SeriesDeleteResponse, error) {
return nil, status.Error(codes.Unavailable, "Admin APIs are disabled") return nil, status.Error(codes.Unavailable, "Admin APIs are disabled")
} }

View file

@ -188,6 +188,8 @@ func New(logger log.Logger, o *Options) *Handler {
return *h.config return *h.config
}, },
h.testReady, h.testReady,
h.options.TSDB,
h.options.EnableAdminAPI,
) )
if o.RoutePrefix != "/" { if o.RoutePrefix != "/" {