Skip to content

Commit

Permalink
feat: Add redis.password-file hot reload via /-/reload (#783)
Browse files Browse the repository at this point in the history
* feat: Add redis.password-file hot reload via `/-/reload`
  • Loading branch information
libin authored Apr 9, 2023
1 parent b855508 commit b5e0200
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 1 deletion.
2 changes: 2 additions & 0 deletions exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type Options struct {
MetricsPath string
RedisMetricsOnly bool
PingOnConnect bool
RedisPwdFile string
Registry *prometheus.Registry
BuildInfo BuildInfo
}
Expand Down Expand Up @@ -428,6 +429,7 @@ func NewRedisExporter(redisURI string, opts Options) (*Exporter, error) {
e.mux.HandleFunc("/", e.indexHandler)
e.mux.HandleFunc("/scrape", e.scrapeHandler)
e.mux.HandleFunc("/health", e.healthHandler)
e.mux.HandleFunc("/-/reload", e.reloadPwdFile)

return e, nil
}
Expand Down
19 changes: 19 additions & 0 deletions exporter/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
)

func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -88,3 +89,21 @@ func (e *Exporter) scrapeHandler(w http.ResponseWriter, r *http.Request) {
registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError},
).ServeHTTP(w, r)
}

func (e *Exporter) reloadPwdFile(w http.ResponseWriter, r *http.Request) {
if e.options.RedisPwdFile == "" {
http.Error(w, "There is no pwd file specified", http.StatusBadRequest)
return
}
log.Debugf("Reload redisPwdFile")
passwordMap, err := LoadPwdFile(e.options.RedisPwdFile)
if err != nil {
log.Errorf("Error reloading redis passwords from file %s, err: %s", e.options.RedisPwdFile, err)
http.Error(w, "failed to reload passwords file: "+err.Error(), http.StatusInternalServerError)
return
}
e.Lock()
e.options.PasswordMap = passwordMap
e.Unlock()
_, _ = w.Write([]byte(`ok`))
}
72 changes: 72 additions & 0 deletions exporter/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,78 @@ func TestHttpHandlers(t *testing.T) {
}
}

func TestReloadHandlers(t *testing.T) {
if os.Getenv("TEST_PWD_REDIS_URI") == "" {
t.Skipf("TEST_PWD_REDIS_URI not set - skipping")
}

eWithPwdfile, _ := NewRedisExporter(os.Getenv("TEST_PWD_REDIS_URI"), Options{Namespace: "test", Registry: prometheus.NewRegistry(), RedisPwdFile: "../contrib/sample-pwd-file.json"})
ts := httptest.NewServer(eWithPwdfile)
defer ts.Close()

for _, tst := range []struct {
e *Exporter
path string
want string
}{
{
path: "/-/reload",
want: `ok`,
},
} {
t.Run(fmt.Sprintf("path: %s", tst.path), func(t *testing.T) {
body := downloadURL(t, ts.URL+tst.path)
if !strings.Contains(body, tst.want) {
t.Fatalf(`error, expected string "%s" in body, got body: \n\n%s`, tst.want, body)
}
})
}

eWithnoPwdfile, _ := NewRedisExporter(os.Getenv("TEST_PWD_REDIS_URI"), Options{Namespace: "test", Registry: prometheus.NewRegistry()})
ts2 := httptest.NewServer(eWithnoPwdfile)
defer ts2.Close()

for _, tst := range []struct {
e *Exporter
path string
want string
}{
{
path: "/-/reload",
want: `There is no pwd file specified`,
},
} {
t.Run(fmt.Sprintf("path: %s", tst.path), func(t *testing.T) {
body := downloadURL(t, ts2.URL+tst.path)
if !strings.Contains(body, tst.want) {
t.Fatalf(`error, expected string "%s" in body, got body: \n\n%s`, tst.want, body)
}
})
}

eWithMalformedPwdfile, _ := NewRedisExporter(os.Getenv("TEST_PWD_REDIS_URI"), Options{Namespace: "test", Registry: prometheus.NewRegistry(), RedisPwdFile: "../contrib/sample-pwd-file.json-malformed"})
ts3 := httptest.NewServer(eWithMalformedPwdfile)
defer ts3.Close()

for _, tst := range []struct {
e *Exporter
path string
want string
}{
{
path: "/-/reload",
want: `failed to reload passwords file: unexpected end of JSON input`,
},
} {
t.Run(fmt.Sprintf("path: %s", tst.path), func(t *testing.T) {
body := downloadURL(t, ts3.URL+tst.path)
if !strings.Contains(body, tst.want) {
t.Fatalf(`error, expected string "%s" in body, got body: \n\n%s`, tst.want, body)
}
})
}
}

func downloadURL(t *testing.T, u string) string {
_, res := downloadURLWithStatusCode(t, u)
return res
Expand Down
2 changes: 1 addition & 1 deletion exporter/pwd_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func LoadPwdFile(passwordFile string) (map[string]string, error) {
return nil, err
}

log.Errorf("Loaded %d entries from %s", len(res), passwordFile)
log.Infof("Loaded %d entries from %s", len(res), passwordFile)
for k := range res {
log.Debugf("%s", k)
}
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func main() {
MetricsPath: *metricPath,
RedisMetricsOnly: *redisMetricsOnly,
PingOnConnect: *pingOnConnect,
RedisPwdFile: *redisPwdFile,
Registry: registry,
BuildInfo: exporter.BuildInfo{
Version: BuildVersion,
Expand Down

0 comments on commit b5e0200

Please sign in to comment.