github.com/criteo-forks/consul@v1.4.5-criteonogrpc/agent/catalog_endpoint.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 8 metrics "github.com/armon/go-metrics" 9 cachetype "github.com/hashicorp/consul/agent/cache-types" 10 "github.com/hashicorp/consul/agent/structs" 11 ) 12 13 var durations = NewDurationFixer("interval", "timeout", "deregistercriticalserviceafter") 14 15 func (s *HTTPServer) CatalogRegister(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 16 metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_register"}, 1, 17 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 18 19 var args structs.RegisterRequest 20 if err := decodeBody(req, &args, durations.FixupDurations); err != nil { 21 resp.WriteHeader(http.StatusBadRequest) 22 fmt.Fprintf(resp, "Request decode failed: %v", err) 23 return nil, nil 24 } 25 26 // Setup the default DC if not provided 27 if args.Datacenter == "" { 28 args.Datacenter = s.agent.config.Datacenter 29 } 30 s.parseToken(req, &args.Token) 31 32 // Forward to the servers 33 var out struct{} 34 if err := s.agent.RPC("Catalog.Register", &args, &out); err != nil { 35 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_register"}, 1, 36 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 37 return nil, err 38 } 39 metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_register"}, 1, 40 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 41 return true, nil 42 } 43 44 func (s *HTTPServer) CatalogDeregister(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 45 metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_deregister"}, 1, 46 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 47 48 var args structs.DeregisterRequest 49 if err := decodeBody(req, &args, nil); err != nil { 50 resp.WriteHeader(http.StatusBadRequest) 51 fmt.Fprintf(resp, "Request decode failed: %v", err) 52 return nil, nil 53 } 54 55 // Setup the default DC if not provided 56 if args.Datacenter == "" { 57 args.Datacenter = s.agent.config.Datacenter 58 } 59 s.parseToken(req, &args.Token) 60 61 // Forward to the servers 62 var out struct{} 63 if err := s.agent.RPC("Catalog.Deregister", &args, &out); err != nil { 64 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_deregister"}, 1, 65 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 66 return nil, err 67 } 68 metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_deregister"}, 1, 69 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 70 return true, nil 71 } 72 73 func (s *HTTPServer) CatalogDatacenters(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 74 metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_datacenters"}, 1, 75 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 76 77 var out []string 78 if err := s.agent.RPC("Catalog.ListDatacenters", struct{}{}, &out); err != nil { 79 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_datacenters"}, 1, 80 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 81 return nil, err 82 } 83 metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_datacenters"}, 1, 84 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 85 return out, nil 86 } 87 88 func (s *HTTPServer) CatalogNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 89 metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_nodes"}, 1, 90 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 91 92 // Setup the request 93 args := structs.DCSpecificRequest{} 94 s.parseSource(req, &args.Source) 95 args.NodeMetaFilters = s.parseMetaFilter(req) 96 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 97 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_nodes"}, 1, 98 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 99 return nil, nil 100 } 101 102 var out structs.IndexedNodes 103 defer setMeta(resp, &out.QueryMeta) 104 RETRY_ONCE: 105 if err := s.agent.RPC("Catalog.ListNodes", &args, &out); err != nil { 106 return nil, err 107 } 108 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 109 args.AllowStale = false 110 args.MaxStaleDuration = 0 111 goto RETRY_ONCE 112 } 113 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 114 115 s.agent.TranslateAddresses(args.Datacenter, out.Nodes) 116 117 // Use empty list instead of nil 118 if out.Nodes == nil { 119 out.Nodes = make(structs.Nodes, 0) 120 } 121 metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_nodes"}, 1, 122 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 123 return out.Nodes, nil 124 } 125 126 func (s *HTTPServer) CatalogServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 127 metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_services"}, 1, 128 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 129 130 // Set default DC 131 args := structs.DCSpecificRequest{} 132 args.NodeMetaFilters = s.parseMetaFilter(req) 133 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 134 return nil, nil 135 } 136 137 var out structs.IndexedServices 138 defer setMeta(resp, &out.QueryMeta) 139 RETRY_ONCE: 140 if err := s.agent.RPC("Catalog.ListServices", &args, &out); err != nil { 141 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_services"}, 1, 142 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 143 return nil, err 144 } 145 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 146 args.AllowStale = false 147 args.MaxStaleDuration = 0 148 goto RETRY_ONCE 149 } 150 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 151 152 // Use empty map instead of nil 153 if out.Services == nil { 154 out.Services = make(structs.Services, 0) 155 } 156 metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_services"}, 1, 157 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 158 return out.Services, nil 159 } 160 161 func (s *HTTPServer) CatalogConnectServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 162 return s.catalogServiceNodes(resp, req, true) 163 } 164 165 func (s *HTTPServer) CatalogServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 166 return s.catalogServiceNodes(resp, req, false) 167 } 168 169 func (s *HTTPServer) catalogServiceNodes(resp http.ResponseWriter, req *http.Request, connect bool) (interface{}, error) { 170 metricsKey := "catalog_service_nodes" 171 pathPrefix := "/v1/catalog/service/" 172 if connect { 173 metricsKey = "catalog_connect_service_nodes" 174 pathPrefix = "/v1/catalog/connect/" 175 } 176 177 metrics.IncrCounterWithLabels([]string{"client", "api", metricsKey}, 1, 178 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 179 180 // Set default DC 181 args := structs.ServiceSpecificRequest{Connect: connect} 182 s.parseSource(req, &args.Source) 183 args.NodeMetaFilters = s.parseMetaFilter(req) 184 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 185 return nil, nil 186 } 187 188 // Check for a tag 189 params := req.URL.Query() 190 if _, ok := params["tag"]; ok { 191 args.ServiceTags = params["tag"] 192 args.TagFilter = true 193 } 194 195 // Pull out the service name 196 args.ServiceName = strings.TrimPrefix(req.URL.Path, pathPrefix) 197 if args.ServiceName == "" { 198 resp.WriteHeader(http.StatusBadRequest) 199 fmt.Fprint(resp, "Missing service name") 200 return nil, nil 201 } 202 203 // Make the RPC request 204 var out structs.IndexedServiceNodes 205 defer setMeta(resp, &out.QueryMeta) 206 207 if args.QueryOptions.UseCache { 208 raw, m, err := s.agent.cache.Get(cachetype.CatalogServicesName, &args) 209 if err != nil { 210 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_service_nodes"}, 1, 211 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 212 return nil, err 213 } 214 defer setCacheMeta(resp, &m) 215 reply, ok := raw.(*structs.IndexedServiceNodes) 216 if !ok { 217 // This should never happen, but we want to protect against panics 218 return nil, fmt.Errorf("internal error: response type not correct") 219 } 220 out = *reply 221 } else { 222 RETRY_ONCE: 223 if err := s.agent.RPC("Catalog.ServiceNodes", &args, &out); err != nil { 224 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_service_nodes"}, 1, 225 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 226 return nil, err 227 } 228 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 229 args.AllowStale = false 230 args.MaxStaleDuration = 0 231 goto RETRY_ONCE 232 } 233 } 234 235 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 236 s.agent.TranslateAddresses(args.Datacenter, out.ServiceNodes) 237 238 // Use empty list instead of nil 239 if out.ServiceNodes == nil { 240 out.ServiceNodes = make(structs.ServiceNodes, 0) 241 } 242 for i, s := range out.ServiceNodes { 243 if s.ServiceTags == nil { 244 clone := *s 245 clone.ServiceTags = make([]string, 0) 246 out.ServiceNodes[i] = &clone 247 } 248 } 249 metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_service_nodes"}, 1, 250 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 251 return out.ServiceNodes, nil 252 } 253 254 func (s *HTTPServer) CatalogNodeServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 255 metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_node_services"}, 1, 256 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 257 258 // Set default Datacenter 259 args := structs.NodeSpecificRequest{} 260 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 261 return nil, nil 262 } 263 264 // Pull out the node name 265 args.Node = strings.TrimPrefix(req.URL.Path, "/v1/catalog/node/") 266 if args.Node == "" { 267 resp.WriteHeader(http.StatusBadRequest) 268 fmt.Fprint(resp, "Missing node name") 269 return nil, nil 270 } 271 272 // Make the RPC request 273 var out structs.IndexedNodeServices 274 defer setMeta(resp, &out.QueryMeta) 275 RETRY_ONCE: 276 if err := s.agent.RPC("Catalog.NodeServices", &args, &out); err != nil { 277 metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_node_services"}, 1, 278 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 279 return nil, err 280 } 281 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { 282 args.AllowStale = false 283 args.MaxStaleDuration = 0 284 goto RETRY_ONCE 285 } 286 out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 287 if out.NodeServices != nil && out.NodeServices.Node != nil { 288 s.agent.TranslateAddresses(args.Datacenter, out.NodeServices.Node) 289 } 290 291 // TODO: The NodeServices object in IndexedNodeServices is a pointer to 292 // something that's created for each request by the state store way down 293 // in https://github.com/hashicorp/consul/blob/v1.0.4/agent/consul/state/catalog.go#L953-L963. 294 // Since this isn't a pointer to a real state store object, it's safe to 295 // modify out.NodeServices.Services in the loop below without making a 296 // copy here. Same for the Tags in each service entry, since that was 297 // created by .ToNodeService() which made a copy. This is safe as-is but 298 // this whole business is tricky and subtle. See #3867 for more context. 299 300 // Use empty list instead of nil 301 if out.NodeServices != nil { 302 for _, s := range out.NodeServices.Services { 303 if s.Tags == nil { 304 s.Tags = make([]string, 0) 305 } 306 } 307 } 308 metrics.IncrCounterWithLabels([]string{"client", "api", "success", "catalog_node_services"}, 1, 309 []metrics.Label{{Name: "node", Value: s.nodeName()}}) 310 return out.NodeServices, nil 311 }