github.com/lusis/distribution@v2.0.1+incompatible/health/health.go (about) 1 package health 2 3 import ( 4 "encoding/json" 5 "net/http" 6 "sync" 7 "time" 8 ) 9 10 var ( 11 mutex sync.RWMutex 12 registeredChecks = make(map[string]Checker) 13 ) 14 15 // Checker is the interface for a Health Checker 16 type Checker interface { 17 // Check returns nil if the service is okay. 18 Check() error 19 } 20 21 // CheckFunc is a convenience type to create functions that implement 22 // the Checker interface 23 type CheckFunc func() error 24 25 // Check Implements the Checker interface to allow for any func() error method 26 // to be passed as a Checker 27 func (cf CheckFunc) Check() error { 28 return cf() 29 } 30 31 // Updater implements a health check that is explicitly set. 32 type Updater interface { 33 Checker 34 35 // Update updates the current status of the health check. 36 Update(status error) 37 } 38 39 // updater implements Checker and Updater, providing an asynchronous Update 40 // method. 41 // This allows us to have a Checker that returns the Check() call immediately 42 // not blocking on a potentially expensive check. 43 type updater struct { 44 mu sync.Mutex 45 status error 46 } 47 48 // Check implements the Checker interface 49 func (u *updater) Check() error { 50 u.mu.Lock() 51 defer u.mu.Unlock() 52 53 return u.status 54 } 55 56 // Update implements the Updater interface, allowing asynchronous access to 57 // the status of a Checker. 58 func (u *updater) Update(status error) { 59 u.mu.Lock() 60 defer u.mu.Unlock() 61 62 u.status = status 63 } 64 65 // NewStatusUpdater returns a new updater 66 func NewStatusUpdater() Updater { 67 return &updater{} 68 } 69 70 // thresholdUpdater implements Checker and Updater, providing an asynchronous Update 71 // method. 72 // This allows us to have a Checker that returns the Check() call immediately 73 // not blocking on a potentially expensive check. 74 type thresholdUpdater struct { 75 mu sync.Mutex 76 status error 77 threshold int 78 count int 79 } 80 81 // Check implements the Checker interface 82 func (tu *thresholdUpdater) Check() error { 83 tu.mu.Lock() 84 defer tu.mu.Unlock() 85 86 if tu.count >= tu.threshold { 87 return tu.status 88 } 89 90 return nil 91 } 92 93 // thresholdUpdater implements the Updater interface, allowing asynchronous 94 // access to the status of a Checker. 95 func (tu *thresholdUpdater) Update(status error) { 96 tu.mu.Lock() 97 defer tu.mu.Unlock() 98 99 if status == nil { 100 tu.count = 0 101 } else if tu.count < tu.threshold { 102 tu.count++ 103 } 104 105 tu.status = status 106 } 107 108 // NewThresholdStatusUpdater returns a new thresholdUpdater 109 func NewThresholdStatusUpdater(t int) Updater { 110 return &thresholdUpdater{threshold: t} 111 } 112 113 // PeriodicChecker wraps an updater to provide a periodic checker 114 func PeriodicChecker(check Checker, period time.Duration) Checker { 115 u := NewStatusUpdater() 116 go func() { 117 t := time.NewTicker(period) 118 for { 119 <-t.C 120 u.Update(check.Check()) 121 } 122 }() 123 124 return u 125 } 126 127 // PeriodicThresholdChecker wraps an updater to provide a periodic checker that 128 // uses a threshold before it changes status 129 func PeriodicThresholdChecker(check Checker, period time.Duration, threshold int) Checker { 130 tu := NewThresholdStatusUpdater(threshold) 131 go func() { 132 t := time.NewTicker(period) 133 for { 134 <-t.C 135 tu.Update(check.Check()) 136 } 137 }() 138 139 return tu 140 } 141 142 // CheckStatus returns a map with all the current health check errors 143 func CheckStatus() map[string]string { 144 mutex.RLock() 145 defer mutex.RUnlock() 146 statusKeys := make(map[string]string) 147 for k, v := range registeredChecks { 148 err := v.Check() 149 if err != nil { 150 statusKeys[k] = err.Error() 151 } 152 } 153 154 return statusKeys 155 } 156 157 // Register associates the checker with the provided name. We allow 158 // overwrites to a specific check status. 159 func Register(name string, check Checker) { 160 mutex.Lock() 161 defer mutex.Unlock() 162 _, ok := registeredChecks[name] 163 if ok { 164 panic("Check already exists: " + name) 165 } 166 registeredChecks[name] = check 167 } 168 169 // RegisterFunc allows the convenience of registering a checker directly 170 // from an arbitrary func() error 171 func RegisterFunc(name string, check func() error) { 172 Register(name, CheckFunc(check)) 173 } 174 175 // RegisterPeriodicFunc allows the convenience of registering a PeriodicChecker 176 // from an arbitrary func() error 177 func RegisterPeriodicFunc(name string, check func() error, period time.Duration) { 178 Register(name, PeriodicChecker(CheckFunc(check), period)) 179 } 180 181 // RegisterPeriodicThresholdFunc allows the convenience of registering a 182 // PeriodicChecker from an arbitrary func() error 183 func RegisterPeriodicThresholdFunc(name string, check func() error, period time.Duration, threshold int) { 184 Register(name, PeriodicThresholdChecker(CheckFunc(check), period, threshold)) 185 } 186 187 // StatusHandler returns a JSON blob with all the currently registered Health Checks 188 // and their corresponding status. 189 // Returns 503 if any Error status exists, 200 otherwise 190 func StatusHandler(w http.ResponseWriter, r *http.Request) { 191 if r.Method == "GET" { 192 w.Header().Set("Content-Type", "application/json; charset=utf-8") 193 checksStatus := CheckStatus() 194 // If there is an error, return 503 195 if len(checksStatus) != 0 { 196 w.WriteHeader(http.StatusServiceUnavailable) 197 } 198 err := json.NewEncoder(w).Encode(checksStatus) 199 200 // Parsing of the JSON failed. Returning generic error message 201 if err != nil { 202 w.Write([]byte("{server_error: 'Could not parse error message'}")) 203 } 204 } else { 205 w.WriteHeader(http.StatusNotFound) 206 } 207 } 208 209 // Registers global /debug/health api endpoint 210 func init() { 211 http.HandleFunc("/debug/health", StatusHandler) 212 }