mirror of https://github.com/status-im/consul.git
Completes rebase of network coordinates to new memdb.
This commit is contained in:
parent
cef9402ab9
commit
ecd3a1d1d2
|
@ -17,7 +17,7 @@ func coordinateDisabled(resp http.ResponseWriter, req *http.Request) (interface{
|
|||
// sorter wraps a coordinate list and implements the sort.Interface to sort by
|
||||
// node name.
|
||||
type sorter struct {
|
||||
coordinates []structs.Coordinate
|
||||
coordinates structs.Coordinates
|
||||
}
|
||||
|
||||
// See sort.Interface.
|
||||
|
|
|
@ -48,6 +48,20 @@ func TestCoordinate_Nodes(t *testing.T) {
|
|||
|
||||
testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
|
||||
|
||||
// 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 := srv.agent.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{
|
||||
|
@ -82,7 +96,7 @@ func TestCoordinate_Nodes(t *testing.T) {
|
|||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
coordinates := obj.([]structs.Coordinate)
|
||||
coordinates := obj.(structs.Coordinates)
|
||||
if len(coordinates) != 2 ||
|
||||
coordinates[0].Node != "bar" ||
|
||||
coordinates[1].Node != "foo" {
|
||||
|
|
|
@ -819,15 +819,18 @@ func TestAgent_sendCoordinate(t *testing.T) {
|
|||
time.Sleep(2 * conf.ConsulConfig.CoordinateUpdatePeriod)
|
||||
|
||||
// Make sure the coordinate is present.
|
||||
req := structs.NodeSpecificRequest{
|
||||
req := structs.DCSpecificRequest{
|
||||
Datacenter: agent.config.Datacenter,
|
||||
Node: agent.config.NodeName,
|
||||
}
|
||||
var reply structs.IndexedCoordinate
|
||||
if err := agent.RPC("Coordinate.Get", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
var reply structs.IndexedCoordinates
|
||||
if err := agent.RPC("Coordinate.ListNodes", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if reply.Coord == nil {
|
||||
t.Fatalf("should get a coordinate")
|
||||
if len(reply.Coordinates) != 1 {
|
||||
t.Fatalf("expected a coordinate: %v", reply)
|
||||
}
|
||||
coord := reply.Coordinates[0]
|
||||
if coord.Node != agent.config.NodeName || coord.Coord == nil {
|
||||
t.Fatalf("bad: %v", coord)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,24 +54,38 @@ func TestRTTCommand_Run_LAN(t *testing.T) {
|
|||
c2 := c1.Clone()
|
||||
c2.Vec[0] = 0.123
|
||||
dist_str := fmt.Sprintf("%.3f ms", c1.DistanceTo(c2).Seconds()*1000.0)
|
||||
|
||||
req1 := structs.CoordinateUpdateRequest{
|
||||
Datacenter: a.config.Datacenter,
|
||||
Node: a.config.NodeName,
|
||||
Coord: c1,
|
||||
{
|
||||
req := structs.CoordinateUpdateRequest{
|
||||
Datacenter: a.config.Datacenter,
|
||||
Node: a.config.NodeName,
|
||||
Coord: c1,
|
||||
}
|
||||
var reply struct{}
|
||||
if err := a.agent.RPC("Coordinate.Update", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
var reply struct{}
|
||||
if err := a.agent.RPC("Coordinate.Update", &req1, &reply); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
{
|
||||
req := structs.RegisterRequest{
|
||||
Datacenter: a.config.Datacenter,
|
||||
Node: "dogs",
|
||||
Address: "127.0.0.2",
|
||||
}
|
||||
var reply struct{}
|
||||
if err := a.agent.RPC("Catalog.Register", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
req2 := structs.CoordinateUpdateRequest{
|
||||
Datacenter: a.config.Datacenter,
|
||||
Node: "dogs",
|
||||
Coord: c2,
|
||||
}
|
||||
if err := a.agent.RPC("Coordinate.Update", &req2, &reply); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
{
|
||||
var reply struct{}
|
||||
req := structs.CoordinateUpdateRequest{
|
||||
Datacenter: a.config.Datacenter,
|
||||
Node: "dogs",
|
||||
Coord: c2,
|
||||
}
|
||||
if err := a.agent.RPC("Coordinate.Update", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the updates to get flushed to the data store.
|
||||
|
|
|
@ -512,21 +512,21 @@ func TestCatalogListNodes_DistanceSort(t *testing.T) {
|
|||
defer codec.Close()
|
||||
|
||||
testutil.WaitForLeader(t, s1.RPC, "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{Node: "aaa", Address: "127.0.0.1"}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if err := s1.fsm.State().EnsureNode(2, structs.Node{"foo", "127.0.0.2"}); err != nil {
|
||||
if err := s1.fsm.State().EnsureNode(2, &structs.Node{Node: "foo", Address: "127.0.0.2"}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if err := s1.fsm.State().EnsureNode(3, structs.Node{"bar", "127.0.0.3"}); err != nil {
|
||||
if err := s1.fsm.State().EnsureNode(3, &structs.Node{Node: "bar", Address: "127.0.0.3"}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if err := s1.fsm.State().EnsureNode(4, structs.Node{"baz", "127.0.0.4"}); err != nil {
|
||||
if err := s1.fsm.State().EnsureNode(4, &structs.Node{Node: "baz", Address: "127.0.0.4"}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Set all but one of the nodes to known coordinates.
|
||||
updates := []structs.Coordinate{
|
||||
updates := structs.Coordinates{
|
||||
{"foo", generateCoordinate(2 * time.Millisecond)},
|
||||
{"bar", generateCoordinate(5 * time.Millisecond)},
|
||||
{"baz", generateCoordinate(1 * time.Millisecond)},
|
||||
|
@ -870,17 +870,17 @@ func TestCatalogListServiceNodes_DistanceSort(t *testing.T) {
|
|||
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||
|
||||
// Add a few nodes for the associated services.
|
||||
s1.fsm.State().EnsureNode(1, structs.Node{"aaa", "127.0.0.1"})
|
||||
s1.fsm.State().EnsureService(2, "aaa", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000, false})
|
||||
s1.fsm.State().EnsureNode(3, structs.Node{"foo", "127.0.0.2"})
|
||||
s1.fsm.State().EnsureService(4, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.2", 5000, false})
|
||||
s1.fsm.State().EnsureNode(5, structs.Node{"bar", "127.0.0.3"})
|
||||
s1.fsm.State().EnsureService(6, "bar", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.3", 5000, false})
|
||||
s1.fsm.State().EnsureNode(7, structs.Node{"baz", "127.0.0.4"})
|
||||
s1.fsm.State().EnsureService(8, "baz", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.4", 5000, false})
|
||||
s1.fsm.State().EnsureNode(1, &structs.Node{Node: "aaa", Address: "127.0.0.1"})
|
||||
s1.fsm.State().EnsureService(2, "aaa", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.1", Port: 5000})
|
||||
s1.fsm.State().EnsureNode(3, &structs.Node{Node: "foo", Address: "127.0.0.2"})
|
||||
s1.fsm.State().EnsureService(4, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.2", Port: 5000})
|
||||
s1.fsm.State().EnsureNode(5, &structs.Node{Node: "bar", Address: "127.0.0.3"})
|
||||
s1.fsm.State().EnsureService(6, "bar", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.3", Port: 5000})
|
||||
s1.fsm.State().EnsureNode(7, &structs.Node{Node: "baz", Address: "127.0.0.4"})
|
||||
s1.fsm.State().EnsureService(8, "baz", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.4", Port: 5000})
|
||||
|
||||
// Set all but one of the nodes to known coordinates.
|
||||
updates := []structs.Coordinate{
|
||||
updates := structs.Coordinates{
|
||||
{"foo", generateCoordinate(2 * time.Millisecond)},
|
||||
{"bar", generateCoordinate(5 * time.Millisecond)},
|
||||
{"baz", generateCoordinate(1 * time.Millisecond)},
|
||||
|
@ -900,13 +900,13 @@ func TestCatalogListServiceNodes_DistanceSort(t *testing.T) {
|
|||
if out.ServiceNodes[0].Node != "aaa" {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
if out.ServiceNodes[1].Node != "foo" {
|
||||
if out.ServiceNodes[1].Node != "bar" {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
if out.ServiceNodes[2].Node != "bar" {
|
||||
if out.ServiceNodes[2].Node != "baz" {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
if out.ServiceNodes[3].Node != "baz" {
|
||||
if out.ServiceNodes[3].Node != "foo" {
|
||||
t.Fatalf("bad: %v", out)
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,8 @@ func (c *Coordinate) batchUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
// batchApplyUpdates applies all pending updates to the Raft log in a series of batches.
|
||||
// batchApplyUpdates applies all pending updates to the Raft log in a series of
|
||||
// batches.
|
||||
func (c *Coordinate) batchApplyUpdates() error {
|
||||
// Grab the pending updates and release the lock so we can still handle
|
||||
// incoming messages.
|
||||
|
@ -68,14 +69,14 @@ func (c *Coordinate) batchApplyUpdates() error {
|
|||
|
||||
// Transform the map into a slice that we can feed to the Raft log in
|
||||
// batches.
|
||||
updates := make([]structs.Coordinate, size)
|
||||
i := 0
|
||||
updates := make(structs.Coordinates, size)
|
||||
for node, coord := range pending {
|
||||
if !(i < size) {
|
||||
break
|
||||
}
|
||||
|
||||
updates[i] = structs.Coordinate{node, coord}
|
||||
updates[i] = &structs.Coordinate{node, coord}
|
||||
i++
|
||||
}
|
||||
|
||||
|
@ -117,24 +118,6 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct
|
|||
return nil
|
||||
}
|
||||
|
||||
// Get returns the coordinate of the given node.
|
||||
func (c *Coordinate) Get(args *structs.NodeSpecificRequest,
|
||||
reply *structs.IndexedCoordinate) error {
|
||||
if done, err := c.srv.forward("Coordinate.Get", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
|
||||
state := c.srv.fsm.State()
|
||||
return c.srv.blockingRPC(&args.QueryOptions,
|
||||
&reply.QueryMeta,
|
||||
state.QueryTables("Coordinates"),
|
||||
func() error {
|
||||
var err error
|
||||
reply.Index, reply.Coord, err = state.CoordinateGet(args.Node)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// ListDatacenters returns the list of datacenters and their respective nodes
|
||||
// and the raw coordinates of those nodes (if no coordinates are available for
|
||||
// any of the nodes, the node list may be empty).
|
||||
|
@ -174,10 +157,14 @@ func (c *Coordinate) ListNodes(args *structs.DCSpecificRequest, reply *structs.I
|
|||
state := c.srv.fsm.State()
|
||||
return c.srv.blockingRPC(&args.QueryOptions,
|
||||
&reply.QueryMeta,
|
||||
state.QueryTables("Coordinates"),
|
||||
state.GetQueryWatch("Coordinates"),
|
||||
func() error {
|
||||
var err error
|
||||
reply.Index, reply.Coordinates, err = state.Coordinates()
|
||||
return err
|
||||
index, coords, err := state.Coordinates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reply.Index, reply.Coordinates = index, coords
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -57,6 +57,20 @@ func TestCoordinate_Update(t *testing.T) {
|
|||
defer codec.Close()
|
||||
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||
|
||||
// Register some nodes.
|
||||
nodes := []string{"node1", "node2"}
|
||||
for _, node := range nodes {
|
||||
req := structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: node,
|
||||
Address: "127.0.0.1",
|
||||
}
|
||||
var reply struct{}
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send an update for the first node.
|
||||
arg1 := structs.CoordinateUpdateRequest{
|
||||
Datacenter: "dc1",
|
||||
|
@ -81,14 +95,14 @@ func TestCoordinate_Update(t *testing.T) {
|
|||
// Make sure the updates did not yet apply because the update period
|
||||
// hasn't expired.
|
||||
state := s1.fsm.State()
|
||||
_, c, err := state.CoordinateGet("node1")
|
||||
c, err := state.CoordinateGetRaw("node1")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if c != nil {
|
||||
t.Fatalf("should be nil because the update should be batched")
|
||||
}
|
||||
_, c, err = state.CoordinateGet("node2")
|
||||
c, err = state.CoordinateGetRaw("node2")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -96,9 +110,16 @@ func TestCoordinate_Update(t *testing.T) {
|
|||
t.Fatalf("should be nil because the update should be batched")
|
||||
}
|
||||
|
||||
// Send another update for the second node. It should take precedence
|
||||
// since there will be two updates in the same batch.
|
||||
arg2.Coord = generateRandomCoordinate()
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Wait a while and the updates should get picked up.
|
||||
time.Sleep(2 * s1.config.CoordinateUpdatePeriod)
|
||||
_, c, err = state.CoordinateGet("node1")
|
||||
c, err = state.CoordinateGetRaw("node1")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -106,7 +127,7 @@ func TestCoordinate_Update(t *testing.T) {
|
|||
t.Fatalf("should return a coordinate but it's nil")
|
||||
}
|
||||
verifyCoordinatesEqual(t, c, arg1.Coord)
|
||||
_, c, err = state.CoordinateGet("node2")
|
||||
c, err = state.CoordinateGetRaw("node2")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -115,10 +136,23 @@ func TestCoordinate_Update(t *testing.T) {
|
|||
}
|
||||
verifyCoordinatesEqual(t, c, arg2.Coord)
|
||||
|
||||
// Register a bunch of additional nodes.
|
||||
spamLen := s1.config.CoordinateUpdateBatchSize*s1.config.CoordinateUpdateMaxBatches + 1
|
||||
for i := 0; i < spamLen; i++ {
|
||||
req := structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: fmt.Sprintf("bogusnode%d", i),
|
||||
Address: "127.0.0.1",
|
||||
}
|
||||
var reply struct{}
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Now spam some coordinate updates and make sure it starts throwing
|
||||
// them away if they exceed the batch allowance. Note we have to make
|
||||
// unique names since these are held in map by node name.
|
||||
spamLen := s1.config.CoordinateUpdateBatchSize*s1.config.CoordinateUpdateMaxBatches + 1
|
||||
for i := 0; i < spamLen; i++ {
|
||||
arg1.Node = fmt.Sprintf("bogusnode%d", i)
|
||||
arg1.Coord = generateRandomCoordinate()
|
||||
|
@ -132,7 +166,7 @@ func TestCoordinate_Update(t *testing.T) {
|
|||
time.Sleep(2 * s1.config.CoordinateUpdatePeriod)
|
||||
numDropped := 0
|
||||
for i := 0; i < spamLen; i++ {
|
||||
_, c, err = state.CoordinateGet(fmt.Sprintf("bogusnode%d", i))
|
||||
c, err = state.CoordinateGetRaw(fmt.Sprintf("bogusnode%d", i))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -153,54 +187,6 @@ func TestCoordinate_Update(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCoordinate_Get(t *testing.T) {
|
||||
dir1, s1 := testServer(t)
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
||||
codec := rpcClient(t, s1)
|
||||
defer codec.Close()
|
||||
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||
|
||||
arg := structs.CoordinateUpdateRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
}
|
||||
|
||||
// Send an initial update, waiting a little while for the batch update
|
||||
// to run.
|
||||
var out struct{}
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
time.Sleep(2 * s1.config.CoordinateUpdatePeriod)
|
||||
|
||||
// Query the coordinate via RPC.
|
||||
arg2 := structs.NodeSpecificRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "node1",
|
||||
}
|
||||
coord := structs.IndexedCoordinate{}
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Get", &arg2, &coord); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
verifyCoordinatesEqual(t, coord.Coord, arg.Coord)
|
||||
|
||||
// Send another coordinate update, waiting after for the flush.
|
||||
arg.Coord = generateRandomCoordinate()
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg, &out); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
time.Sleep(2 * s1.config.CoordinateUpdatePeriod)
|
||||
|
||||
// Now re-query and make sure the results are fresh.
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Get", &arg2, &coord); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
verifyCoordinatesEqual(t, coord.Coord, arg.Coord)
|
||||
}
|
||||
|
||||
func TestCoordinate_ListDatacenters(t *testing.T) {
|
||||
dir1, s1 := testServer(t)
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -240,6 +226,20 @@ func TestCoordinate_ListNodes(t *testing.T) {
|
|||
defer codec.Close()
|
||||
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||
|
||||
// Register some nodes.
|
||||
nodes := []string{"foo", "bar", "baz"}
|
||||
for _, node := range nodes {
|
||||
req := structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: node,
|
||||
Address: "127.0.0.1",
|
||||
}
|
||||
var reply struct{}
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send coordinate updates for a few nodes, waiting a little while for
|
||||
// the batch update to run.
|
||||
arg1 := structs.CoordinateUpdateRequest{
|
||||
|
|
|
@ -253,7 +253,7 @@ func (c *consulFSM) applyTombstoneOperation(buf []byte, index uint64) interface{
|
|||
// update interface that the coordinate endpoint exposes, so we made it single
|
||||
// purpose and avoided the opcode convention.
|
||||
func (c *consulFSM) applyCoordinateBatchUpdate(buf []byte, index uint64) interface{} {
|
||||
var updates []structs.Coordinate
|
||||
var updates structs.Coordinates
|
||||
if err := structs.Decode(buf, &updates); err != nil {
|
||||
panic(fmt.Errorf("failed to decode batch updates: %v", err))
|
||||
}
|
||||
|
@ -362,11 +362,12 @@ func (c *consulFSM) Restore(old io.ReadCloser) error {
|
|||
}
|
||||
|
||||
case structs.CoordinateBatchUpdateType:
|
||||
var req []structs.Coordinate
|
||||
var req structs.Coordinates
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
if err := c.state.CoordinateBatchUpdate(header.LastIndex, req); err != nil {
|
||||
if err := restore.Coordinates(header.LastIndex, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -472,14 +473,20 @@ func (s *consulSnapshot) persistNodes(sink raft.SnapshotSink,
|
|||
}
|
||||
}
|
||||
|
||||
// Save the coordinates separately so we can use the existing batch
|
||||
// interface.
|
||||
sink.Write([]byte{byte(structs.CoordinateBatchUpdateType)})
|
||||
coords := s.state.Coordinates()
|
||||
if err := encoder.Encode(&coords); err != nil {
|
||||
// Save the coordinates separately since they are not part of the
|
||||
// register request interface. To avoid copying them out, we turn
|
||||
// them into batches with a single coordinate each.
|
||||
coords, err := s.state.Coordinates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for coord := coords.Next(); coord != nil; coord = coords.Next() {
|
||||
sink.Write([]byte{byte(structs.CoordinateBatchUpdateType)})
|
||||
updates := structs.Coordinates{coord.(*structs.Coordinate)}
|
||||
if err := encoder.Encode(&updates); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -383,11 +383,19 @@ func TestFSM_SnapshotRestore(t *testing.T) {
|
|||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
|
||||
coord := generateRandomCoordinate()
|
||||
coords := []structs.Coordinate{
|
||||
structs.Coordinate{"foo", coord},
|
||||
updates := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "baz",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
&structs.Coordinate{
|
||||
Node: "foo",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
}
|
||||
if err := fsm.state.CoordinateBatchUpdate(13, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
fsm.state.CoordinateBatchUpdate(13, coords)
|
||||
|
||||
// Snapshot
|
||||
snap, err := fsm.Snapshot()
|
||||
|
@ -499,12 +507,12 @@ func TestFSM_SnapshotRestore(t *testing.T) {
|
|||
}()
|
||||
|
||||
// Verify coordinates are restored
|
||||
_, c, err := fsm2.state.CoordinateGet("foo")
|
||||
_, coords, err := fsm2.state.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c == nil || !reflect.DeepEqual(c, coord) {
|
||||
t.Fatalf("coordinate is missing or doesn't match, %v != %v", c, coord)
|
||||
if !reflect.DeepEqual(coords, updates) {
|
||||
t.Fatalf("bad: %#v", coords)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -745,19 +753,17 @@ func TestFSM_KVSCheckAndSet(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFSM_CoordinateUpdate(t *testing.T) {
|
||||
path, err := ioutil.TempDir("", "fsm")
|
||||
fsm, err := NewFSM(nil, os.Stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
fsm, err := NewFSM(nil, path, os.Stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
defer fsm.Close()
|
||||
|
||||
// Register some nodes.
|
||||
fsm.state.EnsureNode(1, &structs.Node{Node: "node1", Address: "127.0.0.1"})
|
||||
fsm.state.EnsureNode(2, &structs.Node{Node: "node2", Address: "127.0.0.1"})
|
||||
|
||||
// Write a batch of two coordinates.
|
||||
updates := []*structs.Coordinate{
|
||||
updates := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
|
@ -777,23 +783,13 @@ func TestFSM_CoordinateUpdate(t *testing.T) {
|
|||
}
|
||||
|
||||
// Read back the two coordinates to make sure they got updated.
|
||||
_, c, err := fsm.state.CoordinateGet(updates[0].Node)
|
||||
_, coords, err := fsm.state.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Fatalf("missing")
|
||||
if !reflect.DeepEqual(coords, updates) {
|
||||
t.Fatalf("bad: %#v", coords)
|
||||
}
|
||||
verifyCoordinatesEqual(t, c, updates[0].Coord)
|
||||
|
||||
_, c, err = fsm.state.CoordinateGet(updates[1].Node)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Fatalf("missing")
|
||||
}
|
||||
verifyCoordinatesEqual(t, c, updates[1].Coord)
|
||||
}
|
||||
|
||||
func TestFSM_SessionCreate_Destroy(t *testing.T) {
|
||||
|
|
|
@ -64,13 +64,13 @@ func TestHealth_ChecksInState_DistanceSort(t *testing.T) {
|
|||
defer codec.Close()
|
||||
|
||||
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||
if err := s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.2"}); err != nil {
|
||||
if err := s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "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 {
|
||||
if err := s1.fsm.State().EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.3"}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
updates := []structs.Coordinate{
|
||||
updates := structs.Coordinates{
|
||||
{"foo", generateCoordinate(1 * time.Millisecond)},
|
||||
{"bar", generateCoordinate(2 * time.Millisecond)},
|
||||
}
|
||||
|
@ -228,13 +228,13 @@ func TestHealth_ServiceChecks_DistanceSort(t *testing.T) {
|
|||
defer codec.Close()
|
||||
|
||||
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||
if err := s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.2"}); err != nil {
|
||||
if err := s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "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 {
|
||||
if err := s1.fsm.State().EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.3"}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
updates := []structs.Coordinate{
|
||||
updates := structs.Coordinates{
|
||||
{"foo", generateCoordinate(1 * time.Millisecond)},
|
||||
{"bar", generateCoordinate(2 * time.Millisecond)},
|
||||
}
|
||||
|
@ -399,13 +399,13 @@ func TestHealth_ServiceNodes_DistanceSort(t *testing.T) {
|
|||
defer codec.Close()
|
||||
|
||||
testutil.WaitForLeader(t, s1.RPC, "dc1")
|
||||
if err := s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.2"}); err != nil {
|
||||
if err := s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "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 {
|
||||
if err := s1.fsm.State().EnsureNode(2, &structs.Node{Node: "bar", Address: "127.0.0.3"}); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
updates := []structs.Coordinate{
|
||||
updates := structs.Coordinates{
|
||||
{"foo", generateCoordinate(1 * time.Millisecond)},
|
||||
{"bar", generateCoordinate(2 * time.Millisecond)},
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func (s *Server) newNodeSorter(c *coordinate.Coordinate, nodes structs.Nodes) (s
|
|||
state := s.fsm.State()
|
||||
vec := make([]float64, len(nodes))
|
||||
for i, node := range nodes {
|
||||
_, coord, err := state.CoordinateGet(node.Node)
|
||||
coord, err := state.CoordinateGetRaw(node.Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func (s *Server) newServiceNodeSorter(c *coordinate.Coordinate, nodes structs.Se
|
|||
state := s.fsm.State()
|
||||
vec := make([]float64, len(nodes))
|
||||
for i, node := range nodes {
|
||||
_, coord, err := state.CoordinateGet(node.Node)
|
||||
coord, err := state.CoordinateGetRaw(node.Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ func (s *Server) newHealthCheckSorter(c *coordinate.Coordinate, checks structs.H
|
|||
state := s.fsm.State()
|
||||
vec := make([]float64, len(checks))
|
||||
for i, check := range checks {
|
||||
_, coord, err := state.CoordinateGet(check.Node)
|
||||
coord, err := state.CoordinateGetRaw(check.Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ func (s *Server) newCheckServiceNodeSorter(c *coordinate.Coordinate, nodes struc
|
|||
state := s.fsm.State()
|
||||
vec := make([]float64, len(nodes))
|
||||
for i, node := range nodes {
|
||||
_, coord, err := state.CoordinateGet(node.Node.Node)
|
||||
coord, err := state.CoordinateGetRaw(node.Node.Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ func (s *Server) sortNodesByDistanceFrom(source structs.QuerySource, subj interf
|
|||
// There won't always be a coordinate for the source node. If there's not
|
||||
// one then we can bail out because there's no meaning for the sort.
|
||||
state := s.fsm.State()
|
||||
_, coord, err := state.CoordinateGet(source.Node)
|
||||
coord, err := state.CoordinateGetRaw(source.Node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -388,10 +388,7 @@ func getDatacenterMaps(s serfer, dcs []string) []structs.DatacenterMap {
|
|||
nodes := s.GetNodesForDatacenter(dc)
|
||||
for _, node := range nodes {
|
||||
if coord, ok := s.GetCachedCoordinate(node); ok {
|
||||
entry := structs.Coordinate{
|
||||
Node: node,
|
||||
Coord: coord,
|
||||
}
|
||||
entry := &structs.Coordinate{node, coord}
|
||||
m.Coordinates = append(m.Coordinates, entry)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package consul
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/rpc"
|
||||
"os"
|
||||
|
@ -87,6 +88,20 @@ func verifyCheckServiceNodeSort(t *testing.T, nodes structs.CheckServiceNodes, e
|
|||
// 0 1 2 3 4 5 6 7 8 9 10 (ms)
|
||||
//
|
||||
func seedCoordinates(t *testing.T, codec rpc.ClientCodec, server *Server) {
|
||||
// Register some nodes.
|
||||
for i := 0; i < 5; i++ {
|
||||
req := structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: fmt.Sprintf("node%d", i+1),
|
||||
Address: "127.0.0.1",
|
||||
}
|
||||
var reply struct{}
|
||||
if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Seed the fixed setup of the nodes.
|
||||
updates := []structs.CoordinateUpdateRequest{
|
||||
structs.CoordinateUpdateRequest{
|
||||
Datacenter: "dc1",
|
||||
|
@ -137,12 +152,12 @@ func TestRtt_sortNodesByDistanceFrom(t *testing.T) {
|
|||
|
||||
seedCoordinates(t, codec, 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"},
|
||||
&structs.Node{Node: "apple"},
|
||||
&structs.Node{Node: "node1"},
|
||||
&structs.Node{Node: "node2"},
|
||||
&structs.Node{Node: "node3"},
|
||||
&structs.Node{Node: "node4"},
|
||||
&structs.Node{Node: "node5"},
|
||||
}
|
||||
|
||||
// The zero value for the source should not trigger any sorting.
|
||||
|
@ -198,12 +213,12 @@ func TestRtt_sortNodesByDistanceFrom_Nodes(t *testing.T) {
|
|||
|
||||
seedCoordinates(t, codec, 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"},
|
||||
&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
|
||||
|
@ -247,12 +262,12 @@ func TestRtt_sortNodesByDistanceFrom_ServiceNodes(t *testing.T) {
|
|||
|
||||
seedCoordinates(t, codec, server)
|
||||
nodes := structs.ServiceNodes{
|
||||
structs.ServiceNode{Node: "apple"},
|
||||
structs.ServiceNode{Node: "node1"},
|
||||
structs.ServiceNode{Node: "node2"},
|
||||
structs.ServiceNode{Node: "node3"},
|
||||
structs.ServiceNode{Node: "node4"},
|
||||
structs.ServiceNode{Node: "node5"},
|
||||
&structs.ServiceNode{Node: "apple"},
|
||||
&structs.ServiceNode{Node: "node1"},
|
||||
&structs.ServiceNode{Node: "node2"},
|
||||
&structs.ServiceNode{Node: "node3"},
|
||||
&structs.ServiceNode{Node: "node4"},
|
||||
&structs.ServiceNode{Node: "node5"},
|
||||
}
|
||||
|
||||
// Now sort relative to node1, note that apple doesn't have any
|
||||
|
@ -345,12 +360,12 @@ func TestRtt_sortNodesByDistanceFrom_CheckServiceNodes(t *testing.T) {
|
|||
|
||||
seedCoordinates(t, codec, 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"}},
|
||||
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
|
||||
|
|
|
@ -29,6 +29,7 @@ func stateStoreSchema() *memdb.DBSchema {
|
|||
sessionsTableSchema,
|
||||
sessionChecksTableSchema,
|
||||
aclsTableSchema,
|
||||
coordinatesTableSchema,
|
||||
}
|
||||
|
||||
// Add the tables to the root schema
|
||||
|
@ -345,3 +346,22 @@ func aclsTableSchema() *memdb.TableSchema {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// coordinatesTableSchema returns a new table schema used for storing
|
||||
// network coordinates.
|
||||
func coordinatesTableSchema() *memdb.TableSchema {
|
||||
return &memdb.TableSchema{
|
||||
Name: "coordinates",
|
||||
Indexes: map[string]*memdb.IndexSchema{
|
||||
"id": &memdb.IndexSchema{
|
||||
Name: "id",
|
||||
AllowMissing: false,
|
||||
Unique: true,
|
||||
Indexer: &memdb.StringFieldIndex{
|
||||
Field: "Node",
|
||||
Lowercase: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/consul/consul/structs"
|
||||
"github.com/hashicorp/go-memdb"
|
||||
"github.com/hashicorp/serf/coordinate"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -196,6 +197,15 @@ func (s *StateSnapshot) ACLs() (memdb.ResultIterator, error) {
|
|||
return iter, nil
|
||||
}
|
||||
|
||||
// Coordinates is used to pull all the coordinates from the snapshot.
|
||||
func (s *StateSnapshot) Coordinates() (memdb.ResultIterator, error) {
|
||||
iter, err := s.tx.Get("coordinates", "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
// Restore is used to efficiently manage restoring a large amount of data into
|
||||
// the state store. It works by doing all the restores inside of a single
|
||||
// transaction.
|
||||
|
@ -299,6 +309,24 @@ func (s *StateRestore) ACL(acl *structs.ACL) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Coordinates is used when restoring from a snapshot. For general inserts, use
|
||||
// CoordinateBatchUpdate. We do less vetting of the updates here because they
|
||||
// already got checked on the way in during a batch update.
|
||||
func (s *StateRestore) Coordinates(idx uint64, updates structs.Coordinates) error {
|
||||
for _, update := range updates {
|
||||
if err := s.tx.Insert("coordinates", update); err != nil {
|
||||
return fmt.Errorf("failed restoring coordinate: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := indexUpdateMaxTxn(s.tx, idx, "coordinates"); err != nil {
|
||||
return fmt.Errorf("failed updating index: %s", err)
|
||||
}
|
||||
|
||||
s.watches.Arm("coordinates")
|
||||
return nil
|
||||
}
|
||||
|
||||
// maxIndex is a helper used to retrieve the highest known index
|
||||
// amongst a set of tables in the db.
|
||||
func (s *StateStore) maxIndex(tables ...string) uint64 {
|
||||
|
@ -379,6 +407,8 @@ func (s *StateStore) getWatchTables(method string) []string {
|
|||
return []string{"sessions"}
|
||||
case "ACLGet", "ACLList":
|
||||
return []string{"acls"}
|
||||
case "Coordinates":
|
||||
return []string{"coordinates"}
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unknown method %s", method))
|
||||
|
@ -583,7 +613,6 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err
|
|||
// Use a watch manager since the inner functions can perform multiple
|
||||
// ops per table.
|
||||
watches := NewDumbWatchManager(s.tableWatches)
|
||||
watches.Arm("nodes")
|
||||
|
||||
// Delete all services associated with the node and update the service index.
|
||||
services, err := tx.Get("services", "node", nodeID)
|
||||
|
@ -620,6 +649,21 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err
|
|||
}
|
||||
}
|
||||
|
||||
// Delete any coordinate associated with this node.
|
||||
coord, err := tx.First("coordinates", "id", nodeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed coordinate lookup: %s", err)
|
||||
}
|
||||
if coord != nil {
|
||||
if err := tx.Delete("coordinates", coord); err != nil {
|
||||
return fmt.Errorf("failed deleting coordinate: %s", err)
|
||||
}
|
||||
if err := tx.Insert("index", &IndexEntry{"coordinates", idx}); err != nil {
|
||||
return fmt.Errorf("failed updating index: %s", err)
|
||||
}
|
||||
watches.Arm("coordinates")
|
||||
}
|
||||
|
||||
// Delete the node and update the index.
|
||||
if err := tx.Delete("nodes", node); err != nil {
|
||||
return fmt.Errorf("failed deleting node: %s", err)
|
||||
|
@ -645,6 +689,7 @@ func (s *StateStore) deleteNodeTxn(tx *memdb.Txn, idx uint64, nodeID string) err
|
|||
}
|
||||
}
|
||||
|
||||
watches.Arm("nodes")
|
||||
tx.Defer(func() { watches.Notify() })
|
||||
return nil
|
||||
}
|
||||
|
@ -2231,3 +2276,84 @@ func (s *StateStore) aclDeleteTxn(tx *memdb.Txn, idx uint64, aclID string) error
|
|||
tx.Defer(func() { s.tableWatches["acls"].Notify() })
|
||||
return nil
|
||||
}
|
||||
|
||||
// CoordinateGetRaw queries for the coordinate of the given node. This is an
|
||||
// unusual state store method because it just returns the raw coordinate or
|
||||
// nil, none of the Raft or node information is returned. This hits the 90%
|
||||
// internal-to-Consul use case for this data, and this isn't exposed via an
|
||||
// endpoint, so it doesn't matter that the Raft info isn't available.
|
||||
func (s *StateStore) CoordinateGetRaw(node string) (*coordinate.Coordinate, error) {
|
||||
tx := s.db.Txn(false)
|
||||
defer tx.Abort()
|
||||
|
||||
// Pull the full coordinate entry.
|
||||
coord, err := tx.First("coordinates", "id", node)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed coordinate lookup: %s", err)
|
||||
}
|
||||
|
||||
// Pick out just the raw coordinate.
|
||||
if coord != nil {
|
||||
return coord.(*structs.Coordinate).Coord, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Coordinates queries for all nodes with coordinates.
|
||||
func (s *StateStore) Coordinates() (uint64, structs.Coordinates, error) {
|
||||
tx := s.db.Txn(false)
|
||||
defer tx.Abort()
|
||||
|
||||
// Get the table index.
|
||||
idx := maxIndexTxn(tx, s.getWatchTables("Coordinates")...)
|
||||
|
||||
// Pull all the coordinates.
|
||||
coords, err := tx.Get("coordinates", "id")
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed coordinate lookup: %s", err)
|
||||
}
|
||||
var results structs.Coordinates
|
||||
for coord := coords.Next(); coord != nil; coord = coords.Next() {
|
||||
results = append(results, coord.(*structs.Coordinate))
|
||||
}
|
||||
return idx, results, nil
|
||||
}
|
||||
|
||||
// CoordinateBatchUpdate processes a batch of coordinate updates and applies
|
||||
// them in a single transaction.
|
||||
func (s *StateStore) CoordinateBatchUpdate(idx uint64, updates structs.Coordinates) error {
|
||||
tx := s.db.Txn(true)
|
||||
defer tx.Abort()
|
||||
|
||||
// Upsert the coordinates.
|
||||
for _, update := range updates {
|
||||
// Since the cleanup of coordinates is tied to deletion of
|
||||
// nodes, we silently drop any updates for nodes that we don't
|
||||
// know about. This might be possible during normal operation
|
||||
// if we happen to get a coordinate update for a node that
|
||||
// hasn't been able to add itself to the catalog yet. Since we
|
||||
// don't carefully sequence this, and since it will fix itself
|
||||
// on the next coordinate update from that node, we don't return
|
||||
// an error or log anything.
|
||||
node, err := tx.First("nodes", "id", update.Node)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed node lookup: %s", err)
|
||||
}
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := tx.Insert("coordinates", update); err != nil {
|
||||
return fmt.Errorf("failed inserting coordinate: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the index.
|
||||
if err := tx.Insert("index", &IndexEntry{"coordinates", idx}); err != nil {
|
||||
return fmt.Errorf("failed updating index: %s", err)
|
||||
}
|
||||
|
||||
tx.Defer(func() { s.tableWatches["coordinates"].Notify() })
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package state
|
|||
import (
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/consul/structs"
|
||||
"github.com/hashicorp/serf/coordinate"
|
||||
)
|
||||
|
||||
func testUUID() string {
|
||||
|
@ -999,17 +1001,28 @@ func TestStateStore_Node_Watches(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
// Check that a delete of a node + service + check triggers all three
|
||||
// tables in one shot.
|
||||
// Check that a delete of a node + service + check + coordinate triggers
|
||||
// all tables in one shot.
|
||||
testRegisterNode(t, s, 4, "node1")
|
||||
testRegisterService(t, s, 5, "node1", "service1")
|
||||
testRegisterCheck(t, s, 6, "node1", "service1", "check3", structs.HealthPassing)
|
||||
updates := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
}
|
||||
if err := s.CoordinateBatchUpdate(7, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
verifyWatch(t, s.getTableWatch("nodes"), func() {
|
||||
verifyWatch(t, s.getTableWatch("services"), func() {
|
||||
verifyWatch(t, s.getTableWatch("checks"), func() {
|
||||
if err := s.DeleteNode(7, "node1"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
verifyWatch(t, s.getTableWatch("coordinates"), func() {
|
||||
if err := s.DeleteNode(7, "node1"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -4721,3 +4734,291 @@ func TestStateStore_ACL_Watches(t *testing.T) {
|
|||
restore.Commit()
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
config := coordinate.DefaultConfig()
|
||||
coord := coordinate.NewCoordinate(config)
|
||||
for i := range coord.Vec {
|
||||
coord.Vec[i] = rand.NormFloat64()
|
||||
}
|
||||
coord.Error = rand.NormFloat64()
|
||||
coord.Adjustment = rand.NormFloat64()
|
||||
return coord
|
||||
}
|
||||
|
||||
func TestStateStore_Coordinate_Updates(t *testing.T) {
|
||||
s := testStateStore(t)
|
||||
|
||||
// Make sure the coordinates list starts out empty, and that a query for
|
||||
// a raw coordinate for a nonexistent node doesn't do anything bad.
|
||||
idx, coords, err := s.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if idx != 0 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
if coords != nil {
|
||||
t.Fatalf("bad: %#v", coords)
|
||||
}
|
||||
coord, err := s.CoordinateGetRaw("nope")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if coord != nil {
|
||||
t.Fatalf("bad: %#v", coord)
|
||||
}
|
||||
|
||||
// Make an update for nodes that don't exist and make sure they get
|
||||
// ignored.
|
||||
updates := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
&structs.Coordinate{
|
||||
Node: "node2",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
}
|
||||
if err := s.CoordinateBatchUpdate(1, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Should still be empty, though applying an empty batch does bump
|
||||
// the table index.
|
||||
idx, coords, err = s.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if idx != 1 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
if coords != nil {
|
||||
t.Fatalf("bad: %#v", coords)
|
||||
}
|
||||
|
||||
// Register the nodes then do the update again.
|
||||
testRegisterNode(t, s, 1, "node1")
|
||||
testRegisterNode(t, s, 2, "node2")
|
||||
if err := s.CoordinateBatchUpdate(3, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Should go through now.
|
||||
idx, coords, err = s.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if idx != 3 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
if !reflect.DeepEqual(coords, updates) {
|
||||
t.Fatalf("bad: %#v", coords)
|
||||
}
|
||||
|
||||
// Also verify the raw coordinate interface.
|
||||
for _, update := range updates {
|
||||
coord, err := s.CoordinateGetRaw(update.Node)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(coord, update.Coord) {
|
||||
t.Fatalf("bad: %#v", coord)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the coordinate for one of the nodes.
|
||||
updates[1].Coord = generateRandomCoordinate()
|
||||
if err := s.CoordinateBatchUpdate(4, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Verify it got applied.
|
||||
idx, coords, err = s.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if idx != 4 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
if !reflect.DeepEqual(coords, updates) {
|
||||
t.Fatalf("bad: %#v", coords)
|
||||
}
|
||||
|
||||
// And check the raw coordinate version of the same thing.
|
||||
for _, update := range updates {
|
||||
coord, err := s.CoordinateGetRaw(update.Node)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(coord, update.Coord) {
|
||||
t.Fatalf("bad: %#v", coord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateStore_Coordinate_Cleanup(t *testing.T) {
|
||||
s := testStateStore(t)
|
||||
|
||||
// Register a node and update its coordinate.
|
||||
testRegisterNode(t, s, 1, "node1")
|
||||
updates := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
}
|
||||
if err := s.CoordinateBatchUpdate(2, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Make sure it's in there.
|
||||
coord, err := s.CoordinateGetRaw("node1")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(coord, updates[0].Coord) {
|
||||
t.Fatalf("bad: %#v", coord)
|
||||
}
|
||||
|
||||
// Now delete the node.
|
||||
if err := s.DeleteNode(3, "node1"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Make sure the coordinate is gone.
|
||||
coord, err = s.CoordinateGetRaw("node1")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if coord != nil {
|
||||
t.Fatalf("bad: %#v", coord)
|
||||
}
|
||||
|
||||
// Make sure the index got updated.
|
||||
idx, coords, err := s.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if idx != 3 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
if coords != nil {
|
||||
t.Fatalf("bad: %#v", coords)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateStore_Coordinate_Snapshot_Restore(t *testing.T) {
|
||||
s := testStateStore(t)
|
||||
|
||||
// Register two nodes and update their coordinates.
|
||||
testRegisterNode(t, s, 1, "node1")
|
||||
testRegisterNode(t, s, 2, "node2")
|
||||
updates := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
&structs.Coordinate{
|
||||
Node: "node2",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
}
|
||||
if err := s.CoordinateBatchUpdate(3, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Snapshot the coordinates.
|
||||
snap := s.Snapshot()
|
||||
defer snap.Close()
|
||||
|
||||
// Alter the real state store.
|
||||
trash := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
&structs.Coordinate{
|
||||
Node: "node2",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
}
|
||||
if err := s.CoordinateBatchUpdate(4, trash); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Verify the snapshot.
|
||||
if idx := snap.LastIndex(); idx != 3 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
iter, err := snap.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
var dump structs.Coordinates
|
||||
for coord := iter.Next(); coord != nil; coord = iter.Next() {
|
||||
dump = append(dump, coord.(*structs.Coordinate))
|
||||
}
|
||||
if !reflect.DeepEqual(dump, updates) {
|
||||
t.Fatalf("bad: %#v", dump)
|
||||
}
|
||||
|
||||
// Restore the values into a new state store.
|
||||
func() {
|
||||
s := testStateStore(t)
|
||||
restore := s.Restore()
|
||||
if err := restore.Coordinates(5, dump); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
restore.Commit()
|
||||
|
||||
// Read the restored coordinates back out and verify that they match.
|
||||
idx, res, err := s.Coordinates()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if idx != 5 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
if !reflect.DeepEqual(res, updates) {
|
||||
t.Fatalf("bad: %#v", res)
|
||||
}
|
||||
|
||||
// Check that the index was updated (note that it got passed
|
||||
// in during the restore).
|
||||
if idx := s.maxIndex("coordinates"); idx != 5 {
|
||||
t.Fatalf("bad index: %d", idx)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func TestStateStore_Coordinate_Watches(t *testing.T) {
|
||||
s := testStateStore(t)
|
||||
|
||||
testRegisterNode(t, s, 1, "node1")
|
||||
|
||||
// Call functions that update the coordinates table and make sure a watch fires
|
||||
// each time.
|
||||
verifyWatch(t, s.getTableWatch("coordinates"), func() {
|
||||
updates := structs.Coordinates{
|
||||
&structs.Coordinate{
|
||||
Node: "node1",
|
||||
Coord: generateRandomCoordinate(),
|
||||
},
|
||||
}
|
||||
if err := s.CoordinateBatchUpdate(2, updates); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
})
|
||||
verifyWatch(t, s.getTableWatch("coordinates"), func() {
|
||||
if err := s.DeleteNode(3, "node1"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -637,6 +637,8 @@ type Coordinate struct {
|
|||
Coord *coordinate.Coordinate
|
||||
}
|
||||
|
||||
type Coordinates []*Coordinate
|
||||
|
||||
// IndexedCoordinate is used to represent a single node's coordinate from the state
|
||||
// store.
|
||||
type IndexedCoordinate struct {
|
||||
|
@ -647,7 +649,7 @@ type IndexedCoordinate struct {
|
|||
// IndexedCoordinates is used to represent a list of nodes and their
|
||||
// corresponding raw coordinates.
|
||||
type IndexedCoordinates struct {
|
||||
Coordinates []Coordinate
|
||||
Coordinates Coordinates
|
||||
QueryMeta
|
||||
}
|
||||
|
||||
|
@ -655,7 +657,7 @@ type IndexedCoordinates struct {
|
|||
// associated with a datacenter.
|
||||
type DatacenterMap struct {
|
||||
Datacenter string
|
||||
Coordinates []Coordinate
|
||||
Coordinates Coordinates
|
||||
}
|
||||
|
||||
// CoordinateUpdateRequest is used to update the network coordinate of a given
|
||||
|
|
Loading…
Reference in New Issue