2015-04-09 20:23:14 +00:00
|
|
|
package consul
|
|
|
|
|
|
|
|
import (
|
2015-05-14 01:22:34 +00:00
|
|
|
"fmt"
|
2015-04-09 20:23:14 +00:00
|
|
|
"math/rand"
|
|
|
|
"os"
|
2015-04-29 02:07:10 +00:00
|
|
|
"reflect"
|
2015-06-23 02:14:02 +00:00
|
|
|
"strings"
|
2015-04-09 20:23:14 +00:00
|
|
|
"testing"
|
2015-04-13 20:45:42 +00:00
|
|
|
"time"
|
2015-04-09 20:23:14 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/consul/structs"
|
|
|
|
"github.com/hashicorp/consul/testutil"
|
|
|
|
"github.com/hashicorp/serf/coordinate"
|
|
|
|
)
|
|
|
|
|
2015-06-06 03:31:33 +00:00
|
|
|
// generateRandomCoordinate creates a random coordinate. This mucks with the
|
|
|
|
// underlying structure directly, so it's not really useful for any particular
|
|
|
|
// position in the network, but it's a good payload to send through to make
|
|
|
|
// sure things come out the other side or get stored correctly.
|
|
|
|
func generateRandomCoordinate() *coordinate.Coordinate {
|
2015-04-09 20:23:14 +00:00
|
|
|
config := coordinate.DefaultConfig()
|
2015-06-06 03:31:33 +00:00
|
|
|
coord := coordinate.NewCoordinate(config)
|
|
|
|
for i := range coord.Vec {
|
|
|
|
coord.Vec[i] = rand.NormFloat64()
|
2015-04-13 20:45:42 +00:00
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
coord.Error = rand.NormFloat64()
|
|
|
|
coord.Adjustment = rand.NormFloat64()
|
|
|
|
return coord
|
2015-04-09 20:23:14 +00:00
|
|
|
}
|
|
|
|
|
2015-06-06 03:31:33 +00:00
|
|
|
// verifyCoordinatesEqual will compare a and b and fail if they are not exactly
|
2015-06-23 02:14:02 +00:00
|
|
|
// equal (no floating point fuzz is considered since we are trying to make sure
|
|
|
|
// we are getting exactly the coordinates we expect, without math on them).
|
2015-06-06 03:31:33 +00:00
|
|
|
func verifyCoordinatesEqual(t *testing.T, a, b *coordinate.Coordinate) {
|
|
|
|
if !reflect.DeepEqual(a, b) {
|
|
|
|
t.Fatalf("coordinates are not equal: %v != %v", a, b)
|
|
|
|
}
|
2015-04-09 20:23:14 +00:00
|
|
|
}
|
|
|
|
|
2015-05-14 02:09:58 +00:00
|
|
|
func TestCoordinate_Update(t *testing.T) {
|
2015-05-14 01:22:34 +00:00
|
|
|
name := fmt.Sprintf("Node %d", getPort())
|
|
|
|
dir1, config1 := testServerConfig(t, name)
|
2015-06-06 03:31:33 +00:00
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
config1.CoordinateUpdatePeriod = 500 * time.Millisecond
|
2015-06-06 03:31:33 +00:00
|
|
|
config1.CoordinateUpdateMaxBatchSize = 5
|
2015-05-14 01:22:34 +00:00
|
|
|
s1, err := NewServer(config1)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-04-09 20:23:14 +00:00
|
|
|
defer s1.Shutdown()
|
2015-05-14 01:22:34 +00:00
|
|
|
|
2015-04-09 20:23:14 +00:00
|
|
|
client := rpcClient(t, s1)
|
|
|
|
defer client.Close()
|
|
|
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// Send an update for the first node.
|
2015-05-08 08:31:34 +00:00
|
|
|
arg1 := structs.CoordinateUpdateRequest{
|
2015-04-18 21:05:29 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node1",
|
2015-06-06 03:31:33 +00:00
|
|
|
Coord: generateRandomCoordinate(),
|
2015-04-09 20:23:14 +00:00
|
|
|
}
|
2015-06-23 02:14:02 +00:00
|
|
|
var out struct{}
|
|
|
|
if err := client.Call("Coordinate.Update", &arg1, &out); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-04-09 20:23:14 +00:00
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// Send an update for the second node.
|
2015-05-08 08:31:34 +00:00
|
|
|
arg2 := structs.CoordinateUpdateRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node2",
|
2015-06-06 03:31:33 +00:00
|
|
|
Coord: generateRandomCoordinate(),
|
2015-05-08 08:31:34 +00:00
|
|
|
}
|
2015-06-23 02:14:02 +00:00
|
|
|
if err := client.Call("Coordinate.Update", &arg2, &out); err != nil {
|
2015-04-09 20:23:14 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// Make sure the updates did not yet apply because the update period
|
|
|
|
// hasn't expired.
|
2015-04-09 20:23:14 +00:00
|
|
|
state := s1.fsm.State()
|
|
|
|
_, d, err := state.CoordinateGet("node1")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-05-08 08:31:34 +00:00
|
|
|
if d != nil {
|
|
|
|
t.Fatalf("should be nil because the update should be batched")
|
|
|
|
}
|
2015-06-23 02:14:02 +00:00
|
|
|
_, d, err = state.CoordinateGet("node2")
|
|
|
|
if err != nil {
|
2015-05-08 08:31:34 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-06-23 02:14:02 +00:00
|
|
|
if d != nil {
|
|
|
|
t.Fatalf("should be nil because the update should be batched")
|
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// Wait a while and the updates should get picked up.
|
|
|
|
time.Sleep(2 * s1.config.CoordinateUpdatePeriod)
|
2015-05-08 08:31:34 +00:00
|
|
|
_, d, err = state.CoordinateGet("node1")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if d == nil {
|
|
|
|
t.Fatalf("should return a coordinate but it's nil")
|
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
verifyCoordinatesEqual(t, d.Coord, arg1.Coord)
|
2015-05-08 08:31:34 +00:00
|
|
|
_, d, err = state.CoordinateGet("node2")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if d == nil {
|
|
|
|
t.Fatalf("should return a coordinate but it's nil")
|
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
verifyCoordinatesEqual(t, d.Coord, arg2.Coord)
|
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// Now try spamming coordinates and make sure it starts dropping when
|
|
|
|
// the pipe is full.
|
|
|
|
for i := 0; i < s1.config.CoordinateUpdateMaxBatchSize; i++ {
|
2015-06-06 03:31:33 +00:00
|
|
|
arg1.Coord = generateRandomCoordinate()
|
|
|
|
if err := client.Call("Coordinate.Update", &arg1, &out); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-04-09 20:23:14 +00:00
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// This one should get dropped.
|
|
|
|
arg2.Coord = generateRandomCoordinate()
|
|
|
|
err = client.Call("Coordinate.Update", &arg2, &out)
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "rate limit") {
|
|
|
|
t.Fatalf("should have failed with a rate limit error, got %v", err)
|
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// Wait a little while for the batch routine to run, then make sure
|
|
|
|
// all but the last coordinate update made it in.
|
|
|
|
time.Sleep(2 * s1.config.CoordinateUpdatePeriod)
|
2015-06-06 03:31:33 +00:00
|
|
|
_, d, err = state.CoordinateGet("node1")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
if d == nil {
|
|
|
|
t.Fatalf("should return a coordinate but it's nil")
|
|
|
|
}
|
|
|
|
verifyCoordinatesEqual(t, d.Coord, arg1.Coord)
|
2015-04-29 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2015-06-06 03:31:33 +00:00
|
|
|
func TestCoordinate_Get(t *testing.T) {
|
2015-04-29 01:47:41 +00:00
|
|
|
dir1, s1 := testServer(t)
|
|
|
|
defer os.RemoveAll(dir1)
|
|
|
|
defer s1.Shutdown()
|
2015-05-14 02:09:58 +00:00
|
|
|
|
2015-04-29 01:47:41 +00:00
|
|
|
client := rpcClient(t, s1)
|
|
|
|
defer client.Close()
|
|
|
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
|
|
|
|
|
|
|
arg := structs.CoordinateUpdateRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node1",
|
2015-06-06 03:31:33 +00:00
|
|
|
Coord: generateRandomCoordinate(),
|
2015-04-29 01:47:41 +00:00
|
|
|
}
|
|
|
|
|
2015-06-23 02:14:02 +00:00
|
|
|
// Send an initial update, waiting a little while for the batch update
|
2015-06-06 03:31:33 +00:00
|
|
|
// to run.
|
2015-04-29 01:47:41 +00:00
|
|
|
var out struct{}
|
|
|
|
if err := client.Call("Coordinate.Update", &arg, &out); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-05-14 01:22:34 +00:00
|
|
|
time.Sleep(100 * time.Millisecond)
|
2015-04-13 20:53:43 +00:00
|
|
|
|
2015-06-06 03:31:33 +00:00
|
|
|
// Query the coordinate via RPC.
|
2015-05-08 08:31:34 +00:00
|
|
|
arg2 := structs.NodeSpecificRequest{
|
2015-04-13 20:53:43 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node1",
|
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
coord := structs.IndexedCoordinate{}
|
|
|
|
if err := client.Call("Coordinate.Get", &arg2, &coord); err != nil {
|
2015-04-13 20:53:43 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
verifyCoordinatesEqual(t, coord.Coord, arg.Coord)
|
2015-04-16 21:08:56 +00:00
|
|
|
|
2015-06-06 03:31:33 +00:00
|
|
|
// Send another coordinate update, waiting after for the flush.
|
|
|
|
arg.Coord = generateRandomCoordinate()
|
2015-04-16 21:08:56 +00:00
|
|
|
if err := client.Call("Coordinate.Update", &arg, &out); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-05-14 01:22:34 +00:00
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
2015-06-06 03:31:33 +00:00
|
|
|
// Now re-query and make sure the results are fresh.
|
|
|
|
if err := client.Call("Coordinate.Get", &arg2, &coord); err != nil {
|
2015-05-14 02:09:58 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2015-06-06 03:31:33 +00:00
|
|
|
verifyCoordinatesEqual(t, coord.Coord, arg.Coord)
|
2015-05-14 02:09:58 +00:00
|
|
|
}
|