github.com/outbrain/consul@v1.4.5/agent/health_endpoint.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "net/http" 6 "strconv" 7 "strings" 8 9 cachetype "github.com/hashicorp/consul/agent/cache-types" 10 "github.com/hashicorp/consul/agent/structs" 11 "github.com/hashicorp/consul/api" 12 ) 13 14 func (s *HTTPServer) HealthChecksInState(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 15 // Set default DC 16 args := structs.ChecksInStateRequest{} 17 s.parseSource(req, &args.Source) 18 args.NodeMetaFilters = s.parseMetaFilter(req) 19 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 20 return nil, nil 21 } 22 23 // Pull out the service name 24 args.State = strings.TrimPrefix(req.URL.Path, "/v1/health/state/") 25 if args.State == "" { 26 resp.WriteHeader(http.StatusBadRequest) 27 fmt.Fprint(resp, "Missing check state") 28 return nil, nil 29 } 30 31 // Make the RPC request 32 var out structs.IndexedHealthChecks 33 defer setMeta(resp, &out.QueryMeta) 34 RETRY_ONCE: 35 if err := s.agent.RPC("Health.ChecksInState", &args, &out); err != nil { 36 return nil, err 37 } 38 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 39 args.AllowStale = false 40 args.MaxStaleDuration = 0 41 goto RETRY_ONCE 42 } 43 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 44 45 // Use empty list instead of nil 46 if out.HealthChecks == nil { 47 out.HealthChecks = make(structs.HealthChecks, 0) 48 } 49 for i, c := range out.HealthChecks { 50 if c.ServiceTags == nil { 51 clone := *c 52 clone.ServiceTags = make([]string, 0) 53 out.HealthChecks[i] = &clone 54 } 55 } 56 return out.HealthChecks, nil 57 } 58 59 func (s *HTTPServer) HealthNodeChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 60 // Set default DC 61 args := structs.NodeSpecificRequest{} 62 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 63 return nil, nil 64 } 65 66 // Pull out the service name 67 args.Node = strings.TrimPrefix(req.URL.Path, "/v1/health/node/") 68 if args.Node == "" { 69 resp.WriteHeader(http.StatusBadRequest) 70 fmt.Fprint(resp, "Missing node name") 71 return nil, nil 72 } 73 74 // Make the RPC request 75 var out structs.IndexedHealthChecks 76 defer setMeta(resp, &out.QueryMeta) 77 RETRY_ONCE: 78 if err := s.agent.RPC("Health.NodeChecks", &args, &out); err != nil { 79 return nil, err 80 } 81 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 82 args.AllowStale = false 83 args.MaxStaleDuration = 0 84 goto RETRY_ONCE 85 } 86 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 87 88 // Use empty list instead of nil 89 if out.HealthChecks == nil { 90 out.HealthChecks = make(structs.HealthChecks, 0) 91 } 92 for i, c := range out.HealthChecks { 93 if c.ServiceTags == nil { 94 clone := *c 95 clone.ServiceTags = make([]string, 0) 96 out.HealthChecks[i] = &clone 97 } 98 } 99 return out.HealthChecks, nil 100 } 101 102 func (s *HTTPServer) HealthServiceChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 103 // Set default DC 104 args := structs.ServiceSpecificRequest{} 105 s.parseSource(req, &args.Source) 106 args.NodeMetaFilters = s.parseMetaFilter(req) 107 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 108 return nil, nil 109 } 110 111 // Pull out the service name 112 args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/health/checks/") 113 if args.ServiceName == "" { 114 resp.WriteHeader(http.StatusBadRequest) 115 fmt.Fprint(resp, "Missing service name") 116 return nil, nil 117 } 118 119 // Make the RPC request 120 var out structs.IndexedHealthChecks 121 defer setMeta(resp, &out.QueryMeta) 122 RETRY_ONCE: 123 if err := s.agent.RPC("Health.ServiceChecks", &args, &out); err != nil { 124 return nil, err 125 } 126 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 127 args.AllowStale = false 128 args.MaxStaleDuration = 0 129 goto RETRY_ONCE 130 } 131 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 132 133 // Use empty list instead of nil 134 if out.HealthChecks == nil { 135 out.HealthChecks = make(structs.HealthChecks, 0) 136 } 137 for i, c := range out.HealthChecks { 138 if c.ServiceTags == nil { 139 clone := *c 140 clone.ServiceTags = make([]string, 0) 141 out.HealthChecks[i] = &clone 142 } 143 } 144 return out.HealthChecks, nil 145 } 146 147 func (s *HTTPServer) HealthConnectServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 148 return s.healthServiceNodes(resp, req, true) 149 } 150 151 func (s *HTTPServer) HealthServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 152 return s.healthServiceNodes(resp, req, false) 153 } 154 155 func (s *HTTPServer) healthServiceNodes(resp http.ResponseWriter, req *http.Request, connect bool) (interface{}, error) { 156 // Set default DC 157 args := structs.ServiceSpecificRequest{Connect: connect} 158 s.parseSource(req, &args.Source) 159 args.NodeMetaFilters = s.parseMetaFilter(req) 160 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 161 return nil, nil 162 } 163 164 // Check for tags 165 params := req.URL.Query() 166 if _, ok := params["tag"]; ok { 167 args.ServiceTags = params["tag"] 168 args.TagFilter = true 169 } 170 171 // Determine the prefix 172 prefix := "/v1/health/service/" 173 if connect { 174 prefix = "/v1/health/connect/" 175 } 176 177 // Pull out the service name 178 args.ServiceName = strings.TrimPrefix(req.URL.Path, prefix) 179 if args.ServiceName == "" { 180 resp.WriteHeader(http.StatusBadRequest) 181 fmt.Fprint(resp, "Missing service name") 182 return nil, nil 183 } 184 185 // Make the RPC request 186 var out structs.IndexedCheckServiceNodes 187 defer setMeta(resp, &out.QueryMeta) 188 189 if args.QueryOptions.UseCache { 190 raw, m, err := s.agent.cache.Get(cachetype.HealthServicesName, &args) 191 if err != nil { 192 return nil, err 193 } 194 defer setCacheMeta(resp, &m) 195 reply, ok := raw.(*structs.IndexedCheckServiceNodes) 196 if !ok { 197 // This should never happen, but we want to protect against panics 198 return nil, fmt.Errorf("internal error: response type not correct") 199 } 200 out = *reply 201 } else { 202 RETRY_ONCE: 203 if err := s.agent.RPC("Health.ServiceNodes", &args, &out); err != nil { 204 return nil, err 205 } 206 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 207 args.AllowStale = false 208 args.MaxStaleDuration = 0 209 goto RETRY_ONCE 210 } 211 } 212 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 213 214 // Filter to only passing if specified 215 if _, ok := params[api.HealthPassing]; ok { 216 val := params.Get(api.HealthPassing) 217 // Backwards-compat to allow users to specify ?passing without a value. This 218 // should be removed in Consul 0.10. 219 var filter bool 220 if val == "" { 221 filter = true 222 } else { 223 var err error 224 filter, err = strconv.ParseBool(val) 225 if err != nil { 226 resp.WriteHeader(http.StatusBadRequest) 227 fmt.Fprint(resp, "Invalid value for ?passing") 228 return nil, nil 229 } 230 } 231 232 if filter { 233 out.Nodes = filterNonPassing(out.Nodes) 234 } 235 } 236 237 // Translate addresses after filtering so we don't waste effort. 238 s.agent.TranslateAddresses(args.Datacenter, out.Nodes) 239 240 // Use empty list instead of nil 241 if out.Nodes == nil { 242 out.Nodes = make(structs.CheckServiceNodes, 0) 243 } 244 for i := range out.Nodes { 245 if out.Nodes[i].Checks == nil { 246 out.Nodes[i].Checks = make(structs.HealthChecks, 0) 247 } 248 for j, c := range out.Nodes[i].Checks { 249 if c.ServiceTags == nil { 250 clone := *c 251 clone.ServiceTags = make([]string, 0) 252 out.Nodes[i].Checks[j] = &clone 253 } 254 } 255 if out.Nodes[i].Service != nil && out.Nodes[i].Service.Tags == nil { 256 clone := *out.Nodes[i].Service 257 clone.Tags = make([]string, 0) 258 out.Nodes[i].Service = &clone 259 } 260 } 261 return out.Nodes, nil 262 } 263 264 // filterNonPassing is used to filter out any nodes that have check that are not passing 265 func filterNonPassing(nodes structs.CheckServiceNodes) structs.CheckServiceNodes { 266 n := len(nodes) 267 OUTER: 268 for i := 0; i < n; i++ { 269 node := nodes[i] 270 for _, check := range node.Checks { 271 if check.Status != api.HealthPassing { 272 nodes[i], nodes[n-1] = nodes[n-1], structs.CheckServiceNode{} 273 n-- 274 i-- 275 continue OUTER 276 } 277 } 278 } 279 return nodes[:n] 280 }