mirror of https://github.com/hashicorp/consul
agent: add /v1/coordianate/node/:node endpoint
This patch adds a /v1/coordinate/node/:node endpoint to get the network coordinates for a single node in the network. Since Consul Enterprise supports network segments it is still possible to receive mutiple entries for a single node - one per segment.pull/3622/head
parent
b31cfaaf2a
commit
ca9aac746f
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
)
|
||||
|
@ -85,22 +86,53 @@ func (s *HTTPServer) CoordinateNodes(resp http.ResponseWriter, req *http.Request
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Use empty list instead of nil.
|
||||
if out.Coordinates == nil {
|
||||
out.Coordinates = make(structs.Coordinates, 0)
|
||||
}
|
||||
|
||||
// Filter by segment if applicable
|
||||
if v, ok := req.URL.Query()["segment"]; ok && len(v) > 0 {
|
||||
segment := v[0]
|
||||
filtered := make(structs.Coordinates, 0)
|
||||
for _, coord := range out.Coordinates {
|
||||
if coord.Segment == segment {
|
||||
filtered = append(filtered, coord)
|
||||
}
|
||||
}
|
||||
out.Coordinates = filtered
|
||||
}
|
||||
|
||||
return out.Coordinates, nil
|
||||
return filterCoordinates(req, "", out.Coordinates), nil
|
||||
}
|
||||
|
||||
// CoordinateNode returns the LAN node in the given datacenter, along with
|
||||
// raw network coordinates.
|
||||
func (s *HTTPServer) CoordinateNode(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if req.Method != "GET" {
|
||||
return nil, MethodNotAllowedError{req.Method, []string{"GET"}}
|
||||
}
|
||||
|
||||
args := structs.DCSpecificRequest{}
|
||||
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var out structs.IndexedCoordinates
|
||||
defer setMeta(resp, &out.QueryMeta)
|
||||
if err := s.agent.RPC("Coordinate.ListNodes", &args, &out); err != nil {
|
||||
sort.Sort(&sorter{out.Coordinates})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := strings.TrimPrefix(req.URL.Path, "/v1/coordinate/node/")
|
||||
return filterCoordinates(req, node, out.Coordinates), nil
|
||||
}
|
||||
|
||||
func filterCoordinates(req *http.Request, node string, in structs.Coordinates) structs.Coordinates {
|
||||
out := structs.Coordinates{}
|
||||
|
||||
if in == nil {
|
||||
return out
|
||||
}
|
||||
|
||||
segment := ""
|
||||
v, filterBySegment := req.URL.Query()["segment"]
|
||||
if filterBySegment && len(v) > 0 {
|
||||
segment = v[0]
|
||||
}
|
||||
|
||||
for _, c := range in {
|
||||
if node != "" && c.Node != node {
|
||||
continue
|
||||
}
|
||||
if filterBySegment && c.Segment != segment {
|
||||
continue
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -140,3 +140,112 @@ func TestCoordinate_Nodes(t *testing.T) {
|
|||
t.Fatalf("bad: %v", coordinates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoordinate_Node(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := NewTestAgent(t.Name(), "")
|
||||
defer a.Shutdown()
|
||||
|
||||
// Make sure an empty list is non-nil.
|
||||
req, _ := http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.CoordinateNode(resp, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
coordinates := obj.(structs.Coordinates)
|
||||
if coordinates == nil || len(coordinates) != 0 {
|
||||
t.Fatalf("bad: %v", coordinates)
|
||||
}
|
||||
|
||||
// Register the nodes.
|
||||
nodes := []string{"foo", "bar"}
|
||||
for _, node := range nodes {
|
||||
req := structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: node,
|
||||
Address: "127.0.0.1",
|
||||
}
|
||||
var reply struct{}
|
||||
if err := a.RPC("Catalog.Register", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send some coordinates for a few nodes, waiting a little while for the
|
||||
// batch update to run.
|
||||
arg1 := structs.CoordinateUpdateRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "foo",
|
||||
Segment: "alpha",
|
||||
Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()),
|
||||
}
|
||||
var out struct{}
|
||||
if err := a.RPC("Coordinate.Update", &arg1, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
arg2 := structs.CoordinateUpdateRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "bar",
|
||||
Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()),
|
||||
}
|
||||
if err := a.RPC("Coordinate.Update", &arg2, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
// Query back and check the nodes are present and sorted correctly.
|
||||
req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil)
|
||||
resp = httptest.NewRecorder()
|
||||
obj, err = a.srv.CoordinateNode(resp, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
coordinates = obj.(structs.Coordinates)
|
||||
if len(coordinates) != 1 ||
|
||||
coordinates[0].Node != "foo" {
|
||||
t.Fatalf("bad: %v", coordinates)
|
||||
}
|
||||
|
||||
// Filter on a nonexistant node segment
|
||||
req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=nope", nil)
|
||||
resp = httptest.NewRecorder()
|
||||
obj, err = a.srv.CoordinateNode(resp, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
coordinates = obj.(structs.Coordinates)
|
||||
if len(coordinates) != 0 {
|
||||
t.Fatalf("bad: %v", coordinates)
|
||||
}
|
||||
|
||||
// Filter on a real node segment
|
||||
req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=alpha", nil)
|
||||
resp = httptest.NewRecorder()
|
||||
obj, err = a.srv.CoordinateNode(resp, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
coordinates = obj.(structs.Coordinates)
|
||||
if len(coordinates) != 1 || coordinates[0].Node != "foo" {
|
||||
t.Fatalf("bad: %v", coordinates)
|
||||
}
|
||||
|
||||
// Make sure the empty filter works
|
||||
req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=", nil)
|
||||
resp = httptest.NewRecorder()
|
||||
obj, err = a.srv.CoordinateNode(resp, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
coordinates = obj.(structs.Coordinates)
|
||||
if len(coordinates) != 0 {
|
||||
t.Fatalf("bad: %v", coordinates)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,9 +139,11 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
|
|||
if !s.agent.config.DisableCoordinates {
|
||||
handleFuncMetrics("/v1/coordinate/datacenters", s.wrap(s.CoordinateDatacenters))
|
||||
handleFuncMetrics("/v1/coordinate/nodes", s.wrap(s.CoordinateNodes))
|
||||
handleFuncMetrics("/v1/coordinate/node/", s.wrap(s.CoordinateNode))
|
||||
} else {
|
||||
handleFuncMetrics("/v1/coordinate/datacenters", s.wrap(coordinateDisabled))
|
||||
handleFuncMetrics("/v1/coordinate/nodes", s.wrap(coordinateDisabled))
|
||||
handleFuncMetrics("/v1/coordinate/node/", s.wrap(coordinateDisabled))
|
||||
}
|
||||
handleFuncMetrics("/v1/event/fire/", s.wrap(s.EventFire))
|
||||
handleFuncMetrics("/v1/event/list", s.wrap(s.EventList))
|
||||
|
|
|
@ -350,6 +350,7 @@ func TestHTTPAPI_MethodNotAllowed(t *testing.T) {
|
|||
{"GET", "/v1/catalog/services"},
|
||||
{"GET", "/v1/coordinate/datacenters"},
|
||||
{"GET", "/v1/coordinate/nodes"},
|
||||
{"GET", "/v1/coordinate/node/"},
|
||||
{"PUT", "/v1/event/fire/"},
|
||||
{"GET", "/v1/event/list"},
|
||||
{"GET", "/v1/health/checks/"},
|
||||
|
|
|
@ -71,7 +71,7 @@ In **Consul Enterprise**, this will include coordinates for user-added network
|
|||
areas as well, as indicated by the `AreaID`. Coordinates are only compatible
|
||||
within the same area.
|
||||
|
||||
## Read LAN Coordinates
|
||||
## Read LAN Coordinates for all nodes
|
||||
|
||||
This endpoint returns the LAN network coordinates for all nodes in a given
|
||||
datacenter.
|
||||
|
@ -122,3 +122,55 @@ $ curl \
|
|||
In **Consul Enterprise**, this may include multiple coordinates for the same node,
|
||||
each marked with a different `Segment`. Coordinates are only compatible within the same
|
||||
segment.
|
||||
|
||||
## Read LAN Coordinates for a node
|
||||
|
||||
This endpoint returns the LAN network coordinates for all nodes in a given
|
||||
datacenter.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
| `GET` | `/coordinate/node/:node` | `application/json` |
|
||||
|
||||
The table below shows this endpoint's support for
|
||||
[blocking queries](/api/index.html#blocking-queries),
|
||||
[consistency modes](/api/index.html#consistency-modes), and
|
||||
[required ACLs](/api/index.html#acls).
|
||||
|
||||
| Blocking Queries | Consistency Modes | ACL Required |
|
||||
| ---------------- | ----------------- | ------------ |
|
||||
| `YES` | `all` | `node:read` |
|
||||
|
||||
### Parameters
|
||||
|
||||
- `dc` `(string: "")` - Specifies the datacenter to query. This will default to
|
||||
the datacenter of the agent being queried. This is specified as part of the
|
||||
URL as a query parameter.
|
||||
|
||||
### Sample Request
|
||||
|
||||
```text
|
||||
$ curl \
|
||||
https://consul.rocks/v1/coordinate/node/agent-one
|
||||
```
|
||||
|
||||
### Sample Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"Node": "agent-one",
|
||||
"Segment": "",
|
||||
"Coord": {
|
||||
"Adjustment": 0,
|
||||
"Error": 1.5,
|
||||
"Height": 0,
|
||||
"Vec": [0, 0, 0, 0, 0, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
In **Consul Enterprise**, this may include multiple coordinates for the same node,
|
||||
each marked with a different `Segment`. Coordinates are only compatible within the same
|
||||
segment.
|
||||
|
|
Loading…
Reference in New Issue