// New initializes a new web Handler. func New(st local.Storage, qe *promql.Engine, rm *rules.Manager, status *PrometheusStatus, o *Options) *Handler { router := route.New() h := &Handler{ router: router, listenErrCh: make(chan error), quitCh: make(chan struct{}), reloadCh: make(chan struct{}), options: o, statusInfo: status, ruleManager: rm, queryEngine: qe, storage: st, apiV1: &v1.API{ QueryEngine: qe, Storage: st, }, apiLegacy: &legacy.API{ QueryEngine: qe, Storage: st, Now: model.Now, }, } if o.ExternalURL.Path != "" { // If the prefix is missing for the root path, prepend it. router.Get("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, o.ExternalURL.Path, http.StatusFound) }) router = router.WithPrefix(o.ExternalURL.Path) } instrf := prometheus.InstrumentHandlerFunc instrh := prometheus.InstrumentHandler router.Get("/", instrf("status", h.status)) router.Get("/alerts", instrf("alerts", h.alerts)) router.Get("/graph", instrf("graph", h.graph)) router.Get("/version", instrf("version", h.version)) router.Get("/heap", instrf("heap", dumpHeap)) router.Get("/federate", instrf("federate", h.federation)) router.Get(o.MetricsPath, prometheus.Handler().ServeHTTP) h.apiLegacy.Register(router.WithPrefix("/api")) h.apiV1.Register(router.WithPrefix("/api/v1")) router.Get("/consoles/*filepath", instrf("consoles", h.consoles)) if o.UseLocalAssets { router.Get("/static/*filepath", instrf("static", route.FileServe("web/blob/static"))) } else { router.Get("/static/*filepath", instrh("static", blob.Handler{})) } if o.UserAssetsPath != "" { router.Get("/user/*filepath", instrf("user", route.FileServe(o.UserAssetsPath))) } if o.EnableQuit { router.Post("/-/quit", h.quit) } router.Post("/-/reload", h.reload) router.Get("/debug/*subpath", http.DefaultServeMux.ServeHTTP) router.Post("/debug/*subpath", http.DefaultServeMux.ServeHTTP) return h }
func TestQuery(t *testing.T) { scenarios := []struct { // URL query string. queryStr string // Expected HTTP response status code. status int // Regex to match against response body. bodyRe string }{ { queryStr: "", status: http.StatusOK, bodyRe: `{"type":"error","value":"Parse error at char 1: no expression found in input","version":1}`, }, { queryStr: "expr=testmetric", status: http.StatusOK, bodyRe: `{"type":"vector","value":\[\{"metric":{"__name__":"testmetric"},"value":"0","timestamp":\d+\.\d+}\],"version":1\}`, }, { queryStr: "expr=testmetric×tamp=" + testTimestamp.String(), status: http.StatusOK, bodyRe: `{"type":"vector","value":\[\{"metric":{"__name__":"testmetric"},"value":"0","timestamp":` + testTimestamp.String() + `}\],"version":1\}`, }, { queryStr: "expr=testmetric×tamp=" + testTimestamp.Add(-time.Hour).String(), status: http.StatusOK, bodyRe: `{"type":"vector","value":\[\],"version":1\}`, }, { queryStr: "timestamp=invalid", status: http.StatusBadRequest, bodyRe: "invalid query timestamp", }, { queryStr: "expr=(badexpression", status: http.StatusOK, bodyRe: `{"type":"error","value":"Parse error at char 15: unclosed left parenthesis","version":1}`, }, } storage, closer := local.NewTestStorage(t, 1) defer closer.Close() storage.Append(&clientmodel.Sample{ Metric: clientmodel.Metric{ clientmodel.MetricNameLabel: "testmetric", }, Timestamp: testTimestamp, Value: 0, }) storage.WaitForIndexing() api := &API{ Now: testNow, Storage: storage, QueryEngine: promql.NewEngine(storage, nil), } rtr := route.New() api.Register(rtr.WithPrefix("/api")) server := httptest.NewServer(rtr) defer server.Close() for i, s := range scenarios { // Do query. resp, err := http.Get(server.URL + "/api/query?" + s.queryStr) if err != nil { t.Fatalf("%d. Error querying API: %s", i, err) } // Check status code. if resp.StatusCode != s.status { t.Fatalf("%d. Unexpected status code; got %d, want %d", i, resp.StatusCode, s.status) } // Check response headers. ct := resp.Header["Content-Type"] if len(ct) != 1 { t.Fatalf("%d. Unexpected number of 'Content-Type' headers; got %d, want 1", i, len(ct)) } if ct[0] != "application/json" { t.Fatalf("%d. Unexpected 'Content-Type' header; got %s; want %s", i, ct[0], "application/json") } // Check body. b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("%d. Error reading response body: %s", i, err) } re := regexp.MustCompile(s.bodyRe) if !re.Match(b) { t.Fatalf("%d. Body didn't match '%s'. Body: %s", i, s.bodyRe, string(b)) } } }