mirror of
https://github.com/status-im/consul.git
synced 2025-01-10 22:06:20 +00:00
Adds distance sorting to health endpoint. Cleans up unit tests.
This commit is contained in:
parent
497f6782af
commit
9caa5b3653
@ -9,6 +9,7 @@ import (
|
|||||||
func (s *HTTPServer) HealthChecksInState(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) HealthChecksInState(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Set default DC
|
// Set default DC
|
||||||
args := structs.ChecksInStateRequest{}
|
args := structs.ChecksInStateRequest{}
|
||||||
|
s.parseSource(req, &args.Source)
|
||||||
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -57,6 +58,7 @@ func (s *HTTPServer) HealthNodeChecks(resp http.ResponseWriter, req *http.Reques
|
|||||||
func (s *HTTPServer) HealthServiceChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) HealthServiceChecks(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Set default DC
|
// Set default DC
|
||||||
args := structs.ServiceSpecificRequest{}
|
args := structs.ServiceSpecificRequest{}
|
||||||
|
s.parseSource(req, &args.Source)
|
||||||
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -81,6 +83,7 @@ func (s *HTTPServer) HealthServiceChecks(resp http.ResponseWriter, req *http.Req
|
|||||||
func (s *HTTPServer) HealthServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) HealthServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Set default DC
|
// Set default DC
|
||||||
args := structs.ServiceSpecificRequest{}
|
args := structs.ServiceSpecificRequest{}
|
||||||
|
s.parseSource(req, &args.Source)
|
||||||
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,16 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hashicorp/consul/consul/structs"
|
|
||||||
"github.com/hashicorp/consul/testutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/consul/structs"
|
||||||
|
"github.com/hashicorp/consul/testutil"
|
||||||
|
"github.com/hashicorp/serf/coordinate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHealthChecksInState(t *testing.T) {
|
func TestHealthChecksInState(t *testing.T) {
|
||||||
@ -38,6 +41,87 @@ func TestHealthChecksInState(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHealthChecksInState_DistanceSort(t *testing.T) {
|
||||||
|
dir, srv := makeHTTPServer(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer srv.Shutdown()
|
||||||
|
defer srv.agent.Shutdown()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
|
||||||
|
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Node: "bar",
|
||||||
|
Name: "node check",
|
||||||
|
Status: structs.HealthCritical,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Node, args.Check.Node = "foo", "foo"
|
||||||
|
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/v1/health/state/critical?dc=dc1&near=foo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.HealthChecksInState(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
assertIndex(t, resp)
|
||||||
|
nodes := obj.(structs.HealthChecks)
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[0].Node != "bar" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[1].Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an update for the node and wait for it to get applied.
|
||||||
|
arg := structs.CoordinateUpdateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()),
|
||||||
|
}
|
||||||
|
if err := srv.agent.RPC("Coordinate.Update", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// Query again and now foo should have moved to the front of the line.
|
||||||
|
resp = httptest.NewRecorder()
|
||||||
|
obj, err = srv.HealthChecksInState(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
assertIndex(t, resp)
|
||||||
|
nodes = obj.(structs.HealthChecks)
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[0].Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[1].Node != "bar" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHealthNodeChecks(t *testing.T) {
|
func TestHealthNodeChecks(t *testing.T) {
|
||||||
dir, srv := makeHTTPServer(t)
|
dir, srv := makeHTTPServer(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
@ -110,6 +194,92 @@ func TestHealthServiceChecks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHealthServiceChecks_DistanceSort(t *testing.T) {
|
||||||
|
dir, srv := makeHTTPServer(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer srv.Shutdown()
|
||||||
|
defer srv.agent.Shutdown()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
|
||||||
|
|
||||||
|
// Create a service check
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
ID: "test",
|
||||||
|
Service: "test",
|
||||||
|
},
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Node: "bar",
|
||||||
|
Name: "test check",
|
||||||
|
ServiceID: "test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Node, args.Check.Node = "foo", "foo"
|
||||||
|
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/v1/health/checks/test?dc=dc1&near=foo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.HealthServiceChecks(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
assertIndex(t, resp)
|
||||||
|
nodes := obj.(structs.HealthChecks)
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("bad: %v", obj)
|
||||||
|
}
|
||||||
|
if nodes[0].Node != "bar" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[1].Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an update for the node and wait for it to get applied.
|
||||||
|
arg := structs.CoordinateUpdateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()),
|
||||||
|
}
|
||||||
|
if err := srv.agent.RPC("Coordinate.Update", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// Query again and now foo should have moved to the front of the line.
|
||||||
|
resp = httptest.NewRecorder()
|
||||||
|
obj, err = srv.HealthServiceChecks(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
assertIndex(t, resp)
|
||||||
|
nodes = obj.(structs.HealthChecks)
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("bad: %v", obj)
|
||||||
|
}
|
||||||
|
if nodes[0].Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[1].Node != "bar" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHealthServiceNodes(t *testing.T) {
|
func TestHealthServiceNodes(t *testing.T) {
|
||||||
dir, srv := makeHTTPServer(t)
|
dir, srv := makeHTTPServer(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
@ -138,6 +308,92 @@ func TestHealthServiceNodes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHealthServiceNodes_DistanceSort(t *testing.T) {
|
||||||
|
dir, srv := makeHTTPServer(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer srv.Shutdown()
|
||||||
|
defer srv.agent.Shutdown()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
|
||||||
|
|
||||||
|
// Create a service check
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
ID: "test",
|
||||||
|
Service: "test",
|
||||||
|
},
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Node: "bar",
|
||||||
|
Name: "test check",
|
||||||
|
ServiceID: "test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Node, args.Check.Node = "foo", "foo"
|
||||||
|
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/v1/health/service/test?dc=dc1&near=foo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.HealthServiceNodes(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
assertIndex(t, resp)
|
||||||
|
nodes := obj.(structs.CheckServiceNodes)
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("bad: %v", obj)
|
||||||
|
}
|
||||||
|
if nodes[0].Node.Node != "bar" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[1].Node.Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an update for the node and wait for it to get applied.
|
||||||
|
arg := structs.CoordinateUpdateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Coord: coordinate.NewCoordinate(coordinate.DefaultConfig()),
|
||||||
|
}
|
||||||
|
if err := srv.agent.RPC("Coordinate.Update", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// Query again and now foo should have moved to the front of the line.
|
||||||
|
resp = httptest.NewRecorder()
|
||||||
|
obj, err = srv.HealthServiceNodes(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
assertIndex(t, resp)
|
||||||
|
nodes = obj.(structs.CheckServiceNodes)
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("bad: %v", obj)
|
||||||
|
}
|
||||||
|
if nodes[0].Node.Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[1].Node.Node != "bar" {
|
||||||
|
t.Fatalf("bad: %v", nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHealthServiceNodes_PassingFilter(t *testing.T) {
|
func TestHealthServiceNodes_PassingFilter(t *testing.T) {
|
||||||
dir, srv := makeHTTPServer(t)
|
dir, srv := makeHTTPServer(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
@ -294,24 +294,6 @@ func TestCatalogListDatacenters_DistanceSort(t *testing.T) {
|
|||||||
if out[2] != "dc2" {
|
if out[2] != "dc2" {
|
||||||
t.Fatalf("bad: %v", out)
|
t.Fatalf("bad: %v", out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we get the natural order if coordinates are disabled.
|
|
||||||
s1.config.DisableCoordinates = true
|
|
||||||
if err := client.Call("Catalog.ListDatacenters", struct{}{}, &out); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
if len(out) != 3 {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out[0] != "acdc" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out[1] != "dc1" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out[2] != "dc2" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCatalogListNodes(t *testing.T) {
|
func TestCatalogListNodes(t *testing.T) {
|
||||||
@ -529,7 +511,6 @@ func TestCatalogListNodes_DistanceSort(t *testing.T) {
|
|||||||
client := rpcClient(t, s1)
|
client := rpcClient(t, s1)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Add three nodes.
|
|
||||||
testutil.WaitForLeader(t, client.Call, "dc1")
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
if err := s1.fsm.State().EnsureNode(1, structs.Node{"aaa", "127.0.0.1"}); err != nil {
|
if err := s1.fsm.State().EnsureNode(1, structs.Node{"aaa", "127.0.0.1"}); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
@ -609,34 +590,6 @@ func TestCatalogListNodes_DistanceSort(t *testing.T) {
|
|||||||
if out.Nodes[4].Node != s1.config.NodeName {
|
if out.Nodes[4].Node != s1.config.NodeName {
|
||||||
t.Fatalf("bad: %v", out)
|
t.Fatalf("bad: %v", out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we get the natural order if coordinates are disabled.
|
|
||||||
s1.config.DisableCoordinates = true
|
|
||||||
args = structs.DCSpecificRequest{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
Source: structs.QuerySource{Datacenter: "dc1", Node: "foo"},
|
|
||||||
}
|
|
||||||
testutil.WaitForResult(func() (bool, error) {
|
|
||||||
client.Call("Catalog.ListNodes", &args, &out)
|
|
||||||
return len(out.Nodes) == 5, nil
|
|
||||||
}, func(err error) {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
})
|
|
||||||
if out.Nodes[0].Node != "aaa" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.Nodes[1].Node != "bar" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.Nodes[2].Node != "baz" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.Nodes[3].Node != "foo" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.Nodes[4].Node != s1.config.NodeName {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkCatalogListNodes(t *testing.B) {
|
func BenchmarkCatalogListNodes(t *testing.B) {
|
||||||
@ -982,32 +935,6 @@ func TestCatalogListServiceNodes_DistanceSort(t *testing.T) {
|
|||||||
if out.ServiceNodes[3].Node != "aaa" {
|
if out.ServiceNodes[3].Node != "aaa" {
|
||||||
t.Fatalf("bad: %v", out)
|
t.Fatalf("bad: %v", out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we get the natural order if coordinates are disabled.
|
|
||||||
s1.config.DisableCoordinates = true
|
|
||||||
args = structs.ServiceSpecificRequest{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
ServiceName: "db",
|
|
||||||
Source: structs.QuerySource{Datacenter: "dc1", Node: "foo"},
|
|
||||||
}
|
|
||||||
if err := client.Call("Catalog.ServiceNodes", &args, &out); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
if len(out.ServiceNodes) != 4 {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.ServiceNodes[0].Node != "aaa" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.ServiceNodes[1].Node != "foo" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.ServiceNodes[2].Node != "bar" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
if out.ServiceNodes[3].Node != "baz" {
|
|
||||||
t.Fatalf("bad: %v", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCatalogNodeServices(t *testing.T) {
|
func TestCatalogNodeServices(t *testing.T) {
|
||||||
|
@ -30,7 +30,10 @@ func (h *Health) ChecksInState(args *structs.ChecksInStateRequest,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply.Index, reply.HealthChecks = index, checks
|
reply.Index, reply.HealthChecks = index, checks
|
||||||
return h.srv.filterACL(args.Token, reply)
|
if err := h.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.srv.sortNodesByDistanceFrom(args.Source, reply.HealthChecks)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +85,10 @@ func (h *Health) ServiceChecks(args *structs.ServiceSpecificRequest,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply.Index, reply.HealthChecks = index, checks
|
reply.Index, reply.HealthChecks = index, checks
|
||||||
return h.srv.filterACL(args.Token, reply)
|
if err := h.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.srv.sortNodesByDistanceFrom(args.Source, reply.HealthChecks)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,8 +121,12 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Index, reply.Nodes = index, nodes
|
reply.Index, reply.Nodes = index, nodes
|
||||||
return h.srv.filterACL(args.Token, reply)
|
if err := h.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.srv.sortNodesByDistanceFrom(args.Source, reply.Nodes)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Provide some metrics
|
// Provide some metrics
|
||||||
|
@ -3,6 +3,7 @@ package consul
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/consul/structs"
|
"github.com/hashicorp/consul/consul/structs"
|
||||||
"github.com/hashicorp/consul/testutil"
|
"github.com/hashicorp/consul/testutil"
|
||||||
@ -55,6 +56,83 @@ func TestHealth_ChecksInState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHealth_ChecksInState_DistanceSort(t *testing.T) {
|
||||||
|
dir1, s1 := testServer(t)
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
client := rpcClient(t, s1)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
if err := s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.2"}); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if err := s1.fsm.State().EnsureNode(2, structs.Node{"bar", "127.0.0.3"}); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
updates := []structs.Coordinate{
|
||||||
|
{"foo", generateCoordinate(1 * time.Millisecond)},
|
||||||
|
{"bar", generateCoordinate(2 * time.Millisecond)},
|
||||||
|
}
|
||||||
|
if err := s1.fsm.State().CoordinateBatchUpdate(3, updates); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg := structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Name: "memory utilization",
|
||||||
|
Status: structs.HealthPassing,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := client.Call("Catalog.Register", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg.Node = "bar"
|
||||||
|
if err := client.Call("Catalog.Register", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query relative to foo to make sure it shows up first in the list.
|
||||||
|
var out2 structs.IndexedHealthChecks
|
||||||
|
inState := structs.ChecksInStateRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
State: structs.HealthPassing,
|
||||||
|
Source: structs.QuerySource{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := client.Call("Health.ChecksInState", &inState, &out2); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
checks := out2.HealthChecks
|
||||||
|
if len(checks) != 3 {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
if checks[0].Node != "foo" {
|
||||||
|
t.Fatalf("Bad: %v", checks[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now query relative to bar to make sure it shows up first.
|
||||||
|
inState.Source.Node = "bar"
|
||||||
|
if err := client.Call("Health.ChecksInState", &inState, &out2); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
checks = out2.HealthChecks
|
||||||
|
if len(checks) != 3 {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
if checks[0].Node != "bar" {
|
||||||
|
t.Fatalf("Bad: %v", checks[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHealth_NodeChecks(t *testing.T) {
|
func TestHealth_NodeChecks(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
@ -142,6 +220,94 @@ func TestHealth_ServiceChecks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHealth_ServiceChecks_DistanceSort(t *testing.T) {
|
||||||
|
dir1, s1 := testServer(t)
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
client := rpcClient(t, s1)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
if err := s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.2"}); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if err := s1.fsm.State().EnsureNode(2, structs.Node{"bar", "127.0.0.3"}); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
updates := []structs.Coordinate{
|
||||||
|
{"foo", generateCoordinate(1 * time.Millisecond)},
|
||||||
|
{"bar", generateCoordinate(2 * time.Millisecond)},
|
||||||
|
}
|
||||||
|
if err := s1.fsm.State().CoordinateBatchUpdate(3, updates); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg := structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
ID: "db",
|
||||||
|
Service: "db",
|
||||||
|
},
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Name: "db connect",
|
||||||
|
Status: structs.HealthPassing,
|
||||||
|
ServiceID: "db",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := client.Call("Catalog.Register", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg.Node = "bar"
|
||||||
|
if err := client.Call("Catalog.Register", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query relative to foo to make sure it shows up first in the list.
|
||||||
|
var out2 structs.IndexedHealthChecks
|
||||||
|
node := structs.ServiceSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
ServiceName: "db",
|
||||||
|
Source: structs.QuerySource{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := client.Call("Health.ServiceChecks", &node, &out2); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
checks := out2.HealthChecks
|
||||||
|
if len(checks) != 2 {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
if checks[0].Node != "foo" {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
if checks[1].Node != "bar" {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now query relative to bar to make sure it shows up first.
|
||||||
|
node.Source.Node = "bar"
|
||||||
|
if err := client.Call("Health.ServiceChecks", &node, &out2); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
checks = out2.HealthChecks
|
||||||
|
if len(checks) != 2 {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
if checks[0].Node != "bar" {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
if checks[1].Node != "foo" {
|
||||||
|
t.Fatalf("Bad: %v", checks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHealth_ServiceNodes(t *testing.T) {
|
func TestHealth_ServiceNodes(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
@ -225,6 +391,94 @@ func TestHealth_ServiceNodes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHealth_ServiceNodes_DistanceSort(t *testing.T) {
|
||||||
|
dir1, s1 := testServer(t)
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
client := rpcClient(t, s1)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
if err := s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.2"}); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if err := s1.fsm.State().EnsureNode(2, structs.Node{"bar", "127.0.0.3"}); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
updates := []structs.Coordinate{
|
||||||
|
{"foo", generateCoordinate(1 * time.Millisecond)},
|
||||||
|
{"bar", generateCoordinate(2 * time.Millisecond)},
|
||||||
|
}
|
||||||
|
if err := s1.fsm.State().CoordinateBatchUpdate(3, updates); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg := structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
ID: "db",
|
||||||
|
Service: "db",
|
||||||
|
},
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Name: "db connect",
|
||||||
|
Status: structs.HealthPassing,
|
||||||
|
ServiceID: "db",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out struct{}
|
||||||
|
if err := client.Call("Catalog.Register", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arg.Node = "bar"
|
||||||
|
if err := client.Call("Catalog.Register", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query relative to foo to make sure it shows up first in the list.
|
||||||
|
var out2 structs.IndexedCheckServiceNodes
|
||||||
|
req := structs.ServiceSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
ServiceName: "db",
|
||||||
|
Source: structs.QuerySource{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := client.Call("Health.ServiceNodes", &req, &out2); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
nodes := out2.Nodes
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("Bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[0].Node.Node != "foo" {
|
||||||
|
t.Fatalf("Bad: %v", nodes[0])
|
||||||
|
}
|
||||||
|
if nodes[1].Node.Node != "bar" {
|
||||||
|
t.Fatalf("Bad: %v", nodes[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now query relative to bar to make sure it shows up first.
|
||||||
|
req.Source.Node = "bar"
|
||||||
|
if err := client.Call("Health.ServiceNodes", &req, &out2); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
nodes = out2.Nodes
|
||||||
|
if len(nodes) != 2 {
|
||||||
|
t.Fatalf("Bad: %v", nodes)
|
||||||
|
}
|
||||||
|
if nodes[0].Node.Node != "bar" {
|
||||||
|
t.Fatalf("Bad: %v", nodes[0])
|
||||||
|
}
|
||||||
|
if nodes[1].Node.Node != "foo" {
|
||||||
|
t.Fatalf("Bad: %v", nodes[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHealth_NodeChecks_FilterACL(t *testing.T) {
|
func TestHealth_NodeChecks_FilterACL(t *testing.T) {
|
||||||
dir, token, srv, codec := testACLFilterServer(t)
|
dir, token, srv, codec := testACLFilterServer(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
@ -98,6 +98,84 @@ func (n *serviceNodeSorter) Less(i, j int) bool {
|
|||||||
return n.Vec[i] < n.Vec[j]
|
return n.Vec[i] < n.Vec[j]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serviceNodeSorter takes a list of health checks and a parallel vector of
|
||||||
|
// distances and implements sort.Interface, keeping both structures coherent and
|
||||||
|
// sorting by distance.
|
||||||
|
type healthCheckSorter struct {
|
||||||
|
Checks structs.HealthChecks
|
||||||
|
Vec []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHealthCheckSorter returns a new sorter for the given source coordinate and
|
||||||
|
// set of health checks with nodes.
|
||||||
|
func (s *Server) newHealthCheckSorter(c *coordinate.Coordinate, checks structs.HealthChecks) (sort.Interface, error) {
|
||||||
|
state := s.fsm.State()
|
||||||
|
vec := make([]float64, len(checks))
|
||||||
|
for i, check := range checks {
|
||||||
|
_, coord, err := state.CoordinateGet(check.Node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vec[i] = computeDistance(c, coord)
|
||||||
|
}
|
||||||
|
return &healthCheckSorter{checks, vec}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// See sort.Interface.
|
||||||
|
func (n *healthCheckSorter) Len() int {
|
||||||
|
return len(n.Checks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See sort.Interface.
|
||||||
|
func (n *healthCheckSorter) Swap(i, j int) {
|
||||||
|
n.Checks[i], n.Checks[j] = n.Checks[j], n.Checks[i]
|
||||||
|
n.Vec[i], n.Vec[j] = n.Vec[j], n.Vec[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// See sort.Interface.
|
||||||
|
func (n *healthCheckSorter) Less(i, j int) bool {
|
||||||
|
return n.Vec[i] < n.Vec[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkServiceNodeSorter takes a list of service nodes and a parallel vector of
|
||||||
|
// distances and implements sort.Interface, keeping both structures coherent and
|
||||||
|
// sorting by distance.
|
||||||
|
type checkServiceNodeSorter struct {
|
||||||
|
Nodes structs.CheckServiceNodes
|
||||||
|
Vec []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCheckServiceNodeSorter returns a new sorter for the given source coordinate
|
||||||
|
// and set of nodes with health checks.
|
||||||
|
func (s *Server) newCheckServiceNodeSorter(c *coordinate.Coordinate, nodes structs.CheckServiceNodes) (sort.Interface, error) {
|
||||||
|
state := s.fsm.State()
|
||||||
|
vec := make([]float64, len(nodes))
|
||||||
|
for i, node := range nodes {
|
||||||
|
_, coord, err := state.CoordinateGet(node.Node.Node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vec[i] = computeDistance(c, coord)
|
||||||
|
}
|
||||||
|
return &checkServiceNodeSorter{nodes, vec}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// See sort.Interface.
|
||||||
|
func (n *checkServiceNodeSorter) Len() int {
|
||||||
|
return len(n.Nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See sort.Interface.
|
||||||
|
func (n *checkServiceNodeSorter) Swap(i, j int) {
|
||||||
|
n.Nodes[i], n.Nodes[j] = n.Nodes[j], n.Nodes[i]
|
||||||
|
n.Vec[i], n.Vec[j] = n.Vec[j], n.Vec[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// See sort.Interface.
|
||||||
|
func (n *checkServiceNodeSorter) Less(i, j int) bool {
|
||||||
|
return n.Vec[i] < n.Vec[j]
|
||||||
|
}
|
||||||
|
|
||||||
// newSorterByDistanceFrom returns a sorter for the given type.
|
// newSorterByDistanceFrom returns a sorter for the given type.
|
||||||
func (s *Server) newSorterByDistanceFrom(c *coordinate.Coordinate, subj interface{}) (sort.Interface, error) {
|
func (s *Server) newSorterByDistanceFrom(c *coordinate.Coordinate, subj interface{}) (sort.Interface, error) {
|
||||||
switch v := subj.(type) {
|
switch v := subj.(type) {
|
||||||
@ -105,6 +183,10 @@ func (s *Server) newSorterByDistanceFrom(c *coordinate.Coordinate, subj interfac
|
|||||||
return s.newNodeSorter(c, v)
|
return s.newNodeSorter(c, v)
|
||||||
case structs.ServiceNodes:
|
case structs.ServiceNodes:
|
||||||
return s.newServiceNodeSorter(c, v)
|
return s.newServiceNodeSorter(c, v)
|
||||||
|
case structs.HealthChecks:
|
||||||
|
return s.newHealthCheckSorter(c, v)
|
||||||
|
case structs.CheckServiceNodes:
|
||||||
|
return s.newCheckServiceNodeSorter(c, v)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("Unhandled type passed to newSorterByDistanceFrom: %#v", subj))
|
panic(fmt.Errorf("Unhandled type passed to newSorterByDistanceFrom: %#v", subj))
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,32 @@ func verifyServiceNodeSort(t *testing.T, nodes structs.ServiceNodes, expected st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verifyHealthCheckSort makes sure the order of the nodes in the slice is the
|
||||||
|
// same as the expected order, expressed as a comma-separated string.
|
||||||
|
func verifyHealthCheckSort(t *testing.T, checks structs.HealthChecks, expected string) {
|
||||||
|
vec := make([]string, len(checks))
|
||||||
|
for i, check := range checks {
|
||||||
|
vec[i] = check.Node
|
||||||
|
}
|
||||||
|
actual := strings.Join(vec, ",")
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad sort: %s != %s", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyCheckServiceNodeSort makes sure the order of the nodes in the slice is
|
||||||
|
// the same as the expected order, expressed as a comma-separated string.
|
||||||
|
func verifyCheckServiceNodeSort(t *testing.T, nodes structs.CheckServiceNodes, expected string) {
|
||||||
|
vec := make([]string, len(nodes))
|
||||||
|
for i, node := range nodes {
|
||||||
|
vec[i] = node.Node.Node
|
||||||
|
}
|
||||||
|
actual := strings.Join(vec, ",")
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad sort: %s != %s", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// seedCoordinates uses the client to set up a set of nodes with a specific
|
// seedCoordinates uses the client to set up a set of nodes with a specific
|
||||||
// set of distances from the origin. We also include the server so that we
|
// set of distances from the origin. We also include the server so that we
|
||||||
// can wait for the coordinates to get committed to the Raft log.
|
// can wait for the coordinates to get committed to the Raft log.
|
||||||
@ -97,7 +123,7 @@ func seedCoordinates(t *testing.T, client *rpc.Client, server *Server) {
|
|||||||
time.Sleep(2 * server.config.CoordinateUpdatePeriod)
|
time.Sleep(2 * server.config.CoordinateUpdatePeriod)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRtt_sortNodesByDistanceFrom_Nodes(t *testing.T) {
|
func TestRtt_sortNodesByDistanceFrom(t *testing.T) {
|
||||||
dir, server := testServer(t)
|
dir, server := testServer(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
defer server.Shutdown()
|
defer server.Shutdown()
|
||||||
@ -139,9 +165,48 @@ func TestRtt_sortNodesByDistanceFrom_Nodes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
verifyNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5")
|
verifyNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5")
|
||||||
|
|
||||||
|
// Set source to legit values relative to node1 but disable coordinates.
|
||||||
|
source.Node = "node1"
|
||||||
|
source.Datacenter = "dc1"
|
||||||
|
server.config.DisableCoordinates = true
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5")
|
||||||
|
|
||||||
|
// Now enable coordinates and sort relative to node1, note that apple
|
||||||
|
// doesn't have any seeded coordinate info so it should end up at the
|
||||||
|
// end, despite its lexical hegemony.
|
||||||
|
server.config.DisableCoordinates = false
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyNodeSort(t, nodes, "node1,node4,node5,node2,node3,apple")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRtt_sortNodesByDistanceFrom_Nodes(t *testing.T) {
|
||||||
|
dir, server := testServer(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer server.Shutdown()
|
||||||
|
|
||||||
|
client := rpcClient(t, server)
|
||||||
|
defer client.Close()
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
seedCoordinates(t, client, server)
|
||||||
|
|
||||||
|
nodes := structs.Nodes{
|
||||||
|
structs.Node{Node: "apple"},
|
||||||
|
structs.Node{Node: "node1"},
|
||||||
|
structs.Node{Node: "node2"},
|
||||||
|
structs.Node{Node: "node3"},
|
||||||
|
structs.Node{Node: "node4"},
|
||||||
|
structs.Node{Node: "node5"},
|
||||||
|
}
|
||||||
|
|
||||||
// Now sort relative to node1, note that apple doesn't have any
|
// Now sort relative to node1, note that apple doesn't have any
|
||||||
// seeded coordinate info so it should end up at the end, despite
|
// seeded coordinate info so it should end up at the end, despite
|
||||||
// its lexical hegemony.
|
// its lexical hegemony.
|
||||||
|
var source structs.QuerySource
|
||||||
source.Node = "node1"
|
source.Node = "node1"
|
||||||
source.Datacenter = "dc1"
|
source.Datacenter = "dc1"
|
||||||
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
||||||
@ -187,32 +252,10 @@ func TestRtt_sortNodesByDistanceFrom_ServiceNodes(t *testing.T) {
|
|||||||
structs.ServiceNode{Node: "node5"},
|
structs.ServiceNode{Node: "node5"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// The zero value for the source should not trigger any sorting.
|
|
||||||
var source structs.QuerySource
|
|
||||||
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
verifyServiceNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5")
|
|
||||||
|
|
||||||
// Same for a source in some other DC.
|
|
||||||
source.Node = "node1"
|
|
||||||
source.Datacenter = "dc2"
|
|
||||||
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
verifyServiceNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5")
|
|
||||||
|
|
||||||
// Same for a source node in our DC that we have no coordinate for.
|
|
||||||
source.Node = "apple"
|
|
||||||
source.Datacenter = "dc1"
|
|
||||||
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
verifyServiceNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5")
|
|
||||||
|
|
||||||
// Now sort relative to node1, note that apple doesn't have any
|
// Now sort relative to node1, note that apple doesn't have any
|
||||||
// seeded coordinate info so it should end up at the end, despite
|
// seeded coordinate info so it should end up at the end, despite
|
||||||
// its lexical hegemony.
|
// its lexical hegemony.
|
||||||
|
var source structs.QuerySource
|
||||||
source.Node = "node1"
|
source.Node = "node1"
|
||||||
source.Datacenter = "dc1"
|
source.Datacenter = "dc1"
|
||||||
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
||||||
@ -239,6 +282,104 @@ func TestRtt_sortNodesByDistanceFrom_ServiceNodes(t *testing.T) {
|
|||||||
verifyServiceNodeSort(t, nodes, "node2,node3,node5,node4,node1,apple")
|
verifyServiceNodeSort(t, nodes, "node2,node3,node5,node4,node1,apple")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRtt_sortNodesByDistanceFrom_HealthChecks(t *testing.T) {
|
||||||
|
dir, server := testServer(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer server.Shutdown()
|
||||||
|
|
||||||
|
client := rpcClient(t, server)
|
||||||
|
defer client.Close()
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
seedCoordinates(t, client, server)
|
||||||
|
|
||||||
|
checks := structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{Node: "apple"},
|
||||||
|
&structs.HealthCheck{Node: "node1"},
|
||||||
|
&structs.HealthCheck{Node: "node2"},
|
||||||
|
&structs.HealthCheck{Node: "node3"},
|
||||||
|
&structs.HealthCheck{Node: "node4"},
|
||||||
|
&structs.HealthCheck{Node: "node5"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now sort relative to node1, note that apple doesn't have any
|
||||||
|
// seeded coordinate info so it should end up at the end, despite
|
||||||
|
// its lexical hegemony.
|
||||||
|
var source structs.QuerySource
|
||||||
|
source.Node = "node1"
|
||||||
|
source.Datacenter = "dc1"
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, checks); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyHealthCheckSort(t, checks, "node1,node4,node5,node2,node3,apple")
|
||||||
|
|
||||||
|
// Try another sort from node2. Note that node5 and node3 are the
|
||||||
|
// same distance away so the stable sort should preserve the order
|
||||||
|
// they were in from the previous sort.
|
||||||
|
source.Node = "node2"
|
||||||
|
source.Datacenter = "dc1"
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, checks); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyHealthCheckSort(t, checks, "node2,node5,node3,node4,node1,apple")
|
||||||
|
|
||||||
|
// Let's exercise the stable sort explicitly to make sure we didn't
|
||||||
|
// just get lucky.
|
||||||
|
checks[1], checks[2] = checks[2], checks[1]
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, checks); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyHealthCheckSort(t, checks, "node2,node3,node5,node4,node1,apple")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRtt_sortNodesByDistanceFrom_CheckServiceNodes(t *testing.T) {
|
||||||
|
dir, server := testServer(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer server.Shutdown()
|
||||||
|
|
||||||
|
client := rpcClient(t, server)
|
||||||
|
defer client.Close()
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
seedCoordinates(t, client, server)
|
||||||
|
|
||||||
|
nodes := structs.CheckServiceNodes{
|
||||||
|
structs.CheckServiceNode{Node: structs.Node{Node: "apple"}},
|
||||||
|
structs.CheckServiceNode{Node: structs.Node{Node: "node1"}},
|
||||||
|
structs.CheckServiceNode{Node: structs.Node{Node: "node2"}},
|
||||||
|
structs.CheckServiceNode{Node: structs.Node{Node: "node3"}},
|
||||||
|
structs.CheckServiceNode{Node: structs.Node{Node: "node4"}},
|
||||||
|
structs.CheckServiceNode{Node: structs.Node{Node: "node5"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now sort relative to node1, note that apple doesn't have any
|
||||||
|
// seeded coordinate info so it should end up at the end, despite
|
||||||
|
// its lexical hegemony.
|
||||||
|
var source structs.QuerySource
|
||||||
|
source.Node = "node1"
|
||||||
|
source.Datacenter = "dc1"
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyCheckServiceNodeSort(t, nodes, "node1,node4,node5,node2,node3,apple")
|
||||||
|
|
||||||
|
// Try another sort from node2. Note that node5 and node3 are the
|
||||||
|
// same distance away so the stable sort should preserve the order
|
||||||
|
// they were in from the previous sort.
|
||||||
|
source.Node = "node2"
|
||||||
|
source.Datacenter = "dc1"
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyCheckServiceNodeSort(t, nodes, "node2,node5,node3,node4,node1,apple")
|
||||||
|
|
||||||
|
// Let's exercise the stable sort explicitly to make sure we didn't
|
||||||
|
// just get lucky.
|
||||||
|
nodes[1], nodes[2] = nodes[2], nodes[1]
|
||||||
|
if err := server.sortNodesByDistanceFrom(source, nodes); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
verifyCheckServiceNodeSort(t, nodes, "node2,node3,node5,node4,node1,apple")
|
||||||
|
}
|
||||||
|
|
||||||
// mockNodeMap is keyed by node name and the values are the coordinates of the
|
// mockNodeMap is keyed by node name and the values are the coordinates of the
|
||||||
// node.
|
// node.
|
||||||
type mockNodeMap map[string]*coordinate.Coordinate
|
type mockNodeMap map[string]*coordinate.Coordinate
|
||||||
|
@ -203,7 +203,7 @@ func (r *DCSpecificRequest) RequestDatacenter() string {
|
|||||||
return r.Datacenter
|
return r.Datacenter
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceSpecificRequest is used to query about a specific node
|
// ServiceSpecificRequest is used to query about a specific service
|
||||||
type ServiceSpecificRequest struct {
|
type ServiceSpecificRequest struct {
|
||||||
Datacenter string
|
Datacenter string
|
||||||
ServiceName string
|
ServiceName string
|
||||||
@ -232,6 +232,7 @@ func (r *NodeSpecificRequest) RequestDatacenter() string {
|
|||||||
type ChecksInStateRequest struct {
|
type ChecksInStateRequest struct {
|
||||||
Datacenter string
|
Datacenter string
|
||||||
State string
|
State string
|
||||||
|
Source QuerySource
|
||||||
QueryOptions
|
QueryOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +357,7 @@ type HealthCheck struct {
|
|||||||
type HealthChecks []*HealthCheck
|
type HealthChecks []*HealthCheck
|
||||||
|
|
||||||
// CheckServiceNode is used to provide the node, its service
|
// CheckServiceNode is used to provide the node, its service
|
||||||
// definition, as well as a HealthCheck that is associated
|
// definition, as well as a HealthCheck that is associated.
|
||||||
type CheckServiceNode struct {
|
type CheckServiceNode struct {
|
||||||
Node *Node
|
Node *Node
|
||||||
Service *NodeService
|
Service *NodeService
|
||||||
|
@ -70,6 +70,11 @@ This endpoint is hit with a GET and returns the checks associated with
|
|||||||
the service provided on the path. By default, the datacenter of the agent is queried;
|
the service provided on the path. By default, the datacenter of the agent is queried;
|
||||||
however, the dc can be provided using the "?dc=" query parameter.
|
however, the dc can be provided using the "?dc=" query parameter.
|
||||||
|
|
||||||
|
Adding the optional "?near=" parameter with a node name will sort
|
||||||
|
the node list in ascending order based on the estimated round trip
|
||||||
|
time from that node. Passing "?near=self" will use the agent's local
|
||||||
|
node for the sort.
|
||||||
|
|
||||||
It returns a JSON body like this:
|
It returns a JSON body like this:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@ -95,6 +100,11 @@ This endpoint is hit with a GET and returns the nodes providing
|
|||||||
the service indicated on the path. By default, the datacenter of the agent is queried;
|
the service indicated on the path. By default, the datacenter of the agent is queried;
|
||||||
however, the dc can be provided using the "?dc=" query parameter.
|
however, the dc can be provided using the "?dc=" query parameter.
|
||||||
|
|
||||||
|
Adding the optional "?near=" parameter with a node name will sort
|
||||||
|
the node list in ascending order based on the estimated round trip
|
||||||
|
time from that node. Passing "?near=self" will use the agent's local
|
||||||
|
node for the sort.
|
||||||
|
|
||||||
By default, all nodes matching the service are returned. The list can be filtered
|
By default, all nodes matching the service are returned. The list can be filtered
|
||||||
by tag using the "?tag=" query parameter.
|
by tag using the "?tag=" query parameter.
|
||||||
|
|
||||||
@ -159,6 +169,11 @@ This endpoint is hit with a GET and returns the checks in the
|
|||||||
state provided on the path. By default, the datacenter of the agent is queried;
|
state provided on the path. By default, the datacenter of the agent is queried;
|
||||||
however, the dc can be provided using the "?dc=" query parameter.
|
however, the dc can be provided using the "?dc=" query parameter.
|
||||||
|
|
||||||
|
Adding the optional "?near=" parameter with a node name will sort
|
||||||
|
the node list in ascending order based on the estimated round trip
|
||||||
|
time from that node. Passing "?near=self" will use the agent's local
|
||||||
|
node for the sort.
|
||||||
|
|
||||||
The supported states are `any`, `unknown`, `passing`, `warning`, or `critical`.
|
The supported states are `any`, `unknown`, `passing`, `warning`, or `critical`.
|
||||||
The `any` state is a wildcard that can be used to return all checks.
|
The `any` state is a wildcard that can be used to return all checks.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user