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