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