129
vendor/github.com/metalmatze/signal/healthcheck/checks.go
generated
vendored
Normal file
129
vendor/github.com/metalmatze/signal/healthcheck/checks.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2020 by the contributors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TCPDialCheck returns a Check that checks TCP connectivity to the provided
|
||||
// endpoint.
|
||||
func TCPDialCheck(addr string, timeout time.Duration) Check {
|
||||
return func() error {
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPGetCheck returns a Check that performs an HTTP GET request against the
|
||||
// specified URL. The check fails if the response times out or returns a non-200
|
||||
// status code.
|
||||
func HTTPGetCheck(url string, timeout time.Duration) Check {
|
||||
return func() error {
|
||||
return HTTPCheck(url, http.MethodGet, http.StatusOK, timeout)()
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPCheck returns a Check that performs a HTTP request against the specified URL.
|
||||
// The Check fails if the response times out or returns an unexpected status code.
|
||||
func HTTPCheck(url string, method string, status int, timeout time.Duration) Check {
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
CheckRedirect: func(*http.Request, []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
return HTTPCheckClient(client, url, method, status, timeout)
|
||||
}
|
||||
|
||||
// HTTPCheckClient returns a Check that performs a HTTP request against the specified URL.
|
||||
// The Check fails if the response times out or returns an unexpected status code.
|
||||
// On top of that it uses a custom client specified by the caller.
|
||||
func HTTPCheckClient(client *http.Client, url string, method string, status int, timeout time.Duration) Check {
|
||||
return func() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != status {
|
||||
return fmt.Errorf("returned status %d, expected %d", resp.StatusCode, status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DatabasePingCheck returns a Check that validates connectivity to a
|
||||
// database/sql.DB using Ping().
|
||||
func DatabasePingCheck(database *sql.DB, timeout time.Duration) Check {
|
||||
return func() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
if database == nil {
|
||||
return fmt.Errorf("database is nil")
|
||||
}
|
||||
return database.PingContext(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// DNSResolveCheck returns a Check that makes sure the provided host can resolve
|
||||
// to at least one IP address within the specified timeout.
|
||||
func DNSResolveCheck(host string, timeout time.Duration) Check {
|
||||
resolver := net.Resolver{}
|
||||
return func() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
addrs, err := resolver.LookupHost(ctx, host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(addrs) < 1 {
|
||||
return fmt.Errorf("could not resolve host")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GoroutineCountCheck returns a Check that fails if too many goroutines are
|
||||
// running (which could indicate a resource leak).
|
||||
func GoroutineCountCheck(threshold int) Check {
|
||||
return func() error {
|
||||
count := runtime.NumGoroutine()
|
||||
if count > threshold {
|
||||
return fmt.Errorf("too many goroutines (%d > %d)", count, threshold)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
24
vendor/github.com/metalmatze/signal/healthcheck/doc.go
generated
vendored
Normal file
24
vendor/github.com/metalmatze/signal/healthcheck/doc.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2020 by the contributors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package healthcheck helps you implement liveness and readiness checks
|
||||
for your application. It supports synchronous and asynchronous (background)
|
||||
checks. It can optionally report each check's status as a set of Prometheus
|
||||
gauge metrics for cluster-wide monitoring and alerting.
|
||||
|
||||
It also includes a small library of generic checks for DNS, TCP, and HTTP
|
||||
reachability as well as Goroutine usage.
|
||||
*/
|
||||
package healthcheck
|
103
vendor/github.com/metalmatze/signal/healthcheck/handler.go
generated
vendored
Normal file
103
vendor/github.com/metalmatze/signal/healthcheck/handler.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2020 by the contributors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// basicHandler is a basic Handler implementation.
|
||||
type basicHandler struct {
|
||||
http.ServeMux
|
||||
checksMutex sync.RWMutex
|
||||
livenessChecks map[string]Check
|
||||
readinessChecks map[string]Check
|
||||
}
|
||||
|
||||
// NewHandler creates a new basic Handler
|
||||
func NewHandler() Handler {
|
||||
h := &basicHandler{
|
||||
livenessChecks: make(map[string]Check),
|
||||
readinessChecks: make(map[string]Check),
|
||||
}
|
||||
h.Handle("/live", http.HandlerFunc(h.LiveEndpoint))
|
||||
h.Handle("/ready", http.HandlerFunc(h.ReadyEndpoint))
|
||||
return h
|
||||
}
|
||||
|
||||
func (s *basicHandler) LiveEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
s.handle(w, r, s.livenessChecks)
|
||||
}
|
||||
|
||||
func (s *basicHandler) ReadyEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
s.handle(w, r, s.readinessChecks, s.livenessChecks)
|
||||
}
|
||||
|
||||
func (s *basicHandler) AddLivenessCheck(name string, check Check) {
|
||||
s.checksMutex.Lock()
|
||||
defer s.checksMutex.Unlock()
|
||||
s.livenessChecks[name] = check
|
||||
}
|
||||
|
||||
func (s *basicHandler) AddReadinessCheck(name string, check Check) {
|
||||
s.checksMutex.Lock()
|
||||
defer s.checksMutex.Unlock()
|
||||
s.readinessChecks[name] = check
|
||||
}
|
||||
|
||||
func (s *basicHandler) collectChecks(checks map[string]Check, resultsOut map[string]string, statusOut *int) {
|
||||
s.checksMutex.RLock()
|
||||
defer s.checksMutex.RUnlock()
|
||||
for name, check := range checks {
|
||||
if err := check(); err != nil {
|
||||
*statusOut = http.StatusServiceUnavailable
|
||||
resultsOut[name] = err.Error()
|
||||
} else {
|
||||
resultsOut[name] = "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *basicHandler) handle(w http.ResponseWriter, r *http.Request, checks ...map[string]Check) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
checkResults := make(map[string]string)
|
||||
status := http.StatusOK
|
||||
for _, checks := range checks {
|
||||
s.collectChecks(checks, checkResults, &status)
|
||||
}
|
||||
|
||||
// write out the response code and content type header
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
|
||||
// if ?hide=1, return an empty body. Kubernetes only cares about the
|
||||
// HTTP status code, so we won't waste bytes on the full body.
|
||||
if r.URL.Query().Get("hide") == "1" {
|
||||
_, _ = w.Write([]byte("{}\n"))
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, write the JSON body ignoring any encoding errors (which
|
||||
// shouldn't really be possible since we're encoding a map[string]string).
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetIndent("", " ")
|
||||
_ = encoder.Encode(checkResults)
|
||||
}
|
72
vendor/github.com/metalmatze/signal/healthcheck/metrics_handler.go
generated
vendored
Normal file
72
vendor/github.com/metalmatze/signal/healthcheck/metrics_handler.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2020 by the contributors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type metricsHandler struct {
|
||||
handler Handler
|
||||
registry prometheus.Registerer
|
||||
}
|
||||
|
||||
// NewMetricsHandler returns a healthy Handler that writes the current check status
|
||||
// into the provided Prometheus registry.
|
||||
func NewMetricsHandler(handler Handler, registry prometheus.Registerer) Handler {
|
||||
return &metricsHandler{
|
||||
handler: handler,
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *metricsHandler) AddLivenessCheck(name string, check Check) {
|
||||
h.handler.AddLivenessCheck(name, h.wrap(prometheus.Labels{"name": name, "check": "live"}, check))
|
||||
}
|
||||
|
||||
func (h *metricsHandler) AddReadinessCheck(name string, check Check) {
|
||||
h.handler.AddReadinessCheck(name, h.wrap(prometheus.Labels{"name": name, "check": "ready"}, check))
|
||||
}
|
||||
|
||||
func (h *metricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *metricsHandler) LiveEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
h.handler.LiveEndpoint(w, r)
|
||||
}
|
||||
|
||||
func (h *metricsHandler) ReadyEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
h.handler.ReadyEndpoint(w, r)
|
||||
}
|
||||
|
||||
func (h *metricsHandler) wrap(labels prometheus.Labels, check Check) Check {
|
||||
h.registry.MustRegister(prometheus.NewGaugeFunc(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "healthcheck",
|
||||
Help: "Indicates if check is healthy (1 is healthy, 0 is unhealthy)",
|
||||
ConstLabels: labels,
|
||||
},
|
||||
func() float64 {
|
||||
if check() != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
},
|
||||
))
|
||||
return check
|
||||
}
|
52
vendor/github.com/metalmatze/signal/healthcheck/timeout.go
generated
vendored
Normal file
52
vendor/github.com/metalmatze/signal/healthcheck/timeout.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 by the contributors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeoutError is the error returned when a Timeout-wrapped Check takes too long
|
||||
type timeoutError time.Duration
|
||||
|
||||
func (e timeoutError) Error() string {
|
||||
return fmt.Sprintf("timed out after %s", time.Duration(e).String())
|
||||
}
|
||||
|
||||
// Timeout returns whether this error is a timeout (always true for timeoutError)
|
||||
func (e timeoutError) Timeout() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Temporary returns whether this error is temporary (always true for timeoutError)
|
||||
func (e timeoutError) Temporary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Timeout adds a timeout to a Check. If the underlying check takes longer than
|
||||
// the timeout, it returns an error.
|
||||
func Timeout(check Check, timeout time.Duration) Check {
|
||||
return func() error {
|
||||
c := make(chan error, 1)
|
||||
go func() { c <- check() }()
|
||||
select {
|
||||
case err := <-c:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
return timeoutError(timeout)
|
||||
}
|
||||
}
|
||||
}
|
52
vendor/github.com/metalmatze/signal/healthcheck/types.go
generated
vendored
Normal file
52
vendor/github.com/metalmatze/signal/healthcheck/types.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 by the contributors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Check is a health/readiness check.
|
||||
type Check func() error
|
||||
|
||||
// Handler is an http.Handler with additional methods that register health and
|
||||
// readiness checks. It handles handle "/live" and "/ready" HTTP
|
||||
// endpoints.
|
||||
type Handler interface {
|
||||
// The Handler is an http.Handler, so it can be exposed directly and handle
|
||||
// /live and /ready endpoints.
|
||||
http.Handler
|
||||
|
||||
// AddLivenessCheck adds a check that indicates that this instance of the
|
||||
// application should be destroyed or restarted. A failed liveness check
|
||||
// indicates that this instance is unhealthy, not some upstream dependency.
|
||||
// Every liveness check is also included as a readiness check.
|
||||
AddLivenessCheck(name string, check Check)
|
||||
|
||||
// AddReadinessCheck adds a check that indicates that this instance of the
|
||||
// application is currently unable to serve requests because of an upstream
|
||||
// or some transient failure. If a readiness check fails, this instance
|
||||
// should no longer receiver requests, but should not be restarted or
|
||||
// destroyed.
|
||||
AddReadinessCheck(name string, check Check)
|
||||
|
||||
// LiveEndpoint is the HTTP handler for just the /live endpoint, which is
|
||||
// useful if you need to attach it into your own HTTP handler tree.
|
||||
LiveEndpoint(http.ResponseWriter, *http.Request)
|
||||
|
||||
// ReadyEndpoint is the HTTP handler for just the /ready endpoint, which is
|
||||
// useful if you need to attach it into your own HTTP handler tree.
|
||||
ReadyEndpoint(http.ResponseWriter, *http.Request)
|
||||
}
|
Reference in New Issue
Block a user