forked from karolhrdina/healthcheck
-
Notifications
You must be signed in to change notification settings - Fork 0
/
example_test.go
232 lines (195 loc) · 7.21 KB
/
example_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright 2017 by the contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package healthcheck_test
import (
"database/sql"
"fmt"
"net/http"
"net/http/httptest"
"net/http/httputil"
"strings"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/GlobalWebIndex/healthcheck/checks"
"github.com/GlobalWebIndex/healthcheck/handlers"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func Example() {
// Create a Handler that we can use to register liveness and readiness checks.
health := handlers.NewHandler()
// Add a readiness check to make sure an upstream dependency resolves in DNS.
// If this fails we don't want to receive requests, but we shouldn't be
// restarted or rescheduled.
upstreamHost := "upstream.example.com"
err := health.AddReadinessCheck(
"upstream-dep-dns",
checks.DNSResolveCheck(upstreamHost, 50*time.Millisecond))
if err != nil {
panic("`health.AddReadinessCheck()` failed")
}
// Add a liveness check to detect Goroutine leaks. If this fails we want
// to be restarted/rescheduled.
err = health.AddLivenessCheck("goroutine-threshold", checks.GoroutineCountCheck(100))
if err != nil {
panic("`health.AddLivenessCheck()` failed")
}
// Serve http://0.0.0.0:8080/live and http://0.0.0.0:8080/ready endpoints.
// go http.ListenAndServe("0.0.0.0:8080", health)
// Make a request to the readiness endpoint and print the response.
fmt.Print(dumpRequest(health, "GET", "/ready"))
// Output:
// HTTP/1.1 503 Service Unavailable
// Connection: close
// Content-Type: application/json; charset=utf-8
//
// {}
}
func Example_database() {
// Connect to a database/sql database
database := connectToDatabase()
// Create a Handler that we can use to register liveness and readiness checks.
health := handlers.NewHandler()
// Add a readiness check to we don't receive requests unless we can reach
// the database with a ping in <1 second.
err := health.AddReadinessCheck("database", checks.DatabaseSelectCheck(database, 1*time.Second))
if err != nil {
panic("`health.AddReadinessCheck()` failed")
}
// Serve http://0.0.0.0:8080/live and http://0.0.0.0:8080/ready endpoints.
// go http.ListenAndServe("0.0.0.0:8080", health)
// Make a request to the readiness endpoint and print the response.
fmt.Print(dumpRequest(health, "GET", "/ready?full=1"))
// Output:
// HTTP/1.1 200 OK
// Connection: close
// Content-Type: application/json; charset=utf-8
//
// {
// "database": "OK"
// }
}
func Example_advanced() {
// Create a Handler that we can use to register liveness and readiness checks.
health := handlers.NewHandler()
// Make sure we can connect to an upstream dependency over TCP in less than
// 50ms. Run this check asynchronously in the background every 10 seconds
// instead of every time the /ready or /live endpoints are hit.
//
// Async is useful whenever a check is expensive (especially if it causes
// load on upstream services).
upstreamAddr := "upstream.example.com:5432"
err := health.AddReadinessCheck(
"upstream-dep-tcp",
checks.Async(checks.TCPDialCheck(upstreamAddr, 50*time.Millisecond), 10*time.Second))
if err != nil {
panic("`health.AddReadinessCheck()` failed")
}
// Add a readiness check against the health of an upstream HTTP dependency
upstreamURL := "http://upstream-svc.example.com:8080/healthy"
err = health.AddReadinessCheck(
"upstream-dep-http",
checks.HTTPGetCheck(upstreamURL, 500*time.Millisecond))
if err != nil {
panic("`health.AddReadinessCheck()` failed")
}
// Implement a custom check with a 50 millisecond timeout.
err = health.AddLivenessCheck("custom-check-with-timeout", checks.Timeout(func() error {
// Simulate some work that could take a long time
time.Sleep(time.Millisecond * 100)
return nil
}, 50*time.Millisecond))
if err != nil {
panic("`health.AddLivenessCheck()` failed")
}
// Expose the readiness endpoints on a custom path /healthz mixed into
// our main application mux.
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Hello, world!"))
})
mux.HandleFunc("/healthz", health.ReadyEndpoint)
// Sleep for just a moment to make sure our Async handler had a chance to run
time.Sleep(500 * time.Millisecond)
// Make a sample request to the /healthz endpoint and print the response.
fmt.Println(dumpRequest(mux, "GET", "/healthz"))
// Output:
// HTTP/1.1 503 Service Unavailable
// Connection: close
// Content-Type: application/json; charset=utf-8
//
// {}
}
func Example_metrics() {
// Create a new Prometheus registry (you'd likely already have one of these).
registry := prometheus.NewRegistry()
// Create a metrics-exposing Handler for the Prometheus registry
// The healthcheck related metrics will be prefixed with the provided namespace
health := handlers.NewMetricsHandler(registry, "example")
// Add a simple readiness check that always fails.
err := health.AddReadinessCheck("failing-check", func() error {
return fmt.Errorf("example failure")
})
if err != nil {
panic("`health.AddReadinessCheck()` failed")
}
// Add a liveness check that always succeeds
err = health.AddLivenessCheck("successful-check", func() error {
return nil
})
if err != nil {
panic("`health.AddLivenessCheck()` failed")
}
// Create an "admin" listener on 0.0.0.0:9402
adminMux := http.NewServeMux()
// go http.ListenAndServe("0.0.0.0:9402", adminMux)
// Expose prometheus metrics on /metrics
adminMux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
// Expose a liveness check on /live
adminMux.HandleFunc("/live", health.LiveEndpoint)
// Expose a readiness check on /ready
adminMux.HandleFunc("/ready", health.ReadyEndpoint)
// Make a request to the metrics endpoint and print the response.
fmt.Println(dumpRequest(adminMux, "GET", "/metrics"))
// Output:
// HTTP/1.1 200 OK
// Connection: close
// Content-Type: text/plain; version=0.0.4; charset=utf-8
//
// # HELP example_healthcheck_status Current check status (0 indicates success, 1 indicates failure)
// # TYPE example_healthcheck_status gauge
// example_healthcheck_status{check="failing-check"} 1
// example_healthcheck_status{check="successful-check"} 0
}
func dumpRequest(handler http.Handler, method string, path string) string {
req, err := http.NewRequest(method, path, nil)
if err != nil {
panic(err)
}
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
dump, err := httputil.DumpResponse(rr.Result(), true)
if err != nil {
panic(err)
}
return strings.Replace(string(dump), "\r\n", "\n", -1)
}
func connectToDatabase() *sql.DB {
db, mock, err := sqlmock.New()
if err != nil {
panic(err)
}
mock.ExpectExec("SELECT 1").WillReturnResult(sqlmock.NewResult(1, 1))
return db
}