github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent/consul/connect_test.go (about) 1 package consul 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/hashicorp/consul/api" 8 "github.com/hashicorp/nomad/ci" 9 "github.com/hashicorp/nomad/helper/pointer" 10 "github.com/hashicorp/nomad/helper/uuid" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/stretchr/testify/require" 13 ) 14 15 var ( 16 testConnectNetwork = structs.Networks{{ 17 Mode: "bridge", 18 Device: "eth0", 19 IP: "192.168.30.1", 20 DynamicPorts: []structs.Port{ 21 {Label: "healthPort", Value: 23100, To: 23100}, 22 {Label: "metricsPort", Value: 23200, To: 23200}, 23 {Label: "connect-proxy-redis", Value: 3000, To: 3000}, 24 }, 25 }} 26 testConnectPorts = structs.AllocatedPorts{{ 27 Label: "connect-proxy-redis", 28 Value: 3000, 29 To: 3000, 30 HostIP: "192.168.30.1", 31 }} 32 ) 33 34 func TestConnect_newConnect(t *testing.T) { 35 ci.Parallel(t) 36 37 service := "redis" 38 redisID := uuid.Generate() 39 allocID := uuid.Generate() 40 info := structs.AllocInfo{ 41 AllocID: allocID, 42 } 43 44 t.Run("nil", func(t *testing.T) { 45 asr, err := newConnect("", structs.AllocInfo{}, "", nil, nil, nil) 46 require.NoError(t, err) 47 require.Nil(t, asr) 48 }) 49 50 t.Run("native", func(t *testing.T) { 51 asr, err := newConnect(redisID, info, service, &structs.ConsulConnect{ 52 Native: true, 53 }, nil, nil) 54 require.NoError(t, err) 55 require.True(t, asr.Native) 56 require.Nil(t, asr.SidecarService) 57 }) 58 59 t.Run("with sidecar", func(t *testing.T) { 60 asr, err := newConnect(redisID, info, service, &structs.ConsulConnect{ 61 Native: false, 62 SidecarService: &structs.ConsulSidecarService{ 63 Tags: []string{"foo", "bar"}, 64 Port: "connect-proxy-redis", 65 }, 66 }, testConnectNetwork, testConnectPorts) 67 require.NoError(t, err) 68 require.Equal(t, &api.AgentServiceRegistration{ 69 Tags: []string{"foo", "bar"}, 70 Port: 3000, 71 Address: "192.168.30.1", 72 Proxy: &api.AgentServiceConnectProxyConfig{ 73 Config: map[string]interface{}{ 74 "bind_address": "0.0.0.0", 75 "bind_port": 3000, 76 "envoy_stats_tags": []string{"nomad.alloc_id=" + allocID}, 77 }, 78 }, 79 Checks: api.AgentServiceChecks{ 80 { 81 Name: "Connect Sidecar Aliasing " + redisID, 82 AliasService: redisID, 83 }, 84 { 85 Name: "Connect Sidecar Listening", 86 TCP: "192.168.30.1:3000", 87 Interval: "10s", 88 }, 89 }, 90 }, asr.SidecarService) 91 }) 92 93 t.Run("with sidecar without TCP checks", func(t *testing.T) { 94 asr, err := newConnect(redisID, info, service, &structs.ConsulConnect{ 95 Native: false, 96 SidecarService: &structs.ConsulSidecarService{ 97 Tags: []string{"foo", "bar"}, 98 Port: "connect-proxy-redis", 99 DisableDefaultTCPCheck: true, 100 }, 101 }, testConnectNetwork, testConnectPorts) 102 require.NoError(t, err) 103 require.Equal(t, &api.AgentServiceRegistration{ 104 Tags: []string{"foo", "bar"}, 105 Port: 3000, 106 Address: "192.168.30.1", 107 Proxy: &api.AgentServiceConnectProxyConfig{ 108 Config: map[string]interface{}{ 109 "bind_address": "0.0.0.0", 110 "bind_port": 3000, 111 "envoy_stats_tags": []string{"nomad.alloc_id=" + allocID}, 112 }, 113 }, 114 Checks: api.AgentServiceChecks{ 115 { 116 Name: "Connect Sidecar Aliasing " + redisID, 117 AliasService: redisID, 118 }, 119 }, 120 }, asr.SidecarService) 121 }) 122 } 123 124 func TestConnect_connectSidecarRegistration(t *testing.T) { 125 ci.Parallel(t) 126 127 redisID := uuid.Generate() 128 allocID := uuid.Generate() 129 info := structs.AllocInfo{ 130 AllocID: allocID, 131 } 132 133 t.Run("nil", func(t *testing.T) { 134 sidecarReg, err := connectSidecarRegistration(redisID, info, nil, testConnectNetwork, testConnectPorts) 135 require.NoError(t, err) 136 require.Nil(t, sidecarReg) 137 }) 138 139 t.Run("no service port", func(t *testing.T) { 140 _, err := connectSidecarRegistration("unknown-id", info, &structs.ConsulSidecarService{ 141 Port: "unknown-label", 142 }, testConnectNetwork, testConnectPorts) 143 require.EqualError(t, err, `No port of label "unknown-label" defined`) 144 }) 145 146 t.Run("bad proxy", func(t *testing.T) { 147 _, err := connectSidecarRegistration(redisID, info, &structs.ConsulSidecarService{ 148 Port: "connect-proxy-redis", 149 Proxy: &structs.ConsulProxy{ 150 Expose: &structs.ConsulExposeConfig{ 151 Paths: []structs.ConsulExposePath{{ 152 ListenerPort: "badPort", 153 }}, 154 }, 155 }, 156 }, testConnectNetwork, testConnectPorts) 157 require.EqualError(t, err, `No port of label "badPort" defined`) 158 }) 159 160 t.Run("normal", func(t *testing.T) { 161 proxy, err := connectSidecarRegistration(redisID, info, &structs.ConsulSidecarService{ 162 Tags: []string{"foo", "bar"}, 163 Port: "connect-proxy-redis", 164 }, testConnectNetwork, testConnectPorts) 165 require.NoError(t, err) 166 require.Equal(t, &api.AgentServiceRegistration{ 167 Tags: []string{"foo", "bar"}, 168 Port: 3000, 169 Address: "192.168.30.1", 170 Proxy: &api.AgentServiceConnectProxyConfig{ 171 Config: map[string]interface{}{ 172 "bind_address": "0.0.0.0", 173 "bind_port": 3000, 174 "envoy_stats_tags": []string{"nomad.alloc_id=" + allocID}, 175 }, 176 }, 177 Checks: api.AgentServiceChecks{ 178 { 179 Name: "Connect Sidecar Aliasing " + redisID, 180 AliasService: redisID, 181 }, 182 { 183 Name: "Connect Sidecar Listening", 184 TCP: "192.168.30.1:3000", 185 Interval: "10s", 186 }, 187 }, 188 }, proxy) 189 }) 190 } 191 192 func TestConnect_connectProxy(t *testing.T) { 193 ci.Parallel(t) 194 195 allocID := uuid.Generate() 196 info := structs.AllocInfo{ 197 AllocID: allocID, 198 } 199 200 // If the input proxy is nil, we expect the output to be a proxy with its 201 // config set to default values. 202 t.Run("nil proxy", func(t *testing.T) { 203 proxy, err := connectSidecarProxy(info, nil, 2000, testConnectNetwork) 204 require.NoError(t, err) 205 require.Equal(t, &api.AgentServiceConnectProxyConfig{ 206 LocalServiceAddress: "", 207 LocalServicePort: 0, 208 Upstreams: nil, 209 Expose: api.ExposeConfig{}, 210 Config: map[string]interface{}{ 211 "bind_address": "0.0.0.0", 212 "bind_port": 2000, 213 "envoy_stats_tags": []string{"nomad.alloc_id=" + allocID}, 214 }, 215 }, proxy) 216 }) 217 218 t.Run("bad proxy", func(t *testing.T) { 219 _, err := connectSidecarProxy(info, &structs.ConsulProxy{ 220 LocalServiceAddress: "0.0.0.0", 221 LocalServicePort: 2000, 222 Upstreams: nil, 223 Expose: &structs.ConsulExposeConfig{ 224 Paths: []structs.ConsulExposePath{{ 225 ListenerPort: "badPort", 226 }}, 227 }, 228 Config: nil, 229 }, 2000, testConnectNetwork) 230 require.EqualError(t, err, `No port of label "badPort" defined`) 231 }) 232 233 t.Run("normal", func(t *testing.T) { 234 proxy, err := connectSidecarProxy(info, &structs.ConsulProxy{ 235 LocalServiceAddress: "0.0.0.0", 236 LocalServicePort: 2000, 237 Upstreams: nil, 238 Expose: &structs.ConsulExposeConfig{ 239 Paths: []structs.ConsulExposePath{{ 240 Path: "/health", 241 Protocol: "http", 242 LocalPathPort: 8000, 243 ListenerPort: "healthPort", 244 }}, 245 }, 246 Config: nil, 247 }, 2000, testConnectNetwork) 248 require.NoError(t, err) 249 require.Equal(t, &api.AgentServiceConnectProxyConfig{ 250 LocalServiceAddress: "0.0.0.0", 251 LocalServicePort: 2000, 252 Upstreams: nil, 253 Expose: api.ExposeConfig{ 254 Paths: []api.ExposePath{{ 255 Path: "/health", 256 Protocol: "http", 257 LocalPathPort: 8000, 258 ListenerPort: 23100, 259 }}, 260 }, 261 Config: map[string]interface{}{ 262 "bind_address": "0.0.0.0", 263 "bind_port": 2000, 264 "envoy_stats_tags": []string{"nomad.alloc_id=" + allocID}, 265 }, 266 }, proxy) 267 }) 268 } 269 270 func TestConnect_connectProxyExpose(t *testing.T) { 271 ci.Parallel(t) 272 273 t.Run("nil", func(t *testing.T) { 274 exposeConfig, err := connectProxyExpose(nil, nil) 275 require.NoError(t, err) 276 require.Equal(t, api.ExposeConfig{}, exposeConfig) 277 }) 278 279 t.Run("bad port", func(t *testing.T) { 280 _, err := connectProxyExpose(&structs.ConsulExposeConfig{ 281 Paths: []structs.ConsulExposePath{{ 282 ListenerPort: "badPort", 283 }}, 284 }, testConnectNetwork) 285 require.EqualError(t, err, `No port of label "badPort" defined`) 286 }) 287 288 t.Run("normal", func(t *testing.T) { 289 expose, err := connectProxyExpose(&structs.ConsulExposeConfig{ 290 Paths: []structs.ConsulExposePath{{ 291 Path: "/health", 292 Protocol: "http", 293 LocalPathPort: 8000, 294 ListenerPort: "healthPort", 295 }}, 296 }, testConnectNetwork) 297 require.NoError(t, err) 298 require.Equal(t, api.ExposeConfig{ 299 Checks: false, 300 Paths: []api.ExposePath{{ 301 Path: "/health", 302 ListenerPort: 23100, 303 LocalPathPort: 8000, 304 Protocol: "http", 305 ParsedFromCheck: false, 306 }}, 307 }, expose) 308 }) 309 } 310 311 func TestConnect_connectProxyExposePaths(t *testing.T) { 312 ci.Parallel(t) 313 314 t.Run("nil", func(t *testing.T) { 315 upstreams, err := connectProxyExposePaths(nil, nil) 316 require.NoError(t, err) 317 require.Empty(t, upstreams) 318 }) 319 320 t.Run("no network", func(t *testing.T) { 321 original := []structs.ConsulExposePath{{Path: "/path"}} 322 _, err := connectProxyExposePaths(original, nil) 323 require.EqualError(t, err, `Connect only supported with exactly 1 network (found 0)`) 324 }) 325 326 t.Run("normal", func(t *testing.T) { 327 original := []structs.ConsulExposePath{{ 328 Path: "/health", 329 Protocol: "http", 330 LocalPathPort: 8000, 331 ListenerPort: "healthPort", 332 }, { 333 Path: "/metrics", 334 Protocol: "grpc", 335 LocalPathPort: 9500, 336 ListenerPort: "metricsPort", 337 }} 338 exposePaths, err := connectProxyExposePaths(original, testConnectNetwork) 339 require.NoError(t, err) 340 require.Equal(t, []api.ExposePath{ 341 { 342 Path: "/health", 343 Protocol: "http", 344 LocalPathPort: 8000, 345 ListenerPort: 23100, 346 ParsedFromCheck: false, 347 }, 348 { 349 Path: "/metrics", 350 Protocol: "grpc", 351 LocalPathPort: 9500, 352 ListenerPort: 23200, 353 ParsedFromCheck: false, 354 }, 355 }, exposePaths) 356 }) 357 } 358 359 func TestConnect_connectUpstreams(t *testing.T) { 360 ci.Parallel(t) 361 362 t.Run("nil", func(t *testing.T) { 363 require.Nil(t, connectUpstreams(nil)) 364 }) 365 366 t.Run("not empty", func(t *testing.T) { 367 require.Equal(t, 368 []api.Upstream{{ 369 DestinationName: "foo", 370 LocalBindPort: 8000, 371 }, { 372 DestinationName: "bar", 373 DestinationNamespace: "ns2", 374 LocalBindPort: 9000, 375 Datacenter: "dc2", 376 LocalBindAddress: "127.0.0.2", 377 }}, 378 connectUpstreams([]structs.ConsulUpstream{{ 379 DestinationName: "foo", 380 LocalBindPort: 8000, 381 }, { 382 DestinationName: "bar", 383 DestinationNamespace: "ns2", 384 LocalBindPort: 9000, 385 Datacenter: "dc2", 386 LocalBindAddress: "127.0.0.2", 387 }}), 388 ) 389 }) 390 } 391 392 func TestConnect_connectProxyConfig(t *testing.T) { 393 ci.Parallel(t) 394 395 t.Run("nil map", func(t *testing.T) { 396 require.Equal(t, map[string]interface{}{ 397 "bind_address": "0.0.0.0", 398 "bind_port": 42, 399 "envoy_stats_tags": []string{"nomad.alloc_id=test_alloc1"}, 400 }, connectProxyConfig(nil, 42, structs.AllocInfo{AllocID: "test_alloc1"})) 401 }) 402 403 t.Run("pre-existing map", func(t *testing.T) { 404 require.Equal(t, map[string]interface{}{ 405 "bind_address": "0.0.0.0", 406 "bind_port": 42, 407 "foo": "bar", 408 "envoy_stats_tags": []string{"nomad.alloc_id=test_alloc2"}, 409 }, connectProxyConfig(map[string]interface{}{ 410 "foo": "bar", 411 }, 42, structs.AllocInfo{AllocID: "test_alloc2"})) 412 }) 413 } 414 415 func TestConnect_getConnectPort(t *testing.T) { 416 ci.Parallel(t) 417 418 networks := structs.Networks{{ 419 IP: "192.168.30.1", 420 DynamicPorts: []structs.Port{{ 421 Label: "connect-proxy-foo", 422 Value: 23456, 423 To: 23456, 424 }}}} 425 426 ports := structs.AllocatedPorts{{ 427 Label: "foo", 428 Value: 23456, 429 To: 23456, 430 HostIP: "192.168.30.1", 431 }} 432 433 t.Run("normal", func(t *testing.T) { 434 nr, err := connectPort("foo", networks, ports) 435 require.NoError(t, err) 436 require.Equal(t, structs.AllocatedPortMapping{ 437 Label: "foo", 438 Value: 23456, 439 To: 23456, 440 HostIP: "192.168.30.1", 441 }, nr) 442 }) 443 444 t.Run("no such service", func(t *testing.T) { 445 _, err := connectPort("other", networks, ports) 446 require.EqualError(t, err, `No port of label "other" defined`) 447 }) 448 449 t.Run("no network", func(t *testing.T) { 450 _, err := connectPort("foo", nil, nil) 451 require.EqualError(t, err, "Connect only supported with exactly 1 network (found 0)") 452 }) 453 454 t.Run("multi network", func(t *testing.T) { 455 _, err := connectPort("foo", append(networks, &structs.NetworkResource{ 456 Device: "eth1", 457 IP: "10.0.10.0", 458 }), nil) 459 require.EqualError(t, err, "Connect only supported with exactly 1 network (found 2)") 460 }) 461 } 462 463 func TestConnect_getExposePathPort(t *testing.T) { 464 ci.Parallel(t) 465 466 networks := structs.Networks{{ 467 Device: "eth0", 468 IP: "192.168.30.1", 469 DynamicPorts: []structs.Port{{ 470 Label: "myPort", 471 Value: 23456, 472 To: 23456, 473 }}}} 474 475 t.Run("normal", func(t *testing.T) { 476 ip, port, err := connectExposePathPort("myPort", networks) 477 require.NoError(t, err) 478 require.Equal(t, ip, "192.168.30.1") 479 require.Equal(t, 23456, port) 480 }) 481 482 t.Run("no such port label", func(t *testing.T) { 483 _, _, err := connectExposePathPort("otherPort", networks) 484 require.EqualError(t, err, `No port of label "otherPort" defined`) 485 }) 486 487 t.Run("no network", func(t *testing.T) { 488 _, _, err := connectExposePathPort("myPort", nil) 489 require.EqualError(t, err, "Connect only supported with exactly 1 network (found 0)") 490 }) 491 492 t.Run("multi network", func(t *testing.T) { 493 _, _, err := connectExposePathPort("myPort", append(networks, &structs.NetworkResource{ 494 Device: "eth1", 495 IP: "10.0.10.0", 496 })) 497 require.EqualError(t, err, "Connect only supported with exactly 1 network (found 2)") 498 }) 499 } 500 501 func TestConnect_newConnectGateway(t *testing.T) { 502 ci.Parallel(t) 503 504 t.Run("not a gateway", func(t *testing.T) { 505 result := newConnectGateway(&structs.ConsulConnect{Native: true}) 506 require.Nil(t, result) 507 }) 508 509 t.Run("canonical empty", func(t *testing.T) { 510 result := newConnectGateway(&structs.ConsulConnect{ 511 Gateway: &structs.ConsulGateway{ 512 Proxy: &structs.ConsulGatewayProxy{ 513 ConnectTimeout: pointer.Of(1 * time.Second), 514 EnvoyGatewayBindTaggedAddresses: false, 515 EnvoyGatewayBindAddresses: nil, 516 EnvoyGatewayNoDefaultBind: false, 517 Config: nil, 518 }, 519 }, 520 }) 521 require.Equal(t, &api.AgentServiceConnectProxyConfig{ 522 Config: map[string]interface{}{ 523 "connect_timeout_ms": int64(1000), 524 }, 525 }, result) 526 }) 527 528 t.Run("proxy undefined", func(t *testing.T) { 529 result := newConnectGateway(&structs.ConsulConnect{ 530 Gateway: &structs.ConsulGateway{ 531 Proxy: nil, 532 }, 533 }) 534 require.Equal(t, &api.AgentServiceConnectProxyConfig{ 535 Config: nil, 536 }, result) 537 }) 538 539 t.Run("full", func(t *testing.T) { 540 result := newConnectGateway(&structs.ConsulConnect{ 541 Gateway: &structs.ConsulGateway{ 542 Proxy: &structs.ConsulGatewayProxy{ 543 ConnectTimeout: pointer.Of(1 * time.Second), 544 EnvoyGatewayBindTaggedAddresses: true, 545 EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{ 546 "service1": { 547 Address: "10.0.0.1", 548 Port: 2000, 549 }, 550 }, 551 EnvoyGatewayNoDefaultBind: true, 552 EnvoyDNSDiscoveryType: "STRICT_DNS", 553 Config: map[string]interface{}{ 554 "foo": 1, 555 }, 556 }, 557 }, 558 }) 559 require.Equal(t, &api.AgentServiceConnectProxyConfig{ 560 Config: map[string]interface{}{ 561 "connect_timeout_ms": int64(1000), 562 "envoy_gateway_bind_tagged_addresses": true, 563 "envoy_gateway_bind_addresses": map[string]*structs.ConsulGatewayBindAddress{ 564 "service1": { 565 Address: "10.0.0.1", 566 Port: 2000, 567 }, 568 }, 569 "envoy_gateway_no_default_bind": true, 570 "envoy_dns_discovery_type": "STRICT_DNS", 571 "foo": 1, 572 }, 573 }, result) 574 }) 575 } 576 577 func Test_connectMeshGateway(t *testing.T) { 578 ci.Parallel(t) 579 580 t.Run("empty", func(t *testing.T) { 581 result := connectMeshGateway(structs.ConsulMeshGateway{}) 582 require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeDefault}, result) 583 }) 584 585 t.Run("local", func(t *testing.T) { 586 result := connectMeshGateway(structs.ConsulMeshGateway{Mode: "local"}) 587 require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeLocal}, result) 588 }) 589 590 t.Run("remote", func(t *testing.T) { 591 result := connectMeshGateway(structs.ConsulMeshGateway{Mode: "remote"}) 592 require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeRemote}, result) 593 }) 594 595 t.Run("none", func(t *testing.T) { 596 result := connectMeshGateway(structs.ConsulMeshGateway{Mode: "none"}) 597 require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeNone}, result) 598 }) 599 600 t.Run("nonsense", func(t *testing.T) { 601 result := connectMeshGateway(structs.ConsulMeshGateway{}) 602 require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeDefault}, result) 603 }) 604 } 605 606 func Test_injectNomadInfo(t *testing.T) { 607 ci.Parallel(t) 608 609 info1 := func() map[string]string { 610 return map[string]string{ 611 "nomad.alloc_id=": "abc123", 612 } 613 } 614 info2 := func() map[string]string { 615 return map[string]string{ 616 "nomad.alloc_id=": "abc123", 617 "nomad.namespace=": "testns", 618 } 619 } 620 621 try := func(defaultTags map[string]string, cfg, exp map[string]interface{}) { 622 // TODO: defaultTags get modified over the execution 623 injectNomadInfo(cfg, defaultTags) 624 cfgTags, expTags := cfg["envoy_stats_tags"], exp["envoy_stats_tags"] 625 delete(cfg, "envoy_stats_tags") 626 delete(exp, "envoy_stats_tags") 627 require.Equal(t, exp, cfg) 628 require.ElementsMatch(t, expTags, cfgTags, "") 629 } 630 631 // empty 632 try( 633 info1(), 634 make(map[string]interface{}), 635 map[string]interface{}{ 636 "envoy_stats_tags": []string{"nomad.alloc_id=abc123"}, 637 }, 638 ) 639 640 // merge fresh 641 try( 642 info1(), 643 map[string]interface{}{"foo": "bar"}, 644 map[string]interface{}{ 645 "foo": "bar", 646 "envoy_stats_tags": []string{"nomad.alloc_id=abc123"}, 647 }, 648 ) 649 650 // merge append 651 try( 652 info1(), 653 map[string]interface{}{ 654 "foo": "bar", 655 "envoy_stats_tags": []string{"k1=v1", "k2=v2"}, 656 }, 657 map[string]interface{}{ 658 "foo": "bar", 659 "envoy_stats_tags": []string{"k1=v1", "k2=v2", "nomad.alloc_id=abc123"}, 660 }, 661 ) 662 663 // merge exists 664 try( 665 info2(), 666 map[string]interface{}{ 667 "foo": "bar", 668 "envoy_stats_tags": []string{"k1=v1", "k2=v2", "nomad.alloc_id=xyz789"}, 669 }, 670 map[string]interface{}{ 671 "foo": "bar", 672 "envoy_stats_tags": []string{"k1=v1", "k2=v2", "nomad.alloc_id=xyz789", "nomad.namespace=testns"}, 673 }, 674 ) 675 676 // merge wrong type 677 try( 678 info1(), 679 map[string]interface{}{ 680 "envoy_stats_tags": "not a slice of string", 681 }, 682 map[string]interface{}{ 683 "envoy_stats_tags": []string{"nomad.alloc_id=abc123"}, 684 }, 685 ) 686 }