Skip to content

Commit 3b00ca4

Browse files
authored
Merge pull request #41 from hellofresh/feature/expose-check
EES-3610 Expose method to run health checks w/out HTTP handler
2 parents 9ae2f03 + 47627b0 commit 3b00ca4

File tree

3 files changed

+58
-47
lines changed

3 files changed

+58
-47
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
The library exports `Handler` and `HandlerFunc` functions which are fully compatible with `net/http`.
1818

19+
Additionally, library exports `Measure` function that returns summary status for all the registered health checks, so it can be used in non-HTTP environments.
20+
1921
### Handler
2022

2123
```go

health.go

+43-31
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ var (
1515
checkMap = make(map[string]Config)
1616
)
1717

18+
// Status type represents health status
19+
type Status string
20+
21+
// Possible health statuses
1822
const (
19-
statusOK = "OK"
20-
statusPartiallyAvailable = "Partially Available"
21-
statusUnavailable = "Unavailable"
22-
failureTimeout = "Timeout during health check"
23+
StatusOK Status = "OK"
24+
StatusPartiallyAvailable Status = "Partially Available"
25+
StatusUnavailable Status = "Unavailable"
26+
StatusTimeout Status = "Timeout during health check"
2327
)
2428

2529
type (
@@ -41,7 +45,7 @@ type (
4145
// Check represents the health check response.
4246
Check struct {
4347
// Status is the check status.
44-
Status string `json:"status"`
48+
Status Status `json:"status"`
4549
// Timestamp is the time in which the check occurred.
4650
Timestamp time.Time `json:"timestamp"`
4751
// Failures holds the failed checks along with their messages.
@@ -100,10 +104,30 @@ func Handler() http.Handler {
100104

101105
// HandlerFunc is the HTTP handler function.
102106
func HandlerFunc(w http.ResponseWriter, r *http.Request) {
107+
c := Measure()
108+
109+
w.Header().Set("Content-Type", "application/json")
110+
data, err := json.Marshal(c)
111+
if err != nil {
112+
w.WriteHeader(http.StatusInternalServerError)
113+
http.Error(w, err.Error(), http.StatusInternalServerError)
114+
return
115+
}
116+
117+
code := http.StatusOK
118+
if c.Status == StatusUnavailable {
119+
code = http.StatusServiceUnavailable
120+
}
121+
w.WriteHeader(code)
122+
w.Write(data)
123+
}
124+
125+
// Measure runs all the registered health checks and returns summary status
126+
func Measure() Check {
103127
mu.Lock()
104128
defer mu.Unlock()
105129

106-
status := statusOK
130+
status := StatusOK
107131
total := len(checkMap)
108132
failures := make(map[string]string)
109133
resChan := make(chan checkResponse, total)
@@ -113,12 +137,14 @@ func HandlerFunc(w http.ResponseWriter, r *http.Request) {
113137

114138
go func() {
115139
defer close(resChan)
140+
116141
wg.Wait()
117142
}()
118143

119144
for _, c := range checkMap {
120145
go func(c Config) {
121146
defer wg.Done()
147+
122148
select {
123149
case resChan <- checkResponse{c.Name, c.SkipOnErr, c.Check()}:
124150
default:
@@ -129,34 +155,20 @@ func HandlerFunc(w http.ResponseWriter, r *http.Request) {
129155
for {
130156
select {
131157
case <-time.After(c.Timeout):
132-
failures[c.Name] = failureTimeout
133-
setStatus(&status, c.SkipOnErr)
158+
failures[c.Name] = string(StatusTimeout)
159+
status = getAvailability(status, c.SkipOnErr)
134160
break loop
135161
case res := <-resChan:
136162
if res.err != nil {
137163
failures[res.name] = res.err.Error()
138-
setStatus(&status, res.skipOnErr)
164+
status = getAvailability(status, res.skipOnErr)
139165
}
140166
break loop
141167
}
142168
}
143169
}
144170

145-
w.Header().Set("Content-Type", "application/json")
146-
c := newCheck(status, failures)
147-
data, err := json.Marshal(c)
148-
if err != nil {
149-
w.WriteHeader(http.StatusInternalServerError)
150-
http.Error(w, err.Error(), http.StatusInternalServerError)
151-
return
152-
}
153-
154-
code := http.StatusOK
155-
if status == statusUnavailable {
156-
code = http.StatusServiceUnavailable
157-
}
158-
w.WriteHeader(code)
159-
w.Write(data)
171+
return newCheck(status, failures)
160172
}
161173

162174
// Reset unregisters all previously set check configs
@@ -167,9 +179,9 @@ func Reset() {
167179
checkMap = make(map[string]Config)
168180
}
169181

170-
func newCheck(status string, failures map[string]string) Check {
182+
func newCheck(s Status, failures map[string]string) Check {
171183
return Check{
172-
Status: status,
184+
Status: s,
173185
Timestamp: time.Now(),
174186
Failures: failures,
175187
System: newSystemMetrics(),
@@ -189,10 +201,10 @@ func newSystemMetrics() System {
189201
}
190202
}
191203

192-
func setStatus(status *string, skipOnErr bool) {
193-
if skipOnErr && *status != statusUnavailable {
194-
*status = statusPartiallyAvailable
195-
} else {
196-
*status = statusUnavailable
204+
func getAvailability(s Status, skipOnErr bool) Status {
205+
if skipOnErr && s != StatusUnavailable {
206+
return StatusPartiallyAvailable
197207
}
208+
209+
return StatusUnavailable
198210
}

health_test.go

+13-16
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,12 @@ func TestRegisterWithNoName(t *testing.T) {
2323
return nil
2424
},
2525
})
26-
if err == nil {
27-
t.Error("health check registration with empty name should return an error, but did not get one")
28-
}
26+
require.Error(t, err, "health check registration with empty name should return an error")
2927
}
3028

3129
func TestDoubleRegister(t *testing.T) {
3230
Reset()
33-
if len(checkMap) != 0 {
34-
t.Errorf("checks lenght differes from zero: got %d", len(checkMap))
35-
}
31+
assert.Len(t, checkMap, 0)
3632

3733
healthCheckName := "health-check"
3834

@@ -63,22 +59,22 @@ func TestHealthHandler(t *testing.T) {
6359

6460
res := httptest.NewRecorder()
6561
req, err := http.NewRequest("GET", "http://localhost/status", nil)
66-
if err != nil {
67-
t.Fatal(err)
68-
}
62+
require.NoError(t, err)
6963

70-
Register(Config{
64+
err = Register(Config{
7165
Name: "rabbitmq",
7266
SkipOnErr: true,
7367
Check: func() error { return errors.New(checkErr) },
7468
})
69+
require.NoError(t, err)
7570

76-
Register(Config{
71+
err = Register(Config{
7772
Name: "mongodb",
7873
Check: func() error { return nil },
7974
})
75+
require.NoError(t, err)
8076

81-
Register(Config{
77+
err = Register(Config{
8278
Name: "snail-service",
8379
SkipOnErr: true,
8480
Timeout: time.Second * 1,
@@ -87,8 +83,9 @@ func TestHealthHandler(t *testing.T) {
8783
return nil
8884
},
8985
})
86+
require.NoError(t, err)
9087

91-
h := http.Handler(Handler())
88+
h := Handler()
9289
h.ServeHTTP(res, req)
9390

9491
assert.Equal(t, http.StatusOK, res.Code, "status handler returned wrong status code")
@@ -97,7 +94,7 @@ func TestHealthHandler(t *testing.T) {
9794
err = json.NewDecoder(res.Body).Decode(&body)
9895
require.NoError(t, err)
9996

100-
assert.Equal(t, statusPartiallyAvailable, body["status"], "body returned wrong status")
97+
assert.Equal(t, string(StatusPartiallyAvailable), body["status"], "body returned wrong status")
10198

10299
failure, ok := body["failures"]
103100
assert.True(t, ok, "body returned nil failures field")
@@ -106,8 +103,8 @@ func TestHealthHandler(t *testing.T) {
106103
assert.True(t, ok, "body returned nil failures.rabbitmq field")
107104

108105
assert.Equal(t, checkErr, f["rabbitmq"], "body returned wrong status for rabbitmq")
109-
assert.Equal(t, failureTimeout, f["snail-service"], "body returned wrong status for snail-service")
106+
assert.Equal(t, string(StatusTimeout), f["snail-service"], "body returned wrong status for snail-service")
110107

111108
Reset()
112-
assert.Len(t, checkMap, 0, "checks length diffres from zero")
109+
assert.Len(t, checkMap, 0, "checks length differs from zero")
113110
}

0 commit comments

Comments
 (0)