1package api 2 3import ( 4 crand "crypto/rand" 5 "crypto/tls" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "net/url" 11 "os" 12 "path/filepath" 13 "reflect" 14 "runtime" 15 "strings" 16 "testing" 17 "time" 18 19 "github.com/hashicorp/consul/sdk/testutil" 20 "github.com/hashicorp/consul/sdk/testutil/retry" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23) 24 25type configCallback func(c *Config) 26 27func makeClient(t *testing.T) (*Client, *testutil.TestServer) { 28 return makeClientWithConfig(t, nil, nil) 29} 30 31func makeClientWithoutConnect(t *testing.T) (*Client, *testutil.TestServer) { 32 return makeClientWithConfig(t, nil, func(serverConfig *testutil.TestServerConfig) { 33 serverConfig.Connect = nil 34 }) 35} 36 37func makeACLClient(t *testing.T) (*Client, *testutil.TestServer) { 38 return makeClientWithConfig(t, func(clientConfig *Config) { 39 clientConfig.Token = "root" 40 }, func(serverConfig *testutil.TestServerConfig) { 41 serverConfig.PrimaryDatacenter = "dc1" 42 serverConfig.ACL.Tokens.Master = "root" 43 serverConfig.ACL.Tokens.Agent = "root" 44 serverConfig.ACL.Enabled = true 45 serverConfig.ACL.DefaultPolicy = "deny" 46 }) 47} 48 49func makeClientWithConfig( 50 t *testing.T, 51 cb1 configCallback, 52 cb2 testutil.ServerConfigCallback) (*Client, *testutil.TestServer) { 53 54 // Make client config 55 conf := DefaultConfig() 56 if cb1 != nil { 57 cb1(conf) 58 } 59 60 // Create server 61 var server *testutil.TestServer 62 var err error 63 retry.RunWith(retry.ThreeTimes(), t, func(r *retry.R) { 64 server, err = testutil.NewTestServerConfigT(t, cb2) 65 if err != nil { 66 r.Fatalf("Failed to start server: %v", err.Error()) 67 } 68 }) 69 if server.Config.Bootstrap { 70 server.WaitForLeader(t) 71 } 72 73 conf.Address = server.HTTPAddr 74 75 // Create client 76 client, err := NewClient(conf) 77 if err != nil { 78 server.Stop() 79 t.Fatalf("err: %v", err) 80 } 81 82 return client, server 83} 84 85func testKey() string { 86 buf := make([]byte, 16) 87 if _, err := crand.Read(buf); err != nil { 88 panic(fmt.Errorf("Failed to read random bytes: %v", err)) 89 } 90 91 return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", 92 buf[0:4], 93 buf[4:6], 94 buf[6:8], 95 buf[8:10], 96 buf[10:16]) 97} 98 99func testNodeServiceCheckRegistrations(t *testing.T, client *Client, datacenter string) { 100 t.Helper() 101 102 registrations := map[string]*CatalogRegistration{ 103 "Node foo": { 104 Datacenter: datacenter, 105 Node: "foo", 106 ID: "e0155642-135d-4739-9853-a1ee6c9f945b", 107 Address: "127.0.0.2", 108 TaggedAddresses: map[string]string{ 109 "lan": "127.0.0.2", 110 "wan": "198.18.0.2", 111 }, 112 NodeMeta: map[string]string{ 113 "env": "production", 114 "os": "linux", 115 }, 116 Checks: HealthChecks{ 117 &HealthCheck{ 118 Node: "foo", 119 CheckID: "foo:alive", 120 Name: "foo-liveness", 121 Status: HealthPassing, 122 Notes: "foo is alive and well", 123 }, 124 &HealthCheck{ 125 Node: "foo", 126 CheckID: "foo:ssh", 127 Name: "foo-remote-ssh", 128 Status: HealthPassing, 129 Notes: "foo has ssh access", 130 }, 131 }, 132 }, 133 "Service redis v1 on foo": { 134 Datacenter: datacenter, 135 Node: "foo", 136 SkipNodeUpdate: true, 137 Service: &AgentService{ 138 Kind: ServiceKindTypical, 139 ID: "redisV1", 140 Service: "redis", 141 Tags: []string{"v1"}, 142 Meta: map[string]string{"version": "1"}, 143 Port: 1234, 144 Address: "198.18.1.2", 145 }, 146 Checks: HealthChecks{ 147 &HealthCheck{ 148 Node: "foo", 149 CheckID: "foo:redisV1", 150 Name: "redis-liveness", 151 Status: HealthPassing, 152 Notes: "redis v1 is alive and well", 153 ServiceID: "redisV1", 154 ServiceName: "redis", 155 }, 156 }, 157 }, 158 "Service redis v2 on foo": { 159 Datacenter: datacenter, 160 Node: "foo", 161 SkipNodeUpdate: true, 162 Service: &AgentService{ 163 Kind: ServiceKindTypical, 164 ID: "redisV2", 165 Service: "redis", 166 Tags: []string{"v2"}, 167 Meta: map[string]string{"version": "2"}, 168 Port: 1235, 169 Address: "198.18.1.2", 170 }, 171 Checks: HealthChecks{ 172 &HealthCheck{ 173 Node: "foo", 174 CheckID: "foo:redisV2", 175 Name: "redis-v2-liveness", 176 Status: HealthPassing, 177 Notes: "redis v2 is alive and well", 178 ServiceID: "redisV2", 179 ServiceName: "redis", 180 }, 181 }, 182 }, 183 "Node bar": { 184 Datacenter: datacenter, 185 Node: "bar", 186 ID: "c6e7a976-8f4f-44b5-bdd3-631be7e8ecac", 187 Address: "127.0.0.3", 188 TaggedAddresses: map[string]string{ 189 "lan": "127.0.0.3", 190 "wan": "198.18.0.3", 191 }, 192 NodeMeta: map[string]string{ 193 "env": "production", 194 "os": "windows", 195 }, 196 Checks: HealthChecks{ 197 &HealthCheck{ 198 Node: "bar", 199 CheckID: "bar:alive", 200 Name: "bar-liveness", 201 Status: HealthPassing, 202 Notes: "bar is alive and well", 203 }, 204 }, 205 }, 206 "Service redis v1 on bar": { 207 Datacenter: datacenter, 208 Node: "bar", 209 SkipNodeUpdate: true, 210 Service: &AgentService{ 211 Kind: ServiceKindTypical, 212 ID: "redisV1", 213 Service: "redis", 214 Tags: []string{"v1"}, 215 Meta: map[string]string{"version": "1"}, 216 Port: 1234, 217 Address: "198.18.1.3", 218 }, 219 Checks: HealthChecks{ 220 &HealthCheck{ 221 Node: "bar", 222 CheckID: "bar:redisV1", 223 Name: "redis-liveness", 224 Status: HealthPassing, 225 Notes: "redis v1 is alive and well", 226 ServiceID: "redisV1", 227 ServiceName: "redis", 228 }, 229 }, 230 }, 231 "Service web v1 on bar": { 232 Datacenter: datacenter, 233 Node: "bar", 234 SkipNodeUpdate: true, 235 Service: &AgentService{ 236 Kind: ServiceKindTypical, 237 ID: "webV1", 238 Service: "web", 239 Tags: []string{"v1", "connect"}, 240 Meta: map[string]string{"version": "1", "connect": "enabled"}, 241 Port: 443, 242 Address: "198.18.1.4", 243 Connect: &AgentServiceConnect{Native: true}, 244 }, 245 Checks: HealthChecks{ 246 &HealthCheck{ 247 Node: "bar", 248 CheckID: "bar:web:v1", 249 Name: "web-v1-liveness", 250 Status: HealthPassing, 251 Notes: "web connect v1 is alive and well", 252 ServiceID: "webV1", 253 ServiceName: "web", 254 }, 255 }, 256 }, 257 "Node baz": { 258 Datacenter: datacenter, 259 Node: "baz", 260 ID: "12f96b27-a7b0-47bd-add7-044a2bfc7bfb", 261 Address: "127.0.0.4", 262 TaggedAddresses: map[string]string{ 263 "lan": "127.0.0.4", 264 }, 265 NodeMeta: map[string]string{ 266 "env": "qa", 267 "os": "linux", 268 }, 269 Checks: HealthChecks{ 270 &HealthCheck{ 271 Node: "baz", 272 CheckID: "baz:alive", 273 Name: "baz-liveness", 274 Status: HealthPassing, 275 Notes: "baz is alive and well", 276 }, 277 &HealthCheck{ 278 Node: "baz", 279 CheckID: "baz:ssh", 280 Name: "baz-remote-ssh", 281 Status: HealthPassing, 282 Notes: "baz has ssh access", 283 }, 284 }, 285 }, 286 "Service web v1 on baz": { 287 Datacenter: datacenter, 288 Node: "baz", 289 SkipNodeUpdate: true, 290 Service: &AgentService{ 291 Kind: ServiceKindTypical, 292 ID: "webV1", 293 Service: "web", 294 Tags: []string{"v1", "connect"}, 295 Meta: map[string]string{"version": "1", "connect": "enabled"}, 296 Port: 443, 297 Address: "198.18.1.4", 298 Connect: &AgentServiceConnect{Native: true}, 299 }, 300 Checks: HealthChecks{ 301 &HealthCheck{ 302 Node: "baz", 303 CheckID: "baz:web:v1", 304 Name: "web-v1-liveness", 305 Status: HealthPassing, 306 Notes: "web connect v1 is alive and well", 307 ServiceID: "webV1", 308 ServiceName: "web", 309 }, 310 }, 311 }, 312 "Service web v2 on baz": { 313 Datacenter: datacenter, 314 Node: "baz", 315 SkipNodeUpdate: true, 316 Service: &AgentService{ 317 Kind: ServiceKindTypical, 318 ID: "webV2", 319 Service: "web", 320 Tags: []string{"v2", "connect"}, 321 Meta: map[string]string{"version": "2", "connect": "enabled"}, 322 Port: 8443, 323 Address: "198.18.1.4", 324 Connect: &AgentServiceConnect{Native: true}, 325 }, 326 Checks: HealthChecks{ 327 &HealthCheck{ 328 Node: "baz", 329 CheckID: "baz:web:v2", 330 Name: "web-v2-liveness", 331 Status: HealthPassing, 332 Notes: "web connect v2 is alive and well", 333 ServiceID: "webV2", 334 ServiceName: "web", 335 }, 336 }, 337 }, 338 "Service critical on baz": { 339 Datacenter: datacenter, 340 Node: "baz", 341 SkipNodeUpdate: true, 342 Service: &AgentService{ 343 Kind: ServiceKindTypical, 344 ID: "criticalV2", 345 Service: "critical", 346 Tags: []string{"v2"}, 347 Meta: map[string]string{"version": "2"}, 348 Port: 8080, 349 Address: "198.18.1.4", 350 }, 351 Checks: HealthChecks{ 352 &HealthCheck{ 353 Node: "baz", 354 CheckID: "baz:critical:v2", 355 Name: "critical-v2-liveness", 356 Status: HealthCritical, 357 Notes: "critical v2 is in the critical state", 358 ServiceID: "criticalV2", 359 ServiceName: "critical", 360 }, 361 }, 362 }, 363 "Service warning on baz": { 364 Datacenter: datacenter, 365 Node: "baz", 366 SkipNodeUpdate: true, 367 Service: &AgentService{ 368 Kind: ServiceKindTypical, 369 ID: "warningV2", 370 Service: "warning", 371 Tags: []string{"v2"}, 372 Meta: map[string]string{"version": "2"}, 373 Port: 8081, 374 Address: "198.18.1.4", 375 }, 376 Checks: HealthChecks{ 377 &HealthCheck{ 378 Node: "baz", 379 CheckID: "baz:warning:v2", 380 Name: "warning-v2-liveness", 381 Status: HealthWarning, 382 Notes: "warning v2 is in the warning state", 383 ServiceID: "warningV2", 384 ServiceName: "warning", 385 }, 386 }, 387 }, 388 } 389 390 catalog := client.Catalog() 391 for name, reg := range registrations { 392 _, err := catalog.Register(reg, nil) 393 require.NoError(t, err, "Failed catalog registration for %q: %v", name, err) 394 } 395} 396 397func TestAPI_DefaultConfig_env(t *testing.T) { 398 // t.Parallel() // DO NOT ENABLE !!! 399 // do not enable t.Parallel for this test since it modifies global state 400 // (environment) which has non-deterministic effects on the other tests 401 // which derive their default configuration from the environment 402 403 addr := "1.2.3.4:5678" 404 token := "abcd1234" 405 auth := "username:password" 406 407 os.Setenv(HTTPAddrEnvName, addr) 408 defer os.Setenv(HTTPAddrEnvName, "") 409 os.Setenv(HTTPTokenEnvName, token) 410 defer os.Setenv(HTTPTokenEnvName, "") 411 os.Setenv(HTTPAuthEnvName, auth) 412 defer os.Setenv(HTTPAuthEnvName, "") 413 os.Setenv(HTTPSSLEnvName, "1") 414 defer os.Setenv(HTTPSSLEnvName, "") 415 os.Setenv(HTTPCAFile, "ca.pem") 416 defer os.Setenv(HTTPCAFile, "") 417 os.Setenv(HTTPCAPath, "certs/") 418 defer os.Setenv(HTTPCAPath, "") 419 os.Setenv(HTTPClientCert, "client.crt") 420 defer os.Setenv(HTTPClientCert, "") 421 os.Setenv(HTTPClientKey, "client.key") 422 defer os.Setenv(HTTPClientKey, "") 423 os.Setenv(HTTPTLSServerName, "consul.test") 424 defer os.Setenv(HTTPTLSServerName, "") 425 os.Setenv(HTTPSSLVerifyEnvName, "0") 426 defer os.Setenv(HTTPSSLVerifyEnvName, "") 427 428 for i, config := range []*Config{ 429 DefaultConfig(), 430 DefaultConfigWithLogger(testutil.Logger(t)), 431 DefaultNonPooledConfig(), 432 } { 433 if config.Address != addr { 434 t.Errorf("expected %q to be %q", config.Address, addr) 435 } 436 if config.Token != token { 437 t.Errorf("expected %q to be %q", config.Token, token) 438 } 439 if config.HttpAuth == nil { 440 t.Fatalf("expected HttpAuth to be enabled") 441 } 442 if config.HttpAuth.Username != "username" { 443 t.Errorf("expected %q to be %q", config.HttpAuth.Username, "username") 444 } 445 if config.HttpAuth.Password != "password" { 446 t.Errorf("expected %q to be %q", config.HttpAuth.Password, "password") 447 } 448 if config.Scheme != "https" { 449 t.Errorf("expected %q to be %q", config.Scheme, "https") 450 } 451 if config.TLSConfig.CAFile != "ca.pem" { 452 t.Errorf("expected %q to be %q", config.TLSConfig.CAFile, "ca.pem") 453 } 454 if config.TLSConfig.CAPath != "certs/" { 455 t.Errorf("expected %q to be %q", config.TLSConfig.CAPath, "certs/") 456 } 457 if config.TLSConfig.CertFile != "client.crt" { 458 t.Errorf("expected %q to be %q", config.TLSConfig.CertFile, "client.crt") 459 } 460 if config.TLSConfig.KeyFile != "client.key" { 461 t.Errorf("expected %q to be %q", config.TLSConfig.KeyFile, "client.key") 462 } 463 if config.TLSConfig.Address != "consul.test" { 464 t.Errorf("expected %q to be %q", config.TLSConfig.Address, "consul.test") 465 } 466 if !config.TLSConfig.InsecureSkipVerify { 467 t.Errorf("expected SSL verification to be off") 468 } 469 470 // Use keep alives as a check for whether pooling is on or off. 471 if pooled := i != 2; pooled { 472 if config.Transport.DisableKeepAlives != false { 473 t.Errorf("expected keep alives to be enabled") 474 } 475 } else { 476 if config.Transport.DisableKeepAlives != true { 477 t.Errorf("expected keep alives to be disabled") 478 } 479 } 480 } 481} 482 483func TestAPI_SetupTLSConfig(t *testing.T) { 484 t.Parallel() 485 // A default config should result in a clean default client config. 486 tlsConfig := &TLSConfig{} 487 cc, err := SetupTLSConfig(tlsConfig) 488 if err != nil { 489 t.Fatalf("err: %v", err) 490 } 491 expected := &tls.Config{RootCAs: cc.RootCAs} 492 if !reflect.DeepEqual(cc, expected) { 493 t.Fatalf("bad: \n%v, \n%v", cc, expected) 494 } 495 496 // Try some address variations with and without ports. 497 tlsConfig.Address = "127.0.0.1" 498 cc, err = SetupTLSConfig(tlsConfig) 499 if err != nil { 500 t.Fatalf("err: %v", err) 501 } 502 expected.ServerName = "127.0.0.1" 503 if !reflect.DeepEqual(cc, expected) { 504 t.Fatalf("bad: %v", cc) 505 } 506 507 tlsConfig.Address = "127.0.0.1:80" 508 cc, err = SetupTLSConfig(tlsConfig) 509 if err != nil { 510 t.Fatalf("err: %v", err) 511 } 512 expected.ServerName = "127.0.0.1" 513 if !reflect.DeepEqual(cc, expected) { 514 t.Fatalf("bad: %v", cc) 515 } 516 517 tlsConfig.Address = "demo.consul.io:80" 518 cc, err = SetupTLSConfig(tlsConfig) 519 if err != nil { 520 t.Fatalf("err: %v", err) 521 } 522 expected.ServerName = "demo.consul.io" 523 if !reflect.DeepEqual(cc, expected) { 524 t.Fatalf("bad: %v", cc) 525 } 526 527 tlsConfig.Address = "[2001:db8:a0b:12f0::1]" 528 cc, err = SetupTLSConfig(tlsConfig) 529 if err != nil { 530 t.Fatalf("err: %v", err) 531 } 532 expected.ServerName = "[2001:db8:a0b:12f0::1]" 533 if !reflect.DeepEqual(cc, expected) { 534 t.Fatalf("bad: %v", cc) 535 } 536 537 tlsConfig.Address = "[2001:db8:a0b:12f0::1]:80" 538 cc, err = SetupTLSConfig(tlsConfig) 539 if err != nil { 540 t.Fatalf("err: %v", err) 541 } 542 expected.ServerName = "2001:db8:a0b:12f0::1" 543 if !reflect.DeepEqual(cc, expected) { 544 t.Fatalf("bad: %v", cc) 545 } 546 547 // Skip verification. 548 tlsConfig.InsecureSkipVerify = true 549 cc, err = SetupTLSConfig(tlsConfig) 550 if err != nil { 551 t.Fatalf("err: %v", err) 552 } 553 expected.InsecureSkipVerify = true 554 if !reflect.DeepEqual(cc, expected) { 555 t.Fatalf("bad: %v", cc) 556 } 557 558 // Make a new config that hits all the file parsers. 559 tlsConfig = &TLSConfig{ 560 CertFile: "../test/hostname/Alice.crt", 561 KeyFile: "../test/hostname/Alice.key", 562 CAFile: "../test/hostname/CertAuth.crt", 563 } 564 cc, err = SetupTLSConfig(tlsConfig) 565 if err != nil { 566 t.Fatalf("err: %v", err) 567 } 568 if len(cc.Certificates) != 1 { 569 t.Fatalf("missing certificate: %v", cc.Certificates) 570 } 571 if cc.RootCAs == nil { 572 t.Fatalf("didn't load root CAs") 573 } 574 575 // Use a directory to load the certs instead 576 cc, err = SetupTLSConfig(&TLSConfig{ 577 CAPath: "../test/ca_path", 578 }) 579 if err != nil { 580 t.Fatalf("err: %v", err) 581 } 582 if len(cc.RootCAs.Subjects()) != 2 { 583 t.Fatalf("didn't load root CAs") 584 } 585 586 // Load certs in-memory 587 certPEM, err := ioutil.ReadFile("../test/hostname/Alice.crt") 588 if err != nil { 589 t.Fatalf("err: %v", err) 590 } 591 keyPEM, err := ioutil.ReadFile("../test/hostname/Alice.key") 592 if err != nil { 593 t.Fatalf("err: %v", err) 594 } 595 caPEM, err := ioutil.ReadFile("../test/hostname/CertAuth.crt") 596 if err != nil { 597 t.Fatalf("err: %v", err) 598 } 599 600 // Setup config with in-memory certs 601 cc, err = SetupTLSConfig(&TLSConfig{ 602 CertPEM: certPEM, 603 KeyPEM: keyPEM, 604 CAPem: caPEM, 605 }) 606 if err != nil { 607 t.Fatalf("err: %v", err) 608 } 609 if len(cc.Certificates) != 1 { 610 t.Fatalf("missing certificate: %v", cc.Certificates) 611 } 612 if cc.RootCAs == nil { 613 t.Fatalf("didn't load root CAs") 614 } 615} 616 617func TestAPI_ClientTLSOptions(t *testing.T) { 618 t.Parallel() 619 // Start a server that verifies incoming HTTPS connections 620 _, srvVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 621 conf.CAFile = "../test/client_certs/rootca.crt" 622 conf.CertFile = "../test/client_certs/server.crt" 623 conf.KeyFile = "../test/client_certs/server.key" 624 conf.VerifyIncomingHTTPS = true 625 }) 626 defer srvVerify.Stop() 627 628 // Start a server without VerifyIncomingHTTPS 629 _, srvNoVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 630 conf.CAFile = "../test/client_certs/rootca.crt" 631 conf.CertFile = "../test/client_certs/server.crt" 632 conf.KeyFile = "../test/client_certs/server.key" 633 conf.VerifyIncomingHTTPS = false 634 }) 635 defer srvNoVerify.Stop() 636 637 // Client without a cert 638 t.Run("client without cert, validation", func(t *testing.T) { 639 client, err := NewClient(&Config{ 640 Address: srvVerify.HTTPSAddr, 641 Scheme: "https", 642 TLSConfig: TLSConfig{ 643 Address: "consul.test", 644 CAFile: "../test/client_certs/rootca.crt", 645 }, 646 }) 647 if err != nil { 648 t.Fatal(err) 649 } 650 651 // Should fail 652 _, err = client.Agent().Self() 653 if err == nil || !strings.Contains(err.Error(), "bad certificate") { 654 t.Fatal(err) 655 } 656 }) 657 658 // Client with a valid cert 659 t.Run("client with cert, validation", func(t *testing.T) { 660 client, err := NewClient(&Config{ 661 Address: srvVerify.HTTPSAddr, 662 Scheme: "https", 663 TLSConfig: TLSConfig{ 664 Address: "consul.test", 665 CAFile: "../test/client_certs/rootca.crt", 666 CertFile: "../test/client_certs/client.crt", 667 KeyFile: "../test/client_certs/client.key", 668 }, 669 }) 670 if err != nil { 671 t.Fatal(err) 672 } 673 674 // Should succeed 675 _, err = client.Agent().Self() 676 if err != nil { 677 t.Fatal(err) 678 } 679 }) 680 681 // Client without a cert 682 t.Run("client without cert, no validation", func(t *testing.T) { 683 client, err := NewClient(&Config{ 684 Address: srvNoVerify.HTTPSAddr, 685 Scheme: "https", 686 TLSConfig: TLSConfig{ 687 Address: "consul.test", 688 CAFile: "../test/client_certs/rootca.crt", 689 }, 690 }) 691 if err != nil { 692 t.Fatal(err) 693 } 694 695 // Should succeed 696 _, err = client.Agent().Self() 697 if err != nil { 698 t.Fatal(err) 699 } 700 }) 701 702 // Client with a valid cert 703 t.Run("client with cert, no validation", func(t *testing.T) { 704 client, err := NewClient(&Config{ 705 Address: srvNoVerify.HTTPSAddr, 706 Scheme: "https", 707 TLSConfig: TLSConfig{ 708 Address: "consul.test", 709 CAFile: "../test/client_certs/rootca.crt", 710 CertFile: "../test/client_certs/client.crt", 711 KeyFile: "../test/client_certs/client.key", 712 }, 713 }) 714 if err != nil { 715 t.Fatal(err) 716 } 717 718 // Should succeed 719 _, err = client.Agent().Self() 720 if err != nil { 721 t.Fatal(err) 722 } 723 }) 724} 725 726func TestAPI_SetQueryOptions(t *testing.T) { 727 t.Parallel() 728 c, s := makeClient(t) 729 defer s.Stop() 730 731 assert := assert.New(t) 732 733 r := c.newRequest("GET", "/v1/kv/foo") 734 q := &QueryOptions{ 735 Namespace: "operator", 736 Datacenter: "foo", 737 AllowStale: true, 738 RequireConsistent: true, 739 WaitIndex: 1000, 740 WaitTime: 100 * time.Second, 741 Token: "12345", 742 Near: "nodex", 743 LocalOnly: true, 744 } 745 r.setQueryOptions(q) 746 747 if r.params.Get("ns") != "operator" { 748 t.Fatalf("bad: %v", r.params) 749 } 750 if r.params.Get("dc") != "foo" { 751 t.Fatalf("bad: %v", r.params) 752 } 753 if _, ok := r.params["stale"]; !ok { 754 t.Fatalf("bad: %v", r.params) 755 } 756 if _, ok := r.params["consistent"]; !ok { 757 t.Fatalf("bad: %v", r.params) 758 } 759 if r.params.Get("index") != "1000" { 760 t.Fatalf("bad: %v", r.params) 761 } 762 if r.params.Get("wait") != "100000ms" { 763 t.Fatalf("bad: %v", r.params) 764 } 765 if r.header.Get("X-Consul-Token") != "12345" { 766 t.Fatalf("bad: %v", r.header) 767 } 768 if r.params.Get("near") != "nodex" { 769 t.Fatalf("bad: %v", r.params) 770 } 771 if r.params.Get("local-only") != "true" { 772 t.Fatalf("bad: %v", r.params) 773 } 774 assert.Equal("", r.header.Get("Cache-Control")) 775 776 r = c.newRequest("GET", "/v1/kv/foo") 777 q = &QueryOptions{ 778 UseCache: true, 779 MaxAge: 30 * time.Second, 780 StaleIfError: 345678 * time.Millisecond, // Fractional seconds should be rounded 781 } 782 r.setQueryOptions(q) 783 784 _, ok := r.params["cached"] 785 assert.True(ok) 786 assert.Equal("max-age=30, stale-if-error=346", r.header.Get("Cache-Control")) 787} 788 789func TestAPI_SetWriteOptions(t *testing.T) { 790 t.Parallel() 791 c, s := makeClient(t) 792 defer s.Stop() 793 794 r := c.newRequest("GET", "/v1/kv/foo") 795 q := &WriteOptions{ 796 Namespace: "operator", 797 Datacenter: "foo", 798 Token: "23456", 799 } 800 r.setWriteOptions(q) 801 if r.params.Get("ns") != "operator" { 802 t.Fatalf("bad: %v", r.params) 803 } 804 if r.params.Get("dc") != "foo" { 805 t.Fatalf("bad: %v", r.params) 806 } 807 if r.header.Get("X-Consul-Token") != "23456" { 808 t.Fatalf("bad: %v", r.header) 809 } 810} 811 812func TestAPI_Headers(t *testing.T) { 813 t.Parallel() 814 815 var request *http.Request 816 c, s := makeClientWithConfig(t, func(c *Config) { 817 transport := http.DefaultTransport.(*http.Transport).Clone() 818 transport.Proxy = func(r *http.Request) (*url.URL, error) { 819 // Keep track of the last request sent 820 request = r 821 return nil, nil 822 } 823 c.Transport = transport 824 }, nil) 825 defer s.Stop() 826 827 if len(c.Headers()) != 0 { 828 t.Fatalf("expected headers to be empty: %v", c.Headers()) 829 } 830 831 c.AddHeader("Hello", "World") 832 r := c.newRequest("GET", "/v1/kv/foo") 833 834 if r.header.Get("Hello") != "World" { 835 t.Fatalf("Hello header not set : %v", r.header) 836 } 837 838 c.SetHeaders(http.Header{ 839 "Auth": []string{"Token"}, 840 }) 841 842 r = c.newRequest("GET", "/v1/kv/foo") 843 if r.header.Get("Hello") != "" { 844 t.Fatalf("Hello header should not be set: %v", r.header) 845 } 846 847 if r.header.Get("Auth") != "Token" { 848 t.Fatalf("Auth header not set: %v", r.header) 849 } 850 851 kv := c.KV() 852 _, err := kv.Put(&KVPair{Key: "test-headers", Value: []byte("foo")}, nil) 853 require.NoError(t, err) 854 require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type")) 855 856 _, _, err = kv.Get("test-headers", nil) 857 require.NoError(t, err) 858 require.Equal(t, "", request.Header.Get("Content-Type")) 859 860 _, err = kv.Delete("test-headers", nil) 861 require.NoError(t, err) 862 require.Equal(t, "", request.Header.Get("Content-Type")) 863 864 err = c.Snapshot().Restore(nil, strings.NewReader("foo")) 865 require.Error(t, err) 866 require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type")) 867 868 _, err = c.ACL().RulesTranslate(strings.NewReader(` 869 agent "" { 870 policy = "read" 871 } 872 `)) 873 // ACL support is disabled 874 require.Error(t, err) 875 require.Equal(t, "text/plain", request.Header.Get("Content-Type")) 876 877 _, _, err = c.Event().Fire(&UserEvent{ 878 Name: "test", 879 Payload: []byte("foo"), 880 }, nil) 881 require.NoError(t, err) 882 require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type")) 883} 884 885func TestAPI_RequestToHTTP(t *testing.T) { 886 t.Parallel() 887 c, s := makeClient(t) 888 defer s.Stop() 889 890 r := c.newRequest("DELETE", "/v1/kv/foo") 891 q := &QueryOptions{ 892 Datacenter: "foo", 893 } 894 r.setQueryOptions(q) 895 req, err := r.toHTTP() 896 if err != nil { 897 t.Fatalf("err: %v", err) 898 } 899 900 if req.Method != "DELETE" { 901 t.Fatalf("bad: %v", req) 902 } 903 if req.URL.RequestURI() != "/v1/kv/foo?dc=foo" { 904 t.Fatalf("bad: %v", req) 905 } 906} 907 908func TestAPI_ParseQueryMeta(t *testing.T) { 909 t.Parallel() 910 resp := &http.Response{ 911 Header: make(map[string][]string), 912 } 913 resp.Header.Set("X-Consul-Index", "12345") 914 resp.Header.Set("X-Consul-LastContact", "80") 915 resp.Header.Set("X-Consul-KnownLeader", "true") 916 resp.Header.Set("X-Consul-Translate-Addresses", "true") 917 resp.Header.Set("X-Consul-Default-ACL-Policy", "deny") 918 919 qm := &QueryMeta{} 920 if err := parseQueryMeta(resp, qm); err != nil { 921 t.Fatalf("err: %v", err) 922 } 923 924 if qm.LastIndex != 12345 { 925 t.Fatalf("Bad: %v", qm) 926 } 927 if qm.LastContact != 80*time.Millisecond { 928 t.Fatalf("Bad: %v", qm) 929 } 930 if !qm.KnownLeader { 931 t.Fatalf("Bad: %v", qm) 932 } 933 if !qm.AddressTranslationEnabled { 934 t.Fatalf("Bad: %v", qm) 935 } 936 if qm.DefaultACLPolicy != "deny" { 937 t.Fatalf("Bad: %v", qm) 938 } 939} 940 941func TestAPI_UnixSocket(t *testing.T) { 942 t.Parallel() 943 if runtime.GOOS == "windows" { 944 t.SkipNow() 945 } 946 947 tempDir := testutil.TempDir(t, "consul") 948 socket := filepath.Join(tempDir, "test.sock") 949 950 c, s := makeClientWithConfig(t, func(c *Config) { 951 c.Address = "unix://" + socket 952 }, func(c *testutil.TestServerConfig) { 953 c.Addresses = &testutil.TestAddressConfig{ 954 HTTP: "unix://" + socket, 955 } 956 }) 957 defer s.Stop() 958 959 agent := c.Agent() 960 961 info, err := agent.Self() 962 if err != nil { 963 t.Fatalf("err: %s", err) 964 } 965 if info["Config"]["NodeName"].(string) == "" { 966 t.Fatalf("bad: %v", info) 967 } 968} 969 970func TestAPI_durToMsec(t *testing.T) { 971 t.Parallel() 972 if ms := durToMsec(0); ms != "0ms" { 973 t.Fatalf("bad: %s", ms) 974 } 975 976 if ms := durToMsec(time.Millisecond); ms != "1ms" { 977 t.Fatalf("bad: %s", ms) 978 } 979 980 if ms := durToMsec(time.Microsecond); ms != "1ms" { 981 t.Fatalf("bad: %s", ms) 982 } 983 984 if ms := durToMsec(5 * time.Millisecond); ms != "5ms" { 985 t.Fatalf("bad: %s", ms) 986 } 987} 988 989func TestAPI_IsRetryableError(t *testing.T) { 990 t.Parallel() 991 if IsRetryableError(nil) { 992 t.Fatal("should not be a retryable error") 993 } 994 995 if IsRetryableError(fmt.Errorf("not the error you are looking for")) { 996 t.Fatal("should not be a retryable error") 997 } 998 999 if !IsRetryableError(fmt.Errorf(serverError)) { 1000 t.Fatal("should be a retryable error") 1001 } 1002 1003 if !IsRetryableError(&net.OpError{Err: fmt.Errorf("network conn error")}) { 1004 t.Fatal("should be a retryable error") 1005 } 1006} 1007 1008func TestAPI_GenerateEnv(t *testing.T) { 1009 t.Parallel() 1010 1011 c := &Config{ 1012 Address: "127.0.0.1:8500", 1013 Token: "test", 1014 TokenFile: "test.file", 1015 Scheme: "http", 1016 TLSConfig: TLSConfig{ 1017 CAFile: "", 1018 CAPath: "", 1019 CertFile: "", 1020 KeyFile: "", 1021 Address: "", 1022 InsecureSkipVerify: true, 1023 }, 1024 } 1025 1026 expected := []string{ 1027 "CONSUL_HTTP_ADDR=127.0.0.1:8500", 1028 "CONSUL_HTTP_TOKEN=test", 1029 "CONSUL_HTTP_TOKEN_FILE=test.file", 1030 "CONSUL_HTTP_SSL=false", 1031 "CONSUL_CACERT=", 1032 "CONSUL_CAPATH=", 1033 "CONSUL_CLIENT_CERT=", 1034 "CONSUL_CLIENT_KEY=", 1035 "CONSUL_TLS_SERVER_NAME=", 1036 "CONSUL_HTTP_SSL_VERIFY=false", 1037 "CONSUL_HTTP_AUTH=", 1038 } 1039 1040 require.Equal(t, expected, c.GenerateEnv()) 1041} 1042 1043func TestAPI_GenerateEnvHTTPS(t *testing.T) { 1044 t.Parallel() 1045 1046 c := &Config{ 1047 Address: "127.0.0.1:8500", 1048 Token: "test", 1049 TokenFile: "test.file", 1050 Scheme: "https", 1051 TLSConfig: TLSConfig{ 1052 CAFile: "/var/consul/ca.crt", 1053 CAPath: "/var/consul/ca.dir", 1054 CertFile: "/var/consul/server.crt", 1055 KeyFile: "/var/consul/ssl/server.key", 1056 Address: "127.0.0.1:8500", 1057 InsecureSkipVerify: false, 1058 }, 1059 HttpAuth: &HttpBasicAuth{ 1060 Username: "user", 1061 Password: "password", 1062 }, 1063 } 1064 1065 expected := []string{ 1066 "CONSUL_HTTP_ADDR=127.0.0.1:8500", 1067 "CONSUL_HTTP_TOKEN=test", 1068 "CONSUL_HTTP_TOKEN_FILE=test.file", 1069 "CONSUL_HTTP_SSL=true", 1070 "CONSUL_CACERT=/var/consul/ca.crt", 1071 "CONSUL_CAPATH=/var/consul/ca.dir", 1072 "CONSUL_CLIENT_CERT=/var/consul/server.crt", 1073 "CONSUL_CLIENT_KEY=/var/consul/ssl/server.key", 1074 "CONSUL_TLS_SERVER_NAME=127.0.0.1:8500", 1075 "CONSUL_HTTP_SSL_VERIFY=true", 1076 "CONSUL_HTTP_AUTH=user:password", 1077 } 1078 1079 require.Equal(t, expected, c.GenerateEnv()) 1080} 1081