github.com/outbrain/consul@v1.4.5/watch/funcs_test.go (about) 1 package watch_test 2 3 import ( 4 "encoding/json" 5 "errors" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/consul/testrpc" 11 12 "github.com/stretchr/testify/assert" 13 14 "github.com/hashicorp/consul/agent" 15 "github.com/hashicorp/consul/agent/connect" 16 consulapi "github.com/hashicorp/consul/api" 17 "github.com/hashicorp/consul/watch" 18 "github.com/stretchr/testify/require" 19 ) 20 21 var errBadContent = errors.New("bad content") 22 var errTimeout = errors.New("timeout") 23 24 var timeout = 5 * time.Second 25 26 func makeInvokeCh() chan error { 27 ch := make(chan error) 28 time.AfterFunc(timeout, func() { ch <- errTimeout }) 29 return ch 30 } 31 32 func TestKeyWatch(t *testing.T) { 33 t.Parallel() 34 a := agent.NewTestAgent(t, t.Name(), ``) 35 defer a.Shutdown() 36 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 37 38 invoke := makeInvokeCh() 39 plan := mustParse(t, `{"type":"key", "key":"foo/bar/baz"}`) 40 plan.Handler = func(idx uint64, raw interface{}) { 41 if raw == nil { 42 return // ignore 43 } 44 v, ok := raw.(*consulapi.KVPair) 45 if !ok || v == nil { 46 return // ignore 47 } 48 if string(v.Value) != "test" { 49 invoke <- errBadContent 50 return 51 } 52 invoke <- nil 53 } 54 55 var wg sync.WaitGroup 56 wg.Add(1) 57 go func() { 58 defer wg.Done() 59 kv := a.Client().KV() 60 61 time.Sleep(20 * time.Millisecond) 62 pair := &consulapi.KVPair{ 63 Key: "foo/bar/baz", 64 Value: []byte("test"), 65 } 66 if _, err := kv.Put(pair, nil); err != nil { 67 t.Fatalf("err: %v", err) 68 } 69 }() 70 71 wg.Add(1) 72 go func() { 73 defer wg.Done() 74 if err := plan.Run(a.HTTPAddr()); err != nil { 75 t.Fatalf("err: %v", err) 76 } 77 }() 78 79 if err := <-invoke; err != nil { 80 t.Fatalf("err: %v", err) 81 } 82 83 plan.Stop() 84 wg.Wait() 85 } 86 87 func TestKeyWatch_With_PrefixDelete(t *testing.T) { 88 t.Parallel() 89 a := agent.NewTestAgent(t, t.Name(), ``) 90 defer a.Shutdown() 91 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 92 93 invoke := makeInvokeCh() 94 plan := mustParse(t, `{"type":"key", "key":"foo/bar/baz"}`) 95 plan.Handler = func(idx uint64, raw interface{}) { 96 if raw == nil { 97 return // ignore 98 } 99 v, ok := raw.(*consulapi.KVPair) 100 if !ok || v == nil { 101 return // ignore 102 } 103 if string(v.Value) != "test" { 104 invoke <- errBadContent 105 return 106 } 107 invoke <- nil 108 } 109 110 var wg sync.WaitGroup 111 wg.Add(1) 112 go func() { 113 defer wg.Done() 114 kv := a.Client().KV() 115 116 time.Sleep(20 * time.Millisecond) 117 pair := &consulapi.KVPair{ 118 Key: "foo/bar/baz", 119 Value: []byte("test"), 120 } 121 if _, err := kv.Put(pair, nil); err != nil { 122 t.Fatalf("err: %v", err) 123 } 124 }() 125 126 wg.Add(1) 127 go func() { 128 defer wg.Done() 129 if err := plan.Run(a.HTTPAddr()); err != nil { 130 t.Fatalf("err: %v", err) 131 } 132 }() 133 134 if err := <-invoke; err != nil { 135 t.Fatalf("err: %v", err) 136 } 137 138 plan.Stop() 139 wg.Wait() 140 } 141 142 func TestKeyPrefixWatch(t *testing.T) { 143 t.Parallel() 144 a := agent.NewTestAgent(t, t.Name(), ``) 145 defer a.Shutdown() 146 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 147 148 invoke := makeInvokeCh() 149 plan := mustParse(t, `{"type":"keyprefix", "prefix":"foo/"}`) 150 plan.Handler = func(idx uint64, raw interface{}) { 151 if raw == nil { 152 return // ignore 153 } 154 v, ok := raw.(consulapi.KVPairs) 155 if !ok || len(v) == 0 { 156 return 157 } 158 if string(v[0].Key) != "foo/bar" { 159 invoke <- errBadContent 160 return 161 } 162 invoke <- nil 163 } 164 165 var wg sync.WaitGroup 166 wg.Add(1) 167 go func() { 168 defer wg.Done() 169 kv := a.Client().KV() 170 171 time.Sleep(20 * time.Millisecond) 172 pair := &consulapi.KVPair{ 173 Key: "foo/bar", 174 } 175 if _, err := kv.Put(pair, nil); err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 }() 179 180 wg.Add(1) 181 go func() { 182 defer wg.Done() 183 if err := plan.Run(a.HTTPAddr()); err != nil { 184 t.Fatalf("err: %v", err) 185 } 186 }() 187 188 if err := <-invoke; err != nil { 189 t.Fatalf("err: %v", err) 190 } 191 192 plan.Stop() 193 wg.Wait() 194 } 195 196 func TestServicesWatch(t *testing.T) { 197 t.Parallel() 198 a := agent.NewTestAgent(t, t.Name(), ``) 199 defer a.Shutdown() 200 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 201 202 invoke := makeInvokeCh() 203 plan := mustParse(t, `{"type":"services"}`) 204 plan.Handler = func(idx uint64, raw interface{}) { 205 if raw == nil { 206 return // ignore 207 } 208 v, ok := raw.(map[string][]string) 209 if !ok || len(v) == 0 { 210 return // ignore 211 } 212 if v["consul"] == nil { 213 invoke <- errBadContent 214 return 215 } 216 invoke <- nil 217 } 218 219 var wg sync.WaitGroup 220 wg.Add(1) 221 go func() { 222 defer wg.Done() 223 agent := a.Client().Agent() 224 225 time.Sleep(20 * time.Millisecond) 226 reg := &consulapi.AgentServiceRegistration{ 227 ID: "foo", 228 Name: "foo", 229 } 230 if err := agent.ServiceRegister(reg); err != nil { 231 t.Fatalf("err: %v", err) 232 } 233 }() 234 235 wg.Add(1) 236 go func() { 237 defer wg.Done() 238 if err := plan.Run(a.HTTPAddr()); err != nil { 239 t.Fatalf("err: %v", err) 240 } 241 }() 242 243 if err := <-invoke; err != nil { 244 t.Fatalf("err: %v", err) 245 } 246 247 plan.Stop() 248 wg.Wait() 249 } 250 251 func TestNodesWatch(t *testing.T) { 252 t.Parallel() 253 a := agent.NewTestAgent(t, t.Name(), ``) 254 defer a.Shutdown() 255 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 256 257 invoke := makeInvokeCh() 258 plan := mustParse(t, `{"type":"nodes"}`) 259 plan.Handler = func(idx uint64, raw interface{}) { 260 if raw == nil { 261 return // ignore 262 } 263 v, ok := raw.([]*consulapi.Node) 264 if !ok || len(v) == 0 { 265 return // ignore 266 } 267 invoke <- nil 268 } 269 270 var wg sync.WaitGroup 271 wg.Add(1) 272 go func() { 273 defer wg.Done() 274 catalog := a.Client().Catalog() 275 276 time.Sleep(20 * time.Millisecond) 277 reg := &consulapi.CatalogRegistration{ 278 Node: "foobar", 279 Address: "1.1.1.1", 280 Datacenter: "dc1", 281 } 282 if _, err := catalog.Register(reg, nil); err != nil { 283 t.Fatalf("err: %v", err) 284 } 285 }() 286 287 wg.Add(1) 288 go func() { 289 defer wg.Done() 290 if err := plan.Run(a.HTTPAddr()); err != nil { 291 t.Fatalf("err: %v", err) 292 } 293 }() 294 295 if err := <-invoke; err != nil { 296 t.Fatalf("err: %v", err) 297 } 298 299 plan.Stop() 300 wg.Wait() 301 } 302 303 func TestServiceWatch(t *testing.T) { 304 t.Parallel() 305 a := agent.NewTestAgent(t, t.Name(), ``) 306 defer a.Shutdown() 307 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 308 309 invoke := makeInvokeCh() 310 plan := mustParse(t, `{"type":"service", "service":"foo", "tag":"bar", "passingonly":true}`) 311 plan.Handler = func(idx uint64, raw interface{}) { 312 if raw == nil { 313 return // ignore 314 } 315 v, ok := raw.([]*consulapi.ServiceEntry) 316 if !ok || len(v) == 0 { 317 return // ignore 318 } 319 if v[0].Service.ID != "foo" { 320 invoke <- errBadContent 321 return 322 } 323 invoke <- nil 324 } 325 326 var wg sync.WaitGroup 327 328 wg.Add(1) 329 go func() { 330 defer wg.Done() 331 agent := a.Client().Agent() 332 333 time.Sleep(20 * time.Millisecond) 334 reg := &consulapi.AgentServiceRegistration{ 335 ID: "foo", 336 Name: "foo", 337 Tags: []string{"bar"}, 338 } 339 if err := agent.ServiceRegister(reg); err != nil { 340 t.Fatalf("err: %v", err) 341 } 342 }() 343 344 wg.Add(1) 345 go func() { 346 defer wg.Done() 347 if err := plan.Run(a.HTTPAddr()); err != nil { 348 t.Fatalf("err: %v", err) 349 } 350 }() 351 352 if err := <-invoke; err != nil { 353 t.Fatalf("err: %v", err) 354 } 355 356 plan.Stop() 357 wg.Wait() 358 } 359 360 func TestChecksWatch_State(t *testing.T) { 361 t.Parallel() 362 a := agent.NewTestAgent(t, t.Name(), ``) 363 defer a.Shutdown() 364 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 365 366 invoke := makeInvokeCh() 367 plan := mustParse(t, `{"type":"checks", "state":"warning"}`) 368 plan.Handler = func(idx uint64, raw interface{}) { 369 if raw == nil { 370 return // ignore 371 } 372 v, ok := raw.([]*consulapi.HealthCheck) 373 if !ok || len(v) == 0 { 374 return // ignore 375 } 376 if v[0].CheckID != "foobar" || v[0].Status != "warning" { 377 invoke <- errBadContent 378 return 379 } 380 invoke <- nil 381 } 382 383 var wg sync.WaitGroup 384 wg.Add(1) 385 go func() { 386 defer wg.Done() 387 catalog := a.Client().Catalog() 388 389 time.Sleep(20 * time.Millisecond) 390 reg := &consulapi.CatalogRegistration{ 391 Node: "foobar", 392 Address: "1.1.1.1", 393 Datacenter: "dc1", 394 Check: &consulapi.AgentCheck{ 395 Node: "foobar", 396 CheckID: "foobar", 397 Name: "foobar", 398 Status: consulapi.HealthWarning, 399 }, 400 } 401 if _, err := catalog.Register(reg, nil); err != nil { 402 t.Fatalf("err: %v", err) 403 } 404 }() 405 406 wg.Add(1) 407 go func() { 408 defer wg.Done() 409 if err := plan.Run(a.HTTPAddr()); err != nil { 410 t.Fatalf("err: %v", err) 411 } 412 }() 413 414 if err := <-invoke; err != nil { 415 t.Fatalf("err: %v", err) 416 } 417 418 plan.Stop() 419 wg.Wait() 420 } 421 422 func TestChecksWatch_Service(t *testing.T) { 423 t.Parallel() 424 a := agent.NewTestAgent(t, t.Name(), ``) 425 defer a.Shutdown() 426 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 427 428 invoke := makeInvokeCh() 429 plan := mustParse(t, `{"type":"checks", "service":"foobar"}`) 430 plan.Handler = func(idx uint64, raw interface{}) { 431 if raw == nil { 432 return // ignore 433 } 434 v, ok := raw.([]*consulapi.HealthCheck) 435 if !ok || len(v) == 0 { 436 return // ignore 437 } 438 if v[0].CheckID != "foobar" { 439 invoke <- errBadContent 440 return 441 } 442 invoke <- nil 443 } 444 445 var wg sync.WaitGroup 446 wg.Add(1) 447 go func() { 448 defer wg.Done() 449 catalog := a.Client().Catalog() 450 451 time.Sleep(20 * time.Millisecond) 452 reg := &consulapi.CatalogRegistration{ 453 Node: "foobar", 454 Address: "1.1.1.1", 455 Datacenter: "dc1", 456 Service: &consulapi.AgentService{ 457 ID: "foobar", 458 Service: "foobar", 459 }, 460 Check: &consulapi.AgentCheck{ 461 Node: "foobar", 462 CheckID: "foobar", 463 Name: "foobar", 464 Status: consulapi.HealthPassing, 465 ServiceID: "foobar", 466 }, 467 } 468 if _, err := catalog.Register(reg, nil); err != nil { 469 t.Fatalf("err: %v", err) 470 } 471 }() 472 473 wg.Add(1) 474 go func() { 475 defer wg.Done() 476 if err := plan.Run(a.HTTPAddr()); err != nil { 477 t.Fatalf("err: %v", err) 478 } 479 }() 480 481 if err := <-invoke; err != nil { 482 t.Fatalf("err: %v", err) 483 } 484 485 plan.Stop() 486 wg.Wait() 487 } 488 489 func TestEventWatch(t *testing.T) { 490 t.Parallel() 491 a := agent.NewTestAgent(t, t.Name(), ``) 492 defer a.Shutdown() 493 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 494 495 invoke := makeInvokeCh() 496 plan := mustParse(t, `{"type":"event", "name": "foo"}`) 497 plan.Handler = func(idx uint64, raw interface{}) { 498 if raw == nil { 499 return 500 } 501 v, ok := raw.([]*consulapi.UserEvent) 502 if !ok || len(v) == 0 { 503 return // ignore 504 } 505 if string(v[len(v)-1].Name) != "foo" { 506 invoke <- errBadContent 507 return 508 } 509 invoke <- nil 510 } 511 512 var wg sync.WaitGroup 513 wg.Add(1) 514 go func() { 515 defer wg.Done() 516 event := a.Client().Event() 517 518 time.Sleep(20 * time.Millisecond) 519 params := &consulapi.UserEvent{Name: "foo"} 520 if _, _, err := event.Fire(params, nil); err != nil { 521 t.Fatalf("err: %v", err) 522 } 523 }() 524 525 wg.Add(1) 526 go func() { 527 defer wg.Done() 528 if err := plan.Run(a.HTTPAddr()); err != nil { 529 t.Fatalf("err: %v", err) 530 } 531 }() 532 533 if err := <-invoke; err != nil { 534 t.Fatalf("err: %v", err) 535 } 536 537 plan.Stop() 538 wg.Wait() 539 } 540 541 func TestConnectRootsWatch(t *testing.T) { 542 t.Parallel() 543 // NewTestAgent will bootstrap a new CA 544 a := agent.NewTestAgent(t, t.Name(), "") 545 defer a.Shutdown() 546 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 547 548 var originalCAID string 549 invoke := makeInvokeCh() 550 plan := mustParse(t, `{"type":"connect_roots"}`) 551 plan.Handler = func(idx uint64, raw interface{}) { 552 if raw == nil { 553 return // ignore 554 } 555 v, ok := raw.(*consulapi.CARootList) 556 if !ok || v == nil { 557 return // ignore 558 } 559 // Only 1 CA is the bootstrapped state (i.e. first response). Ignore this 560 // state and wait for the new CA to show up too. 561 if len(v.Roots) == 1 { 562 originalCAID = v.ActiveRootID 563 return 564 } 565 assert.NotEmpty(t, originalCAID) 566 assert.NotEqual(t, originalCAID, v.ActiveRootID) 567 invoke <- nil 568 } 569 570 var wg sync.WaitGroup 571 wg.Add(1) 572 go func() { 573 defer wg.Done() 574 time.Sleep(20 * time.Millisecond) 575 // Set a new CA 576 connect.TestCAConfigSet(t, a, nil) 577 }() 578 579 wg.Add(1) 580 go func() { 581 defer wg.Done() 582 if err := plan.Run(a.HTTPAddr()); err != nil { 583 t.Fatalf("err: %v", err) 584 } 585 }() 586 587 if err := <-invoke; err != nil { 588 t.Fatalf("err: %v", err) 589 } 590 591 plan.Stop() 592 wg.Wait() 593 } 594 595 func TestConnectLeafWatch(t *testing.T) { 596 t.Parallel() 597 // NewTestAgent will bootstrap a new CA 598 a := agent.NewTestAgent(t, t.Name(), ``) 599 defer a.Shutdown() 600 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 601 602 // Register a web service to get certs for 603 { 604 agent := a.Client().Agent() 605 reg := consulapi.AgentServiceRegistration{ 606 ID: "web", 607 Name: "web", 608 Port: 9090, 609 } 610 err := agent.ServiceRegister(®) 611 require.Nil(t, err) 612 } 613 614 var lastCert *consulapi.LeafCert 615 616 //invoke := makeInvokeCh() 617 invoke := make(chan error) 618 plan := mustParse(t, `{"type":"connect_leaf", "service":"web"}`) 619 plan.Handler = func(idx uint64, raw interface{}) { 620 if raw == nil { 621 return // ignore 622 } 623 v, ok := raw.(*consulapi.LeafCert) 624 if !ok || v == nil { 625 return // ignore 626 } 627 if lastCert == nil { 628 // Initial fetch, just store the cert and return 629 lastCert = v 630 return 631 } 632 // TODO(banks): right now the root rotation actually causes Serial numbers 633 // to reset so these end up all being the same. That needs fixing but it's 634 // a bigger task than I want to bite off for this PR. 635 //assert.NotEqual(t, lastCert.SerialNumber, v.SerialNumber) 636 assert.NotEqual(t, lastCert.CertPEM, v.CertPEM) 637 assert.NotEqual(t, lastCert.PrivateKeyPEM, v.PrivateKeyPEM) 638 assert.NotEqual(t, lastCert.ModifyIndex, v.ModifyIndex) 639 invoke <- nil 640 } 641 642 var wg sync.WaitGroup 643 wg.Add(1) 644 go func() { 645 defer wg.Done() 646 time.Sleep(20 * time.Millisecond) 647 // Change the CA to trigger a leaf change 648 connect.TestCAConfigSet(t, a, nil) 649 }() 650 651 wg.Add(1) 652 go func() { 653 defer wg.Done() 654 if err := plan.Run(a.HTTPAddr()); err != nil { 655 t.Fatalf("err: %v", err) 656 } 657 }() 658 659 if err := <-invoke; err != nil { 660 t.Fatalf("err: %v", err) 661 } 662 663 plan.Stop() 664 wg.Wait() 665 } 666 667 func TestConnectProxyConfigWatch(t *testing.T) { 668 t.Parallel() 669 a := agent.NewTestAgent(t, t.Name(), ` 670 connect { 671 enabled = true 672 proxy { 673 allow_managed_api_registration = true 674 } 675 } 676 `) 677 defer a.Shutdown() 678 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 679 680 // Register a local agent service with a managed proxy 681 reg := &consulapi.AgentServiceRegistration{ 682 Name: "web", 683 Port: 8080, 684 Connect: &consulapi.AgentServiceConnect{ 685 Proxy: &consulapi.AgentServiceConnectProxy{ 686 Config: map[string]interface{}{ 687 "foo": "bar", 688 }, 689 }, 690 }, 691 } 692 client := a.Client() 693 agent := client.Agent() 694 err := agent.ServiceRegister(reg) 695 require.NoError(t, err) 696 697 invoke := makeInvokeCh() 698 plan := mustParse(t, `{"type":"connect_proxy_config", "proxy_service_id":"web-proxy"}`) 699 plan.HybridHandler = func(blockParamVal watch.BlockingParamVal, raw interface{}) { 700 if raw == nil { 701 return // ignore 702 } 703 v, ok := raw.(*consulapi.ConnectProxyConfig) 704 if !ok || v == nil { 705 return // ignore 706 } 707 invoke <- nil 708 } 709 710 var wg sync.WaitGroup 711 wg.Add(1) 712 go func() { 713 defer wg.Done() 714 time.Sleep(20 * time.Millisecond) 715 716 // Change the proxy's config 717 reg.Connect.Proxy.Config["foo"] = "buzz" 718 reg.Connect.Proxy.Config["baz"] = "qux" 719 err := agent.ServiceRegister(reg) 720 require.NoError(t, err) 721 }() 722 723 wg.Add(1) 724 go func() { 725 defer wg.Done() 726 if err := plan.Run(a.HTTPAddr()); err != nil { 727 t.Fatalf("err: %v", err) 728 } 729 }() 730 731 if err := <-invoke; err != nil { 732 t.Fatalf("err: %v", err) 733 } 734 735 plan.Stop() 736 wg.Wait() 737 } 738 739 func TestAgentServiceWatch(t *testing.T) { 740 t.Parallel() 741 a := agent.NewTestAgent(t, t.Name(), ``) 742 defer a.Shutdown() 743 testrpc.WaitForTestAgent(t, a.RPC, "dc1") 744 745 // Register a local agent service 746 reg := &consulapi.AgentServiceRegistration{ 747 Name: "web", 748 Port: 8080, 749 } 750 client := a.Client() 751 agent := client.Agent() 752 err := agent.ServiceRegister(reg) 753 require.NoError(t, err) 754 755 invoke := makeInvokeCh() 756 plan := mustParse(t, `{"type":"agent_service", "service_id":"web"}`) 757 plan.HybridHandler = func(blockParamVal watch.BlockingParamVal, raw interface{}) { 758 if raw == nil { 759 return // ignore 760 } 761 v, ok := raw.(*consulapi.AgentService) 762 if !ok || v == nil { 763 return // ignore 764 } 765 invoke <- nil 766 } 767 768 var wg sync.WaitGroup 769 wg.Add(1) 770 go func() { 771 defer wg.Done() 772 time.Sleep(20 * time.Millisecond) 773 774 // Change the service definition 775 reg.Port = 9090 776 err := agent.ServiceRegister(reg) 777 require.NoError(t, err) 778 }() 779 780 wg.Add(1) 781 go func() { 782 defer wg.Done() 783 if err := plan.Run(a.HTTPAddr()); err != nil { 784 t.Fatalf("err: %v", err) 785 } 786 }() 787 788 if err := <-invoke; err != nil { 789 t.Fatalf("err: %v", err) 790 } 791 792 plan.Stop() 793 wg.Wait() 794 } 795 796 func mustParse(t *testing.T, q string) *watch.Plan { 797 t.Helper() 798 var params map[string]interface{} 799 if err := json.Unmarshal([]byte(q), ¶ms); err != nil { 800 t.Fatal(err) 801 } 802 plan, err := watch.Parse(params) 803 if err != nil { 804 t.Fatalf("err: %v", err) 805 } 806 return plan 807 }