blob: f455e40a74068c1a9f4b9320872161faff9a45e5 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2017 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package etcdhttp
16
17import (
18 "context"
19 "encoding/json"
20 "net/http"
21 "time"
22
23 "go.etcd.io/etcd/etcdserver"
24 "go.etcd.io/etcd/etcdserver/etcdserverpb"
25 "go.etcd.io/etcd/raft"
26
27 "github.com/prometheus/client_golang/prometheus"
28 "github.com/prometheus/client_golang/prometheus/promhttp"
29)
30
31const (
32 PathMetrics = "/metrics"
33 PathHealth = "/health"
34)
35
36// HandleMetricsHealth registers metrics and health handlers.
37func HandleMetricsHealth(mux *http.ServeMux, srv etcdserver.ServerV2) {
38 mux.Handle(PathMetrics, promhttp.Handler())
39 mux.Handle(PathHealth, NewHealthHandler(func() Health { return checkHealth(srv) }))
40}
41
42// HandlePrometheus registers prometheus handler on '/metrics'.
43func HandlePrometheus(mux *http.ServeMux) {
44 mux.Handle(PathMetrics, promhttp.Handler())
45}
46
47// NewHealthHandler handles '/health' requests.
48func NewHealthHandler(hfunc func() Health) http.HandlerFunc {
49 return func(w http.ResponseWriter, r *http.Request) {
50 if r.Method != http.MethodGet {
51 w.Header().Set("Allow", http.MethodGet)
52 http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
53 return
54 }
55 h := hfunc()
56 d, _ := json.Marshal(h)
57 if h.Health != "true" {
58 http.Error(w, string(d), http.StatusServiceUnavailable)
59 return
60 }
61 w.WriteHeader(http.StatusOK)
62 w.Write(d)
63 }
64}
65
66var (
67 healthSuccess = prometheus.NewCounter(prometheus.CounterOpts{
68 Namespace: "etcd",
69 Subsystem: "server",
70 Name: "health_success",
71 Help: "The total number of successful health checks",
72 })
73 healthFailed = prometheus.NewCounter(prometheus.CounterOpts{
74 Namespace: "etcd",
75 Subsystem: "server",
76 Name: "health_failures",
77 Help: "The total number of failed health checks",
78 })
79)
80
81func init() {
82 prometheus.MustRegister(healthSuccess)
83 prometheus.MustRegister(healthFailed)
84}
85
86// Health defines etcd server health status.
87// TODO: remove manual parsing in etcdctl cluster-health
88type Health struct {
89 Health string `json:"health"`
90}
91
92// TODO: server NOSPACE, etcdserver.ErrNoLeader in health API
93
94func checkHealth(srv etcdserver.ServerV2) Health {
95 h := Health{Health: "true"}
96
97 as := srv.Alarms()
98 if len(as) > 0 {
99 h.Health = "false"
100 }
101
102 if h.Health == "true" {
103 if uint64(srv.Leader()) == raft.None {
104 h.Health = "false"
105 }
106 }
107
108 if h.Health == "true" {
109 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
110 _, err := srv.Do(ctx, etcdserverpb.Request{Method: "QGET"})
111 cancel()
112 if err != nil {
113 h.Health = "false"
114 }
115 }
116
117 if h.Health == "true" {
118 healthSuccess.Inc()
119 } else {
120 healthFailed.Inc()
121 }
122 return h
123}