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  }