func TestOptionsMethod(t *testing.T) { r := route.New() api := &API{} api.Register(r) s := httptest.NewServer(r) defer s.Close() req, err := http.NewRequest("OPTIONS", s.URL+"/any_path", nil) if err != nil { t.Fatalf("Error creating OPTIONS request: %s", err) } client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("Error executing OPTIONS request: %s", err) } if resp.StatusCode != http.StatusNoContent { t.Fatalf("Expected status %d, got %d", http.StatusNoContent, resp.StatusCode) } for h, v := range corsHeaders { if resp.Header.Get(h) != v { t.Fatalf("Expected %q for header %q, got %q", v, h, resp.Header.Get(h)) } } }
func main() { flag.Parse() if *showVersion { fmt.Fprintln(os.Stdout, version.Print("alertmanager")) os.Exit(0) } log.Infoln("Starting alertmanager", version.Info()) log.Infoln("Build context", version.BuildContext()) err := os.MkdirAll(*dataDir, 0777) if err != nil { log.Fatal(err) } marker := types.NewMarker() alerts, err := boltmem.NewAlerts(*dataDir) if err != nil { log.Fatal(err) } defer alerts.Close() notifies, err := boltmem.NewNotificationInfo(*dataDir) if err != nil { log.Fatal(err) } defer notifies.Close() silences, err := boltmem.NewSilences(*dataDir, marker) if err != nil { log.Fatal(err) } defer silences.Close() var ( inhibitor *Inhibitor tmpl *template.Template disp *Dispatcher ) defer disp.Stop() api := NewAPI(alerts, silences, func() AlertOverview { return disp.Groups() }) build := func(rcvs []*config.Receiver) notify.Notifier { var ( router = notify.Router{} fanouts = notify.Build(rcvs, tmpl) ) for name, fo := range fanouts { for i, n := range fo { n = notify.Retry(n) n = notify.Log(n, log.With("step", "retry")) n = notify.Dedup(notifies, n) n = notify.Log(n, log.With("step", "dedup")) fo[i] = n } router[name] = fo } n := notify.Notifier(router) n = notify.Log(n, log.With("step", "route")) n = notify.Silence(silences, n, marker) n = notify.Log(n, log.With("step", "silence")) n = notify.Inhibit(inhibitor, n, marker) n = notify.Log(n, log.With("step", "inhibit")) return n } amURL, err := extURL(*externalURL) if err != nil { log.Fatal(err) } reload := func() (err error) { log.With("file", *configFile).Infof("Loading configuration file") defer func() { if err != nil { log.With("file", *configFile).Errorf("Loading configuration file failed: %s", err) configSuccess.Set(0) } else { configSuccess.Set(1) configSuccessTime.Set(float64(time.Now().Unix())) } }() conf, err := config.LoadFile(*configFile) if err != nil { return err } api.Update(conf.String(), time.Duration(conf.Global.ResolveTimeout)) tmpl, err = template.FromGlobs(conf.Templates...) if err != nil { return err } tmpl.ExternalURL = amURL inhibitor.Stop() disp.Stop() inhibitor = NewInhibitor(alerts, conf.InhibitRules, marker) disp = NewDispatcher(alerts, NewRoute(conf.Route, nil), build(conf.Receivers), marker) go disp.Run() go inhibitor.Run() return nil } if err := reload(); err != nil { os.Exit(1) } router := route.New() webReload := make(chan struct{}) RegisterWeb(router.WithPrefix(amURL.Path), webReload) api.Register(router.WithPrefix(path.Join(amURL.Path, "/api"))) log.Infoln("Listening on", *listenAddress) go listen(router) var ( hup = make(chan os.Signal) hupReady = make(chan bool) term = make(chan os.Signal) ) signal.Notify(hup, syscall.SIGHUP) signal.Notify(term, os.Interrupt, syscall.SIGTERM) go func() { <-hupReady for { select { case <-hup: case <-webReload: } reload() } }() // Wait for reload or termination signals. close(hupReady) // Unblock SIGHUP handler. <-term log.Infoln("Received SIGTERM, exiting gracefully...") }
// 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) } instrh := prometheus.InstrumentHandler instrf := prometheus.InstrumentHandlerFunc router.Get("/", func(w http.ResponseWriter, r *http.Request) { router.Redirect(w, r, "/graph", http.StatusFound) }) router.Get("/graph", instrf("graph", h.graph)) router.Get("/status", instrf("status", h.status)) router.Get("/alerts", instrf("alerts", h.alerts)) router.Get("/version", instrf("version", h.version)) router.Get("/heap", instrf("heap", dumpHeap)) router.Get(o.MetricsPath, prometheus.Handler().ServeHTTP) router.Get("/federate", instrh("federate", httputil.CompressionHandler{ Handler: http.HandlerFunc(h.federation), })) 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 }
// New initializes a new web Handler. func New( st local.Storage, qe *promql.Engine, tm *retrieval.TargetManager, rm *rules.Manager, version *PrometheusVersion, flags map[string]string, o *Options, ) *Handler { router := route.New() h := &Handler{ router: router, listenErrCh: make(chan error), quitCh: make(chan struct{}), reloadCh: make(chan struct{}), options: o, versionInfo: version, birth: time.Now(), flagsMap: flags, targetManager: tm, ruleManager: rm, queryEngine: qe, storage: st, apiV1: api_v1.NewAPI(qe, st), } 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) } instrh := prometheus.InstrumentHandler instrf := prometheus.InstrumentHandlerFunc router.Get("/", func(w http.ResponseWriter, r *http.Request) { router.Redirect(w, r, "/graph", http.StatusFound) }) router.Get("/alerts", instrf("alerts", h.alerts)) router.Get("/graph", instrf("graph", h.graph)) router.Get("/status", instrf("status", h.status)) router.Get("/flags", instrf("flags", h.flags)) router.Get("/config", instrf("config", h.config)) router.Get("/rules", instrf("rules", h.rules)) router.Get("/targets", instrf("targets", h.targets)) router.Get("/version", instrf("version", h.version)) router.Get("/heap", instrf("heap", dumpHeap)) router.Get(o.MetricsPath, prometheus.Handler().ServeHTTP) router.Get("/federate", instrh("federate", httputil.CompressionHandler{ Handler: http.HandlerFunc(h.federation), })) h.apiV1.Register(router.WithPrefix("/api/v1")) router.Get("/consoles/*filepath", instrf("consoles", h.consoles)) router.Get("/static/*filepath", instrf("static", serveStaticAsset)) 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=1.4", status: http.StatusOK, bodyRe: `{"type":"scalar","value":"1.4","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(&model.Sample{ Metric: model.Metric{ model.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)) } } }
func main() { flag.Parse() printVersion() if *showVersion { os.Exit(0) } err := os.MkdirAll(*dataDir, 0777) if err != nil { log.Fatal(err) } db, err := sql.Open("sqlite3", filepath.Join(*dataDir, "am.db")) if err != nil { log.Fatal(err) } defer db.Close() marker := types.NewMarker() alerts, err := sqlite.NewAlerts(db) if err != nil { log.Fatal(err) } notifies, err := sqlite.NewNotifies(db) if err != nil { log.Fatal(err) } silences, err := sqlite.NewSilences(db, marker) if err != nil { log.Fatal(err) } var ( inhibitor *Inhibitor tmpl *template.Template disp *Dispatcher ) defer disp.Stop() api := NewAPI(alerts, silences, func() AlertOverview { return disp.Groups() }) build := func(rcvs []*config.Receiver) notify.Notifier { var ( router = notify.Router{} fanouts = notify.Build(rcvs, tmpl) ) for name, fo := range fanouts { for i, n := range fo { n = notify.Retry(n) n = notify.Log(n, log.With("step", "retry")) n = notify.Dedup(notifies, n) n = notify.Log(n, log.With("step", "dedup")) fo[i] = n } router[name] = fo } n := notify.Notifier(router) n = notify.Log(n, log.With("step", "route")) n = notify.Silence(silences, n, marker) n = notify.Log(n, log.With("step", "silence")) n = notify.Inhibit(inhibitor, n, marker) n = notify.Log(n, log.With("step", "inhibit")) return n } reload := func() (err error) { log.With("file", *configFile).Infof("Loading configuration file") defer func() { if err != nil { log.With("file", *configFile).Errorf("Loading configuration file failed: %s", err) } }() conf, err := config.LoadFile(*configFile) if err != nil { return err } api.Update(conf.String(), time.Duration(conf.Global.ResolveTimeout)) tmpl, err = template.FromGlobs(conf.Templates...) if err != nil { return err } if tmpl.ExternalURL, err = extURL(*externalURL); err != nil { return err } disp.Stop() inhibitor = NewInhibitor(alerts, conf.InhibitRules, marker) disp = NewDispatcher(alerts, NewRoute(conf.Route, nil), build(conf.Receivers), marker) go disp.Run() return nil } if err := reload(); err != nil { os.Exit(1) } router := route.New() RegisterWeb(router) api.Register(router.WithPrefix("/api")) go http.ListenAndServe(*listenAddress, router) var ( hup = make(chan os.Signal) term = make(chan os.Signal) ) signal.Notify(hup, syscall.SIGHUP) signal.Notify(term, os.Interrupt, syscall.SIGTERM) go func() { for range hup { reload() } }() <-term log.Infoln("Received SIGTERM, exiting gracefully...") }
func main() { peers := &stringset{} var ( showVersion = flag.Bool("version", false, "Print version information.") configFile = flag.String("config.file", "alertmanager.yml", "Alertmanager configuration file name.") dataDir = flag.String("storage.path", "data/", "Base path for data storage.") retention = flag.Duration("data.retention", 5*24*time.Hour, "How long to keep data for.") externalURL = flag.String("web.external-url", "", "The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Alertmanager. If omitted, relevant URL components will be derived automatically.") listenAddress = flag.String("web.listen-address", ":9093", "Address to listen on for the web interface and API.") meshListen = flag.String("mesh.listen-address", net.JoinHostPort("0.0.0.0", strconv.Itoa(mesh.Port)), "mesh listen address") hwaddr = flag.String("mesh.hardware-address", mustHardwareAddr(), "MAC address, i.e. mesh peer ID") nickname = flag.String("mesh.nickname", mustHostname(), "peer nickname") ) flag.Var(peers, "mesh.peer", "initial peers (may be repeated)") flag.Parse() if len(flag.Args()) > 0 { log.Fatalln("Received unexpected and unparsed arguments: ", strings.Join(flag.Args(), ", ")) } if *showVersion { fmt.Fprintln(os.Stdout, version.Print("alertmanager")) os.Exit(0) } log.Infoln("Starting alertmanager", version.Info()) log.Infoln("Build context", version.BuildContext()) err := os.MkdirAll(*dataDir, 0777) if err != nil { log.Fatal(err) } logger := log.NewLogger(os.Stderr) mrouter := initMesh(*meshListen, *hwaddr, *nickname) stopc := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) notificationLog, err := nflog.New( nflog.WithMesh(func(g mesh.Gossiper) mesh.Gossip { return mrouter.NewGossip("nflog", g) }), nflog.WithRetention(*retention), nflog.WithSnapshot(filepath.Join(*dataDir, "nflog")), nflog.WithMaintenance(15*time.Minute, stopc, wg.Done), nflog.WithLogger(logger.With("component", "nflog")), ) if err != nil { log.Fatal(err) } marker := types.NewMarker() silences, err := silence.New(silence.Options{ SnapshotFile: filepath.Join(*dataDir, "silences"), Retention: *retention, Logger: logger.With("component", "silences"), Gossip: func(g mesh.Gossiper) mesh.Gossip { return mrouter.NewGossip("silences", g) }, }) if err != nil { log.Fatal(err) } // Start providers before router potentially sends updates. wg.Add(1) go func() { silences.Maintenance(15*time.Minute, filepath.Join(*dataDir, "silences"), stopc) wg.Done() }() mrouter.Start() defer func() { close(stopc) // Stop receiving updates from router before shutting down. mrouter.Stop() wg.Wait() }() mrouter.ConnectionMaker.InitiateConnections(peers.slice(), true) alerts, err := mem.NewAlerts(*dataDir) if err != nil { log.Fatal(err) } defer alerts.Close() var ( inhibitor *inhibit.Inhibitor tmpl *template.Template pipeline notify.Stage disp *dispatch.Dispatcher ) defer disp.Stop() apiv := api.New(alerts, silences, func() dispatch.AlertOverview { return disp.Groups() }) amURL, err := extURL(*listenAddress, *externalURL) if err != nil { log.Fatal(err) } waitFunc := meshWait(mrouter, 5*time.Second) timeoutFunc := func(d time.Duration) time.Duration { if d < notify.MinTimeout { d = notify.MinTimeout } return d + waitFunc() } reload := func() (err error) { log.With("file", *configFile).Infof("Loading configuration file") defer func() { if err != nil { log.With("file", *configFile).Errorf("Loading configuration file failed: %s", err) configSuccess.Set(0) } else { configSuccess.Set(1) configSuccessTime.Set(float64(time.Now().Unix())) } }() conf, err := config.LoadFile(*configFile) if err != nil { return err } apiv.Update(conf.String(), time.Duration(conf.Global.ResolveTimeout)) tmpl, err = template.FromGlobs(conf.Templates...) if err != nil { return err } tmpl.ExternalURL = amURL inhibitor.Stop() disp.Stop() inhibitor = inhibit.NewInhibitor(alerts, conf.InhibitRules, marker) pipeline = notify.BuildPipeline( conf.Receivers, tmpl, waitFunc, inhibitor, silences, notificationLog, marker, ) disp = dispatch.NewDispatcher(alerts, dispatch.NewRoute(conf.Route, nil), pipeline, marker, timeoutFunc) go disp.Run() go inhibitor.Run() return nil } if err := reload(); err != nil { os.Exit(1) } router := route.New() webReload := make(chan struct{}) ui.Register(router.WithPrefix(amURL.Path), webReload) apiv.Register(router.WithPrefix(path.Join(amURL.Path, "/api"))) log.Infoln("Listening on", *listenAddress) go listen(*listenAddress, router) var ( hup = make(chan os.Signal) hupReady = make(chan bool) term = make(chan os.Signal) ) signal.Notify(hup, syscall.SIGHUP) signal.Notify(term, os.Interrupt, syscall.SIGTERM) go func() { <-hupReady for { select { case <-hup: case <-webReload: } reload() } }() // Wait for reload or termination signals. close(hupReady) // Unblock SIGHUP handler. <-term log.Infoln("Received SIGTERM, exiting gracefully...") }
// New initializes a new web Handler. func New(o *Options) *Handler { router := route.New(func(r *http.Request) (context.Context, error) { return o.Context, nil }) h := &Handler{ router: router, listenErrCh: make(chan error), quitCh: make(chan struct{}), reloadCh: make(chan chan error), options: o, versionInfo: o.Version, birth: time.Now(), flagsMap: o.Flags, context: o.Context, targetManager: o.TargetManager, ruleManager: o.RuleManager, queryEngine: o.QueryEngine, storage: o.Storage, apiV1: api_v1.NewAPI(o.QueryEngine, o.Storage), now: model.Now, } if o.RoutePrefix != "/" { // 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.RoutePrefix, http.StatusFound) }) router = router.WithPrefix(o.RoutePrefix) } instrh := prometheus.InstrumentHandler instrf := prometheus.InstrumentHandlerFunc router.Get("/", func(w http.ResponseWriter, r *http.Request) { router.Redirect(w, r, "/graph", http.StatusFound) }) router.Get("/alerts", instrf("alerts", h.alerts)) router.Get("/graph", instrf("graph", h.graph)) router.Get("/status", instrf("status", h.status)) router.Get("/flags", instrf("flags", h.flags)) router.Get("/config", instrf("config", h.config)) router.Get("/rules", instrf("rules", h.rules)) router.Get("/targets", instrf("targets", h.targets)) router.Get("/version", instrf("version", h.version)) router.Get("/heap", instrf("heap", dumpHeap)) router.Get(o.MetricsPath, prometheus.Handler().ServeHTTP) router.Get("/federate", instrh("federate", httputil.CompressionHandler{ Handler: http.HandlerFunc(h.federation), })) h.apiV1.Register(router.WithPrefix("/api/v1")) router.Get("/consoles/*filepath", instrf("consoles", h.consoles)) router.Get("/static/*filepath", instrf("static", serveStaticAsset)) 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 }