From 7bb7e565a4f5d65ecc0bace3c64cffc5bd3bc2fa Mon Sep 17 00:00:00 2001 From: Fabian Reinartz Date: Tue, 9 Jun 2015 16:09:31 +0200 Subject: [PATCH] web/api: add GET and DELETE /series endpoints --- web/api/v1/api.go | 57 ++++++++++++++++++++++++++ web/api/v1/api_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 8f97e345e..35e69a764 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -93,6 +93,9 @@ func (api *API) Register(r *route.Router) { r.Get("/query_range", instr("query_range", api.queryRange)) r.Get("/label/:name/values", instr("label_values", api.labelValues)) + + r.Get("/series", instr("series", api.series)) + r.Del("/series", instr("drop_series", api.dropSeries)) } type queryData struct { @@ -180,6 +183,60 @@ func (api *API) labelValues(r *http.Request) (interface{}, *apiError) { return vals, nil } +func (api *API) series(r *http.Request) (interface{}, *apiError) { + r.ParseForm() + if len(r.Form["match[]"]) == 0 { + return nil, &apiError{errorBadData, fmt.Errorf("no match[] parameter provided")} + } + fps := map[clientmodel.Fingerprint]struct{}{} + + for _, lm := range r.Form["match[]"] { + matchers, err := promql.ParseMetricSelector(lm) + if err != nil { + return nil, &apiError{errorBadData, err} + } + for _, fp := range api.Storage.FingerprintsForLabelMatchers(matchers) { + fps[fp] = struct{}{} + } + } + + metrics := make([]clientmodel.Metric, 0, len(fps)) + for fp := range fps { + if met := api.Storage.MetricForFingerprint(fp).Metric; met != nil { + metrics = append(metrics, met) + } + } + return metrics, nil +} + +func (api *API) dropSeries(r *http.Request) (interface{}, *apiError) { + r.ParseForm() + if len(r.Form["match[]"]) == 0 { + return nil, &apiError{errorBadData, fmt.Errorf("no match[] parameter provided")} + } + fps := map[clientmodel.Fingerprint]struct{}{} + + for _, lm := range r.Form["match[]"] { + matchers, err := promql.ParseMetricSelector(lm) + if err != nil { + return nil, &apiError{errorBadData, err} + } + for _, fp := range api.Storage.FingerprintsForLabelMatchers(matchers) { + fps[fp] = struct{}{} + } + } + for fp := range fps { + api.Storage.DropMetricsForFingerprints(fp) + } + + res := struct { + NumDeleted int `json:"numDeleted"` + }{ + NumDeleted: len(fps), + } + return res, nil +} + func respond(w http.ResponseWriter, data interface{}) { w.WriteHeader(200) w.Header().Set("Content-Type", "application/json") diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 103f1d0c8..efc114087 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -195,6 +195,94 @@ func TestEndpoints(t *testing.T) { }, errType: errorBadData, }, + { + endpoint: api.series, + query: url.Values{ + "match[]": []string{`test_metric2`}, + }, + response: []clientmodel.Metric{ + { + "__name__": "test_metric2", + "foo": "boo", + }, + }, + }, + { + endpoint: api.series, + query: url.Values{ + "match[]": []string{`test_metric1{foo=~"o$"}`}, + }, + response: []clientmodel.Metric{ + { + "__name__": "test_metric1", + "foo": "boo", + }, + }, + }, + { + endpoint: api.series, + query: url.Values{ + "match[]": []string{`test_metric1{foo=~"o$"}`, `test_metric1{foo=~"o$"}`}, + }, + response: []clientmodel.Metric{ + { + "__name__": "test_metric1", + "foo": "boo", + }, + }, + }, + { + endpoint: api.series, + query: url.Values{ + "match[]": []string{`test_metric1{foo=~"o$"}`, `none`}, + }, + response: []clientmodel.Metric{ + { + "__name__": "test_metric1", + "foo": "boo", + }, + }, + }, + // Missing match[] query params in series requests. + { + endpoint: api.series, + errType: errorBadData, + }, + { + endpoint: api.dropSeries, + errType: errorBadData, + }, + // The following tests delete time series from the test storage. They + // must remain at the end and are fixed in their order. + { + endpoint: api.dropSeries, + query: url.Values{ + "match[]": []string{`test_metric1{foo=~"o$"}`}, + }, + response: struct { + NumDeleted int `json:"numDeleted"` + }{1}, + }, + { + endpoint: api.series, + query: url.Values{ + "match[]": []string{`test_metric1`}, + }, + response: []clientmodel.Metric{ + { + "__name__": "test_metric1", + "foo": "bar", + }, + }, + }, { + endpoint: api.dropSeries, + query: url.Values{ + "match[]": []string{`{__name__=~".*"}`}, + }, + response: struct { + NumDeleted int `json:"numDeleted"` + }{2}, + }, } for _, test := range tests { @@ -227,6 +315,8 @@ func TestEndpoints(t *testing.T) { if !reflect.DeepEqual(resp, test.response) { t.Fatalf("Response does not match, expected:\n%#v\ngot:\n%#v", test.response, resp) } + // Ensure that removed metrics are unindexed before the next request. + suite.Storage().WaitForIndexing() } }