blob: 224f1eda2c44f86916365e199a45c21fa360a708 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package healthz
18
19import (
20 "bytes"
21 "fmt"
22 "net/http"
23 "strings"
24 "sync"
25
26 "github.com/golang/glog"
27)
28
29// HealthzChecker is a named healthz checker.
30type HealthzChecker interface {
31 Name() string
32 Check(req *http.Request) error
33}
34
35var defaultHealthz = sync.Once{}
36
37// DefaultHealthz installs the default healthz check to the http.DefaultServeMux.
38func DefaultHealthz(checks ...HealthzChecker) {
39 defaultHealthz.Do(func() {
40 InstallHandler(http.DefaultServeMux, checks...)
41 })
42}
43
44// PingHealthz returns true automatically when checked
45var PingHealthz HealthzChecker = ping{}
46
47// ping implements the simplest possible healthz checker.
48type ping struct{}
49
50func (ping) Name() string {
51 return "ping"
52}
53
54// PingHealthz is a health check that returns true.
55func (ping) Check(_ *http.Request) error {
56 return nil
57}
58
59// NamedCheck returns a healthz checker for the given name and function.
60func NamedCheck(name string, check func(r *http.Request) error) HealthzChecker {
61 return &healthzCheck{name, check}
62}
63
64// InstallHandler registers handlers for health checking on the path
65// "/healthz" to mux. *All handlers* for mux must be specified in
66// exactly one call to InstallHandler. Calling InstallHandler more
67// than once for the same mux will result in a panic.
68func InstallHandler(mux mux, checks ...HealthzChecker) {
69 InstallPathHandler(mux, "/healthz", checks...)
70}
71
72// InstallPathHandler registers handlers for health checking on
73// a specific path to mux. *All handlers* for the path must be
74// specified in exactly one call to InstallPathHandler. Calling
75// InstallPathHandler more than once for the same path and mux will
76// result in a panic.
77func InstallPathHandler(mux mux, path string, checks ...HealthzChecker) {
78 if len(checks) == 0 {
79 glog.V(5).Info("No default health checks specified. Installing the ping handler.")
80 checks = []HealthzChecker{PingHealthz}
81 }
82
83 glog.V(5).Info("Installing healthz checkers:", strings.Join(checkerNames(checks...), ", "))
84
85 mux.Handle(path, handleRootHealthz(checks...))
86 for _, check := range checks {
87 mux.Handle(fmt.Sprintf("%s/%v", path, check.Name()), adaptCheckToHandler(check.Check))
88 }
89}
90
91// mux is an interface describing the methods InstallHandler requires.
92type mux interface {
93 Handle(pattern string, handler http.Handler)
94}
95
96// healthzCheck implements HealthzChecker on an arbitrary name and check function.
97type healthzCheck struct {
98 name string
99 check func(r *http.Request) error
100}
101
102var _ HealthzChecker = &healthzCheck{}
103
104func (c *healthzCheck) Name() string {
105 return c.name
106}
107
108func (c *healthzCheck) Check(r *http.Request) error {
109 return c.check(r)
110}
111
112// handleRootHealthz returns an http.HandlerFunc that serves the provided checks.
113func handleRootHealthz(checks ...HealthzChecker) http.HandlerFunc {
114 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
115 failed := false
116 var verboseOut bytes.Buffer
117 for _, check := range checks {
118 if err := check.Check(r); err != nil {
119 // don't include the error since this endpoint is public. If someone wants more detail
120 // they should have explicit permission to the detailed checks.
121 glog.V(6).Infof("healthz check %v failed: %v", check.Name(), err)
122 fmt.Fprintf(&verboseOut, "[-]%v failed: reason withheld\n", check.Name())
123 failed = true
124 } else {
125 fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.Name())
126 }
127 }
128 // always be verbose on failure
129 if failed {
130 http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
131 return
132 }
133
134 if _, found := r.URL.Query()["verbose"]; !found {
135 fmt.Fprint(w, "ok")
136 return
137 }
138
139 verboseOut.WriteTo(w)
140 fmt.Fprint(w, "healthz check passed\n")
141 })
142}
143
144// adaptCheckToHandler returns an http.HandlerFunc that serves the provided checks.
145func adaptCheckToHandler(c func(r *http.Request) error) http.HandlerFunc {
146 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
147 err := c(r)
148 if err != nil {
149 http.Error(w, fmt.Sprintf("internal server error: %v", err), http.StatusInternalServerError)
150 } else {
151 fmt.Fprint(w, "ok")
152 }
153 })
154}
155
156// checkerNames returns the names of the checks in the same order as passed in.
157func checkerNames(checks ...HealthzChecker) []string {
158 if len(checks) > 0 {
159 // accumulate the names of checks for printing them out.
160 checkerNames := make([]string, 0, len(checks))
161 for _, check := range checks {
162 // quote the Name so we can disambiguate
163 checkerNames = append(checkerNames, fmt.Sprintf("%q", check.Name()))
164 }
165 return checkerNames
166 }
167 return nil
168}