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  }