github.phpd.cn/hashicorp/consul@v1.4.5/agent/consul/coordinate_endpoint_test.go (about)

     1  package consul
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"math/rand"
     7  	"net/rpc"
     8  	"os"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/hashicorp/consul/acl"
    14  	"github.com/hashicorp/consul/agent/structs"
    15  	"github.com/hashicorp/consul/lib"
    16  	"github.com/hashicorp/consul/testrpc"
    17  	"github.com/hashicorp/consul/testutil/retry"
    18  	"github.com/hashicorp/net-rpc-msgpackrpc"
    19  	"github.com/hashicorp/serf/coordinate"
    20  	"github.com/pascaldekloe/goe/verify"
    21  )
    22  
    23  // generateRandomCoordinate creates a random coordinate. This mucks with the
    24  // underlying structure directly, so it's not really useful for any particular
    25  // position in the network, but it's a good payload to send through to make
    26  // sure things come out the other side or get stored correctly.
    27  func generateRandomCoordinate() *coordinate.Coordinate {
    28  	config := coordinate.DefaultConfig()
    29  	coord := coordinate.NewCoordinate(config)
    30  	for i := range coord.Vec {
    31  		coord.Vec[i] = rand.NormFloat64()
    32  	}
    33  	coord.Error = rand.NormFloat64()
    34  	coord.Adjustment = rand.NormFloat64()
    35  	return coord
    36  }
    37  
    38  func TestCoordinate_Update(t *testing.T) {
    39  	t.Parallel()
    40  	dir1, s1 := testServerWithConfig(t, func(c *Config) {
    41  		c.CoordinateUpdatePeriod = 500 * time.Millisecond
    42  		c.CoordinateUpdateBatchSize = 5
    43  		c.CoordinateUpdateMaxBatches = 2
    44  	})
    45  	defer os.RemoveAll(dir1)
    46  	defer s1.Shutdown()
    47  
    48  	codec := rpcClient(t, s1)
    49  	defer codec.Close()
    50  	testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
    51  
    52  	// Register some nodes.
    53  	nodes := []string{"node1", "node2"}
    54  	if err := registerNodes(nodes, codec); err != nil {
    55  		t.Fatal(err)
    56  	}
    57  
    58  	// Send an update for the first node.
    59  	arg1 := structs.CoordinateUpdateRequest{
    60  		Datacenter: "dc1",
    61  		Node:       "node1",
    62  		Coord:      generateRandomCoordinate(),
    63  	}
    64  	var out struct{}
    65  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg1, &out); err != nil {
    66  		t.Fatalf("err: %v", err)
    67  	}
    68  
    69  	// Send an update for the second node.
    70  	arg2 := structs.CoordinateUpdateRequest{
    71  		Datacenter: "dc1",
    72  		Node:       "node2",
    73  		Coord:      generateRandomCoordinate(),
    74  	}
    75  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out); err != nil {
    76  		t.Fatalf("err: %v", err)
    77  	}
    78  
    79  	// Make sure the updates did not yet apply because the update period
    80  	// hasn't expired.
    81  	state := s1.fsm.State()
    82  	_, c, err := state.Coordinate("node1", nil)
    83  	if err != nil {
    84  		t.Fatalf("err: %v", err)
    85  	}
    86  	verify.Values(t, "", c, lib.CoordinateSet{})
    87  
    88  	_, c, err = state.Coordinate("node2", nil)
    89  	if err != nil {
    90  		t.Fatalf("err: %v", err)
    91  	}
    92  	verify.Values(t, "", c, lib.CoordinateSet{})
    93  
    94  	// Send another update for the second node. It should take precedence
    95  	// since there will be two updates in the same batch.
    96  	arg2.Coord = generateRandomCoordinate()
    97  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out); err != nil {
    98  		t.Fatalf("err: %v", err)
    99  	}
   100  
   101  	// Wait a while and the updates should get picked up.
   102  	time.Sleep(3 * s1.config.CoordinateUpdatePeriod)
   103  	_, c, err = state.Coordinate("node1", nil)
   104  	if err != nil {
   105  		t.Fatalf("err: %v", err)
   106  	}
   107  	expected := lib.CoordinateSet{
   108  		"": arg1.Coord,
   109  	}
   110  	verify.Values(t, "", c, expected)
   111  
   112  	_, c, err = state.Coordinate("node2", nil)
   113  	if err != nil {
   114  		t.Fatalf("err: %v", err)
   115  	}
   116  	expected = lib.CoordinateSet{
   117  		"": arg2.Coord,
   118  	}
   119  	verify.Values(t, "", c, expected)
   120  
   121  	// Register a bunch of additional nodes.
   122  	spamLen := s1.config.CoordinateUpdateBatchSize*s1.config.CoordinateUpdateMaxBatches + 1
   123  	for i := 0; i < spamLen; i++ {
   124  		req := structs.RegisterRequest{
   125  			Datacenter: "dc1",
   126  			Node:       fmt.Sprintf("bogusnode%d", i),
   127  			Address:    "127.0.0.1",
   128  		}
   129  		var reply struct{}
   130  		if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
   131  			t.Fatalf("err: %v", err)
   132  		}
   133  	}
   134  
   135  	// Now spam some coordinate updates and make sure it starts throwing
   136  	// them away if they exceed the batch allowance. Note we have to make
   137  	// unique names since these are held in map by node name.
   138  	for i := 0; i < spamLen; i++ {
   139  		arg1.Node = fmt.Sprintf("bogusnode%d", i)
   140  		arg1.Coord = generateRandomCoordinate()
   141  		if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg1, &out); err != nil {
   142  			t.Fatalf("err: %v", err)
   143  		}
   144  	}
   145  
   146  	// Wait a little while for the batch routine to run, then make sure
   147  	// exactly one of the updates got dropped (we won't know which one).
   148  	time.Sleep(3 * s1.config.CoordinateUpdatePeriod)
   149  	numDropped := 0
   150  	for i := 0; i < spamLen; i++ {
   151  		_, c, err = state.Coordinate(fmt.Sprintf("bogusnode%d", i), nil)
   152  		if err != nil {
   153  			t.Fatalf("err: %v", err)
   154  		}
   155  		if len(c) == 0 {
   156  			numDropped++
   157  		}
   158  	}
   159  	if numDropped != 1 {
   160  		t.Fatalf("wrong number of coordinates dropped, %d != 1", numDropped)
   161  	}
   162  
   163  	// Send a coordinate with a NaN to make sure that we don't absorb that
   164  	// into the database.
   165  	arg2.Coord.Vec[0] = math.NaN()
   166  	err = msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out)
   167  	if err == nil || !strings.Contains(err.Error(), "invalid coordinate") {
   168  		t.Fatalf("should have failed with an error, got %v", err)
   169  	}
   170  
   171  	// Finally, send a coordinate with the wrong dimensionality to make sure
   172  	// there are no panics, and that it gets rejected.
   173  	arg2.Coord.Vec = make([]float64, 2*len(arg2.Coord.Vec))
   174  	err = msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out)
   175  	if err == nil || !strings.Contains(err.Error(), "incompatible coordinate") {
   176  		t.Fatalf("should have failed with an error, got %v", err)
   177  	}
   178  }
   179  
   180  func TestCoordinate_Update_ACLDeny(t *testing.T) {
   181  	t.Parallel()
   182  	dir1, s1 := testServerWithConfig(t, func(c *Config) {
   183  		c.ACLDatacenter = "dc1"
   184  		c.ACLsEnabled = true
   185  		c.ACLMasterToken = "root"
   186  		c.ACLDefaultPolicy = "deny"
   187  		c.ACLEnforceVersion8 = false
   188  	})
   189  	defer os.RemoveAll(dir1)
   190  	defer s1.Shutdown()
   191  	codec := rpcClient(t, s1)
   192  	defer codec.Close()
   193  
   194  	testrpc.WaitForLeader(t, s1.RPC, "dc1")
   195  
   196  	// Register some nodes.
   197  	nodes := []string{"node1", "node2"}
   198  	if err := registerNodes(nodes, codec); err != nil {
   199  		t.Fatal(err)
   200  	}
   201  
   202  	// Send an update for the first node. This should go through since we
   203  	// don't have version 8 ACLs enforced yet.
   204  	req := structs.CoordinateUpdateRequest{
   205  		Datacenter: "dc1",
   206  		Node:       "node1",
   207  		Coord:      generateRandomCoordinate(),
   208  	}
   209  	var out struct{}
   210  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil {
   211  		t.Fatalf("err: %v", err)
   212  	}
   213  
   214  	// Now turn on version 8 enforcement and try again.
   215  	s1.config.ACLEnforceVersion8 = true
   216  	err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out)
   217  	if !acl.IsErrPermissionDenied(err) {
   218  		t.Fatalf("err: %v", err)
   219  	}
   220  
   221  	// Create an ACL that can write to the node.
   222  	arg := structs.ACLRequest{
   223  		Datacenter: "dc1",
   224  		Op:         structs.ACLSet,
   225  		ACL: structs.ACL{
   226  			Name: "User token",
   227  			Type: structs.ACLTokenTypeClient,
   228  			Rules: `
   229  node "node1" {
   230  	policy = "write"
   231  }
   232  `,
   233  		},
   234  		WriteRequest: structs.WriteRequest{Token: "root"},
   235  	}
   236  	var id string
   237  	if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &id); err != nil {
   238  		t.Fatalf("err: %v", err)
   239  	}
   240  
   241  	// With the token, it should now go through.
   242  	req.Token = id
   243  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil {
   244  		t.Fatalf("err: %v", err)
   245  	}
   246  
   247  	// But it should be blocked for the other node.
   248  	req.Node = "node2"
   249  	err = msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out)
   250  	if !acl.IsErrPermissionDenied(err) {
   251  		t.Fatalf("err: %v", err)
   252  	}
   253  }
   254  
   255  func TestCoordinate_ListDatacenters(t *testing.T) {
   256  	t.Parallel()
   257  	dir1, s1 := testServer(t)
   258  	defer os.RemoveAll(dir1)
   259  	defer s1.Shutdown()
   260  	codec := rpcClient(t, s1)
   261  	defer codec.Close()
   262  
   263  	testrpc.WaitForLeader(t, s1.RPC, "dc1")
   264  
   265  	// It's super hard to force the Serfs into a known configuration of
   266  	// coordinates, so the best we can do is make sure our own DC shows
   267  	// up in the list with the proper coordinates. The guts of the algorithm
   268  	// are extensively tested in rtt_test.go using a mock database.
   269  	var out []structs.DatacenterMap
   270  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListDatacenters", struct{}{}, &out); err != nil {
   271  		t.Fatalf("err: %v", err)
   272  	}
   273  	if len(out) != 1 ||
   274  		out[0].Datacenter != "dc1" ||
   275  		len(out[0].Coordinates) != 1 ||
   276  		out[0].Coordinates[0].Node != s1.config.NodeName {
   277  		t.Fatalf("bad: %v", out)
   278  	}
   279  	c, err := s1.serfWAN.GetCoordinate()
   280  	if err != nil {
   281  		t.Fatalf("bad: %v", err)
   282  	}
   283  	verify.Values(t, "", c, out[0].Coordinates[0].Coord)
   284  }
   285  
   286  func TestCoordinate_ListNodes(t *testing.T) {
   287  	t.Parallel()
   288  	dir1, s1 := testServer(t)
   289  	defer os.RemoveAll(dir1)
   290  	defer s1.Shutdown()
   291  
   292  	codec := rpcClient(t, s1)
   293  	defer codec.Close()
   294  	testrpc.WaitForLeader(t, s1.RPC, "dc1")
   295  
   296  	// Register some nodes.
   297  	nodes := []string{"foo", "bar", "baz"}
   298  	if err := registerNodes(nodes, codec); err != nil {
   299  		t.Fatal(err)
   300  	}
   301  
   302  	// Send coordinate updates for a few nodes.
   303  	arg1 := structs.CoordinateUpdateRequest{
   304  		Datacenter: "dc1",
   305  		Node:       "foo",
   306  		Coord:      generateRandomCoordinate(),
   307  	}
   308  	var out struct{}
   309  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg1, &out); err != nil {
   310  		t.Fatalf("err: %v", err)
   311  	}
   312  
   313  	arg2 := structs.CoordinateUpdateRequest{
   314  		Datacenter: "dc1",
   315  		Node:       "bar",
   316  		Coord:      generateRandomCoordinate(),
   317  	}
   318  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out); err != nil {
   319  		t.Fatalf("err: %v", err)
   320  	}
   321  
   322  	arg3 := structs.CoordinateUpdateRequest{
   323  		Datacenter: "dc1",
   324  		Node:       "baz",
   325  		Coord:      generateRandomCoordinate(),
   326  	}
   327  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg3, &out); err != nil {
   328  		t.Fatalf("err: %v", err)
   329  	}
   330  	// Now query back for all the nodes.
   331  	retry.Run(t, func(r *retry.R) {
   332  		arg := structs.DCSpecificRequest{
   333  			Datacenter: "dc1",
   334  		}
   335  		resp := structs.IndexedCoordinates{}
   336  		if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
   337  			r.Fatalf("err: %v", err)
   338  		}
   339  		if len(resp.Coordinates) != 3 ||
   340  			resp.Coordinates[0].Node != "bar" ||
   341  			resp.Coordinates[1].Node != "baz" ||
   342  			resp.Coordinates[2].Node != "foo" {
   343  			r.Fatalf("bad: %v", resp.Coordinates)
   344  		}
   345  		verify.Values(t, "", resp.Coordinates[0].Coord, arg2.Coord) // bar
   346  		verify.Values(t, "", resp.Coordinates[1].Coord, arg3.Coord) // baz
   347  		verify.Values(t, "", resp.Coordinates[2].Coord, arg1.Coord) // foo
   348  	})
   349  }
   350  
   351  func TestCoordinate_ListNodes_ACLFilter(t *testing.T) {
   352  	t.Parallel()
   353  	dir1, s1 := testServerWithConfig(t, func(c *Config) {
   354  		c.ACLDatacenter = "dc1"
   355  		c.ACLsEnabled = true
   356  		c.ACLMasterToken = "root"
   357  		c.ACLDefaultPolicy = "deny"
   358  		c.ACLEnforceVersion8 = false
   359  	})
   360  	defer os.RemoveAll(dir1)
   361  	defer s1.Shutdown()
   362  	codec := rpcClient(t, s1)
   363  	defer codec.Close()
   364  
   365  	testrpc.WaitForLeader(t, s1.RPC, "dc1")
   366  
   367  	// Register some nodes.
   368  	nodes := []string{"foo", "bar", "baz"}
   369  	for _, node := range nodes {
   370  		req := structs.RegisterRequest{
   371  			Datacenter: "dc1",
   372  			Node:       node,
   373  			Address:    "127.0.0.1",
   374  			WriteRequest: structs.WriteRequest{
   375  				Token: "root",
   376  			},
   377  		}
   378  		var reply struct{}
   379  		if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
   380  			t.Fatalf("err: %v", err)
   381  		}
   382  	}
   383  
   384  	// Send coordinate updates for a few nodes.
   385  	arg1 := structs.CoordinateUpdateRequest{
   386  		Datacenter: "dc1",
   387  		Node:       "foo",
   388  		Coord:      generateRandomCoordinate(),
   389  		WriteRequest: structs.WriteRequest{
   390  			Token: "root",
   391  		},
   392  	}
   393  	var out struct{}
   394  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg1, &out); err != nil {
   395  		t.Fatalf("err: %v", err)
   396  	}
   397  
   398  	arg2 := structs.CoordinateUpdateRequest{
   399  		Datacenter: "dc1",
   400  		Node:       "bar",
   401  		Coord:      generateRandomCoordinate(),
   402  		WriteRequest: structs.WriteRequest{
   403  			Token: "root",
   404  		},
   405  	}
   406  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out); err != nil {
   407  		t.Fatalf("err: %v", err)
   408  	}
   409  
   410  	arg3 := structs.CoordinateUpdateRequest{
   411  		Datacenter: "dc1",
   412  		Node:       "baz",
   413  		Coord:      generateRandomCoordinate(),
   414  		WriteRequest: structs.WriteRequest{
   415  			Token: "root",
   416  		},
   417  	}
   418  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg3, &out); err != nil {
   419  		t.Fatalf("err: %v", err)
   420  	}
   421  	// Wait for all the coordinate updates to apply. Since we aren't
   422  	// enforcing version 8 ACLs, this should also allow us to read
   423  	// everything back without a token.
   424  	retry.Run(t, func(r *retry.R) {
   425  		arg := structs.DCSpecificRequest{
   426  			Datacenter: "dc1",
   427  		}
   428  		resp := structs.IndexedCoordinates{}
   429  		if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
   430  			r.Fatalf("err: %v", err)
   431  		}
   432  		if got, want := len(resp.Coordinates), 3; got != want {
   433  			r.Fatalf("got %d coordinates want %d", got, want)
   434  		}
   435  	})
   436  
   437  	// Now that we've waited for the batch processing to ingest the
   438  	// coordinates we can do the rest of the requests without the loop. We
   439  	// will start by turning on version 8 ACL support which should block
   440  	// everything.
   441  	s1.config.ACLEnforceVersion8 = true
   442  	arg := structs.DCSpecificRequest{
   443  		Datacenter: "dc1",
   444  	}
   445  	resp := structs.IndexedCoordinates{}
   446  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
   447  		t.Fatalf("err: %v", err)
   448  	}
   449  	if len(resp.Coordinates) != 0 {
   450  		t.Fatalf("bad: %#v", resp.Coordinates)
   451  	}
   452  
   453  	// Create an ACL that can read one of the nodes.
   454  	var id string
   455  	{
   456  		req := structs.ACLRequest{
   457  			Datacenter: "dc1",
   458  			Op:         structs.ACLSet,
   459  			ACL: structs.ACL{
   460  				Name: "User token",
   461  				Type: structs.ACLTokenTypeClient,
   462  				Rules: `
   463  node "foo" {
   464  	policy = "read"
   465  }
   466  `,
   467  			},
   468  			WriteRequest: structs.WriteRequest{Token: "root"},
   469  		}
   470  		if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &id); err != nil {
   471  			t.Fatalf("err: %v", err)
   472  		}
   473  	}
   474  
   475  	// With the token, it should now go through.
   476  	arg.Token = id
   477  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.ListNodes", &arg, &resp); err != nil {
   478  		t.Fatalf("err: %v", err)
   479  	}
   480  	if len(resp.Coordinates) != 1 || resp.Coordinates[0].Node != "foo" {
   481  		t.Fatalf("bad: %#v", resp.Coordinates)
   482  	}
   483  }
   484  
   485  func TestCoordinate_Node(t *testing.T) {
   486  	t.Parallel()
   487  	dir1, s1 := testServer(t)
   488  	defer os.RemoveAll(dir1)
   489  	defer s1.Shutdown()
   490  
   491  	codec := rpcClient(t, s1)
   492  	defer codec.Close()
   493  	testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
   494  
   495  	// Register some nodes.
   496  	nodes := []string{"foo", "bar"}
   497  	if err := registerNodes(nodes, codec); err != nil {
   498  		t.Fatal(err)
   499  	}
   500  
   501  	// Send coordinate updates for each node.
   502  	arg1 := structs.CoordinateUpdateRequest{
   503  		Datacenter: "dc1",
   504  		Node:       "foo",
   505  		Coord:      generateRandomCoordinate(),
   506  	}
   507  	var out struct{}
   508  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg1, &out); err != nil {
   509  		t.Fatalf("err: %v", err)
   510  	}
   511  
   512  	arg2 := structs.CoordinateUpdateRequest{
   513  		Datacenter: "dc1",
   514  		Node:       "bar",
   515  		Coord:      generateRandomCoordinate(),
   516  	}
   517  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &arg2, &out); err != nil {
   518  		t.Fatalf("err: %v", err)
   519  	}
   520  
   521  	// Now query back for a specific node (make sure we only get coordinates for foo).
   522  	retry.Run(t, func(r *retry.R) {
   523  		arg := structs.NodeSpecificRequest{
   524  			Node:       "foo",
   525  			Datacenter: "dc1",
   526  		}
   527  		resp := structs.IndexedCoordinates{}
   528  		if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp); err != nil {
   529  			r.Fatalf("err: %v", err)
   530  		}
   531  		if len(resp.Coordinates) != 1 ||
   532  			resp.Coordinates[0].Node != "foo" {
   533  			r.Fatalf("bad: %v", resp.Coordinates)
   534  		}
   535  		verify.Values(t, "", resp.Coordinates[0].Coord, arg1.Coord) // foo
   536  	})
   537  }
   538  
   539  func TestCoordinate_Node_ACLDeny(t *testing.T) {
   540  	t.Parallel()
   541  	dir1, s1 := testServerWithConfig(t, func(c *Config) {
   542  		c.ACLDatacenter = "dc1"
   543  		c.ACLsEnabled = true
   544  		c.ACLMasterToken = "root"
   545  		c.ACLDefaultPolicy = "deny"
   546  		c.ACLEnforceVersion8 = false
   547  	})
   548  	defer os.RemoveAll(dir1)
   549  	defer s1.Shutdown()
   550  	codec := rpcClient(t, s1)
   551  	defer codec.Close()
   552  
   553  	testrpc.WaitForLeader(t, s1.RPC, "dc1")
   554  
   555  	// Register some nodes.
   556  	nodes := []string{"node1", "node2"}
   557  	if err := registerNodes(nodes, codec); err != nil {
   558  		t.Fatal(err)
   559  	}
   560  
   561  	coord := generateRandomCoordinate()
   562  	req := structs.CoordinateUpdateRequest{
   563  		Datacenter: "dc1",
   564  		Node:       "node1",
   565  		Coord:      coord,
   566  	}
   567  	var out struct{}
   568  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &req, &out); err != nil {
   569  		t.Fatalf("err: %v", err)
   570  	}
   571  
   572  	// Try a read for the first node. This should go through since we
   573  	// don't have version 8 ACLs enforced yet.
   574  	arg := structs.NodeSpecificRequest{
   575  		Node:       "node1",
   576  		Datacenter: "dc1",
   577  	}
   578  	resp := structs.IndexedCoordinates{}
   579  	retry.Run(t, func(r *retry.R) {
   580  		if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp); err != nil {
   581  			r.Fatalf("err: %v", err)
   582  		}
   583  		if len(resp.Coordinates) != 1 ||
   584  			resp.Coordinates[0].Node != "node1" {
   585  			r.Fatalf("bad: %v", resp.Coordinates)
   586  		}
   587  		verify.Values(t, "", resp.Coordinates[0].Coord, coord)
   588  	})
   589  
   590  	// Now turn on version 8 enforcement and try again.
   591  	s1.config.ACLEnforceVersion8 = true
   592  	err := msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp)
   593  	if !acl.IsErrPermissionDenied(err) {
   594  		t.Fatalf("err: %v", err)
   595  	}
   596  
   597  	// Create an ACL that can read from the node.
   598  	aclReq := structs.ACLRequest{
   599  		Datacenter: "dc1",
   600  		Op:         structs.ACLSet,
   601  		ACL: structs.ACL{
   602  			Name: "User token",
   603  			Type: structs.ACLTokenTypeClient,
   604  			Rules: `
   605  node "node1" {
   606  	policy = "read"
   607  }
   608  `,
   609  		},
   610  		WriteRequest: structs.WriteRequest{Token: "root"},
   611  	}
   612  	var id string
   613  	if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &aclReq, &id); err != nil {
   614  		t.Fatalf("err: %v", err)
   615  	}
   616  
   617  	// With the token, it should now go through.
   618  	arg.Token = id
   619  	if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp); err != nil {
   620  		t.Fatalf("err: %v", err)
   621  	}
   622  
   623  	// But it should be blocked for the other node.
   624  	arg.Node = "node2"
   625  	err = msgpackrpc.CallWithCodec(codec, "Coordinate.Node", &arg, &resp)
   626  	if !acl.IsErrPermissionDenied(err) {
   627  		t.Fatalf("err: %v", err)
   628  	}
   629  }
   630  
   631  func registerNodes(nodes []string, codec rpc.ClientCodec) error {
   632  	for _, node := range nodes {
   633  		req := structs.RegisterRequest{
   634  			Datacenter: "dc1",
   635  			Node:       node,
   636  			Address:    "127.0.0.1",
   637  		}
   638  		var reply struct{}
   639  		if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil {
   640  			return err
   641  		}
   642  	}
   643  
   644  	return nil
   645  }