github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/hyperledger/fabric-lib-go/healthz/checker.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 // Package healthz provides an HTTP handler which returns the health status of 8 // one or more components of an application or service. 9 package healthz 10 11 import ( 12 "context" 13 "encoding/json" 14 "fmt" 15 "github.com/hellobchain/newcryptosm/http" 16 "sync" 17 "time" 18 ) 19 20 type AlreadyRegisteredError string 21 22 func (are AlreadyRegisteredError) Error() string { 23 return fmt.Sprintf("'%s' is already registered", are) 24 } 25 26 const ( 27 // StatusOK is returned if all health checks pass. 28 StatusOK = "OK" 29 // StatusUnavailable is returned if any health check fails. 30 StatusUnavailable = "Service Unavailable" 31 ) 32 33 //go:generate counterfeiter -o mock/health_checker.go -fake-name HealthChecker . HealthChecker 34 35 // HealthChecker defines the interface components must implement in order to 36 // register with the Handler in order to be included in the health status. 37 // HealthCheck is passed a context with a Done channel which is closed due to 38 // a timeout or cancellation. 39 type HealthChecker interface { 40 HealthCheck(context.Context) error 41 } 42 43 // FailedCheck represents a failed status check for a component. 44 type FailedCheck struct { 45 Component string `json:"component"` 46 Reason string `json:"reason"` 47 } 48 49 // HealthStatus represents the current health status of all registered components. 50 type HealthStatus struct { 51 Status string `json:"status"` 52 Time time.Time `json:"time"` 53 FailedChecks []FailedCheck `json:"failed_checks,omitempty"` 54 } 55 56 // NewHealthHandler returns a new HealthHandler instance. 57 func NewHealthHandler() *HealthHandler { 58 return &HealthHandler{ 59 healthCheckers: map[string]HealthChecker{}, 60 now: time.Now, 61 timeout: 30 * time.Second, 62 } 63 } 64 65 // HealthHandler is responsible for executing registered health checks. It 66 // provides an HTTP handler which returns the health status for all registered 67 // components. 68 type HealthHandler struct { 69 mutex sync.RWMutex 70 healthCheckers map[string]HealthChecker 71 now func() time.Time 72 timeout time.Duration 73 } 74 75 // RegisterChecker registers a HealthChecker for a named component and adds it to 76 // the list of status checks to run. It returns an error if the component has 77 // already been registered. 78 func (h *HealthHandler) RegisterChecker(component string, checker HealthChecker) error { 79 h.mutex.Lock() 80 defer h.mutex.Unlock() 81 82 if _, ok := h.healthCheckers[component]; ok { 83 return AlreadyRegisteredError(component) 84 } 85 h.healthCheckers[component] = checker 86 return nil 87 } 88 89 // DeregisterChecker deregisters a named HealthChecker. 90 func (h *HealthHandler) DeregisterChecker(component string) { 91 h.mutex.Lock() 92 defer h.mutex.Unlock() 93 delete(h.healthCheckers, component) 94 } 95 96 // SetTimeout sets the timeout for handling HTTP requests. If not explicitly 97 // set, defaults to 30 seconds. 98 func (h *HealthHandler) SetTimeout(timeout time.Duration) { 99 h.timeout = timeout 100 } 101 102 // ServerHTTP is an HTTP handler (see http.Handler) which can be used as 103 // an HTTP endpoint for health checks. If all registered checks pass, it returns 104 // an HTTP status `200 OK` with a JSON payload of `{"status": "OK"}`. If all 105 // checks do not pass, it returns an HTTP status `503 Service Unavailable` with 106 // a JSON payload of `{"status": "Service Unavailable","failed_checks":[...]}. 107 func (h *HealthHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 108 if req.Method != "GET" { 109 rw.WriteHeader(http.StatusMethodNotAllowed) 110 return 111 } 112 checksCtx, cancel := context.WithTimeout(req.Context(), h.timeout) 113 defer cancel() 114 115 failedCheckCh := make(chan []FailedCheck) 116 go func() { 117 failedCheckCh <- h.RunChecks(checksCtx) 118 }() 119 120 select { 121 case failedChecks := <-failedCheckCh: 122 hs := HealthStatus{ 123 Status: StatusOK, 124 Time: h.now(), 125 } 126 if len(failedChecks) > 0 { 127 hs.Status = StatusUnavailable 128 hs.FailedChecks = failedChecks 129 } 130 writeHTTPResponse(rw, hs) 131 case <-checksCtx.Done(): 132 if checksCtx.Err() == context.DeadlineExceeded { 133 rw.WriteHeader(http.StatusRequestTimeout) 134 } 135 } 136 } 137 138 // RunChecks runs all healthCheckers and returns any failures. 139 func (h *HealthHandler) RunChecks(ctx context.Context) []FailedCheck { 140 h.mutex.RLock() 141 defer h.mutex.RUnlock() 142 143 var failedChecks []FailedCheck 144 for component, checker := range h.healthCheckers { 145 if err := checker.HealthCheck(ctx); err != nil { 146 failedCheck := FailedCheck{ 147 Component: component, 148 Reason: err.Error(), 149 } 150 failedChecks = append(failedChecks, failedCheck) 151 } 152 } 153 return failedChecks 154 } 155 156 // write the HTTP response 157 func writeHTTPResponse(rw http.ResponseWriter, hs HealthStatus) { 158 var resp []byte 159 rc := http.StatusOK 160 rw.Header().Set("Content-Type", "application/json") 161 if len(hs.FailedChecks) > 0 { 162 rc = http.StatusServiceUnavailable 163 } 164 resp, err := json.Marshal(hs) 165 if err != nil { 166 rc = http.StatusInternalServerError 167 } 168 rw.WriteHeader(rc) 169 rw.Write(resp) 170 }