blob: eae6a01a8682844ade02559dfc892360e61e7451 [file] [log] [blame]
khenaidooac637102019-01-14 15:44:34 -05001package api
2
3import (
4 "fmt"
5 "strings"
6)
7
8const (
9 // HealthAny is special, and is used as a wild card,
10 // not as a specific state.
11 HealthAny = "any"
12 HealthPassing = "passing"
13 HealthWarning = "warning"
14 HealthCritical = "critical"
15 HealthMaint = "maintenance"
16)
17
18const (
19 // NodeMaint is the special key set by a node in maintenance mode.
20 NodeMaint = "_node_maintenance"
21
22 // ServiceMaintPrefix is the prefix for a service in maintenance mode.
23 ServiceMaintPrefix = "_service_maintenance:"
24)
25
26// HealthCheck is used to represent a single check
27type HealthCheck struct {
28 Node string
29 CheckID string
30 Name string
31 Status string
32 Notes string
33 Output string
34 ServiceID string
35 ServiceName string
36 ServiceTags []string
37
38 Definition HealthCheckDefinition
39}
40
41// HealthCheckDefinition is used to store the details about
42// a health check's execution.
43type HealthCheckDefinition struct {
44 HTTP string
45 Header map[string][]string
46 Method string
47 TLSSkipVerify bool
48 TCP string
49 Interval ReadableDuration
50 Timeout ReadableDuration
51 DeregisterCriticalServiceAfter ReadableDuration
52}
53
54// HealthChecks is a collection of HealthCheck structs.
55type HealthChecks []*HealthCheck
56
57// AggregatedStatus returns the "best" status for the list of health checks.
58// Because a given entry may have many service and node-level health checks
59// attached, this function determines the best representative of the status as
60// as single string using the following heuristic:
61//
62// maintenance > critical > warning > passing
63//
64func (c HealthChecks) AggregatedStatus() string {
65 var passing, warning, critical, maintenance bool
66 for _, check := range c {
67 id := string(check.CheckID)
68 if id == NodeMaint || strings.HasPrefix(id, ServiceMaintPrefix) {
69 maintenance = true
70 continue
71 }
72
73 switch check.Status {
74 case HealthPassing:
75 passing = true
76 case HealthWarning:
77 warning = true
78 case HealthCritical:
79 critical = true
80 default:
81 return ""
82 }
83 }
84
85 switch {
86 case maintenance:
87 return HealthMaint
88 case critical:
89 return HealthCritical
90 case warning:
91 return HealthWarning
92 case passing:
93 return HealthPassing
94 default:
95 return HealthPassing
96 }
97}
98
99// ServiceEntry is used for the health service endpoint
100type ServiceEntry struct {
101 Node *Node
102 Service *AgentService
103 Checks HealthChecks
104}
105
106// Health can be used to query the Health endpoints
107type Health struct {
108 c *Client
109}
110
111// Health returns a handle to the health endpoints
112func (c *Client) Health() *Health {
113 return &Health{c}
114}
115
116// Node is used to query for checks belonging to a given node
117func (h *Health) Node(node string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
118 r := h.c.newRequest("GET", "/v1/health/node/"+node)
119 r.setQueryOptions(q)
120 rtt, resp, err := requireOK(h.c.doRequest(r))
121 if err != nil {
122 return nil, nil, err
123 }
124 defer resp.Body.Close()
125
126 qm := &QueryMeta{}
127 parseQueryMeta(resp, qm)
128 qm.RequestTime = rtt
129
130 var out HealthChecks
131 if err := decodeBody(resp, &out); err != nil {
132 return nil, nil, err
133 }
134 return out, qm, nil
135}
136
137// Checks is used to return the checks associated with a service
138func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
139 r := h.c.newRequest("GET", "/v1/health/checks/"+service)
140 r.setQueryOptions(q)
141 rtt, resp, err := requireOK(h.c.doRequest(r))
142 if err != nil {
143 return nil, nil, err
144 }
145 defer resp.Body.Close()
146
147 qm := &QueryMeta{}
148 parseQueryMeta(resp, qm)
149 qm.RequestTime = rtt
150
151 var out HealthChecks
152 if err := decodeBody(resp, &out); err != nil {
153 return nil, nil, err
154 }
155 return out, qm, nil
156}
157
158// Service is used to query health information along with service info
159// for a given service. It can optionally do server-side filtering on a tag
160// or nodes with passing health checks only.
161func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
162 var tags []string
163 if tag != "" {
164 tags = []string{tag}
165 }
166 return h.service(service, tags, passingOnly, q, false)
167}
168
169func (h *Health) ServiceMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
170 return h.service(service, tags, passingOnly, q, false)
171}
172
173// Connect is equivalent to Service except that it will only return services
174// which are Connect-enabled and will returns the connection address for Connect
175// client's to use which may be a proxy in front of the named service. If
176// passingOnly is true only instances where both the service and any proxy are
177// healthy will be returned.
178func (h *Health) Connect(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
179 var tags []string
180 if tag != "" {
181 tags = []string{tag}
182 }
183 return h.service(service, tags, passingOnly, q, true)
184}
185
186func (h *Health) ConnectMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
187 return h.service(service, tags, passingOnly, q, true)
188}
189
190func (h *Health) service(service string, tags []string, passingOnly bool, q *QueryOptions, connect bool) ([]*ServiceEntry, *QueryMeta, error) {
191 path := "/v1/health/service/" + service
192 if connect {
193 path = "/v1/health/connect/" + service
194 }
195 r := h.c.newRequest("GET", path)
196 r.setQueryOptions(q)
197 if len(tags) > 0 {
198 for _, tag := range tags {
199 r.params.Add("tag", tag)
200 }
201 }
202 if passingOnly {
203 r.params.Set(HealthPassing, "1")
204 }
205 rtt, resp, err := requireOK(h.c.doRequest(r))
206 if err != nil {
207 return nil, nil, err
208 }
209 defer resp.Body.Close()
210
211 qm := &QueryMeta{}
212 parseQueryMeta(resp, qm)
213 qm.RequestTime = rtt
214
215 var out []*ServiceEntry
216 if err := decodeBody(resp, &out); err != nil {
217 return nil, nil, err
218 }
219 return out, qm, nil
220}
221
222// State is used to retrieve all the checks in a given state.
223// The wildcard "any" state can also be used for all checks.
224func (h *Health) State(state string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
225 switch state {
226 case HealthAny:
227 case HealthWarning:
228 case HealthCritical:
229 case HealthPassing:
230 default:
231 return nil, nil, fmt.Errorf("Unsupported state: %v", state)
232 }
233 r := h.c.newRequest("GET", "/v1/health/state/"+state)
234 r.setQueryOptions(q)
235 rtt, resp, err := requireOK(h.c.doRequest(r))
236 if err != nil {
237 return nil, nil, err
238 }
239 defer resp.Body.Close()
240
241 qm := &QueryMeta{}
242 parseQueryMeta(resp, qm)
243 qm.RequestTime = rtt
244
245 var out HealthChecks
246 if err := decodeBody(resp, &out); err != nil {
247 return nil, nil, err
248 }
249 return out, qm, nil
250}