1package agent 2 3import ( 4 "encoding/json" 5 "fmt" 6 "github.com/mitchellh/copystructure" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "testing" 11 12 "github.com/hashicorp/consul/agent/structs" 13 "github.com/hashicorp/consul/sdk/testutil/retry" 14 "github.com/hashicorp/consul/testrpc" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17) 18 19func TestServiceManager_RegisterService(t *testing.T) { 20 if testing.Short() { 21 t.Skip("too slow for testing.Short") 22 } 23 24 require := require.New(t) 25 26 a := NewTestAgent(t, "") 27 defer a.Shutdown() 28 29 testrpc.WaitForLeader(t, a.RPC, "dc1") 30 31 // Register a global proxy and service config 32 testApplyConfigEntries(t, a, 33 &structs.ProxyConfigEntry{ 34 Config: map[string]interface{}{ 35 "foo": 1, 36 }, 37 }, 38 &structs.ServiceConfigEntry{ 39 Kind: structs.ServiceDefaults, 40 Name: "redis", 41 Protocol: "tcp", 42 }, 43 ) 44 45 // Now register a service locally with no sidecar, it should be a no-op. 46 svc := &structs.NodeService{ 47 ID: "redis", 48 Service: "redis", 49 Port: 8000, 50 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 51 } 52 require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal)) 53 54 // Verify both the service and sidecar. 55 redisService := a.State.Service(structs.NewServiceID("redis", nil)) 56 require.NotNil(redisService) 57 require.Equal(&structs.NodeService{ 58 ID: "redis", 59 Service: "redis", 60 Port: 8000, 61 TaggedAddresses: map[string]structs.ServiceAddress{}, 62 Weights: &structs.Weights{ 63 Passing: 1, 64 Warning: 1, 65 }, 66 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 67 }, redisService) 68} 69 70func TestServiceManager_RegisterSidecar(t *testing.T) { 71 if testing.Short() { 72 t.Skip("too slow for testing.Short") 73 } 74 75 require := require.New(t) 76 77 a := NewTestAgent(t, "") 78 defer a.Shutdown() 79 80 testrpc.WaitForLeader(t, a.RPC, "dc1") 81 82 // Register a global proxy and service config 83 testApplyConfigEntries(t, a, 84 &structs.ProxyConfigEntry{ 85 Config: map[string]interface{}{ 86 "foo": 1, 87 }, 88 }, 89 &structs.ServiceConfigEntry{ 90 Kind: structs.ServiceDefaults, 91 Name: "web", 92 Protocol: "http", 93 }, 94 &structs.ServiceConfigEntry{ 95 Kind: structs.ServiceDefaults, 96 Name: "redis", 97 Protocol: "tcp", 98 }, 99 ) 100 101 // Now register a sidecar proxy. Note we don't use SidecarService here because 102 // that gets resolved earlier in config handling than the AddService call 103 // here. 104 svc := &structs.NodeService{ 105 Kind: structs.ServiceKindConnectProxy, 106 ID: "web-sidecar-proxy", 107 Service: "web-sidecar-proxy", 108 Port: 21000, 109 Proxy: structs.ConnectProxyConfig{ 110 DestinationServiceName: "web", 111 DestinationServiceID: "web", 112 LocalServiceAddress: "127.0.0.1", 113 LocalServicePort: 8000, 114 Upstreams: structs.Upstreams{ 115 { 116 DestinationName: "redis", 117 DestinationNamespace: "default", 118 LocalBindPort: 5000, 119 }, 120 }, 121 }, 122 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 123 } 124 require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal)) 125 126 // Verify sidecar got global config loaded 127 sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 128 require.NotNil(sidecarService) 129 require.Equal(&structs.NodeService{ 130 Kind: structs.ServiceKindConnectProxy, 131 ID: "web-sidecar-proxy", 132 Service: "web-sidecar-proxy", 133 Port: 21000, 134 TaggedAddresses: map[string]structs.ServiceAddress{}, 135 Proxy: structs.ConnectProxyConfig{ 136 DestinationServiceName: "web", 137 DestinationServiceID: "web", 138 LocalServiceAddress: "127.0.0.1", 139 LocalServicePort: 8000, 140 Config: map[string]interface{}{ 141 "foo": int64(1), 142 "protocol": "http", 143 }, 144 Upstreams: structs.Upstreams{ 145 { 146 DestinationName: "redis", 147 DestinationNamespace: "default", 148 LocalBindPort: 5000, 149 Config: map[string]interface{}{ 150 "protocol": "tcp", 151 }, 152 }, 153 }, 154 }, 155 Weights: &structs.Weights{ 156 Passing: 1, 157 Warning: 1, 158 }, 159 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 160 }, sidecarService) 161} 162 163func TestServiceManager_RegisterMeshGateway(t *testing.T) { 164 if testing.Short() { 165 t.Skip("too slow for testing.Short") 166 } 167 168 require := require.New(t) 169 170 a := NewTestAgent(t, "") 171 defer a.Shutdown() 172 173 testrpc.WaitForLeader(t, a.RPC, "dc1") 174 175 // Register a global proxy and service config 176 testApplyConfigEntries(t, a, 177 &structs.ProxyConfigEntry{ 178 Config: map[string]interface{}{ 179 "foo": 1, 180 }, 181 }, 182 &structs.ServiceConfigEntry{ 183 Kind: structs.ServiceDefaults, 184 Name: "mesh-gateway", 185 Protocol: "http", 186 }, 187 ) 188 189 // Now register a mesh-gateway. 190 svc := &structs.NodeService{ 191 Kind: structs.ServiceKindMeshGateway, 192 ID: "mesh-gateway", 193 Service: "mesh-gateway", 194 Port: 443, 195 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 196 } 197 198 require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal)) 199 200 // Verify gateway got global config loaded 201 gateway := a.State.Service(structs.NewServiceID("mesh-gateway", nil)) 202 require.NotNil(gateway) 203 require.Equal(&structs.NodeService{ 204 Kind: structs.ServiceKindMeshGateway, 205 ID: "mesh-gateway", 206 Service: "mesh-gateway", 207 Port: 443, 208 TaggedAddresses: map[string]structs.ServiceAddress{}, 209 Proxy: structs.ConnectProxyConfig{ 210 Config: map[string]interface{}{ 211 "foo": int64(1), 212 "protocol": "http", 213 }, 214 }, 215 Weights: &structs.Weights{ 216 Passing: 1, 217 Warning: 1, 218 }, 219 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 220 }, gateway) 221} 222 223func TestServiceManager_RegisterTerminatingGateway(t *testing.T) { 224 if testing.Short() { 225 t.Skip("too slow for testing.Short") 226 } 227 228 require := require.New(t) 229 230 a := NewTestAgent(t, "") 231 defer a.Shutdown() 232 233 testrpc.WaitForLeader(t, a.RPC, "dc1") 234 235 // Register a global proxy and service config 236 testApplyConfigEntries(t, a, 237 &structs.ProxyConfigEntry{ 238 Config: map[string]interface{}{ 239 "foo": 1, 240 }, 241 }, 242 &structs.ServiceConfigEntry{ 243 Kind: structs.ServiceDefaults, 244 Name: "terminating-gateway", 245 Protocol: "http", 246 }, 247 ) 248 249 // Now register a terminating-gateway. 250 svc := &structs.NodeService{ 251 Kind: structs.ServiceKindTerminatingGateway, 252 ID: "terminating-gateway", 253 Service: "terminating-gateway", 254 Port: 443, 255 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 256 } 257 258 require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal)) 259 260 // Verify gateway got global config loaded 261 gateway := a.State.Service(structs.NewServiceID("terminating-gateway", nil)) 262 require.NotNil(gateway) 263 require.Equal(&structs.NodeService{ 264 Kind: structs.ServiceKindTerminatingGateway, 265 ID: "terminating-gateway", 266 Service: "terminating-gateway", 267 Port: 443, 268 TaggedAddresses: map[string]structs.ServiceAddress{}, 269 Proxy: structs.ConnectProxyConfig{ 270 Config: map[string]interface{}{ 271 "foo": int64(1), 272 "protocol": "http", 273 }, 274 }, 275 Weights: &structs.Weights{ 276 Passing: 1, 277 Warning: 1, 278 }, 279 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 280 }, gateway) 281} 282 283func TestServiceManager_PersistService_API(t *testing.T) { 284 if testing.Short() { 285 t.Skip("too slow for testing.Short") 286 } 287 288 // This is the ServiceManager version of TestAgent_PersistService and 289 // TestAgent_PurgeService. 290 t.Parallel() 291 292 require := require.New(t) 293 294 // Launch a server to manage the config entries. 295 serverAgent := NewTestAgent(t, "") 296 defer serverAgent.Shutdown() 297 testrpc.WaitForLeader(t, serverAgent.RPC, "dc1") 298 299 // Register a global proxy and service config 300 testApplyConfigEntries(t, serverAgent, 301 &structs.ProxyConfigEntry{ 302 Config: map[string]interface{}{ 303 "foo": 1, 304 }, 305 }, 306 &structs.ServiceConfigEntry{ 307 Kind: structs.ServiceDefaults, 308 Name: "web", 309 Protocol: "http", 310 }, 311 &structs.ServiceConfigEntry{ 312 Kind: structs.ServiceDefaults, 313 Name: "redis", 314 Protocol: "tcp", 315 }, 316 ) 317 318 // Now launch a single client agent 319 cfg := ` 320 server = false 321 bootstrap = false 322 ` 323 a := StartTestAgent(t, TestAgent{HCL: cfg}) 324 defer a.Shutdown() 325 326 // Join first 327 _, err := a.JoinLAN([]string{ 328 fmt.Sprintf("127.0.0.1:%d", serverAgent.Config.SerfPortLAN), 329 }) 330 require.NoError(err) 331 332 testrpc.WaitForLeader(t, a.RPC, "dc1") 333 334 // Now register a sidecar proxy via the API. 335 svc := &structs.NodeService{ 336 Kind: structs.ServiceKindConnectProxy, 337 ID: "web-sidecar-proxy", 338 Service: "web-sidecar-proxy", 339 Port: 21000, 340 Proxy: structs.ConnectProxyConfig{ 341 DestinationServiceName: "web", 342 DestinationServiceID: "web", 343 LocalServiceAddress: "127.0.0.1", 344 LocalServicePort: 8000, 345 Upstreams: structs.Upstreams{ 346 { 347 DestinationName: "redis", 348 DestinationNamespace: "default", 349 LocalBindPort: 5000, 350 }, 351 }, 352 }, 353 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 354 } 355 356 expectState := &structs.NodeService{ 357 Kind: structs.ServiceKindConnectProxy, 358 ID: "web-sidecar-proxy", 359 Service: "web-sidecar-proxy", 360 Port: 21000, 361 TaggedAddresses: map[string]structs.ServiceAddress{}, 362 Proxy: structs.ConnectProxyConfig{ 363 DestinationServiceName: "web", 364 DestinationServiceID: "web", 365 LocalServiceAddress: "127.0.0.1", 366 LocalServicePort: 8000, 367 Config: map[string]interface{}{ 368 "foo": int64(1), 369 "protocol": "http", 370 }, 371 Upstreams: structs.Upstreams{ 372 { 373 DestinationName: "redis", 374 DestinationNamespace: "default", 375 LocalBindPort: 5000, 376 Config: map[string]interface{}{ 377 "protocol": "tcp", 378 }, 379 }, 380 }, 381 }, 382 Weights: &structs.Weights{ 383 Passing: 1, 384 Warning: 1, 385 }, 386 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 387 } 388 389 svcID := svc.CompoundServiceID() 390 391 svcFile := filepath.Join(a.Config.DataDir, servicesDir, svcID.StringHash()) 392 configFile := filepath.Join(a.Config.DataDir, serviceConfigDir, svcID.StringHash()) 393 394 // Service is not persisted unless requested, but we always persist service configs. 395 err = a.AddService(AddServiceRequest{Service: svc, Source: ConfigSourceRemote}) 396 require.NoError(err) 397 requireFileIsAbsent(t, svcFile) 398 requireFileIsPresent(t, configFile) 399 400 // Persists to file if requested 401 err = a.AddService(AddServiceRequest{ 402 Service: svc, 403 persist: true, 404 token: "mytoken", 405 Source: ConfigSourceRemote, 406 }) 407 require.NoError(err) 408 requireFileIsPresent(t, svcFile) 409 requireFileIsPresent(t, configFile) 410 411 // Service definition file is sane. 412 expectJSONFile(t, svcFile, persistedService{ 413 Token: "mytoken", 414 Service: svc, 415 Source: "remote", 416 }, nil) 417 418 // Service config file is sane. 419 pcfg := persistedServiceConfig{ 420 ServiceID: "web-sidecar-proxy", 421 Defaults: &structs.ServiceConfigResponse{ 422 ProxyConfig: map[string]interface{}{ 423 "foo": 1, 424 "protocol": "http", 425 }, 426 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 427 structs.OpaqueUpstreamConfig{ 428 Upstream: structs.NewServiceID("redis", nil), 429 Config: map[string]interface{}{ 430 "protocol": "tcp", 431 }, 432 }, 433 }, 434 }, 435 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 436 } 437 expectJSONFile(t, configFile, pcfg, resetDefaultsQueryMeta) 438 439 // Verify in memory state. 440 { 441 sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 442 require.NotNil(sidecarService) 443 require.Equal(expectState, sidecarService) 444 } 445 446 // Updates service definition on disk 447 svc.Proxy.LocalServicePort = 8001 448 require.NoError(a.addServiceFromSource(svc, nil, true, "mytoken", ConfigSourceRemote)) 449 requireFileIsPresent(t, svcFile) 450 requireFileIsPresent(t, configFile) 451 452 // Service definition file is updated. 453 expectJSONFile(t, svcFile, persistedService{ 454 Token: "mytoken", 455 Service: svc, 456 Source: "remote", 457 }, nil) 458 459 // Service config file is the same. 460 pcfg = persistedServiceConfig{ 461 ServiceID: "web-sidecar-proxy", 462 Defaults: &structs.ServiceConfigResponse{ 463 ProxyConfig: map[string]interface{}{ 464 "foo": 1, 465 "protocol": "http", 466 }, 467 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 468 structs.OpaqueUpstreamConfig{ 469 Upstream: structs.NewServiceID("redis", nil), 470 Config: map[string]interface{}{ 471 "protocol": "tcp", 472 }, 473 }, 474 }, 475 }, 476 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 477 } 478 expectJSONFile(t, configFile, pcfg, resetDefaultsQueryMeta) 479 480 // Verify in memory state. 481 expectState.Proxy.LocalServicePort = 8001 482 { 483 sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 484 require.NotNil(sidecarService) 485 require.Equal(expectState, sidecarService) 486 } 487 488 // Kill the agent to restart it. 489 a.Shutdown() 490 491 // Kill the server so that it can't phone home and must rely upon the persisted defaults. 492 serverAgent.Shutdown() 493 494 // Should load it back during later start. 495 a2 := StartTestAgent(t, TestAgent{HCL: cfg, DataDir: a.DataDir}) 496 defer a2.Shutdown() 497 498 { 499 restored := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 500 require.NotNil(restored) 501 require.Equal(expectState, restored) 502 } 503 504 // Now remove it. 505 require.NoError(a2.RemoveService(structs.NewServiceID("web-sidecar-proxy", nil))) 506 requireFileIsAbsent(t, svcFile) 507 requireFileIsAbsent(t, configFile) 508} 509 510func TestServiceManager_PersistService_ConfigFiles(t *testing.T) { 511 if testing.Short() { 512 t.Skip("too slow for testing.Short") 513 } 514 515 // This is the ServiceManager version of TestAgent_PersistService and 516 // TestAgent_PurgeService but for config files. 517 t.Parallel() 518 519 // Launch a server to manage the config entries. 520 serverAgent := NewTestAgent(t, "") 521 defer serverAgent.Shutdown() 522 testrpc.WaitForLeader(t, serverAgent.RPC, "dc1") 523 524 // Register a global proxy and service config 525 testApplyConfigEntries(t, serverAgent, 526 &structs.ProxyConfigEntry{ 527 Config: map[string]interface{}{ 528 "foo": 1, 529 }, 530 }, 531 &structs.ServiceConfigEntry{ 532 Kind: structs.ServiceDefaults, 533 Name: "web", 534 Protocol: "http", 535 }, 536 &structs.ServiceConfigEntry{ 537 Kind: structs.ServiceDefaults, 538 Name: "redis", 539 Protocol: "tcp", 540 }, 541 ) 542 543 // Now launch a single client agent 544 serviceSnippet := ` 545 service = { 546 kind = "connect-proxy" 547 id = "web-sidecar-proxy" 548 name = "web-sidecar-proxy" 549 port = 21000 550 token = "mytoken" 551 proxy { 552 destination_service_name = "web" 553 destination_service_id = "web" 554 local_service_address = "127.0.0.1" 555 local_service_port = 8000 556 upstreams = [{ 557 destination_name = "redis" 558 destination_namespace = "default" 559 local_bind_port = 5000 560 }] 561 } 562 } 563 ` 564 565 cfg := ` 566 server = false 567 bootstrap = false 568 ` + serviceSnippet 569 570 a := StartTestAgent(t, TestAgent{HCL: cfg}) 571 defer a.Shutdown() 572 573 // Join first 574 _, err := a.JoinLAN([]string{ 575 fmt.Sprintf("127.0.0.1:%d", serverAgent.Config.SerfPortLAN), 576 }) 577 require.NoError(t, err) 578 579 testrpc.WaitForLeader(t, a.RPC, "dc1") 580 581 // Now register a sidecar proxy via the API. 582 svcID := "web-sidecar-proxy" 583 584 expectState := &structs.NodeService{ 585 Kind: structs.ServiceKindConnectProxy, 586 ID: "web-sidecar-proxy", 587 Service: "web-sidecar-proxy", 588 Port: 21000, 589 TaggedAddresses: map[string]structs.ServiceAddress{}, 590 Proxy: structs.ConnectProxyConfig{ 591 DestinationServiceName: "web", 592 DestinationServiceID: "web", 593 LocalServiceAddress: "127.0.0.1", 594 LocalServicePort: 8000, 595 Config: map[string]interface{}{ 596 "foo": int64(1), 597 "protocol": "http", 598 }, 599 Upstreams: structs.Upstreams{ 600 { 601 DestinationType: "service", 602 DestinationName: "redis", 603 DestinationNamespace: "default", 604 LocalBindPort: 5000, 605 Config: map[string]interface{}{ 606 "protocol": "tcp", 607 }, 608 }, 609 }, 610 }, 611 Weights: &structs.Weights{ 612 Passing: 1, 613 Warning: 1, 614 }, 615 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 616 } 617 618 // Now wait until we've re-registered using central config updated data. 619 retry.Run(t, func(r *retry.R) { 620 a.stateLock.Lock() 621 defer a.stateLock.Unlock() 622 current := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 623 if current == nil { 624 r.Fatalf("service is missing") 625 } 626 require.Equal(r, expectState, current) 627 }) 628 629 svcFile := filepath.Join(a.Config.DataDir, servicesDir, stringHash(svcID)) 630 configFile := filepath.Join(a.Config.DataDir, serviceConfigDir, stringHash(svcID)) 631 632 // Service is never persisted, but we always persist service configs. 633 requireFileIsAbsent(t, svcFile) 634 requireFileIsPresent(t, configFile) 635 636 // Service config file is sane. 637 expectJSONFile(t, configFile, persistedServiceConfig{ 638 ServiceID: "web-sidecar-proxy", 639 Defaults: &structs.ServiceConfigResponse{ 640 ProxyConfig: map[string]interface{}{ 641 "foo": 1, 642 "protocol": "http", 643 }, 644 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 645 structs.OpaqueUpstreamConfig{ 646 Upstream: structs.NewServiceID("redis", nil), 647 Config: map[string]interface{}{ 648 "protocol": "tcp", 649 }, 650 }, 651 }, 652 }, 653 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 654 }, resetDefaultsQueryMeta) 655 656 // Verify in memory state. 657 { 658 sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 659 require.NotNil(t, sidecarService) 660 require.Equal(t, expectState, sidecarService) 661 } 662 663 // Kill the agent to restart it. 664 a.Shutdown() 665 666 // Kill the server so that it can't phone home and must rely upon the persisted defaults. 667 serverAgent.Shutdown() 668 669 // Should load it back during later start. 670 a2 := StartTestAgent(t, TestAgent{HCL: cfg, DataDir: a.DataDir}) 671 defer a2.Shutdown() 672 673 { 674 restored := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 675 require.NotNil(t, restored) 676 require.Equal(t, expectState, restored) 677 } 678 679 // Now remove it. 680 require.NoError(t, a2.RemoveService(structs.NewServiceID("web-sidecar-proxy", nil))) 681 requireFileIsAbsent(t, svcFile) 682 requireFileIsAbsent(t, configFile) 683} 684 685func TestServiceManager_Disabled(t *testing.T) { 686 if testing.Short() { 687 t.Skip("too slow for testing.Short") 688 } 689 690 require := require.New(t) 691 692 a := NewTestAgent(t, "enable_central_service_config = false") 693 defer a.Shutdown() 694 695 testrpc.WaitForLeader(t, a.RPC, "dc1") 696 697 // Register a global proxy and service config 698 testApplyConfigEntries(t, a, 699 &structs.ProxyConfigEntry{ 700 Config: map[string]interface{}{ 701 "foo": 1, 702 }, 703 }, 704 &structs.ServiceConfigEntry{ 705 Kind: structs.ServiceDefaults, 706 Name: "web", 707 Protocol: "http", 708 }, 709 &structs.ServiceConfigEntry{ 710 Kind: structs.ServiceDefaults, 711 Name: "redis", 712 Protocol: "tcp", 713 }, 714 ) 715 716 // Now register a sidecar proxy. Note we don't use SidecarService here because 717 // that gets resolved earlier in config handling than the AddService call 718 // here. 719 svc := &structs.NodeService{ 720 Kind: structs.ServiceKindConnectProxy, 721 ID: "web-sidecar-proxy", 722 Service: "web-sidecar-proxy", 723 Port: 21000, 724 Proxy: structs.ConnectProxyConfig{ 725 DestinationServiceName: "web", 726 DestinationServiceID: "web", 727 LocalServiceAddress: "127.0.0.1", 728 LocalServicePort: 8000, 729 Upstreams: structs.Upstreams{ 730 { 731 DestinationName: "redis", 732 LocalBindPort: 5000, 733 }, 734 }, 735 }, 736 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 737 } 738 require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal)) 739 740 // Verify sidecar got global config loaded 741 sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil)) 742 require.NotNil(sidecarService) 743 require.Equal(&structs.NodeService{ 744 Kind: structs.ServiceKindConnectProxy, 745 ID: "web-sidecar-proxy", 746 Service: "web-sidecar-proxy", 747 Port: 21000, 748 TaggedAddresses: map[string]structs.ServiceAddress{}, 749 Proxy: structs.ConnectProxyConfig{ 750 DestinationServiceName: "web", 751 DestinationServiceID: "web", 752 LocalServiceAddress: "127.0.0.1", 753 LocalServicePort: 8000, 754 // No config added 755 Upstreams: structs.Upstreams{ 756 { 757 DestinationName: "redis", 758 LocalBindPort: 5000, 759 // No config added 760 }, 761 }, 762 }, 763 Weights: &structs.Weights{ 764 Passing: 1, 765 Warning: 1, 766 }, 767 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 768 }, sidecarService) 769} 770 771func testApplyConfigEntries(t *testing.T, a *TestAgent, entries ...structs.ConfigEntry) { 772 t.Helper() 773 for _, entry := range entries { 774 args := &structs.ConfigEntryRequest{ 775 Datacenter: "dc1", 776 Entry: entry, 777 } 778 var out bool 779 require.NoError(t, a.RPC("ConfigEntry.Apply", args, &out)) 780 } 781} 782 783func requireFileIsAbsent(t *testing.T, file string) { 784 t.Helper() 785 if _, err := os.Stat(file); !os.IsNotExist(err) { 786 t.Fatalf("should not persist") 787 } 788} 789 790func requireFileIsPresent(t *testing.T, file string) { 791 t.Helper() 792 if _, err := os.Stat(file); err != nil { 793 t.Fatalf("err: %v", err) 794 } 795} 796 797func expectJSONFile(t *testing.T, file string, expect interface{}, fixupContentBeforeCompareFn func([]byte) ([]byte, error)) { 798 t.Helper() 799 800 expected, err := json.Marshal(expect) 801 require.NoError(t, err) 802 803 content, err := ioutil.ReadFile(file) 804 require.NoError(t, err) 805 806 if fixupContentBeforeCompareFn != nil { 807 content, err = fixupContentBeforeCompareFn(content) 808 require.NoError(t, err) 809 } 810 811 require.JSONEq(t, string(expected), string(content)) 812} 813 814// resetDefaultsQueryMeta will reset the embedded fields from structs.QueryMeta 815// to their zero values in the json object keyed under 'Defaults'. 816func resetDefaultsQueryMeta(content []byte) ([]byte, error) { 817 var raw map[string]interface{} 818 if err := json.Unmarshal(content, &raw); err != nil { 819 return nil, err 820 } 821 def, ok := raw["Defaults"] 822 if !ok { 823 return content, nil 824 } 825 826 rawDef, ok := def.(map[string]interface{}) 827 if !ok { 828 return nil, fmt.Errorf("unexpected structure found in 'Defaults' key") 829 } 830 831 qmZero, err := convertToMap(structs.QueryMeta{}) 832 if err != nil { 833 return nil, err 834 } 835 836 for k, v := range qmZero { 837 rawDef[k] = v 838 } 839 840 raw["Defaults"] = rawDef 841 842 return json.Marshal(raw) 843} 844 845func convertToMap(v interface{}) (map[string]interface{}, error) { 846 b, err := json.Marshal(v) 847 if err != nil { 848 return nil, err 849 } 850 851 var raw map[string]interface{} 852 if err := json.Unmarshal(b, &raw); err != nil { 853 return nil, err 854 } 855 856 return raw, nil 857} 858 859func Test_mergeServiceConfig_UpstreamOverrides(t *testing.T) { 860 type args struct { 861 defaults *structs.ServiceConfigResponse 862 service *structs.NodeService 863 } 864 tests := []struct { 865 name string 866 args args 867 want *structs.NodeService 868 }{ 869 { 870 name: "new config fields", 871 args: args{ 872 defaults: &structs.ServiceConfigResponse{ 873 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 874 { 875 Upstream: structs.ServiceID{ 876 ID: "zap", 877 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 878 }, 879 Config: map[string]interface{}{ 880 "passive_health_check": map[string]interface{}{ 881 "Interval": int64(10), 882 "MaxFailures": int64(2), 883 }, 884 "mesh_gateway": map[string]interface{}{ 885 "Mode": "local", 886 }, 887 "protocol": "grpc", 888 }, 889 }, 890 }, 891 }, 892 service: &structs.NodeService{ 893 ID: "foo-proxy", 894 Service: "foo-proxy", 895 Proxy: structs.ConnectProxyConfig{ 896 DestinationServiceName: "foo", 897 DestinationServiceID: "foo", 898 Upstreams: structs.Upstreams{ 899 structs.Upstream{ 900 DestinationNamespace: "default", 901 DestinationName: "zap", 902 }, 903 }, 904 }, 905 }, 906 }, 907 want: &structs.NodeService{ 908 ID: "foo-proxy", 909 Service: "foo-proxy", 910 Proxy: structs.ConnectProxyConfig{ 911 DestinationServiceName: "foo", 912 DestinationServiceID: "foo", 913 Upstreams: structs.Upstreams{ 914 structs.Upstream{ 915 DestinationNamespace: "default", 916 DestinationName: "zap", 917 Config: map[string]interface{}{ 918 "passive_health_check": map[string]interface{}{ 919 "Interval": int64(10), 920 "MaxFailures": int64(2), 921 }, 922 "protocol": "grpc", 923 }, 924 MeshGateway: structs.MeshGatewayConfig{ 925 Mode: structs.MeshGatewayModeLocal, 926 }, 927 }, 928 }, 929 }, 930 }, 931 }, 932 { 933 name: "remote upstream config expands local upstream list in transparent mode", 934 args: args{ 935 defaults: &structs.ServiceConfigResponse{ 936 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 937 { 938 Upstream: structs.ServiceID{ 939 ID: "zap", 940 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 941 }, 942 Config: map[string]interface{}{ 943 "protocol": "grpc", 944 }, 945 }, 946 }, 947 }, 948 service: &structs.NodeService{ 949 ID: "foo-proxy", 950 Service: "foo-proxy", 951 Proxy: structs.ConnectProxyConfig{ 952 DestinationServiceName: "foo", 953 DestinationServiceID: "foo", 954 Mode: structs.ProxyModeTransparent, 955 TransparentProxy: structs.TransparentProxyConfig{ 956 OutboundListenerPort: 10101, 957 DialedDirectly: true, 958 }, 959 Upstreams: structs.Upstreams{ 960 structs.Upstream{ 961 DestinationNamespace: "default", 962 DestinationName: "zip", 963 LocalBindPort: 8080, 964 Config: map[string]interface{}{ 965 "protocol": "http", 966 }, 967 }, 968 }, 969 }, 970 }, 971 }, 972 want: &structs.NodeService{ 973 ID: "foo-proxy", 974 Service: "foo-proxy", 975 Proxy: structs.ConnectProxyConfig{ 976 DestinationServiceName: "foo", 977 DestinationServiceID: "foo", 978 Mode: structs.ProxyModeTransparent, 979 TransparentProxy: structs.TransparentProxyConfig{ 980 OutboundListenerPort: 10101, 981 DialedDirectly: true, 982 }, 983 Upstreams: structs.Upstreams{ 984 structs.Upstream{ 985 DestinationNamespace: "default", 986 DestinationName: "zip", 987 LocalBindPort: 8080, 988 Config: map[string]interface{}{ 989 "protocol": "http", 990 }, 991 }, 992 structs.Upstream{ 993 DestinationNamespace: "default", 994 DestinationName: "zap", 995 Config: map[string]interface{}{ 996 "protocol": "grpc", 997 }, 998 CentrallyConfigured: true, 999 }, 1000 }, 1001 }, 1002 }, 1003 }, 1004 { 1005 name: "remote upstream config not added to local upstream list outside of transparent mode", 1006 args: args{ 1007 defaults: &structs.ServiceConfigResponse{ 1008 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 1009 { 1010 Upstream: structs.ServiceID{ 1011 ID: "zap", 1012 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 1013 }, 1014 Config: map[string]interface{}{ 1015 "protocol": "grpc", 1016 }, 1017 }, 1018 }, 1019 }, 1020 service: &structs.NodeService{ 1021 ID: "foo-proxy", 1022 Service: "foo-proxy", 1023 Proxy: structs.ConnectProxyConfig{ 1024 DestinationServiceName: "foo", 1025 DestinationServiceID: "foo", 1026 Mode: structs.ProxyModeDirect, 1027 Upstreams: structs.Upstreams{ 1028 structs.Upstream{ 1029 DestinationNamespace: "default", 1030 DestinationName: "zip", 1031 LocalBindPort: 8080, 1032 Config: map[string]interface{}{ 1033 "protocol": "http", 1034 }, 1035 }, 1036 }, 1037 }, 1038 }, 1039 }, 1040 want: &structs.NodeService{ 1041 ID: "foo-proxy", 1042 Service: "foo-proxy", 1043 Proxy: structs.ConnectProxyConfig{ 1044 DestinationServiceName: "foo", 1045 DestinationServiceID: "foo", 1046 Mode: structs.ProxyModeDirect, 1047 Upstreams: structs.Upstreams{ 1048 structs.Upstream{ 1049 DestinationNamespace: "default", 1050 DestinationName: "zip", 1051 LocalBindPort: 8080, 1052 Config: map[string]interface{}{ 1053 "protocol": "http", 1054 }, 1055 }, 1056 }, 1057 }, 1058 }, 1059 }, 1060 { 1061 name: "upstream mode from remote defaults overrides local default", 1062 args: args{ 1063 defaults: &structs.ServiceConfigResponse{ 1064 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 1065 { 1066 Upstream: structs.ServiceID{ 1067 ID: "zap", 1068 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 1069 }, 1070 Config: map[string]interface{}{ 1071 "mesh_gateway": map[string]interface{}{ 1072 "Mode": "local", 1073 }, 1074 }, 1075 }, 1076 }, 1077 }, 1078 service: &structs.NodeService{ 1079 ID: "foo-proxy", 1080 Service: "foo-proxy", 1081 Proxy: structs.ConnectProxyConfig{ 1082 DestinationServiceName: "foo", 1083 DestinationServiceID: "foo", 1084 MeshGateway: structs.MeshGatewayConfig{ 1085 Mode: structs.MeshGatewayModeRemote, 1086 }, 1087 Upstreams: structs.Upstreams{ 1088 structs.Upstream{ 1089 DestinationNamespace: "default", 1090 DestinationName: "zap", 1091 }, 1092 }, 1093 }, 1094 }, 1095 }, 1096 want: &structs.NodeService{ 1097 ID: "foo-proxy", 1098 Service: "foo-proxy", 1099 Proxy: structs.ConnectProxyConfig{ 1100 DestinationServiceName: "foo", 1101 DestinationServiceID: "foo", 1102 MeshGateway: structs.MeshGatewayConfig{ 1103 Mode: structs.MeshGatewayModeRemote, 1104 }, 1105 Upstreams: structs.Upstreams{ 1106 structs.Upstream{ 1107 DestinationNamespace: "default", 1108 DestinationName: "zap", 1109 Config: map[string]interface{}{}, 1110 MeshGateway: structs.MeshGatewayConfig{ 1111 Mode: structs.MeshGatewayModeLocal, 1112 }, 1113 }, 1114 }, 1115 }, 1116 }, 1117 }, 1118 { 1119 name: "mode in local upstream config overrides all", 1120 args: args{ 1121 defaults: &structs.ServiceConfigResponse{ 1122 UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{ 1123 { 1124 Upstream: structs.ServiceID{ 1125 ID: "zap", 1126 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 1127 }, 1128 Config: map[string]interface{}{ 1129 "mesh_gateway": map[string]interface{}{ 1130 "Mode": "local", 1131 }, 1132 }, 1133 }, 1134 }, 1135 }, 1136 service: &structs.NodeService{ 1137 ID: "foo-proxy", 1138 Service: "foo-proxy", 1139 Proxy: structs.ConnectProxyConfig{ 1140 DestinationServiceName: "foo", 1141 DestinationServiceID: "foo", 1142 MeshGateway: structs.MeshGatewayConfig{ 1143 Mode: structs.MeshGatewayModeRemote, 1144 }, 1145 Upstreams: structs.Upstreams{ 1146 structs.Upstream{ 1147 DestinationNamespace: "default", 1148 DestinationName: "zap", 1149 MeshGateway: structs.MeshGatewayConfig{ 1150 Mode: structs.MeshGatewayModeNone, 1151 }, 1152 }, 1153 }, 1154 }, 1155 }, 1156 }, 1157 want: &structs.NodeService{ 1158 ID: "foo-proxy", 1159 Service: "foo-proxy", 1160 Proxy: structs.ConnectProxyConfig{ 1161 DestinationServiceName: "foo", 1162 DestinationServiceID: "foo", 1163 MeshGateway: structs.MeshGatewayConfig{ 1164 Mode: structs.MeshGatewayModeRemote, 1165 }, 1166 Upstreams: structs.Upstreams{ 1167 structs.Upstream{ 1168 DestinationNamespace: "default", 1169 DestinationName: "zap", 1170 Config: map[string]interface{}{}, 1171 MeshGateway: structs.MeshGatewayConfig{ 1172 Mode: structs.MeshGatewayModeNone, 1173 }, 1174 }, 1175 }, 1176 }, 1177 }, 1178 }, 1179 } 1180 for _, tt := range tests { 1181 t.Run(tt.name, func(t *testing.T) { 1182 defaultsCopy, err := copystructure.Copy(tt.args.defaults) 1183 require.NoError(t, err) 1184 1185 got, err := mergeServiceConfig(tt.args.defaults, tt.args.service) 1186 require.NoError(t, err) 1187 assert.Equal(t, tt.want, got) 1188 1189 // The input defaults must not be modified by the merge. 1190 // See PR #10647 1191 assert.Equal(t, tt.args.defaults, defaultsCopy) 1192 }) 1193 } 1194} 1195 1196func Test_mergeServiceConfig_TransparentProxy(t *testing.T) { 1197 type args struct { 1198 defaults *structs.ServiceConfigResponse 1199 service *structs.NodeService 1200 } 1201 tests := []struct { 1202 name string 1203 args args 1204 want *structs.NodeService 1205 }{ 1206 { 1207 name: "inherit transparent proxy settings", 1208 args: args{ 1209 defaults: &structs.ServiceConfigResponse{ 1210 Mode: structs.ProxyModeTransparent, 1211 TransparentProxy: structs.TransparentProxyConfig{ 1212 OutboundListenerPort: 10101, 1213 DialedDirectly: true, 1214 }, 1215 }, 1216 service: &structs.NodeService{ 1217 ID: "foo-proxy", 1218 Service: "foo-proxy", 1219 Proxy: structs.ConnectProxyConfig{ 1220 DestinationServiceName: "foo", 1221 DestinationServiceID: "foo", 1222 Mode: structs.ProxyModeDefault, 1223 TransparentProxy: structs.TransparentProxyConfig{}, 1224 }, 1225 }, 1226 }, 1227 want: &structs.NodeService{ 1228 ID: "foo-proxy", 1229 Service: "foo-proxy", 1230 Proxy: structs.ConnectProxyConfig{ 1231 DestinationServiceName: "foo", 1232 DestinationServiceID: "foo", 1233 Mode: structs.ProxyModeTransparent, 1234 TransparentProxy: structs.TransparentProxyConfig{ 1235 OutboundListenerPort: 10101, 1236 DialedDirectly: true, 1237 }, 1238 }, 1239 }, 1240 }, 1241 { 1242 name: "override transparent proxy settings", 1243 args: args{ 1244 defaults: &structs.ServiceConfigResponse{ 1245 Mode: structs.ProxyModeTransparent, 1246 TransparentProxy: structs.TransparentProxyConfig{ 1247 OutboundListenerPort: 10101, 1248 DialedDirectly: false, 1249 }, 1250 }, 1251 service: &structs.NodeService{ 1252 ID: "foo-proxy", 1253 Service: "foo-proxy", 1254 Proxy: structs.ConnectProxyConfig{ 1255 DestinationServiceName: "foo", 1256 DestinationServiceID: "foo", 1257 Mode: structs.ProxyModeDirect, 1258 TransparentProxy: structs.TransparentProxyConfig{ 1259 OutboundListenerPort: 808, 1260 DialedDirectly: true, 1261 }, 1262 }, 1263 }, 1264 }, 1265 want: &structs.NodeService{ 1266 ID: "foo-proxy", 1267 Service: "foo-proxy", 1268 Proxy: structs.ConnectProxyConfig{ 1269 DestinationServiceName: "foo", 1270 DestinationServiceID: "foo", 1271 Mode: structs.ProxyModeDirect, 1272 TransparentProxy: structs.TransparentProxyConfig{ 1273 OutboundListenerPort: 808, 1274 DialedDirectly: true, 1275 }, 1276 }, 1277 }, 1278 }, 1279 } 1280 for _, tt := range tests { 1281 t.Run(tt.name, func(t *testing.T) { 1282 defaultsCopy, err := copystructure.Copy(tt.args.defaults) 1283 require.NoError(t, err) 1284 1285 got, err := mergeServiceConfig(tt.args.defaults, tt.args.service) 1286 require.NoError(t, err) 1287 assert.Equal(t, tt.want, got) 1288 1289 // The input defaults must not be modified by the merge. 1290 // See PR #10647 1291 assert.Equal(t, tt.args.defaults, defaultsCopy) 1292 }) 1293 } 1294} 1295