github.imxd.top/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 }