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  }