1// Copyright 2016 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package v1
15
16import (
17	"encoding/json"
18	"errors"
19	"fmt"
20	"math"
21	"net/http"
22	"net/url"
23	"sort"
24	"strconv"
25	"time"
26
27	"github.com/prometheus/client_golang/prometheus"
28	"github.com/prometheus/common/model"
29	"github.com/prometheus/common/route"
30	"golang.org/x/net/context"
31
32	"github.com/prometheus/prometheus/config"
33	"github.com/prometheus/prometheus/promql"
34	"github.com/prometheus/prometheus/retrieval"
35	"github.com/prometheus/prometheus/storage/local"
36	"github.com/prometheus/prometheus/storage/metric"
37	"github.com/prometheus/prometheus/storage/remote"
38	"github.com/prometheus/prometheus/util/httputil"
39)
40
41type status string
42
43const (
44	statusSuccess status = "success"
45	statusError          = "error"
46)
47
48type errorType string
49
50const (
51	errorNone     errorType = ""
52	errorTimeout            = "timeout"
53	errorCanceled           = "canceled"
54	errorExec               = "execution"
55	errorBadData            = "bad_data"
56	errorInternal           = "internal"
57)
58
59var corsHeaders = map[string]string{
60	"Access-Control-Allow-Headers":  "Accept, Authorization, Content-Type, Origin",
61	"Access-Control-Allow-Methods":  "GET, OPTIONS",
62	"Access-Control-Allow-Origin":   "*",
63	"Access-Control-Expose-Headers": "Date",
64}
65
66type apiError struct {
67	typ errorType
68	err error
69}
70
71func (e *apiError) Error() string {
72	return fmt.Sprintf("%s: %s", e.typ, e.err)
73}
74
75type targetRetriever interface {
76	Targets() []*retrieval.Target
77}
78
79type alertmanagerRetriever interface {
80	Alertmanagers() []*url.URL
81}
82
83type response struct {
84	Status    status      `json:"status"`
85	Data      interface{} `json:"data,omitempty"`
86	ErrorType errorType   `json:"errorType,omitempty"`
87	Error     string      `json:"error,omitempty"`
88}
89
90// Enables cross-site script calls.
91func setCORS(w http.ResponseWriter) {
92	for h, v := range corsHeaders {
93		w.Header().Set(h, v)
94	}
95}
96
97type apiFunc func(r *http.Request) (interface{}, *apiError)
98
99// API can register a set of endpoints in a router and handle
100// them using the provided storage and query engine.
101type API struct {
102	Storage     local.Storage
103	QueryEngine *promql.Engine
104
105	targetRetriever       targetRetriever
106	alertmanagerRetriever alertmanagerRetriever
107
108	now    func() model.Time
109	config func() config.Config
110	ready  func(http.HandlerFunc) http.HandlerFunc
111}
112
113// NewAPI returns an initialized API type.
114func NewAPI(
115	qe *promql.Engine,
116	st local.Storage,
117	tr targetRetriever,
118	ar alertmanagerRetriever,
119	configFunc func() config.Config,
120	readyFunc func(http.HandlerFunc) http.HandlerFunc,
121) *API {
122	return &API{
123		QueryEngine:           qe,
124		Storage:               st,
125		targetRetriever:       tr,
126		alertmanagerRetriever: ar,
127		now:    model.Now,
128		config: configFunc,
129		ready:  readyFunc,
130	}
131}
132
133// Register the API's endpoints in the given router.
134func (api *API) Register(r *route.Router) {
135	instr := func(name string, f apiFunc) http.HandlerFunc {
136		hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
137			setCORS(w)
138			if data, err := f(r); err != nil {
139				respondError(w, err, data)
140			} else if data != nil {
141				respond(w, data)
142			} else {
143				w.WriteHeader(http.StatusNoContent)
144			}
145		})
146		return api.ready(prometheus.InstrumentHandler(name, httputil.CompressionHandler{
147			Handler: hf,
148		}))
149	}
150
151	r.Options("/*path", instr("options", api.options))
152
153	r.Get("/query", instr("query", api.query))
154	r.Get("/query_range", instr("query_range", api.queryRange))
155
156	r.Get("/label/:name/values", instr("label_values", api.labelValues))
157
158	r.Get("/series", instr("series", api.series))
159	r.Del("/series", instr("drop_series", api.dropSeries))
160
161	r.Get("/targets", instr("targets", api.targets))
162	r.Get("/alertmanagers", instr("alertmanagers", api.alertmanagers))
163
164	r.Get("/status/config", instr("config", api.serveConfig))
165	r.Post("/read", api.ready(prometheus.InstrumentHandler("read", http.HandlerFunc(api.remoteRead))))
166}
167
168type queryData struct {
169	ResultType model.ValueType `json:"resultType"`
170	Result     model.Value     `json:"result"`
171}
172
173func (api *API) options(r *http.Request) (interface{}, *apiError) {
174	return nil, nil
175}
176
177func (api *API) query(r *http.Request) (interface{}, *apiError) {
178	var ts model.Time
179	if t := r.FormValue("time"); t != "" {
180		var err error
181		ts, err = parseTime(t)
182		if err != nil {
183			return nil, &apiError{errorBadData, err}
184		}
185	} else {
186		ts = api.now()
187	}
188
189	ctx := r.Context()
190	if to := r.FormValue("timeout"); to != "" {
191		var cancel context.CancelFunc
192		timeout, err := parseDuration(to)
193		if err != nil {
194			return nil, &apiError{errorBadData, err}
195		}
196
197		ctx, cancel = context.WithTimeout(ctx, timeout)
198		defer cancel()
199	}
200
201	qry, err := api.QueryEngine.NewInstantQuery(r.FormValue("query"), ts)
202	if err != nil {
203		return nil, &apiError{errorBadData, err}
204	}
205
206	res := qry.Exec(ctx)
207	if res.Err != nil {
208		switch res.Err.(type) {
209		case promql.ErrQueryCanceled:
210			return nil, &apiError{errorCanceled, res.Err}
211		case promql.ErrQueryTimeout:
212			return nil, &apiError{errorTimeout, res.Err}
213		case promql.ErrStorage:
214			return nil, &apiError{errorInternal, res.Err}
215		}
216		return nil, &apiError{errorExec, res.Err}
217	}
218	return &queryData{
219		ResultType: res.Value.Type(),
220		Result:     res.Value,
221	}, nil
222}
223
224func (api *API) queryRange(r *http.Request) (interface{}, *apiError) {
225	start, err := parseTime(r.FormValue("start"))
226	if err != nil {
227		return nil, &apiError{errorBadData, err}
228	}
229	end, err := parseTime(r.FormValue("end"))
230	if err != nil {
231		return nil, &apiError{errorBadData, err}
232	}
233	if end.Before(start) {
234		err := errors.New("end timestamp must not be before start time")
235		return nil, &apiError{errorBadData, err}
236	}
237
238	step, err := parseDuration(r.FormValue("step"))
239	if err != nil {
240		return nil, &apiError{errorBadData, err}
241	}
242
243	if step <= 0 {
244		err := errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer")
245		return nil, &apiError{errorBadData, err}
246	}
247
248	// For safety, limit the number of returned points per timeseries.
249	// This is sufficient for 60s resolution for a week or 1h resolution for a year.
250	if end.Sub(start)/step > 11000 {
251		err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
252		return nil, &apiError{errorBadData, err}
253	}
254
255	ctx := r.Context()
256	if to := r.FormValue("timeout"); to != "" {
257		var cancel context.CancelFunc
258		timeout, err := parseDuration(to)
259		if err != nil {
260			return nil, &apiError{errorBadData, err}
261		}
262
263		ctx, cancel = context.WithTimeout(ctx, timeout)
264		defer cancel()
265	}
266
267	qry, err := api.QueryEngine.NewRangeQuery(r.FormValue("query"), start, end, step)
268	if err != nil {
269		return nil, &apiError{errorBadData, err}
270	}
271
272	res := qry.Exec(ctx)
273	if res.Err != nil {
274		switch res.Err.(type) {
275		case promql.ErrQueryCanceled:
276			return nil, &apiError{errorCanceled, res.Err}
277		case promql.ErrQueryTimeout:
278			return nil, &apiError{errorTimeout, res.Err}
279		}
280		return nil, &apiError{errorExec, res.Err}
281	}
282	return &queryData{
283		ResultType: res.Value.Type(),
284		Result:     res.Value,
285	}, nil
286}
287
288func (api *API) labelValues(r *http.Request) (interface{}, *apiError) {
289	name := route.Param(r.Context(), "name")
290
291	if !model.LabelNameRE.MatchString(name) {
292		return nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)}
293	}
294	q, err := api.Storage.Querier()
295	if err != nil {
296		return nil, &apiError{errorExec, err}
297	}
298	defer q.Close()
299
300	vals, err := q.LabelValuesForLabelName(r.Context(), model.LabelName(name))
301	if err != nil {
302		return nil, &apiError{errorExec, err}
303	}
304	sort.Sort(vals)
305
306	return vals, nil
307}
308
309func (api *API) series(r *http.Request) (interface{}, *apiError) {
310	r.ParseForm()
311	if len(r.Form["match[]"]) == 0 {
312		return nil, &apiError{errorBadData, fmt.Errorf("no match[] parameter provided")}
313	}
314
315	var start model.Time
316	if t := r.FormValue("start"); t != "" {
317		var err error
318		start, err = parseTime(t)
319		if err != nil {
320			return nil, &apiError{errorBadData, err}
321		}
322	} else {
323		start = model.Earliest
324	}
325
326	var end model.Time
327	if t := r.FormValue("end"); t != "" {
328		var err error
329		end, err = parseTime(t)
330		if err != nil {
331			return nil, &apiError{errorBadData, err}
332		}
333	} else {
334		end = model.Latest
335	}
336
337	var matcherSets []metric.LabelMatchers
338	for _, s := range r.Form["match[]"] {
339		matchers, err := promql.ParseMetricSelector(s)
340		if err != nil {
341			return nil, &apiError{errorBadData, err}
342		}
343		matcherSets = append(matcherSets, matchers)
344	}
345
346	q, err := api.Storage.Querier()
347	if err != nil {
348		return nil, &apiError{errorExec, err}
349	}
350	defer q.Close()
351
352	res, err := q.MetricsForLabelMatchers(r.Context(), start, end, matcherSets...)
353	if err != nil {
354		return nil, &apiError{errorExec, err}
355	}
356
357	metrics := make([]model.Metric, 0, len(res))
358	for _, met := range res {
359		metrics = append(metrics, met.Metric)
360	}
361	return metrics, nil
362}
363
364func (api *API) dropSeries(r *http.Request) (interface{}, *apiError) {
365	r.ParseForm()
366	if len(r.Form["match[]"]) == 0 {
367		return nil, &apiError{errorBadData, fmt.Errorf("no match[] parameter provided")}
368	}
369
370	numDeleted := 0
371	for _, s := range r.Form["match[]"] {
372		matchers, err := promql.ParseMetricSelector(s)
373		if err != nil {
374			return nil, &apiError{errorBadData, err}
375		}
376		n, err := api.Storage.DropMetricsForLabelMatchers(context.TODO(), matchers...)
377		if err != nil {
378			return nil, &apiError{errorExec, err}
379		}
380		numDeleted += n
381	}
382
383	res := struct {
384		NumDeleted int `json:"numDeleted"`
385	}{
386		NumDeleted: numDeleted,
387	}
388	return res, nil
389}
390
391// Target has the information for one target.
392type Target struct {
393	// Labels before any processing.
394	DiscoveredLabels model.LabelSet `json:"discoveredLabels"`
395	// Any labels that are added to this target and its metrics.
396	Labels model.LabelSet `json:"labels"`
397
398	ScrapeURL string `json:"scrapeUrl"`
399
400	LastError  string                 `json:"lastError"`
401	LastScrape time.Time              `json:"lastScrape"`
402	Health     retrieval.TargetHealth `json:"health"`
403}
404
405// TargetDiscovery has all the active targets.
406type TargetDiscovery struct {
407	ActiveTargets []*Target `json:"activeTargets"`
408}
409
410func (api *API) targets(r *http.Request) (interface{}, *apiError) {
411	targets := api.targetRetriever.Targets()
412	res := &TargetDiscovery{ActiveTargets: make([]*Target, len(targets))}
413
414	for i, t := range targets {
415		lastErrStr := ""
416		lastErr := t.LastError()
417		if lastErr != nil {
418			lastErrStr = lastErr.Error()
419		}
420
421		res.ActiveTargets[i] = &Target{
422			DiscoveredLabels: t.DiscoveredLabels(),
423			Labels:           t.Labels(),
424			ScrapeURL:        t.URL().String(),
425			LastError:        lastErrStr,
426			LastScrape:       t.LastScrape(),
427			Health:           t.Health(),
428		}
429	}
430
431	return res, nil
432}
433
434// AlertmanagerDiscovery has all the active Alertmanagers.
435type AlertmanagerDiscovery struct {
436	ActiveAlertmanagers []*AlertmanagerTarget `json:"activeAlertmanagers"`
437}
438
439// AlertmanagerTarget has info on one AM.
440type AlertmanagerTarget struct {
441	URL string `json:"url"`
442}
443
444func (api *API) alertmanagers(r *http.Request) (interface{}, *apiError) {
445	urls := api.alertmanagerRetriever.Alertmanagers()
446	ams := &AlertmanagerDiscovery{ActiveAlertmanagers: make([]*AlertmanagerTarget, len(urls))}
447
448	for i, url := range urls {
449		ams.ActiveAlertmanagers[i] = &AlertmanagerTarget{URL: url.String()}
450	}
451
452	return ams, nil
453}
454
455type prometheusConfig struct {
456	YAML string `json:"yaml"`
457}
458
459func (api *API) serveConfig(r *http.Request) (interface{}, *apiError) {
460	cfg := &prometheusConfig{
461		YAML: api.config().String(),
462	}
463	return cfg, nil
464}
465
466func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
467	req, err := remote.DecodeReadRequest(r)
468	if err != nil {
469		http.Error(w, err.Error(), http.StatusBadRequest)
470		return
471	}
472
473	resp := remote.ReadResponse{
474		Results: make([]*remote.QueryResult, len(req.Queries)),
475	}
476	for i, query := range req.Queries {
477		querier, err := api.Storage.Querier()
478		if err != nil {
479			http.Error(w, err.Error(), http.StatusInternalServerError)
480			return
481		}
482		defer querier.Close()
483
484		from, through, matchers, err := remote.FromQuery(query)
485		if err != nil {
486			http.Error(w, err.Error(), http.StatusBadRequest)
487			return
488		}
489		// Change equality matchers which match external labels
490		// to a matcher that looks for an empty label,
491		// as that label should not be present in the storage.
492		externalLabels := api.config().GlobalConfig.ExternalLabels.Clone()
493		filteredMatchers := make([]*metric.LabelMatcher, 0, len(matchers))
494		for _, m := range matchers {
495			value := externalLabels[m.Name]
496			if m.Type == metric.Equal && value == m.Value {
497				matcher, err := metric.NewLabelMatcher(metric.Equal, m.Name, "")
498				if err != nil {
499					http.Error(w, err.Error(), http.StatusInternalServerError)
500					return
501				}
502				filteredMatchers = append(filteredMatchers, matcher)
503			} else {
504				filteredMatchers = append(filteredMatchers, m)
505			}
506		}
507
508		iters, err := querier.QueryRange(r.Context(), from, through, filteredMatchers...)
509		if err != nil {
510			http.Error(w, err.Error(), http.StatusInternalServerError)
511			return
512		}
513
514		resp.Results[i] = remote.ToQueryResult(remote.IteratorsToMatrix(iters, metric.Interval{
515			OldestInclusive: from,
516			NewestInclusive: through,
517		}))
518		for _, ts := range resp.Results[i].Timeseries {
519			globalUsed := map[string]struct{}{}
520			for _, l := range ts.Labels {
521				if _, ok := externalLabels[model.LabelName(l.Name)]; ok {
522					globalUsed[l.Name] = struct{}{}
523				}
524			}
525			for ln, lv := range externalLabels {
526				if _, ok := globalUsed[string(ln)]; !ok {
527					ts.Labels = append(ts.Labels, &remote.LabelPair{
528						Name:  string(ln),
529						Value: string(lv),
530					})
531				}
532			}
533		}
534	}
535
536	if err := remote.EncodeReadResponse(&resp, w); err != nil {
537		http.Error(w, err.Error(), http.StatusInternalServerError)
538		return
539	}
540}
541
542func respond(w http.ResponseWriter, data interface{}) {
543	w.Header().Set("Content-Type", "application/json")
544	w.WriteHeader(http.StatusOK)
545
546	b, err := json.Marshal(&response{
547		Status: statusSuccess,
548		Data:   data,
549	})
550	if err != nil {
551		return
552	}
553	w.Write(b)
554}
555
556func respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) {
557	w.Header().Set("Content-Type", "application/json")
558
559	var code int
560	switch apiErr.typ {
561	case errorBadData:
562		code = http.StatusBadRequest
563	case errorExec:
564		code = 422
565	case errorCanceled, errorTimeout:
566		code = http.StatusServiceUnavailable
567	case errorInternal:
568		code = http.StatusInternalServerError
569	default:
570		code = http.StatusInternalServerError
571	}
572	w.WriteHeader(code)
573
574	b, err := json.Marshal(&response{
575		Status:    statusError,
576		ErrorType: apiErr.typ,
577		Error:     apiErr.err.Error(),
578		Data:      data,
579	})
580	if err != nil {
581		return
582	}
583	w.Write(b)
584}
585
586func parseTime(s string) (model.Time, error) {
587	if t, err := strconv.ParseFloat(s, 64); err == nil {
588		ts := t * float64(time.Second)
589		if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
590			return 0, fmt.Errorf("cannot parse %q to a valid timestamp. It overflows int64", s)
591		}
592		return model.TimeFromUnixNano(int64(ts)), nil
593	}
594	if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
595		return model.TimeFromUnixNano(t.UnixNano()), nil
596	}
597	return 0, fmt.Errorf("cannot parse %q to a valid timestamp", s)
598}
599
600func parseDuration(s string) (time.Duration, error) {
601	if d, err := strconv.ParseFloat(s, 64); err == nil {
602		ts := d * float64(time.Second)
603		if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
604			return 0, fmt.Errorf("cannot parse %q to a valid duration. It overflows int64", s)
605		}
606		return time.Duration(ts), nil
607	}
608	if d, err := model.ParseDuration(s); err == nil {
609		return time.Duration(d), nil
610	}
611	return 0, fmt.Errorf("cannot parse %q to a valid duration", s)
612}
613