interval
diff --git a/health/checks/checks.go b/health/checks/checks.go
index 89d5f3db..86e914b1 100644
--- a/health/checks/checks.go
+++ b/health/checks/checks.go
@@ -2,15 +2,17 @@ package checks
import (
"errors"
+ "net"
"net/http"
"os"
"strconv"
+ "time"
"github.com/docker/distribution/health"
)
-// FileChecker checks the existence of a file and returns and error
-// if the file exists, taking the application out of rotation
+// FileChecker checks the existence of a file and returns an error
+// if the file exists.
func FileChecker(f string) health.Checker {
return health.CheckFunc(func() error {
if _, err := os.Stat(f); err == nil {
@@ -20,18 +22,32 @@ func FileChecker(f string) health.Checker {
})
}
-// HTTPChecker does a HEAD request and verifies if the HTTP status
-// code return is a 200, taking the application out of rotation if
-// otherwise
-func HTTPChecker(r string) health.Checker {
+// HTTPChecker does a HEAD request and verifies that the HTTP status code
+// returned matches statusCode.
+func HTTPChecker(r string, statusCode int, timeout time.Duration) health.Checker {
return health.CheckFunc(func() error {
- response, err := http.Head(r)
+ client := http.Client{
+ Timeout: timeout,
+ }
+ response, err := client.Head(r)
if err != nil {
return errors.New("error while checking: " + r)
}
- if response.StatusCode != http.StatusOK {
+ if response.StatusCode != statusCode {
return errors.New("downstream service returned unexpected status: " + strconv.Itoa(response.StatusCode))
}
return nil
})
}
+
+// TCPChecker attempts to open a TCP connection.
+func TCPChecker(addr string, timeout time.Duration) health.Checker {
+ return health.CheckFunc(func() error {
+ conn, err := net.DialTimeout("tcp", addr, timeout)
+ if err != nil {
+ return errors.New("connection to " + addr + " failed")
+ }
+ conn.Close()
+ return nil
+ })
+}
diff --git a/health/checks/checks_test.go b/health/checks/checks_test.go
index 4e49d118..8ba24d33 100644
--- a/health/checks/checks_test.go
+++ b/health/checks/checks_test.go
@@ -15,11 +15,11 @@ func TestFileChecker(t *testing.T) {
}
func TestHTTPChecker(t *testing.T) {
- if err := HTTPChecker("https://www.google.cybertron").Check(); err == nil {
+ if err := HTTPChecker("https://www.google.cybertron", 200, 0).Check(); err == nil {
t.Errorf("Google on Cybertron was expected as not exists")
}
- if err := HTTPChecker("https://www.google.pt").Check(); err != nil {
+ if err := HTTPChecker("https://www.google.pt", 200, 0).Check(); err != nil {
t.Errorf("Google at Portugal was expected as exists, error:%v", err)
}
}
diff --git a/registry/handlers/app.go b/registry/handlers/app.go
index 91f4e1a3..24f43f37 100644
--- a/registry/handlers/app.go
+++ b/registry/handlers/app.go
@@ -266,13 +266,8 @@ func (app *App) RegisterHealthChecks(healthRegistries ...*health.Registry) {
if interval == 0 {
interval = defaultCheckInterval
}
- if fileChecker.Threshold != 0 {
- ctxu.GetLogger(app).Infof("configuring file health check path=%s, interval=%d, threshold=%d", fileChecker.File, interval/time.Second, fileChecker.Threshold)
- healthRegistry.Register(fileChecker.File, health.PeriodicThresholdChecker(checks.FileChecker(fileChecker.File), interval, fileChecker.Threshold))
- } else {
- ctxu.GetLogger(app).Infof("configuring file health check path=%s, interval=%d", fileChecker.File, interval/time.Second)
- healthRegistry.Register(fileChecker.File, health.PeriodicChecker(checks.FileChecker(fileChecker.File), interval))
- }
+ ctxu.GetLogger(app).Infof("configuring file health check path=%s, interval=%d", fileChecker.File, interval/time.Second)
+ healthRegistry.Register(fileChecker.File, health.PeriodicChecker(checks.FileChecker(fileChecker.File), interval))
}
for _, httpChecker := range app.Config.Health.HTTPCheckers {
@@ -280,12 +275,37 @@ func (app *App) RegisterHealthChecks(healthRegistries ...*health.Registry) {
if interval == 0 {
interval = defaultCheckInterval
}
+
+ statusCode := httpChecker.StatusCode
+ if statusCode == 0 {
+ statusCode = 200
+ }
+
+ checker := checks.HTTPChecker(httpChecker.URI, statusCode, httpChecker.Timeout)
+
if httpChecker.Threshold != 0 {
ctxu.GetLogger(app).Infof("configuring HTTP health check uri=%s, interval=%d, threshold=%d", httpChecker.URI, interval/time.Second, httpChecker.Threshold)
- healthRegistry.Register(httpChecker.URI, health.PeriodicThresholdChecker(checks.HTTPChecker(httpChecker.URI), interval, httpChecker.Threshold))
+ healthRegistry.Register(httpChecker.URI, health.PeriodicThresholdChecker(checker, interval, httpChecker.Threshold))
} else {
ctxu.GetLogger(app).Infof("configuring HTTP health check uri=%s, interval=%d", httpChecker.URI, interval/time.Second)
- healthRegistry.Register(httpChecker.URI, health.PeriodicChecker(checks.HTTPChecker(httpChecker.URI), interval))
+ healthRegistry.Register(httpChecker.URI, health.PeriodicChecker(checker, interval))
+ }
+ }
+
+ for _, tcpChecker := range app.Config.Health.TCPCheckers {
+ interval := tcpChecker.Interval
+ if interval == 0 {
+ interval = defaultCheckInterval
+ }
+
+ checker := checks.TCPChecker(tcpChecker.Addr, tcpChecker.Timeout)
+
+ if tcpChecker.Threshold != 0 {
+ ctxu.GetLogger(app).Infof("configuring TCP health check addr=%s, interval=%d, threshold=%d", tcpChecker.Addr, interval/time.Second, tcpChecker.Threshold)
+ healthRegistry.Register(tcpChecker.Addr, health.PeriodicThresholdChecker(checker, interval, tcpChecker.Threshold))
+ } else {
+ ctxu.GetLogger(app).Infof("configuring TCP health check addr=%s, interval=%d", tcpChecker.Addr, interval/time.Second)
+ healthRegistry.Register(tcpChecker.Addr, health.PeriodicChecker(checker, interval))
}
}
}
diff --git a/registry/handlers/health_test.go b/registry/handlers/health_test.go
index de2b71cc..bb460b47 100644
--- a/registry/handlers/health_test.go
+++ b/registry/handlers/health_test.go
@@ -2,6 +2,7 @@ package handlers
import (
"io/ioutil"
+ "net"
"net/http"
"net/http/httptest"
"os"
@@ -61,6 +62,68 @@ func TestFileHealthCheck(t *testing.T) {
}
}
+func TestTCPHealthCheck(t *testing.T) {
+ interval := time.Second
+
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("could not create listener: %v", err)
+ }
+ addrStr := ln.Addr().String()
+
+ // Start accepting
+ go func() {
+ for {
+ conn, err := ln.Accept()
+ if err != nil {
+ // listener was closed
+ return
+ }
+ defer conn.Close()
+ }
+ }()
+
+ config := configuration.Configuration{
+ Storage: configuration.Storage{
+ "inmemory": configuration.Parameters{},
+ },
+ Health: configuration.Health{
+ TCPCheckers: []configuration.TCPChecker{
+ {
+ Interval: interval,
+ Addr: addrStr,
+ Timeout: 500 * time.Millisecond,
+ },
+ },
+ },
+ }
+
+ ctx := context.Background()
+
+ app := NewApp(ctx, config)
+ healthRegistry := health.NewRegistry()
+ app.RegisterHealthChecks(healthRegistry)
+
+ // Wait for health check to happen
+ <-time.After(2 * interval)
+
+ if len(healthRegistry.CheckStatus()) != 0 {
+ t.Fatal("expected 0 items in health check results")
+ }
+
+ ln.Close()
+ <-time.After(2 * interval)
+
+ // Health check should now fail
+ status := healthRegistry.CheckStatus()
+ if len(status) != 1 {
+ t.Fatal("expected 1 item in health check results")
+ }
+ if status[addrStr] != "connection to "+addrStr+" failed" {
+ t.Fatal(`did not get "connection failed" result for health check`)
+ }
+}
+
func TestHTTPHealthCheck(t *testing.T) {
interval := time.Second
threshold := 3
|