github.imxd.top/hashicorp/consul@v1.4.5/api/health.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 "time" 8 ) 9 10 const ( 11 // HealthAny is special, and is used as a wild card, 12 // not as a specific state. 13 HealthAny = "any" 14 HealthPassing = "passing" 15 HealthWarning = "warning" 16 HealthCritical = "critical" 17 HealthMaint = "maintenance" 18 ) 19 20 const ( 21 // NodeMaint is the special key set by a node in maintenance mode. 22 NodeMaint = "_node_maintenance" 23 24 // ServiceMaintPrefix is the prefix for a service in maintenance mode. 25 ServiceMaintPrefix = "_service_maintenance:" 26 ) 27 28 // HealthCheck is used to represent a single check 29 type HealthCheck struct { 30 Node string 31 CheckID string 32 Name string 33 Status string 34 Notes string 35 Output string 36 ServiceID string 37 ServiceName string 38 ServiceTags []string 39 40 Definition HealthCheckDefinition 41 42 CreateIndex uint64 43 ModifyIndex uint64 44 } 45 46 // HealthCheckDefinition is used to store the details about 47 // a health check's execution. 48 type HealthCheckDefinition struct { 49 HTTP string 50 Header map[string][]string 51 Method string 52 TLSSkipVerify bool 53 TCP string 54 IntervalDuration time.Duration `json:"-"` 55 TimeoutDuration time.Duration `json:"-"` 56 DeregisterCriticalServiceAfterDuration time.Duration `json:"-"` 57 58 // DEPRECATED in Consul 1.4.1. Use the above time.Duration fields instead. 59 Interval ReadableDuration 60 Timeout ReadableDuration 61 DeregisterCriticalServiceAfter ReadableDuration 62 } 63 64 func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) { 65 type Alias HealthCheckDefinition 66 out := &struct { 67 Interval string 68 Timeout string 69 DeregisterCriticalServiceAfter string 70 *Alias 71 }{ 72 Interval: d.Interval.String(), 73 Timeout: d.Timeout.String(), 74 DeregisterCriticalServiceAfter: d.DeregisterCriticalServiceAfter.String(), 75 Alias: (*Alias)(d), 76 } 77 78 if d.IntervalDuration != 0 { 79 out.Interval = d.IntervalDuration.String() 80 } else if d.Interval != 0 { 81 out.Interval = d.Interval.String() 82 } 83 if d.TimeoutDuration != 0 { 84 out.Timeout = d.TimeoutDuration.String() 85 } else if d.Timeout != 0 { 86 out.Timeout = d.Timeout.String() 87 } 88 if d.DeregisterCriticalServiceAfterDuration != 0 { 89 out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfterDuration.String() 90 } else if d.DeregisterCriticalServiceAfter != 0 { 91 out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfter.String() 92 } 93 94 return json.Marshal(out) 95 } 96 97 func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error { 98 type Alias HealthCheckDefinition 99 aux := &struct { 100 Interval string 101 Timeout string 102 DeregisterCriticalServiceAfter string 103 *Alias 104 }{ 105 Alias: (*Alias)(d), 106 } 107 if err := json.Unmarshal(data, &aux); err != nil { 108 return err 109 } 110 111 // Parse the values into both the time.Duration and old ReadableDuration fields. 112 var err error 113 if aux.Interval != "" { 114 if d.IntervalDuration, err = time.ParseDuration(aux.Interval); err != nil { 115 return err 116 } 117 d.Interval = ReadableDuration(d.IntervalDuration) 118 } 119 if aux.Timeout != "" { 120 if d.TimeoutDuration, err = time.ParseDuration(aux.Timeout); err != nil { 121 return err 122 } 123 d.Timeout = ReadableDuration(d.TimeoutDuration) 124 } 125 if aux.DeregisterCriticalServiceAfter != "" { 126 if d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil { 127 return err 128 } 129 d.DeregisterCriticalServiceAfter = ReadableDuration(d.DeregisterCriticalServiceAfterDuration) 130 } 131 return nil 132 } 133 134 // HealthChecks is a collection of HealthCheck structs. 135 type HealthChecks []*HealthCheck 136 137 // AggregatedStatus returns the "best" status for the list of health checks. 138 // Because a given entry may have many service and node-level health checks 139 // attached, this function determines the best representative of the status as 140 // as single string using the following heuristic: 141 // 142 // maintenance > critical > warning > passing 143 // 144 func (c HealthChecks) AggregatedStatus() string { 145 var passing, warning, critical, maintenance bool 146 for _, check := range c { 147 id := string(check.CheckID) 148 if id == NodeMaint || strings.HasPrefix(id, ServiceMaintPrefix) { 149 maintenance = true 150 continue 151 } 152 153 switch check.Status { 154 case HealthPassing: 155 passing = true 156 case HealthWarning: 157 warning = true 158 case HealthCritical: 159 critical = true 160 default: 161 return "" 162 } 163 } 164 165 switch { 166 case maintenance: 167 return HealthMaint 168 case critical: 169 return HealthCritical 170 case warning: 171 return HealthWarning 172 case passing: 173 return HealthPassing 174 default: 175 return HealthPassing 176 } 177 } 178 179 // ServiceEntry is used for the health service endpoint 180 type ServiceEntry struct { 181 Node *Node 182 Service *AgentService 183 Checks HealthChecks 184 } 185 186 // Health can be used to query the Health endpoints 187 type Health struct { 188 c *Client 189 } 190 191 // Health returns a handle to the health endpoints 192 func (c *Client) Health() *Health { 193 return &Health{c} 194 } 195 196 // Node is used to query for checks belonging to a given node 197 func (h *Health) Node(node string, q *QueryOptions) (HealthChecks, *QueryMeta, error) { 198 r := h.c.newRequest("GET", "/v1/health/node/"+node) 199 r.setQueryOptions(q) 200 rtt, resp, err := requireOK(h.c.doRequest(r)) 201 if err != nil { 202 return nil, nil, err 203 } 204 defer resp.Body.Close() 205 206 qm := &QueryMeta{} 207 parseQueryMeta(resp, qm) 208 qm.RequestTime = rtt 209 210 var out HealthChecks 211 if err := decodeBody(resp, &out); err != nil { 212 return nil, nil, err 213 } 214 return out, qm, nil 215 } 216 217 // Checks is used to return the checks associated with a service 218 func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMeta, error) { 219 r := h.c.newRequest("GET", "/v1/health/checks/"+service) 220 r.setQueryOptions(q) 221 rtt, resp, err := requireOK(h.c.doRequest(r)) 222 if err != nil { 223 return nil, nil, err 224 } 225 defer resp.Body.Close() 226 227 qm := &QueryMeta{} 228 parseQueryMeta(resp, qm) 229 qm.RequestTime = rtt 230 231 var out HealthChecks 232 if err := decodeBody(resp, &out); err != nil { 233 return nil, nil, err 234 } 235 return out, qm, nil 236 } 237 238 // Service is used to query health information along with service info 239 // for a given service. It can optionally do server-side filtering on a tag 240 // or nodes with passing health checks only. 241 func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) { 242 var tags []string 243 if tag != "" { 244 tags = []string{tag} 245 } 246 return h.service(service, tags, passingOnly, q, false) 247 } 248 249 func (h *Health) ServiceMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) { 250 return h.service(service, tags, passingOnly, q, false) 251 } 252 253 // Connect is equivalent to Service except that it will only return services 254 // which are Connect-enabled and will returns the connection address for Connect 255 // client's to use which may be a proxy in front of the named service. If 256 // passingOnly is true only instances where both the service and any proxy are 257 // healthy will be returned. 258 func (h *Health) Connect(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) { 259 var tags []string 260 if tag != "" { 261 tags = []string{tag} 262 } 263 return h.service(service, tags, passingOnly, q, true) 264 } 265 266 func (h *Health) ConnectMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) { 267 return h.service(service, tags, passingOnly, q, true) 268 } 269 270 func (h *Health) service(service string, tags []string, passingOnly bool, q *QueryOptions, connect bool) ([]*ServiceEntry, *QueryMeta, error) { 271 path := "/v1/health/service/" + service 272 if connect { 273 path = "/v1/health/connect/" + service 274 } 275 r := h.c.newRequest("GET", path) 276 r.setQueryOptions(q) 277 if len(tags) > 0 { 278 for _, tag := range tags { 279 r.params.Add("tag", tag) 280 } 281 } 282 if passingOnly { 283 r.params.Set(HealthPassing, "1") 284 } 285 rtt, resp, err := requireOK(h.c.doRequest(r)) 286 if err != nil { 287 return nil, nil, err 288 } 289 defer resp.Body.Close() 290 291 qm := &QueryMeta{} 292 parseQueryMeta(resp, qm) 293 qm.RequestTime = rtt 294 295 var out []*ServiceEntry 296 if err := decodeBody(resp, &out); err != nil { 297 return nil, nil, err 298 } 299 return out, qm, nil 300 } 301 302 // State is used to retrieve all the checks in a given state. 303 // The wildcard "any" state can also be used for all checks. 304 func (h *Health) State(state string, q *QueryOptions) (HealthChecks, *QueryMeta, error) { 305 switch state { 306 case HealthAny: 307 case HealthWarning: 308 case HealthCritical: 309 case HealthPassing: 310 default: 311 return nil, nil, fmt.Errorf("Unsupported state: %v", state) 312 } 313 r := h.c.newRequest("GET", "/v1/health/state/"+state) 314 r.setQueryOptions(q) 315 rtt, resp, err := requireOK(h.c.doRequest(r)) 316 if err != nil { 317 return nil, nil, err 318 } 319 defer resp.Body.Close() 320 321 qm := &QueryMeta{} 322 parseQueryMeta(resp, qm) 323 qm.RequestTime = rtt 324 325 var out HealthChecks 326 if err := decodeBody(resp, &out); err != nil { 327 return nil, nil, err 328 } 329 return out, qm, nil 330 }