From c2529c1ea81958d8b87d5d7c8ebe9a66eec7b991 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez-Fernandez Date: Thu, 28 Mar 2024 21:56:22 -0700 Subject: [PATCH] set up `healthy` and `reload` endpoints * `/-/healthy` indicates that the http server successfully started, and consequentially the config was successfully parsed. * `/-/reload` allows to attempt to reload the config, which is expected to be called by a sidecar that watches for config file changes Signed-off-by: Carlos Rodriguez-Fernandez --- README.md | 7 ++++++- main.go | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ab61ad..f323b6a 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ In each host group the `interval`, `network`, and `protocol` are optional. The interval Duration is in [Go time.ParseDuration()](https://golang.org/pkg/time/#ParseDuration) syntax. -The config is read on startup, and can be reloaded with the SIGHUP signal. +The config is read on startup, and can be reloaded with the SIGHUP signal, or with an HTTP POST to the URI path `/-/reload`. ## Building and running @@ -100,3 +100,8 @@ The Smokeping Prober supports TLS and basic authentication. To use TLS and/or basic authentication, you need to pass a configuration file using the `--web.config.file` parameter. The format of the file is described [in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). + + +### Health check + +A health check can be requested in the URI path `/-/healthy`. diff --git a/main.go b/main.go index a358384..b8d23d6 100644 --- a/main.go +++ b/main.go @@ -264,20 +264,37 @@ func main() { hup := make(chan os.Signal, 1) signal.Notify(hup, syscall.SIGHUP) + reloadCh := make(chan chan error) go func() { for { - <-hup + var errCallback func(e error) + var successCallback func() + select { + case <-hup: + errCallback = func(e error) {} + successCallback = func() {} + case rc := <-reloadCh: + errCallback = func(e error) { + rc <- e + } + successCallback = func() { + rc <- nil + } + } if err := sc.ReloadConfig(*configFile); err != nil { level.Error(logger).Log("msg", "Error reloading config", "err", err) + errCallback(err) continue } err = smokePingers.prepare(hosts, interval, privileged, sizeBytes) if err != nil { level.Error(logger).Log("msg", "Unable to create ping from config", "err", err) + errCallback(err) continue } if smokePingers.sizeOfPrepared() == 0 { level.Error(logger).Log("msg", "No targets specified on command line or in config file") + errCallback(fmt.Errorf("no targets specified")) continue } @@ -285,10 +302,29 @@ func main() { smokepingCollector.updatePingers(smokePingers.started, *pingResponseSeconds) level.Info(logger).Log("msg", "Reloaded config file") + successCallback() } }() http.Handle(*metricsPath, promhttp.Handler()) + http.HandleFunc("/-/healthy", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Healthy")) + }) + http.HandleFunc("/-/reload", + func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "This endpoint requires a POST request.\n") + return + } + + rc := make(chan error) + reloadCh <- rc + if err := <-rc; err != nil { + http.Error(w, fmt.Sprintf("Failed to reload config: %s", err), http.StatusInternalServerError) + } + }) if *metricsPath != "/" && *metricsPath != "" { landingConfig := web.LandingConfig{ Name: "Smokeping Prober",