1package plugin 2 3import ( 4 "bytes" 5 "crypto/sha256" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "log" 10 "net" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "strings" 15 "sync" 16 "testing" 17 "time" 18 19 hclog "github.com/hashicorp/go-hclog" 20) 21 22func TestClient(t *testing.T) { 23 process := helperProcess("mock") 24 c := NewClient(&ClientConfig{ 25 Cmd: process, 26 HandshakeConfig: testHandshake, 27 Plugins: testPluginMap, 28 }) 29 defer c.Kill() 30 31 // Test that it parses the proper address 32 addr, err := c.Start() 33 if err != nil { 34 t.Fatalf("err should be nil, got %s", err) 35 } 36 37 if addr.Network() != "tcp" { 38 t.Fatalf("bad: %#v", addr) 39 } 40 41 if addr.String() != ":1234" { 42 t.Fatalf("bad: %#v", addr) 43 } 44 45 // Test that it exits properly if killed 46 c.Kill() 47 48 // Test that it knows it is exited 49 if !c.Exited() { 50 t.Fatal("should say client has exited") 51 } 52 53 // this test isn't expected to get a client 54 if !c.killed() { 55 t.Fatal("Client should have failed") 56 } 57} 58 59// This tests a bug where Kill would start 60func TestClient_killStart(t *testing.T) { 61 // Create a temporary dir to store the result file 62 td, err := ioutil.TempDir("", "plugin") 63 if err != nil { 64 t.Fatalf("err: %s", err) 65 } 66 defer os.RemoveAll(td) 67 68 // Start the client 69 path := filepath.Join(td, "booted") 70 process := helperProcess("bad-version", path) 71 c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake}) 72 defer c.Kill() 73 74 // Verify our path doesn't exist 75 if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { 76 t.Fatalf("bad: %s", err) 77 } 78 79 // Test that it parses the proper address 80 if _, err := c.Start(); err == nil { 81 t.Fatal("expected error") 82 } 83 84 // Verify we started 85 if _, err := os.Stat(path); err != nil { 86 t.Fatalf("bad: %s", err) 87 } 88 if err := os.Remove(path); err != nil { 89 t.Fatalf("bad: %s", err) 90 } 91 92 // Test that Kill does nothing really 93 c.Kill() 94 95 // Test that it knows it is exited 96 if !c.Exited() { 97 t.Fatal("should say client has exited") 98 } 99 100 if !c.killed() { 101 t.Fatal("process should have failed") 102 } 103 104 // Verify our path doesn't exist 105 if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { 106 t.Fatalf("bad: %s", err) 107 } 108} 109 110func TestClient_testCleanup(t *testing.T) { 111 // Create a temporary dir to store the result file 112 td, err := ioutil.TempDir("", "plugin") 113 if err != nil { 114 t.Fatalf("err: %s", err) 115 } 116 defer os.RemoveAll(td) 117 118 // Create a path that the helper process will write on cleanup 119 path := filepath.Join(td, "output") 120 121 // Test the cleanup 122 process := helperProcess("cleanup", path) 123 c := NewClient(&ClientConfig{ 124 Cmd: process, 125 HandshakeConfig: testHandshake, 126 Plugins: testPluginMap, 127 }) 128 129 // Grab the client so the process starts 130 if _, err := c.Client(); err != nil { 131 c.Kill() 132 t.Fatalf("err: %s", err) 133 } 134 135 // Kill it gracefully 136 c.Kill() 137 138 // Test for the file 139 if _, err := os.Stat(path); err != nil { 140 t.Fatalf("err: %s", err) 141 } 142} 143 144func TestClient_testInterface(t *testing.T) { 145 process := helperProcess("test-interface") 146 c := NewClient(&ClientConfig{ 147 Cmd: process, 148 HandshakeConfig: testHandshake, 149 Plugins: testPluginMap, 150 }) 151 defer c.Kill() 152 153 // Grab the RPC client 154 client, err := c.Client() 155 if err != nil { 156 t.Fatalf("err should be nil, got %s", err) 157 } 158 159 // Grab the impl 160 raw, err := client.Dispense("test") 161 if err != nil { 162 t.Fatalf("err should be nil, got %s", err) 163 } 164 165 impl, ok := raw.(testInterface) 166 if !ok { 167 t.Fatalf("bad: %#v", raw) 168 } 169 170 result := impl.Double(21) 171 if result != 42 { 172 t.Fatalf("bad: %#v", result) 173 } 174 175 // Kill it 176 c.Kill() 177 178 // Test that it knows it is exited 179 if !c.Exited() { 180 t.Fatal("should say client has exited") 181 } 182 183 if c.killed() { 184 t.Fatal("process failed to exit gracefully") 185 } 186} 187 188func TestClient_grpc_servercrash(t *testing.T) { 189 process := helperProcess("test-grpc") 190 c := NewClient(&ClientConfig{ 191 Cmd: process, 192 HandshakeConfig: testHandshake, 193 Plugins: testGRPCPluginMap, 194 AllowedProtocols: []Protocol{ProtocolGRPC}, 195 }) 196 defer c.Kill() 197 198 if _, err := c.Start(); err != nil { 199 t.Fatalf("err: %s", err) 200 } 201 202 if v := c.Protocol(); v != ProtocolGRPC { 203 t.Fatalf("bad: %s", v) 204 } 205 206 // Grab the RPC client 207 client, err := c.Client() 208 if err != nil { 209 t.Fatalf("err should be nil, got %s", err) 210 } 211 212 // Grab the impl 213 raw, err := client.Dispense("test") 214 if err != nil { 215 t.Fatalf("err should be nil, got %s", err) 216 } 217 218 _, ok := raw.(testInterface) 219 if !ok { 220 t.Fatalf("bad: %#v", raw) 221 } 222 223 c.process.Kill() 224 225 select { 226 case <-c.doneCtx.Done(): 227 case <-time.After(time.Second * 2): 228 t.Fatal("Context was not closed") 229 } 230} 231 232func TestClient_grpc(t *testing.T) { 233 process := helperProcess("test-grpc") 234 c := NewClient(&ClientConfig{ 235 Cmd: process, 236 HandshakeConfig: testHandshake, 237 Plugins: testGRPCPluginMap, 238 AllowedProtocols: []Protocol{ProtocolGRPC}, 239 }) 240 defer c.Kill() 241 242 if _, err := c.Start(); err != nil { 243 t.Fatalf("err: %s", err) 244 } 245 246 if v := c.Protocol(); v != ProtocolGRPC { 247 t.Fatalf("bad: %s", v) 248 } 249 250 // Grab the RPC client 251 client, err := c.Client() 252 if err != nil { 253 t.Fatalf("err should be nil, got %s", err) 254 } 255 256 // Grab the impl 257 raw, err := client.Dispense("test") 258 if err != nil { 259 t.Fatalf("err should be nil, got %s", err) 260 } 261 262 impl, ok := raw.(testInterface) 263 if !ok { 264 t.Fatalf("bad: %#v", raw) 265 } 266 267 result := impl.Double(21) 268 if result != 42 { 269 t.Fatalf("bad: %#v", result) 270 } 271 272 // Kill it 273 c.Kill() 274 275 // Test that it knows it is exited 276 if !c.Exited() { 277 t.Fatal("should say client has exited") 278 } 279 280 if c.killed() { 281 t.Fatal("process failed to exit gracefully") 282 } 283} 284 285func TestClient_grpcNotAllowed(t *testing.T) { 286 process := helperProcess("test-grpc") 287 c := NewClient(&ClientConfig{ 288 Cmd: process, 289 HandshakeConfig: testHandshake, 290 Plugins: testPluginMap, 291 }) 292 defer c.Kill() 293 294 if _, err := c.Start(); err == nil { 295 t.Fatal("should error") 296 } 297} 298 299func TestClient_grpcSyncStdio(t *testing.T) { 300 var syncOut, syncErr safeBuffer 301 302 process := helperProcess("test-grpc") 303 c := NewClient(&ClientConfig{ 304 Cmd: process, 305 HandshakeConfig: testHandshake, 306 Plugins: testGRPCPluginMap, 307 AllowedProtocols: []Protocol{ProtocolGRPC}, 308 SyncStdout: &syncOut, 309 SyncStderr: &syncErr, 310 }) 311 defer c.Kill() 312 313 if _, err := c.Start(); err != nil { 314 t.Fatalf("err: %s", err) 315 } 316 317 if v := c.Protocol(); v != ProtocolGRPC { 318 t.Fatalf("bad: %s", v) 319 } 320 321 // Grab the RPC client 322 client, err := c.Client() 323 if err != nil { 324 t.Fatalf("err should be nil, got %s", err) 325 } 326 327 // Grab the impl 328 raw, err := client.Dispense("test") 329 if err != nil { 330 t.Fatalf("err should be nil, got %s", err) 331 } 332 333 impl, ok := raw.(testInterface) 334 if !ok { 335 t.Fatalf("bad: %#v", raw) 336 } 337 338 // Print the data 339 stdout := []byte("hello\nworld!") 340 stderr := []byte("and some error\n messages!") 341 impl.PrintStdio(stdout, stderr) 342 343 // Wait for it to be copied 344 for syncOut.String() == "" || syncErr.String() == "" { 345 time.Sleep(10 * time.Millisecond) 346 } 347 348 // We should get the data 349 if syncOut.String() != string(stdout) { 350 t.Fatalf("stdout didn't match: %s", syncOut.String()) 351 } 352 if syncErr.String() != string(stderr) { 353 t.Fatalf("stderr didn't match: %s", syncErr.String()) 354 } 355} 356 357func TestClient_cmdAndReattach(t *testing.T) { 358 config := &ClientConfig{ 359 Cmd: helperProcess("start-timeout"), 360 Reattach: &ReattachConfig{}, 361 } 362 363 c := NewClient(config) 364 defer c.Kill() 365 366 _, err := c.Start() 367 if err == nil { 368 t.Fatal("err should not be nil") 369 } 370} 371 372func TestClient_reattach(t *testing.T) { 373 process := helperProcess("test-interface") 374 c := NewClient(&ClientConfig{ 375 Cmd: process, 376 HandshakeConfig: testHandshake, 377 Plugins: testPluginMap, 378 }) 379 defer c.Kill() 380 381 // Grab the RPC client 382 _, err := c.Client() 383 if err != nil { 384 t.Fatalf("err should be nil, got %s", err) 385 } 386 387 // Get the reattach configuration 388 reattach := c.ReattachConfig() 389 390 // Create a new client 391 c = NewClient(&ClientConfig{ 392 Reattach: reattach, 393 HandshakeConfig: testHandshake, 394 Plugins: testPluginMap, 395 }) 396 397 // Grab the RPC client 398 client, err := c.Client() 399 if err != nil { 400 t.Fatalf("err should be nil, got %s", err) 401 } 402 403 // Grab the impl 404 raw, err := client.Dispense("test") 405 if err != nil { 406 t.Fatalf("err should be nil, got %s", err) 407 } 408 409 impl, ok := raw.(testInterface) 410 if !ok { 411 t.Fatalf("bad: %#v", raw) 412 } 413 414 result := impl.Double(21) 415 if result != 42 { 416 t.Fatalf("bad: %#v", result) 417 } 418 419 // Kill it 420 c.Kill() 421 422 // Test that it knows it is exited 423 if !c.Exited() { 424 t.Fatal("should say client has exited") 425 } 426 427 if c.killed() { 428 t.Fatal("process failed to exit gracefully") 429 } 430} 431 432func TestClient_reattachNoProtocol(t *testing.T) { 433 process := helperProcess("test-interface") 434 c := NewClient(&ClientConfig{ 435 Cmd: process, 436 HandshakeConfig: testHandshake, 437 Plugins: testPluginMap, 438 }) 439 defer c.Kill() 440 441 // Grab the RPC client 442 _, err := c.Client() 443 if err != nil { 444 t.Fatalf("err should be nil, got %s", err) 445 } 446 447 // Get the reattach configuration 448 reattach := c.ReattachConfig() 449 reattach.Protocol = "" 450 451 // Create a new client 452 c = NewClient(&ClientConfig{ 453 Reattach: reattach, 454 HandshakeConfig: testHandshake, 455 Plugins: testPluginMap, 456 }) 457 458 // Grab the RPC client 459 client, err := c.Client() 460 if err != nil { 461 t.Fatalf("err should be nil, got %s", err) 462 } 463 464 // Grab the impl 465 raw, err := client.Dispense("test") 466 if err != nil { 467 t.Fatalf("err should be nil, got %s", err) 468 } 469 470 impl, ok := raw.(testInterface) 471 if !ok { 472 t.Fatalf("bad: %#v", raw) 473 } 474 475 result := impl.Double(21) 476 if result != 42 { 477 t.Fatalf("bad: %#v", result) 478 } 479 480 // Kill it 481 c.Kill() 482 483 // Test that it knows it is exited 484 if !c.Exited() { 485 t.Fatal("should say client has exited") 486 } 487 488 if c.killed() { 489 t.Fatal("process failed to exit gracefully") 490 } 491} 492 493func TestClient_reattachGRPC(t *testing.T) { 494 process := helperProcess("test-grpc") 495 c := NewClient(&ClientConfig{ 496 Cmd: process, 497 HandshakeConfig: testHandshake, 498 Plugins: testGRPCPluginMap, 499 AllowedProtocols: []Protocol{ProtocolGRPC}, 500 }) 501 defer c.Kill() 502 503 // Grab the RPC client 504 _, err := c.Client() 505 if err != nil { 506 t.Fatalf("err should be nil, got %s", err) 507 } 508 509 // Get the reattach configuration 510 reattach := c.ReattachConfig() 511 512 // Create a new client 513 c = NewClient(&ClientConfig{ 514 Reattach: reattach, 515 HandshakeConfig: testHandshake, 516 Plugins: testGRPCPluginMap, 517 AllowedProtocols: []Protocol{ProtocolGRPC}, 518 }) 519 520 // Grab the RPC client 521 client, err := c.Client() 522 if err != nil { 523 t.Fatalf("err should be nil, got %s", err) 524 } 525 526 // Grab the impl 527 raw, err := client.Dispense("test") 528 if err != nil { 529 t.Fatalf("err should be nil, got %s", err) 530 } 531 532 impl, ok := raw.(testInterface) 533 if !ok { 534 t.Fatalf("bad: %#v", raw) 535 } 536 537 result := impl.Double(21) 538 if result != 42 { 539 t.Fatalf("bad: %#v", result) 540 } 541 542 // Kill it 543 c.Kill() 544 545 // Test that it knows it is exited 546 if !c.Exited() { 547 t.Fatal("should say client has exited") 548 } 549 550 if c.killed() { 551 t.Fatal("process failed to exit gracefully") 552 } 553} 554 555func TestClient_reattachNotFound(t *testing.T) { 556 // Find a bad pid 557 var pid int = 5000 558 for i := pid; i < 32000; i++ { 559 if _, err := os.FindProcess(i); err != nil { 560 pid = i 561 break 562 } 563 } 564 565 // Addr that won't work 566 l, err := net.Listen("tcp", "127.0.0.1:0") 567 if err != nil { 568 t.Fatalf("err: %s", err) 569 } 570 addr := l.Addr() 571 l.Close() 572 573 // Reattach 574 c := NewClient(&ClientConfig{ 575 Reattach: &ReattachConfig{ 576 Addr: addr, 577 Pid: pid, 578 }, 579 HandshakeConfig: testHandshake, 580 Plugins: testPluginMap, 581 }) 582 583 // Start shouldn't error 584 if _, err := c.Start(); err == nil { 585 t.Fatal("should error") 586 } else if err != ErrProcessNotFound { 587 t.Fatalf("err: %s", err) 588 } 589} 590 591func TestClientStart_badVersion(t *testing.T) { 592 config := &ClientConfig{ 593 Cmd: helperProcess("bad-version"), 594 StartTimeout: 50 * time.Millisecond, 595 HandshakeConfig: testHandshake, 596 Plugins: testPluginMap, 597 } 598 599 c := NewClient(config) 600 defer c.Kill() 601 602 _, err := c.Start() 603 if err == nil { 604 t.Fatal("err should not be nil") 605 } 606} 607 608func TestClientStart_badNegotiatedVersion(t *testing.T) { 609 config := &ClientConfig{ 610 Cmd: helperProcess("test-versioned-plugins"), 611 StartTimeout: 50 * time.Millisecond, 612 // test-versioned-plugins only has version 2 613 HandshakeConfig: testHandshake, 614 Plugins: testPluginMap, 615 } 616 617 c := NewClient(config) 618 defer c.Kill() 619 620 _, err := c.Start() 621 if err == nil { 622 t.Fatal("err should not be nil") 623 } 624 fmt.Println(err) 625} 626 627func TestClient_Start_Timeout(t *testing.T) { 628 config := &ClientConfig{ 629 Cmd: helperProcess("start-timeout"), 630 StartTimeout: 50 * time.Millisecond, 631 HandshakeConfig: testHandshake, 632 Plugins: testPluginMap, 633 } 634 635 c := NewClient(config) 636 defer c.Kill() 637 638 _, err := c.Start() 639 if err == nil { 640 t.Fatal("err should not be nil") 641 } 642} 643 644func TestClient_Stderr(t *testing.T) { 645 stderr := new(bytes.Buffer) 646 process := helperProcess("stderr") 647 c := NewClient(&ClientConfig{ 648 Cmd: process, 649 Stderr: stderr, 650 HandshakeConfig: testHandshake, 651 Plugins: testPluginMap, 652 }) 653 defer c.Kill() 654 655 if _, err := c.Start(); err != nil { 656 t.Fatalf("err: %s", err) 657 } 658 659 for !c.Exited() { 660 time.Sleep(10 * time.Millisecond) 661 } 662 663 if c.killed() { 664 t.Fatal("process failed to exit gracefully") 665 } 666 667 if !strings.Contains(stderr.String(), "HELLO\n") { 668 t.Fatalf("bad log data: '%s'", stderr.String()) 669 } 670 671 if !strings.Contains(stderr.String(), "WORLD\n") { 672 t.Fatalf("bad log data: '%s'", stderr.String()) 673 } 674} 675 676func TestClient_StderrJSON(t *testing.T) { 677 stderr := new(bytes.Buffer) 678 process := helperProcess("stderr-json") 679 680 var logBuf bytes.Buffer 681 mutex := new(sync.Mutex) 682 // Custom hclog.Logger 683 testLogger := hclog.New(&hclog.LoggerOptions{ 684 Name: "test-logger", 685 Level: hclog.Trace, 686 Output: &logBuf, 687 Mutex: mutex, 688 }) 689 690 c := NewClient(&ClientConfig{ 691 Cmd: process, 692 Stderr: stderr, 693 HandshakeConfig: testHandshake, 694 Logger: testLogger, 695 Plugins: testPluginMap, 696 }) 697 defer c.Kill() 698 699 if _, err := c.Start(); err != nil { 700 t.Fatalf("err: %s", err) 701 } 702 703 for !c.Exited() { 704 time.Sleep(10 * time.Millisecond) 705 } 706 707 if c.killed() { 708 t.Fatal("process failed to exit gracefully") 709 } 710 711 logOut := logBuf.String() 712 713 if !strings.Contains(logOut, "[\"HELLO\"]\n") { 714 t.Fatalf("missing json list: '%s'", logOut) 715 } 716 717 if !strings.Contains(logOut, "12345\n") { 718 t.Fatalf("missing line with raw number: '%s'", logOut) 719 } 720 721 if !strings.Contains(logOut, "{\"a\":1}") { 722 t.Fatalf("missing json object: '%s'", logOut) 723 } 724} 725 726func TestClient_textLogLevel(t *testing.T) { 727 stderr := new(bytes.Buffer) 728 process := helperProcess("level-warn-text") 729 730 var logBuf bytes.Buffer 731 mutex := new(sync.Mutex) 732 // Custom hclog.Logger 733 testLogger := hclog.New(&hclog.LoggerOptions{ 734 Name: "test-logger", 735 Level: hclog.Warn, 736 Output: &logBuf, 737 Mutex: mutex, 738 }) 739 740 c := NewClient(&ClientConfig{ 741 Cmd: process, 742 Stderr: stderr, 743 HandshakeConfig: testHandshake, 744 Logger: testLogger, 745 Plugins: testPluginMap, 746 }) 747 defer c.Kill() 748 749 if _, err := c.Start(); err != nil { 750 t.Fatalf("err: %s", err) 751 } 752 753 for !c.Exited() { 754 time.Sleep(10 * time.Millisecond) 755 } 756 757 if c.killed() { 758 t.Fatal("process failed to exit gracefully") 759 } 760 761 logOut := logBuf.String() 762 763 if !strings.Contains(logOut, "test line 98765") { 764 log.Fatalf("test string not found in log: %q\n", logOut) 765 } 766} 767 768func TestClient_Stdin(t *testing.T) { 769 // Overwrite stdin for this test with a temporary file 770 tf, err := ioutil.TempFile("", "terraform") 771 if err != nil { 772 t.Fatalf("err: %s", err) 773 } 774 defer os.Remove(tf.Name()) 775 defer tf.Close() 776 777 if _, err = tf.WriteString("hello"); err != nil { 778 t.Fatalf("error: %s", err) 779 } 780 781 if err = tf.Sync(); err != nil { 782 t.Fatalf("error: %s", err) 783 } 784 785 if _, err = tf.Seek(0, 0); err != nil { 786 t.Fatalf("error: %s", err) 787 } 788 789 oldStdin := os.Stdin 790 defer func() { os.Stdin = oldStdin }() 791 os.Stdin = tf 792 793 process := helperProcess("stdin") 794 c := NewClient(&ClientConfig{ 795 Cmd: process, 796 HandshakeConfig: testHandshake, 797 Plugins: testPluginMap, 798 }) 799 defer c.Kill() 800 801 _, err = c.Start() 802 if err != nil { 803 t.Fatalf("error: %s", err) 804 } 805 806 for { 807 if c.Exited() { 808 break 809 } 810 811 time.Sleep(50 * time.Millisecond) 812 } 813 814 if !process.ProcessState.Success() { 815 t.Fatal("process didn't exit cleanly") 816 } 817} 818 819func TestClient_SecureConfig(t *testing.T) { 820 // Test failure case 821 secureConfig := &SecureConfig{ 822 Checksum: []byte{'1'}, 823 Hash: sha256.New(), 824 } 825 process := helperProcess("test-interface") 826 c := NewClient(&ClientConfig{ 827 Cmd: process, 828 HandshakeConfig: testHandshake, 829 Plugins: testPluginMap, 830 SecureConfig: secureConfig, 831 }) 832 833 // Grab the RPC client, should error 834 _, err := c.Client() 835 c.Kill() 836 if err != ErrChecksumsDoNotMatch { 837 t.Fatalf("err should be %s, got %s", ErrChecksumsDoNotMatch, err) 838 } 839 840 // Get the checksum of the executable 841 file, err := os.Open(os.Args[0]) 842 if err != nil { 843 t.Fatal(err) 844 } 845 defer file.Close() 846 847 hash := sha256.New() 848 849 _, err = io.Copy(hash, file) 850 if err != nil { 851 t.Fatal(err) 852 } 853 854 sum := hash.Sum(nil) 855 856 secureConfig = &SecureConfig{ 857 Checksum: sum, 858 Hash: sha256.New(), 859 } 860 861 process = helperProcess("test-interface") 862 c = NewClient(&ClientConfig{ 863 Cmd: process, 864 HandshakeConfig: testHandshake, 865 Plugins: testPluginMap, 866 SecureConfig: secureConfig, 867 }) 868 defer c.Kill() 869 870 // Grab the RPC client 871 _, err = c.Client() 872 if err != nil { 873 t.Fatalf("err should be nil, got %s", err) 874 } 875} 876 877func TestClient_TLS(t *testing.T) { 878 // Test failure case 879 process := helperProcess("test-interface-tls") 880 cBad := NewClient(&ClientConfig{ 881 Cmd: process, 882 HandshakeConfig: testHandshake, 883 Plugins: testPluginMap, 884 }) 885 defer cBad.Kill() 886 887 // Grab the RPC client 888 clientBad, err := cBad.Client() 889 if err != nil { 890 t.Fatalf("err should be nil, got %s", err) 891 } 892 893 // Grab the impl 894 raw, err := clientBad.Dispense("test") 895 if err == nil { 896 t.Fatal("expected error, got nil") 897 } 898 899 cBad.Kill() 900 901 // Add TLS config to client 902 tlsConfig, err := helperTLSProvider() 903 if err != nil { 904 t.Fatalf("err should be nil, got %s", err) 905 } 906 907 process = helperProcess("test-interface-tls") 908 c := NewClient(&ClientConfig{ 909 Cmd: process, 910 HandshakeConfig: testHandshake, 911 Plugins: testPluginMap, 912 TLSConfig: tlsConfig, 913 }) 914 defer c.Kill() 915 916 // Grab the RPC client 917 client, err := c.Client() 918 if err != nil { 919 t.Fatalf("err should be nil, got %s", err) 920 } 921 922 // Grab the impl 923 raw, err = client.Dispense("test") 924 if err != nil { 925 t.Fatalf("err should be nil, got %s", err) 926 } 927 928 impl, ok := raw.(testInterface) 929 if !ok { 930 t.Fatalf("bad: %#v", raw) 931 } 932 933 result := impl.Double(21) 934 if result != 42 { 935 t.Fatalf("bad: %#v", result) 936 } 937 938 // Kill it 939 c.Kill() 940 941 // Test that it knows it is exited 942 if !c.Exited() { 943 t.Fatal("should say client has exited") 944 } 945 946 if c.killed() { 947 t.Fatal("process failed to exit gracefully") 948 } 949} 950 951func TestClient_TLS_grpc(t *testing.T) { 952 // Add TLS config to client 953 tlsConfig, err := helperTLSProvider() 954 if err != nil { 955 t.Fatalf("err should be nil, got %s", err) 956 } 957 958 process := helperProcess("test-grpc-tls") 959 c := NewClient(&ClientConfig{ 960 Cmd: process, 961 HandshakeConfig: testHandshake, 962 Plugins: testGRPCPluginMap, 963 TLSConfig: tlsConfig, 964 AllowedProtocols: []Protocol{ProtocolGRPC}, 965 }) 966 defer c.Kill() 967 968 // Grab the RPC client 969 client, err := c.Client() 970 if err != nil { 971 t.Fatalf("err should be nil, got %s", err) 972 } 973 974 // Grab the impl 975 raw, err := client.Dispense("test") 976 if err != nil { 977 t.Fatalf("err should be nil, got %s", err) 978 } 979 980 impl, ok := raw.(testInterface) 981 if !ok { 982 t.Fatalf("bad: %#v", raw) 983 } 984 985 result := impl.Double(21) 986 if result != 42 { 987 t.Fatalf("bad: %#v", result) 988 } 989 990 // Kill it 991 c.Kill() 992 993 if !c.Exited() { 994 t.Fatal("should say client has exited") 995 } 996 997 if c.killed() { 998 t.Fatal("process failed to exit gracefully") 999 } 1000} 1001 1002func TestClient_secureConfigAndReattach(t *testing.T) { 1003 config := &ClientConfig{ 1004 SecureConfig: &SecureConfig{}, 1005 Reattach: &ReattachConfig{}, 1006 } 1007 1008 c := NewClient(config) 1009 defer c.Kill() 1010 1011 _, err := c.Start() 1012 if err != ErrSecureConfigAndReattach { 1013 t.Fatalf("err should not be %s, got %s", ErrSecureConfigAndReattach, err) 1014 } 1015} 1016 1017func TestClient_ping(t *testing.T) { 1018 process := helperProcess("test-interface") 1019 c := NewClient(&ClientConfig{ 1020 Cmd: process, 1021 HandshakeConfig: testHandshake, 1022 Plugins: testPluginMap, 1023 }) 1024 defer c.Kill() 1025 1026 // Get the client 1027 client, err := c.Client() 1028 if err != nil { 1029 t.Fatalf("err: %s", err) 1030 } 1031 1032 // Ping, should work 1033 if err := client.Ping(); err != nil { 1034 t.Fatalf("err: %s", err) 1035 } 1036 1037 // Kill it 1038 c.Kill() 1039 if err := client.Ping(); err == nil { 1040 t.Fatal("should error") 1041 } 1042} 1043 1044func TestClient_wrongVersion(t *testing.T) { 1045 process := helperProcess("test-proto-upgraded-plugin") 1046 c := NewClient(&ClientConfig{ 1047 Cmd: process, 1048 HandshakeConfig: testHandshake, 1049 Plugins: testGRPCPluginMap, 1050 AllowedProtocols: []Protocol{ProtocolGRPC}, 1051 }) 1052 defer c.Kill() 1053 1054 // Get the client 1055 _, err := c.Client() 1056 if err == nil { 1057 t.Fatal("expected incorrect protocol version server") 1058 } 1059 1060} 1061 1062func TestClient_legacyClient(t *testing.T) { 1063 process := helperProcess("test-proto-upgraded-plugin") 1064 c := NewClient(&ClientConfig{ 1065 Cmd: process, 1066 HandshakeConfig: testVersionedHandshake, 1067 VersionedPlugins: map[int]PluginSet{ 1068 1: testPluginMap, 1069 }, 1070 }) 1071 defer c.Kill() 1072 1073 // Get the client 1074 client, err := c.Client() 1075 if err != nil { 1076 t.Fatalf("err: %s", err) 1077 } 1078 1079 if c.NegotiatedVersion() != 1 { 1080 t.Fatal("using incorrect version", c.NegotiatedVersion()) 1081 } 1082 1083 // Ping, should work 1084 if err := client.Ping(); err == nil { 1085 t.Fatal("expected error, should negotiate wrong plugin") 1086 } 1087} 1088 1089func TestClient_legacyServer(t *testing.T) { 1090 // test using versioned plugins version when the server supports only 1091 // supports one 1092 process := helperProcess("test-proto-upgraded-client") 1093 c := NewClient(&ClientConfig{ 1094 Cmd: process, 1095 HandshakeConfig: testVersionedHandshake, 1096 VersionedPlugins: map[int]PluginSet{ 1097 2: testGRPCPluginMap, 1098 }, 1099 AllowedProtocols: []Protocol{ProtocolGRPC}, 1100 }) 1101 defer c.Kill() 1102 1103 // Get the client 1104 client, err := c.Client() 1105 if err != nil { 1106 t.Fatalf("err: %s", err) 1107 } 1108 1109 if c.NegotiatedVersion() != 2 { 1110 t.Fatal("using incorrect version", c.NegotiatedVersion()) 1111 } 1112 1113 // Ping, should work 1114 if err := client.Ping(); err == nil { 1115 t.Fatal("expected error, should negotiate wrong plugin") 1116 } 1117} 1118 1119func TestClient_versionedClient(t *testing.T) { 1120 process := helperProcess("test-versioned-plugins") 1121 c := NewClient(&ClientConfig{ 1122 Cmd: process, 1123 HandshakeConfig: testVersionedHandshake, 1124 VersionedPlugins: map[int]PluginSet{ 1125 2: testGRPCPluginMap, 1126 }, 1127 AllowedProtocols: []Protocol{ProtocolGRPC}, 1128 }) 1129 defer c.Kill() 1130 1131 if _, err := c.Start(); err != nil { 1132 t.Fatalf("err: %s", err) 1133 } 1134 1135 if v := c.Protocol(); v != ProtocolGRPC { 1136 t.Fatalf("bad: %s", v) 1137 } 1138 1139 // Grab the RPC client 1140 client, err := c.Client() 1141 if err != nil { 1142 t.Fatalf("err should be nil, got %s", err) 1143 } 1144 1145 if c.NegotiatedVersion() != 2 { 1146 t.Fatal("using incorrect version", c.NegotiatedVersion()) 1147 } 1148 1149 // Grab the impl 1150 raw, err := client.Dispense("test") 1151 if err != nil { 1152 t.Fatalf("err should be nil, got %s", err) 1153 } 1154 1155 _, ok := raw.(testInterface) 1156 if !ok { 1157 t.Fatalf("bad: %#v", raw) 1158 } 1159 1160 c.process.Kill() 1161 1162 select { 1163 case <-c.doneCtx.Done(): 1164 case <-time.After(time.Second * 2): 1165 t.Fatal("Context was not closed") 1166 } 1167} 1168 1169func TestClient_mtlsClient(t *testing.T) { 1170 process := helperProcess("test-mtls") 1171 c := NewClient(&ClientConfig{ 1172 AutoMTLS: true, 1173 Cmd: process, 1174 HandshakeConfig: testVersionedHandshake, 1175 VersionedPlugins: map[int]PluginSet{ 1176 2: testGRPCPluginMap, 1177 }, 1178 AllowedProtocols: []Protocol{ProtocolGRPC}, 1179 }) 1180 defer c.Kill() 1181 1182 if _, err := c.Start(); err != nil { 1183 t.Fatalf("err: %s", err) 1184 } 1185 1186 if v := c.Protocol(); v != ProtocolGRPC { 1187 t.Fatalf("bad: %s", v) 1188 } 1189 1190 // Grab the RPC client 1191 client, err := c.Client() 1192 if err != nil { 1193 t.Fatalf("err should be nil, got %s", err) 1194 } 1195 1196 if c.NegotiatedVersion() != 2 { 1197 t.Fatal("using incorrect version", c.NegotiatedVersion()) 1198 } 1199 1200 // Grab the impl 1201 raw, err := client.Dispense("test") 1202 if err != nil { 1203 t.Fatalf("err should be nil, got %s", err) 1204 } 1205 1206 tester, ok := raw.(testInterface) 1207 if !ok { 1208 t.Fatalf("bad: %#v", raw) 1209 } 1210 1211 n := tester.Double(3) 1212 if n != 6 { 1213 t.Fatal("invalid response", n) 1214 } 1215 1216 c.process.Kill() 1217 1218 select { 1219 case <-c.doneCtx.Done(): 1220 case <-time.After(time.Second * 2): 1221 t.Fatal("Context was not closed") 1222 } 1223} 1224 1225func TestClient_mtlsNetRPCClient(t *testing.T) { 1226 process := helperProcess("test-interface-mtls") 1227 c := NewClient(&ClientConfig{ 1228 AutoMTLS: true, 1229 Cmd: process, 1230 HandshakeConfig: testVersionedHandshake, 1231 Plugins: testPluginMap, 1232 AllowedProtocols: []Protocol{ProtocolNetRPC}, 1233 }) 1234 defer c.Kill() 1235 1236 if _, err := c.Start(); err != nil { 1237 t.Fatalf("err: %s", err) 1238 } 1239 1240 // Grab the RPC client 1241 client, err := c.Client() 1242 if err != nil { 1243 t.Fatalf("err should be nil, got %s", err) 1244 } 1245 1246 // Grab the impl 1247 raw, err := client.Dispense("test") 1248 if err != nil { 1249 t.Fatalf("err should be nil, got %s", err) 1250 } 1251 1252 tester, ok := raw.(testInterface) 1253 if !ok { 1254 t.Fatalf("bad: %#v", raw) 1255 } 1256 1257 n := tester.Double(3) 1258 if n != 6 { 1259 t.Fatal("invalid response", n) 1260 } 1261 1262 c.process.Kill() 1263 1264 select { 1265 case <-c.doneCtx.Done(): 1266 case <-time.After(time.Second * 2): 1267 t.Fatal("Context was not closed") 1268 } 1269} 1270 1271func TestClient_logger(t *testing.T) { 1272 t.Run("net/rpc", func(t *testing.T) { testClient_logger(t, "netrpc") }) 1273 t.Run("grpc", func(t *testing.T) { testClient_logger(t, "grpc") }) 1274} 1275 1276func testClient_logger(t *testing.T, proto string) { 1277 var buffer bytes.Buffer 1278 mutex := new(sync.Mutex) 1279 stderr := io.MultiWriter(os.Stderr, &buffer) 1280 // Custom hclog.Logger 1281 clientLogger := hclog.New(&hclog.LoggerOptions{ 1282 Name: "test-logger", 1283 Level: hclog.Trace, 1284 Output: stderr, 1285 Mutex: mutex, 1286 }) 1287 1288 process := helperProcess("test-interface-logger-" + proto) 1289 c := NewClient(&ClientConfig{ 1290 Cmd: process, 1291 HandshakeConfig: testHandshake, 1292 Plugins: testGRPCPluginMap, 1293 Logger: clientLogger, 1294 AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC}, 1295 }) 1296 defer c.Kill() 1297 1298 // Grab the RPC client 1299 client, err := c.Client() 1300 if err != nil { 1301 t.Fatalf("err should be nil, got %s", err) 1302 } 1303 1304 // Grab the impl 1305 raw, err := client.Dispense("test") 1306 if err != nil { 1307 t.Fatalf("err should be nil, got %s", err) 1308 } 1309 1310 impl, ok := raw.(testInterface) 1311 if !ok { 1312 t.Fatalf("bad: %#v", raw) 1313 } 1314 1315 { 1316 // Discard everything else, and capture the output we care about 1317 mutex.Lock() 1318 buffer.Reset() 1319 mutex.Unlock() 1320 impl.PrintKV("foo", "bar") 1321 time.Sleep(100 * time.Millisecond) 1322 mutex.Lock() 1323 line, err := buffer.ReadString('\n') 1324 mutex.Unlock() 1325 if err != nil { 1326 t.Fatal(err) 1327 } 1328 if !strings.Contains(line, "foo=bar") { 1329 t.Fatalf("bad: %q", line) 1330 } 1331 } 1332 1333 { 1334 // Try an integer type 1335 mutex.Lock() 1336 buffer.Reset() 1337 mutex.Unlock() 1338 impl.PrintKV("foo", 12) 1339 time.Sleep(100 * time.Millisecond) 1340 mutex.Lock() 1341 line, err := buffer.ReadString('\n') 1342 mutex.Unlock() 1343 if err != nil { 1344 t.Fatal(err) 1345 } 1346 if !strings.Contains(line, "foo=12") { 1347 t.Fatalf("bad: %q", line) 1348 } 1349 } 1350 1351 // Kill it 1352 c.Kill() 1353 1354 // Test that it knows it is exited 1355 if !c.Exited() { 1356 t.Fatal("should say client has exited") 1357 } 1358 1359 if c.killed() { 1360 t.Fatal("process failed to exit gracefully") 1361 } 1362} 1363 1364// Test that we continue to consume stderr over long lines. 1365func TestClient_logStderr(t *testing.T) { 1366 orig := stdErrBufferSize 1367 stdErrBufferSize = 32 1368 defer func() { 1369 stdErrBufferSize = orig 1370 }() 1371 1372 stderr := bytes.Buffer{} 1373 c := NewClient(&ClientConfig{ 1374 Stderr: &stderr, 1375 Cmd: &exec.Cmd{ 1376 Path: "test", 1377 }, 1378 }) 1379 c.clientWaitGroup.Add(1) 1380 1381 msg := ` 1382this line is more than 32 bytes long 1383and this line is more than 32 bytes long 1384{"a": "b", "@level": "debug"} 1385this line is short 1386` 1387 1388 reader := strings.NewReader(msg) 1389 1390 c.stderrWaitGroup.Add(1) 1391 c.logStderr(reader) 1392 read := stderr.String() 1393 1394 if read != msg { 1395 t.Fatalf("\nexpected output: %q\ngot output: %q", msg, read) 1396 } 1397} 1398