-
Notifications
You must be signed in to change notification settings - Fork 669
/
service.go
146 lines (124 loc) · 4.29 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// (c) 2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package health
import (
"net/http"
"time"
stdjson "encoding/json"
"github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/utils/json"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/gorilla/rpc/v2"
"github.com/prometheus/client_golang/prometheus"
health "github.com/AppsFlyer/go-sundheit"
healthlib "github.com/ava-labs/avalanchego/health"
)
// Service wraps a [healthlib.Service]. Handler() returns a handler
// that handles incoming HTTP API requests. We have this in a separate
// package from [healthlib] to avoid a circular import where this service
// imports snow/engine/common but that package imports [healthlib].Checkable
type Service interface {
healthlib.Service
Handler() (*common.HTTPHandler, error)
}
func NewService(checkFreq time.Duration, log logging.Logger, namespace string, registry prometheus.Registerer) (Service, error) {
service, err := healthlib.NewService(checkFreq, log, namespace, registry)
if err != nil {
return nil, err
}
return &apiServer{
Service: service,
log: log,
}, nil
}
// APIServer serves HTTP for a health service
type apiServer struct {
healthlib.Service
log logging.Logger
}
func (as *apiServer) Handler() (*common.HTTPHandler, error) {
newServer := rpc.NewServer()
codec := json.NewCodec()
newServer.RegisterCodec(codec, "application/json")
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
if err := newServer.RegisterService(as, "health"); err != nil {
return nil, err
}
// If a GET request is sent, we respond with a 200 if the node is healthy or
// a 503 if the node isn't healthy.
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
newServer.ServeHTTP(w, r)
return
}
// Make sure the content type is set before writing the header.
w.Header().Set("Content-Type", "application/json")
checks, healthy := as.Results()
if !healthy {
// If a health check has failed, we should return a 503.
w.WriteHeader(http.StatusServiceUnavailable)
}
// The encoder will call write on the writer, which will write the
// header with a 200.
err := stdjson.NewEncoder(w).Encode(APIHealthReply{
Checks: checks,
Healthy: healthy,
})
if err != nil {
as.log.Debug("failed to encode the health check response due to %s", err)
}
})
return &common.HTTPHandler{LockOptions: common.NoLock, Handler: handler}, nil
}
// APIHealthArgs are the arguments for Health
type APIHealthArgs struct{}
// APIHealthReply is the response for Health
type APIHealthReply struct {
Checks map[string]health.Result `json:"checks"`
Healthy bool `json:"healthy"`
}
// Health returns a summation of the health of the node
func (as *apiServer) Health(_ *http.Request, _ *APIHealthArgs, reply *APIHealthReply) error {
as.log.Info("Health.health called")
reply.Checks, reply.Healthy = as.Results()
if reply.Healthy {
return nil
}
replyStr, err := stdjson.Marshal(reply.Checks)
as.log.Warn("Health.health is returning an error: %s", string(replyStr))
return err
}
// GetLiveness returns a summation of the health of the node
// Deprecated: in favor of Health
func (as *apiServer) GetLiveness(_ *http.Request, _ *APIHealthArgs, reply *APIHealthReply) error {
as.log.Info("Health.getLiveness called")
reply.Checks, reply.Healthy = as.Results()
if reply.Healthy {
return nil
}
replyStr, err := stdjson.Marshal(reply.Checks)
as.log.Warn("Health.getLiveness is returning an error: %s", string(replyStr))
return err
}
type noOp struct{}
// NewNoOpService returns a NoOp version of health check
// for when the Health API is disabled
func NewNoOpService() Service {
return &noOp{}
}
// RegisterCheck implements the Service interface
func (n *noOp) Results() (map[string]health.Result, bool) {
return map[string]health.Result{}, true
}
// RegisterCheck implements the Service interface
func (n *noOp) Handler() (_ *common.HTTPHandler, _ error) {
return nil, nil
}
// RegisterCheckFn implements the Service interface
func (n *noOp) RegisterCheck(_ string, _ healthlib.Check) error {
return nil
}
// RegisterMonotonicCheckFn implements the Service interface
func (n *noOp) RegisterMonotonicCheck(_ string, _ healthlib.Check) error {
return nil
}