1 package health
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "sync"
8 "time"
9
10 "github.com/docker/distribution/context"
11 "github.com/docker/distribution/registry/api/errcode"
12 )
13
14
15
16
17 type Registry struct {
18 mu sync.RWMutex
19 registeredChecks map[string]Checker
20 }
21
22
23
24
25 func NewRegistry() *Registry {
26 return &Registry{
27 registeredChecks: make(map[string]Checker),
28 }
29 }
30
31
32
33 var DefaultRegistry *Registry
34
35
36 type Checker interface {
37
38 Check() error
39 }
40
41
42
43 type CheckFunc func() error
44
45
46
47 func (cf CheckFunc) Check() error {
48 return cf()
49 }
50
51
52 type Updater interface {
53 Checker
54
55
56 Update(status error)
57 }
58
59
60
61
62
63 type updater struct {
64 mu sync.Mutex
65 status error
66 }
67
68
69 func (u *updater) Check() error {
70 u.mu.Lock()
71 defer u.mu.Unlock()
72
73 return u.status
74 }
75
76
77
78 func (u *updater) Update(status error) {
79 u.mu.Lock()
80 defer u.mu.Unlock()
81
82 u.status = status
83 }
84
85
86 func NewStatusUpdater() Updater {
87 return &updater{}
88 }
89
90
91
92
93
94 type thresholdUpdater struct {
95 mu sync.Mutex
96 status error
97 threshold int
98 count int
99 }
100
101
102 func (tu *thresholdUpdater) Check() error {
103 tu.mu.Lock()
104 defer tu.mu.Unlock()
105
106 if tu.count >= tu.threshold {
107 return tu.status
108 }
109
110 return nil
111 }
112
113
114
115 func (tu *thresholdUpdater) Update(status error) {
116 tu.mu.Lock()
117 defer tu.mu.Unlock()
118
119 if status == nil {
120 tu.count = 0
121 } else if tu.count < tu.threshold {
122 tu.count++
123 }
124
125 tu.status = status
126 }
127
128
129 func NewThresholdStatusUpdater(t int) Updater {
130 return &thresholdUpdater{threshold: t}
131 }
132
133
134 func PeriodicChecker(check Checker, period time.Duration) Checker {
135 u := NewStatusUpdater()
136 go func() {
137 t := time.NewTicker(period)
138 for {
139 <-t.C
140 u.Update(check.Check())
141 }
142 }()
143
144 return u
145 }
146
147
148
149 func PeriodicThresholdChecker(check Checker, period time.Duration, threshold int) Checker {
150 tu := NewThresholdStatusUpdater(threshold)
151 go func() {
152 t := time.NewTicker(period)
153 for {
154 <-t.C
155 tu.Update(check.Check())
156 }
157 }()
158
159 return tu
160 }
161
162
163 func (registry *Registry) CheckStatus() map[string]string {
164 registry.mu.RLock()
165 defer registry.mu.RUnlock()
166 statusKeys := make(map[string]string)
167 for k, v := range registry.registeredChecks {
168 err := v.Check()
169 if err != nil {
170 statusKeys[k] = err.Error()
171 }
172 }
173
174 return statusKeys
175 }
176
177
178
179 func CheckStatus() map[string]string {
180 return DefaultRegistry.CheckStatus()
181 }
182
183
184 func (registry *Registry) Register(name string, check Checker) {
185 if registry == nil {
186 registry = DefaultRegistry
187 }
188 registry.mu.Lock()
189 defer registry.mu.Unlock()
190 _, ok := registry.registeredChecks[name]
191 if ok {
192 panic("Check already exists: " + name)
193 }
194 registry.registeredChecks[name] = check
195 }
196
197
198
199 func Register(name string, check Checker) {
200 DefaultRegistry.Register(name, check)
201 }
202
203
204
205 func (registry *Registry) RegisterFunc(name string, check func() error) {
206 registry.Register(name, CheckFunc(check))
207 }
208
209
210
211 func RegisterFunc(name string, check func() error) {
212 DefaultRegistry.RegisterFunc(name, check)
213 }
214
215
216
217 func (registry *Registry) RegisterPeriodicFunc(name string, period time.Duration, check CheckFunc) {
218 registry.Register(name, PeriodicChecker(check, period))
219 }
220
221
222
223 func RegisterPeriodicFunc(name string, period time.Duration, check CheckFunc) {
224 DefaultRegistry.RegisterPeriodicFunc(name, period, check)
225 }
226
227
228
229 func (registry *Registry) RegisterPeriodicThresholdFunc(name string, period time.Duration, threshold int, check CheckFunc) {
230 registry.Register(name, PeriodicThresholdChecker(check, period, threshold))
231 }
232
233
234
235 func RegisterPeriodicThresholdFunc(name string, period time.Duration, threshold int, check CheckFunc) {
236 DefaultRegistry.RegisterPeriodicThresholdFunc(name, period, threshold, check)
237 }
238
239
240
241
242 func StatusHandler(w http.ResponseWriter, r *http.Request) {
243 if r.Method == "GET" {
244 checks := CheckStatus()
245 status := http.StatusOK
246
247
248 if len(checks) != 0 {
249 status = http.StatusServiceUnavailable
250 }
251
252 statusResponse(w, r, status, checks)
253 } else {
254 http.NotFound(w, r)
255 }
256 }
257
258
259
260
261
262 func Handler(handler http.Handler) http.Handler {
263 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
264 checks := CheckStatus()
265 if len(checks) != 0 {
266 errcode.ServeJSON(w, errcode.ErrorCodeUnavailable.
267 WithDetail("health check failed: please see /debug/health"))
268 return
269 }
270
271 handler.ServeHTTP(w, r)
272 })
273 }
274
275
276
277 func statusResponse(w http.ResponseWriter, r *http.Request, status int, checks map[string]string) {
278 p, err := json.Marshal(checks)
279 if err != nil {
280 context.GetLogger(context.Background()).Errorf("error serializing health status: %v", err)
281 p, err = json.Marshal(struct {
282 ServerError string `json:"server_error"`
283 }{
284 ServerError: "Could not parse error message",
285 })
286 status = http.StatusInternalServerError
287
288 if err != nil {
289 context.GetLogger(context.Background()).Errorf("error serializing health status failure message: %v", err)
290 return
291 }
292 }
293
294 w.Header().Set("Content-Type", "application/json; charset=utf-8")
295 w.Header().Set("Content-Length", fmt.Sprint(len(p)))
296 w.WriteHeader(status)
297 if _, err := w.Write(p); err != nil {
298 context.GetLogger(context.Background()).Errorf("error writing health status response body: %v", err)
299 }
300 }
301
302
303 func init() {
304 DefaultRegistry = NewRegistry()
305 http.HandleFunc("/debug/health", StatusHandler)
306 }
307
View as plain text