github.com/outbrain/consul@v1.4.5/agent/coordinate_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/consul/agent/structs"
    10  )
    11  
    12  // checkCoordinateDisabled will return a standard response if coordinates are
    13  // disabled. This returns true if they are disabled and we should not continue.
    14  func (s *HTTPServer) checkCoordinateDisabled(resp http.ResponseWriter, req *http.Request) bool {
    15  	if !s.agent.config.DisableCoordinates {
    16  		return false
    17  	}
    18  
    19  	resp.WriteHeader(http.StatusUnauthorized)
    20  	fmt.Fprint(resp, "Coordinate support disabled")
    21  	return true
    22  }
    23  
    24  // sorter wraps a coordinate list and implements the sort.Interface to sort by
    25  // node name.
    26  type sorter struct {
    27  	coordinates structs.Coordinates
    28  }
    29  
    30  // See sort.Interface.
    31  func (s *sorter) Len() int {
    32  	return len(s.coordinates)
    33  }
    34  
    35  // See sort.Interface.
    36  func (s *sorter) Swap(i, j int) {
    37  	s.coordinates[i], s.coordinates[j] = s.coordinates[j], s.coordinates[i]
    38  }
    39  
    40  // See sort.Interface.
    41  func (s *sorter) Less(i, j int) bool {
    42  	return s.coordinates[i].Node < s.coordinates[j].Node
    43  }
    44  
    45  // CoordinateDatacenters returns the WAN nodes in each datacenter, along with
    46  // raw network coordinates.
    47  func (s *HTTPServer) CoordinateDatacenters(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    48  	if s.checkCoordinateDisabled(resp, req) {
    49  		return nil, nil
    50  	}
    51  
    52  	var out []structs.DatacenterMap
    53  	if err := s.agent.RPC("Coordinate.ListDatacenters", struct{}{}, &out); err != nil {
    54  		for i := range out {
    55  			sort.Sort(&sorter{out[i].Coordinates})
    56  		}
    57  		return nil, err
    58  	}
    59  
    60  	// Use empty list instead of nil (these aren't really possible because
    61  	// Serf will give back a default coordinate and there's always one DC,
    62  	// but it's better to be explicit about what we want here).
    63  	for i := range out {
    64  		if out[i].Coordinates == nil {
    65  			out[i].Coordinates = make(structs.Coordinates, 0)
    66  		}
    67  	}
    68  	if out == nil {
    69  		out = make([]structs.DatacenterMap, 0)
    70  	}
    71  	return out, nil
    72  }
    73  
    74  // CoordinateNodes returns the LAN nodes in the given datacenter, along with
    75  // raw network coordinates.
    76  func (s *HTTPServer) CoordinateNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    77  	if s.checkCoordinateDisabled(resp, req) {
    78  		return nil, nil
    79  	}
    80  
    81  	args := structs.DCSpecificRequest{}
    82  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
    83  		return nil, nil
    84  	}
    85  
    86  	var out structs.IndexedCoordinates
    87  	defer setMeta(resp, &out.QueryMeta)
    88  	if err := s.agent.RPC("Coordinate.ListNodes", &args, &out); err != nil {
    89  		sort.Sort(&sorter{out.Coordinates})
    90  		return nil, err
    91  	}
    92  
    93  	return filterCoordinates(req, out.Coordinates), nil
    94  }
    95  
    96  // CoordinateNode returns the LAN node in the given datacenter, along with
    97  // raw network coordinates.
    98  func (s *HTTPServer) CoordinateNode(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    99  	if s.checkCoordinateDisabled(resp, req) {
   100  		return nil, nil
   101  	}
   102  
   103  	node := strings.TrimPrefix(req.URL.Path, "/v1/coordinate/node/")
   104  	args := structs.NodeSpecificRequest{Node: node}
   105  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   106  		return nil, nil
   107  	}
   108  
   109  	var out structs.IndexedCoordinates
   110  	defer setMeta(resp, &out.QueryMeta)
   111  	if err := s.agent.RPC("Coordinate.Node", &args, &out); err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	result := filterCoordinates(req, out.Coordinates)
   116  	if len(result) == 0 {
   117  		resp.WriteHeader(http.StatusNotFound)
   118  		return nil, nil
   119  	}
   120  
   121  	return result, nil
   122  }
   123  
   124  func filterCoordinates(req *http.Request, in structs.Coordinates) structs.Coordinates {
   125  	out := structs.Coordinates{}
   126  
   127  	if in == nil {
   128  		return out
   129  	}
   130  
   131  	segment := ""
   132  	v, filterBySegment := req.URL.Query()["segment"]
   133  	if filterBySegment && len(v) > 0 {
   134  		segment = v[0]
   135  	}
   136  
   137  	for _, c := range in {
   138  		if filterBySegment && c.Segment != segment {
   139  			continue
   140  		}
   141  		out = append(out, c)
   142  	}
   143  	return out
   144  }
   145  
   146  // CoordinateUpdate inserts or updates the LAN coordinate of a node.
   147  func (s *HTTPServer) CoordinateUpdate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   148  	if s.checkCoordinateDisabled(resp, req) {
   149  		return nil, nil
   150  	}
   151  
   152  	args := structs.CoordinateUpdateRequest{}
   153  	if err := decodeBody(req, &args, nil); err != nil {
   154  		resp.WriteHeader(http.StatusBadRequest)
   155  		fmt.Fprintf(resp, "Request decode failed: %v", err)
   156  		return nil, nil
   157  	}
   158  	s.parseDC(req, &args.Datacenter)
   159  	s.parseToken(req, &args.Token)
   160  
   161  	var reply struct{}
   162  	if err := s.agent.RPC("Coordinate.Update", &args, &reply); err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	return nil, nil
   167  }