github.com/hernad/nomad@v1.6.112/nomad/job_endpoint_hook_connect_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package nomad 5 6 import ( 7 "fmt" 8 "testing" 9 "time" 10 11 "github.com/hernad/nomad/ci" 12 "github.com/hernad/nomad/helper" 13 "github.com/hernad/nomad/helper/pointer" 14 "github.com/hernad/nomad/helper/testlog" 15 "github.com/hernad/nomad/nomad/mock" 16 "github.com/hernad/nomad/nomad/structs" 17 "github.com/shoenig/test/must" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestJobEndpointConnect_isSidecarForService(t *testing.T) { 22 ci.Parallel(t) 23 24 cases := []struct { 25 task *structs.Task 26 sidecar string 27 exp bool 28 }{ 29 { 30 &structs.Task{}, 31 "api", 32 false, 33 }, 34 { 35 &structs.Task{ 36 Kind: "connect-proxy:api", 37 }, 38 "api", 39 true, 40 }, 41 { 42 &structs.Task{ 43 Kind: "connect-proxy:api", 44 }, 45 "db", 46 false, 47 }, 48 { 49 &structs.Task{ 50 Kind: "api", 51 }, 52 "api", 53 false, 54 }, 55 } 56 57 for _, c := range cases { 58 require.Equal(t, c.exp, isSidecarForService(c.task, c.sidecar)) 59 } 60 } 61 62 func TestJobEndpointConnect_groupConnectGuessTaskDriver(t *testing.T) { 63 ci.Parallel(t) 64 65 cases := []struct { 66 name string 67 drivers []string 68 exp string 69 }{ 70 { 71 name: "none", 72 drivers: nil, 73 exp: "docker", 74 }, 75 { 76 name: "neither", 77 drivers: []string{"exec", "raw_exec", "rkt"}, 78 exp: "docker", 79 }, 80 { 81 name: "docker only", 82 drivers: []string{"docker"}, 83 exp: "docker", 84 }, 85 { 86 name: "podman only", 87 drivers: []string{"podman"}, 88 exp: "podman", 89 }, 90 { 91 name: "mix with docker", 92 drivers: []string{"podman", "docker", "exec"}, 93 exp: "docker", 94 }, 95 { 96 name: "mix without docker", 97 drivers: []string{"exec", "podman", "raw_exec"}, 98 exp: "podman", 99 }, 100 } 101 102 for _, tc := range cases { 103 tasks := helper.ConvertSlice(tc.drivers, func(driver string) *structs.Task { 104 return &structs.Task{Driver: driver} 105 }) 106 tg := &structs.TaskGroup{Tasks: tasks} 107 result := groupConnectGuessTaskDriver(tg) 108 must.Eq(t, tc.exp, result) 109 } 110 } 111 112 func TestJobEndpointConnect_newConnectSidecarTask(t *testing.T) { 113 ci.Parallel(t) 114 115 task := newConnectSidecarTask("redis", "podman") 116 must.Eq(t, "connect-proxy-redis", task.Name) 117 must.Eq(t, "podman", task.Driver) 118 119 task2 := newConnectSidecarTask("db", "docker") 120 must.Eq(t, "connect-proxy-db", task2.Name) 121 must.Eq(t, "docker", task2.Driver) 122 } 123 124 func TestJobEndpointConnect_groupConnectHook(t *testing.T) { 125 ci.Parallel(t) 126 127 // Test that connect-proxy task is inserted for backend service 128 job := mock.Job() 129 130 job.Meta = map[string]string{ 131 "backend_name": "backend", 132 "admin_name": "admin", 133 } 134 135 job.TaskGroups[0] = &structs.TaskGroup{ 136 Networks: structs.Networks{{ 137 Mode: "bridge", 138 }}, 139 Services: []*structs.Service{{ 140 Name: "${NOMAD_META_backend_name}", 141 PortLabel: "8080", 142 Connect: &structs.ConsulConnect{ 143 SidecarService: &structs.ConsulSidecarService{}, 144 }, 145 }, { 146 Name: "${NOMAD_META_admin_name}", 147 PortLabel: "9090", 148 Connect: &structs.ConsulConnect{ 149 SidecarService: &structs.ConsulSidecarService{}, 150 }, 151 }}, 152 } 153 154 // Expected tasks 155 tgExp := job.TaskGroups[0].Copy() 156 tgExp.Tasks = []*structs.Task{ 157 newConnectSidecarTask("backend", "docker"), 158 newConnectSidecarTask("admin", "docker"), 159 } 160 tgExp.Services[0].Name = "backend" 161 tgExp.Services[1].Name = "admin" 162 163 // Expect sidecar tasks to be in canonical form. 164 tgExp.Tasks[0].Canonicalize(job, tgExp) 165 tgExp.Tasks[1].Canonicalize(job, tgExp) 166 tgExp.Networks[0].DynamicPorts = []structs.Port{{ 167 Label: fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, "backend"), 168 To: -1, 169 }, { 170 Label: fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, "admin"), 171 To: -1, 172 }} 173 tgExp.Networks[0].Canonicalize() 174 175 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 176 require.Exactly(t, tgExp, job.TaskGroups[0]) 177 178 // Test that hook is idempotent 179 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 180 require.Exactly(t, tgExp, job.TaskGroups[0]) 181 } 182 183 func TestJobEndpointConnect_groupConnectHook_IngressGateway_BridgeNetwork(t *testing.T) { 184 ci.Parallel(t) 185 186 // Test that the connect ingress gateway task is inserted if a gateway service 187 // exists and since this is a bridge network, will rewrite the default gateway proxy 188 // block with correct configuration. 189 job := mock.ConnectIngressGatewayJob("bridge", false) 190 job.Meta = map[string]string{ 191 "gateway_name": "my-gateway", 192 } 193 job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}" 194 job.TaskGroups[0].Services[0].Connect.Gateway.Ingress.TLS = &structs.ConsulGatewayTLSConfig{ 195 Enabled: true, 196 TLSMinVersion: "TLSv1_2", 197 } 198 199 // setup expectations 200 expTG := job.TaskGroups[0].Copy() 201 expTG.Tasks = []*structs.Task{ 202 // inject the gateway task 203 newConnectGatewayTask(structs.ConnectIngressPrefix, "my-gateway", false, true), 204 } 205 expTG.Services[0].Name = "my-gateway" 206 expTG.Tasks[0].Canonicalize(job, expTG) 207 expTG.Networks[0].Canonicalize() 208 209 // rewrite the service gateway proxy configuration 210 expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge") 211 212 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 213 require.Exactly(t, expTG, job.TaskGroups[0]) 214 215 // Test that the hook is idempotent 216 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 217 require.Exactly(t, expTG, job.TaskGroups[0]) 218 219 // Test that the hook populates the correct constraint for customized tls 220 require.Contains(t, job.TaskGroups[0].Tasks[0].Constraints, &structs.Constraint{ 221 LTarget: "${attr.consul.version}", 222 RTarget: ">= 1.11.2", 223 Operand: structs.ConstraintSemver, 224 }) 225 } 226 227 func TestJobEndpointConnect_groupConnectHook_IngressGateway_HostNetwork(t *testing.T) { 228 ci.Parallel(t) 229 230 // Test that the connect ingress gateway task is inserted if a gateway service 231 // exists. In host network mode, the default values are used. 232 job := mock.ConnectIngressGatewayJob("host", false) 233 job.Meta = map[string]string{ 234 "gateway_name": "my-gateway", 235 } 236 job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}" 237 238 // setup expectations 239 expTG := job.TaskGroups[0].Copy() 240 expTG.Tasks = []*structs.Task{ 241 // inject the gateway task 242 newConnectGatewayTask(structs.ConnectIngressPrefix, "my-gateway", true, false), 243 } 244 expTG.Services[0].Name = "my-gateway" 245 expTG.Tasks[0].Canonicalize(job, expTG) 246 expTG.Networks[0].Canonicalize() 247 248 // rewrite the service gateway proxy configuration 249 expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "host") 250 251 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 252 require.Exactly(t, expTG, job.TaskGroups[0]) 253 254 // Test that the hook is idempotent 255 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 256 require.Exactly(t, expTG, job.TaskGroups[0]) 257 } 258 259 func TestJobEndpointConnect_groupConnectHook_IngressGateway_CustomTask(t *testing.T) { 260 ci.Parallel(t) 261 262 // Test that the connect gateway task is inserted if a gateway service exists 263 // and since this is a bridge network, will rewrite the default gateway proxy 264 // block with correct configuration. 265 job := mock.ConnectIngressGatewayJob("bridge", false) 266 job.Meta = map[string]string{ 267 "gateway_name": "my-gateway", 268 } 269 job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}" 270 job.TaskGroups[0].Services[0].Connect.SidecarTask = &structs.SidecarTask{ 271 Driver: "raw_exec", 272 User: "sidecars", 273 Config: map[string]interface{}{ 274 "command": "/bin/sidecar", 275 "args": []string{"a", "b"}, 276 }, 277 Resources: &structs.Resources{ 278 CPU: 400, 279 // Memory: inherit 128 280 }, 281 KillSignal: "SIGHUP", 282 } 283 284 // setup expectations 285 expTG := job.TaskGroups[0].Copy() 286 expTG.Tasks = []*structs.Task{ 287 // inject merged gateway task 288 { 289 Name: "connect-ingress-my-gateway", 290 Kind: structs.NewTaskKind(structs.ConnectIngressPrefix, "my-gateway"), 291 Driver: "raw_exec", 292 User: "sidecars", 293 Config: map[string]interface{}{ 294 "command": "/bin/sidecar", 295 "args": []string{"a", "b"}, 296 }, 297 Resources: &structs.Resources{ 298 CPU: 400, 299 MemoryMB: 128, 300 }, 301 LogConfig: &structs.LogConfig{ 302 MaxFiles: 2, 303 MaxFileSizeMB: 2, 304 }, 305 ShutdownDelay: 5 * time.Second, 306 KillSignal: "SIGHUP", 307 Constraints: structs.Constraints{ 308 connectGatewayVersionConstraint(), 309 connectListenerConstraint(), 310 }, 311 }, 312 } 313 expTG.Services[0].Name = "my-gateway" 314 expTG.Tasks[0].Canonicalize(job, expTG) 315 expTG.Networks[0].Canonicalize() 316 317 // rewrite the service gateway proxy configuration 318 expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge") 319 320 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 321 require.Exactly(t, expTG, job.TaskGroups[0]) 322 323 // Test that the hook is idempotent 324 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 325 require.Exactly(t, expTG, job.TaskGroups[0]) 326 } 327 328 func TestJobEndpointConnect_groupConnectHook_TerminatingGateway(t *testing.T) { 329 ci.Parallel(t) 330 331 // Tests that the connect terminating gateway task is inserted if a gateway 332 // service exists and since this is a bridge network, will rewrite the default 333 // gateway proxy block with correct configuration. 334 job := mock.ConnectTerminatingGatewayJob("bridge", false) 335 job.Meta = map[string]string{ 336 "gateway_name": "my-gateway", 337 } 338 job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}" 339 340 // setup expectations 341 expTG := job.TaskGroups[0].Copy() 342 expTG.Tasks = []*structs.Task{ 343 // inject the gateway task 344 newConnectGatewayTask(structs.ConnectTerminatingPrefix, "my-gateway", false, false), 345 } 346 expTG.Services[0].Name = "my-gateway" 347 expTG.Tasks[0].Canonicalize(job, expTG) 348 expTG.Networks[0].Canonicalize() 349 350 // rewrite the service gateway proxy configuration 351 expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge") 352 353 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 354 require.Exactly(t, expTG, job.TaskGroups[0]) 355 356 // Test that the hook is idempotent 357 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 358 require.Exactly(t, expTG, job.TaskGroups[0]) 359 } 360 361 func TestJobEndpointConnect_groupConnectHook_MeshGateway(t *testing.T) { 362 ci.Parallel(t) 363 364 // Test that the connect mesh gateway task is inserted if a gateway service 365 // exists and since this is a bridge network, will rewrite the default gateway 366 // proxy block with correct configuration, injecting a dynamic port for use 367 // by the envoy lan listener. 368 job := mock.ConnectMeshGatewayJob("bridge", false) 369 job.Meta = map[string]string{ 370 "gateway_name": "my-gateway", 371 } 372 job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}" 373 374 // setup expectations 375 expTG := job.TaskGroups[0].Copy() 376 expTG.Tasks = []*structs.Task{ 377 // inject the gateway task 378 newConnectGatewayTask(structs.ConnectMeshPrefix, "my-gateway", false, false), 379 } 380 expTG.Services[0].Name = "my-gateway" 381 expTG.Services[0].PortLabel = "public_port" 382 expTG.Networks[0].DynamicPorts = []structs.Port{{ 383 Label: "connect-mesh-my-gateway-lan", 384 Value: 0, 385 To: -1, 386 HostNetwork: "default", 387 }} 388 expTG.Tasks[0].Canonicalize(job, expTG) 389 expTG.Networks[0].Canonicalize() 390 391 // rewrite the service gateway proxy configuration 392 expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge") 393 394 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 395 require.Exactly(t, expTG, job.TaskGroups[0]) 396 397 // Test that the hook is idempotent 398 require.NoError(t, groupConnectHook(job, job.TaskGroups[0])) 399 require.Exactly(t, expTG, job.TaskGroups[0]) 400 } 401 402 // TestJobEndpoint_ConnectInterpolation asserts that when a Connect sidecar 403 // proxy task is being created for a group service with an interpolated name, 404 // the service name is interpolated *before the task is created. 405 // 406 // See https://github.com/hernad/nomad/issues/6853 407 func TestJobEndpointConnect_ConnectInterpolation(t *testing.T) { 408 ci.Parallel(t) 409 410 server := &Server{logger: testlog.HCLogger(t)} 411 jobEndpoint := NewJobEndpoints(server, nil) 412 413 j := mock.ConnectJob() 414 j.TaskGroups[0].Services[0].Name = "${JOB}-api" 415 j, warnings, err := jobEndpoint.admissionMutators(j) 416 require.NoError(t, err) 417 require.Nil(t, warnings) 418 419 require.Len(t, j.TaskGroups[0].Tasks, 2) 420 require.Equal(t, "connect-proxy-my-job-api", j.TaskGroups[0].Tasks[1].Name) 421 } 422 423 func TestJobEndpointConnect_groupConnectSidecarValidate(t *testing.T) { 424 ci.Parallel(t) 425 426 // network validation 427 428 makeService := func(name string) *structs.Service { 429 return &structs.Service{Name: name, Connect: &structs.ConsulConnect{ 430 SidecarService: new(structs.ConsulSidecarService), 431 }} 432 } 433 434 t.Run("sidecar 0 networks", func(t *testing.T) { 435 require.EqualError(t, groupConnectSidecarValidate(&structs.TaskGroup{ 436 Name: "g1", 437 Networks: nil, 438 }, makeService("connect-service")), `Consul Connect sidecars require exactly 1 network, found 0 in group "g1"`) 439 }) 440 441 t.Run("sidecar non bridge", func(t *testing.T) { 442 require.EqualError(t, groupConnectSidecarValidate(&structs.TaskGroup{ 443 Name: "g2", 444 Networks: structs.Networks{{ 445 Mode: "host", 446 }}, 447 }, makeService("connect-service")), `Consul Connect sidecar requires bridge network, found "host" in group "g2"`) 448 }) 449 450 t.Run("sidecar okay", func(t *testing.T) { 451 require.NoError(t, groupConnectSidecarValidate(&structs.TaskGroup{ 452 Name: "g3", 453 Networks: structs.Networks{{ 454 Mode: "bridge", 455 }}, 456 }, makeService("connect-service"))) 457 }) 458 459 // group and service name validation 460 461 t.Run("non-connect service contains uppercase characters", func(t *testing.T) { 462 err := groupConnectValidate(&structs.TaskGroup{ 463 Name: "group", 464 Networks: structs.Networks{{Mode: "bridge"}}, 465 Services: []*structs.Service{{ 466 Name: "Other-Service", 467 }}, 468 }) 469 require.NoError(t, err) 470 }) 471 472 t.Run("connect service contains uppercase characters", func(t *testing.T) { 473 err := groupConnectValidate(&structs.TaskGroup{ 474 Name: "group", 475 Networks: structs.Networks{{Mode: "bridge"}}, 476 Services: []*structs.Service{{ 477 Name: "Other-Service", 478 }, makeService("Connect-Service")}, 479 }) 480 require.EqualError(t, err, `Consul Connect service name "Connect-Service" in group "group" must not contain uppercase characters`) 481 }) 482 483 t.Run("non-connect group contains uppercase characters", func(t *testing.T) { 484 err := groupConnectValidate(&structs.TaskGroup{ 485 Name: "Other-Group", 486 Networks: structs.Networks{{Mode: "bridge"}}, 487 Services: []*structs.Service{{ 488 Name: "other-service", 489 }}, 490 }) 491 require.NoError(t, err) 492 }) 493 494 t.Run("connect-group contains uppercase characters", func(t *testing.T) { 495 err := groupConnectValidate(&structs.TaskGroup{ 496 Name: "Connect-Group", 497 Networks: structs.Networks{{Mode: "bridge"}}, 498 Services: []*structs.Service{{ 499 Name: "other-service", 500 }, makeService("connect-service")}, 501 }) 502 require.EqualError(t, err, `Consul Connect group "Connect-Group" with service "connect-service" must not contain uppercase characters`) 503 }) 504 505 t.Run("connect group and service lowercase", func(t *testing.T) { 506 err := groupConnectValidate(&structs.TaskGroup{ 507 Name: "connect-group", 508 Networks: structs.Networks{{Mode: "bridge"}}, 509 Services: []*structs.Service{{ 510 Name: "other-service", 511 }, makeService("connect-service")}, 512 }) 513 require.NoError(t, err) 514 }) 515 516 t.Run("connect group overlap upstreams", func(t *testing.T) { 517 s1 := makeService("s1") 518 s2 := makeService("s2") 519 s1.Connect.SidecarService.Proxy = &structs.ConsulProxy{ 520 Upstreams: []structs.ConsulUpstream{{ 521 LocalBindPort: 8999, 522 }}, 523 } 524 s2.Connect.SidecarService.Proxy = &structs.ConsulProxy{ 525 Upstreams: []structs.ConsulUpstream{{ 526 LocalBindPort: 8999, 527 }}, 528 } 529 err := groupConnectValidate(&structs.TaskGroup{ 530 Name: "connect-group", 531 Networks: structs.Networks{{Mode: "bridge"}}, 532 Services: []*structs.Service{s1, s2}, 533 }) 534 require.EqualError(t, err, `Consul Connect services "s2" and "s1" in group "connect-group" using same address for upstreams (:8999)`) 535 }) 536 } 537 538 func TestJobEndpointConnect_groupConnectUpstreamsValidate(t *testing.T) { 539 ci.Parallel(t) 540 541 t.Run("no connect services", func(t *testing.T) { 542 err := groupConnectUpstreamsValidate("group", 543 []*structs.Service{{Name: "s1"}, {Name: "s2"}}) 544 require.NoError(t, err) 545 }) 546 547 t.Run("connect services no overlap", func(t *testing.T) { 548 err := groupConnectUpstreamsValidate("group", 549 []*structs.Service{ 550 { 551 Name: "s1", 552 Connect: &structs.ConsulConnect{ 553 SidecarService: &structs.ConsulSidecarService{ 554 Proxy: &structs.ConsulProxy{ 555 Upstreams: []structs.ConsulUpstream{{ 556 LocalBindAddress: "127.0.0.1", 557 LocalBindPort: 9001, 558 }, { 559 LocalBindAddress: "127.0.0.1", 560 LocalBindPort: 9002, 561 }}, 562 }, 563 }, 564 }, 565 }, 566 { 567 Name: "s2", 568 Connect: &structs.ConsulConnect{ 569 SidecarService: &structs.ConsulSidecarService{ 570 Proxy: &structs.ConsulProxy{ 571 Upstreams: []structs.ConsulUpstream{{ 572 LocalBindAddress: "10.0.0.1", 573 LocalBindPort: 9001, 574 }, { 575 LocalBindAddress: "127.0.0.1", 576 LocalBindPort: 9003, 577 }}, 578 }, 579 }, 580 }, 581 }, 582 }) 583 require.NoError(t, err) 584 }) 585 586 t.Run("connect services overlap port", func(t *testing.T) { 587 err := groupConnectUpstreamsValidate("group", 588 []*structs.Service{ 589 { 590 Name: "s1", 591 Connect: &structs.ConsulConnect{ 592 SidecarService: &structs.ConsulSidecarService{ 593 Proxy: &structs.ConsulProxy{ 594 Upstreams: []structs.ConsulUpstream{{ 595 LocalBindAddress: "127.0.0.1", 596 LocalBindPort: 9001, 597 }, { 598 LocalBindAddress: "127.0.0.1", 599 LocalBindPort: 9002, 600 }}, 601 }, 602 }, 603 }, 604 }, 605 { 606 Name: "s2", 607 Connect: &structs.ConsulConnect{ 608 SidecarService: &structs.ConsulSidecarService{ 609 Proxy: &structs.ConsulProxy{ 610 Upstreams: []structs.ConsulUpstream{{ 611 LocalBindAddress: "127.0.0.1", 612 LocalBindPort: 9002, 613 }, { 614 LocalBindAddress: "127.0.0.1", 615 LocalBindPort: 9003, 616 }}, 617 }, 618 }, 619 }, 620 }, 621 }) 622 require.EqualError(t, err, `Consul Connect services "s2" and "s1" in group "group" using same address for upstreams (127.0.0.1:9002)`) 623 }) 624 } 625 626 func TestJobEndpointConnect_getNamedTaskForNativeService(t *testing.T) { 627 ci.Parallel(t) 628 629 t.Run("named exists", func(t *testing.T) { 630 task, err := getNamedTaskForNativeService(&structs.TaskGroup{ 631 Name: "g1", 632 Tasks: []*structs.Task{{Name: "t1"}, {Name: "t2"}}, 633 }, "s1", "t2") 634 require.NoError(t, err) 635 require.Equal(t, "t2", task.Name) 636 }) 637 638 t.Run("infer exists", func(t *testing.T) { 639 task, err := getNamedTaskForNativeService(&structs.TaskGroup{ 640 Name: "g1", 641 Tasks: []*structs.Task{{Name: "t2"}}, 642 }, "s1", "") 643 require.NoError(t, err) 644 require.Equal(t, "t2", task.Name) 645 }) 646 647 t.Run("infer ambiguous", func(t *testing.T) { 648 task, err := getNamedTaskForNativeService(&structs.TaskGroup{ 649 Name: "g1", 650 Tasks: []*structs.Task{{Name: "t1"}, {Name: "t2"}}, 651 }, "s1", "") 652 require.EqualError(t, err, "task for Consul Connect Native service g1->s1 is ambiguous and must be set") 653 require.Nil(t, task) 654 }) 655 656 t.Run("named absent", func(t *testing.T) { 657 task, err := getNamedTaskForNativeService(&structs.TaskGroup{ 658 Name: "g1", 659 Tasks: []*structs.Task{{Name: "t1"}, {Name: "t2"}}, 660 }, "s1", "t3") 661 require.EqualError(t, err, "task t3 named by Consul Connect Native service g1->s1 does not exist") 662 require.Nil(t, task) 663 }) 664 } 665 666 func TestJobEndpointConnect_groupConnectGatewayValidate(t *testing.T) { 667 ci.Parallel(t) 668 669 t.Run("no group network", func(t *testing.T) { 670 err := groupConnectGatewayValidate(&structs.TaskGroup{ 671 Name: "g1", 672 Networks: nil, 673 }) 674 require.EqualError(t, err, `Consul Connect gateways require exactly 1 network, found 0 in group "g1"`) 675 }) 676 677 t.Run("bad network mode", func(t *testing.T) { 678 err := groupConnectGatewayValidate(&structs.TaskGroup{ 679 Name: "g1", 680 Networks: structs.Networks{{ 681 Mode: "", 682 }}, 683 }) 684 require.EqualError(t, err, `Consul Connect Gateway service requires Task Group with network mode of type "bridge" or "host"`) 685 }) 686 } 687 688 func TestJobEndpointConnect_newConnectGatewayTask_host(t *testing.T) { 689 ci.Parallel(t) 690 691 t.Run("ingress", func(t *testing.T) { 692 task := newConnectGatewayTask(structs.ConnectIngressPrefix, "foo", true, false) 693 require.Equal(t, "connect-ingress-foo", task.Name) 694 require.Equal(t, "connect-ingress:foo", string(task.Kind)) 695 require.Equal(t, ">= 1.8.0", task.Constraints[0].RTarget) 696 require.Equal(t, "host", task.Config["network_mode"]) 697 require.Nil(t, task.Lifecycle) 698 }) 699 700 t.Run("terminating", func(t *testing.T) { 701 task := newConnectGatewayTask(structs.ConnectTerminatingPrefix, "bar", true, false) 702 require.Equal(t, "connect-terminating-bar", task.Name) 703 require.Equal(t, "connect-terminating:bar", string(task.Kind)) 704 require.Equal(t, ">= 1.8.0", task.Constraints[0].RTarget) 705 require.Equal(t, "host", task.Config["network_mode"]) 706 require.Nil(t, task.Lifecycle) 707 }) 708 } 709 710 func TestJobEndpointConnect_newConnectGatewayTask_bridge(t *testing.T) { 711 ci.Parallel(t) 712 713 task := newConnectGatewayTask(structs.ConnectIngressPrefix, "service1", false, false) 714 require.NotContains(t, task.Config, "network_mode") 715 } 716 717 func TestJobEndpointConnect_hasGatewayTaskForService(t *testing.T) { 718 ci.Parallel(t) 719 720 t.Run("no gateway task", func(t *testing.T) { 721 result := hasGatewayTaskForService(&structs.TaskGroup{ 722 Name: "group", 723 Tasks: []*structs.Task{{ 724 Name: "task1", 725 Kind: "", 726 }}, 727 }, "my-service") 728 require.False(t, result) 729 }) 730 731 t.Run("has ingress task", func(t *testing.T) { 732 result := hasGatewayTaskForService(&structs.TaskGroup{ 733 Name: "group", 734 Tasks: []*structs.Task{{ 735 Name: "ingress-gateway-my-service", 736 Kind: structs.NewTaskKind(structs.ConnectIngressPrefix, "my-service"), 737 }}, 738 }, "my-service") 739 require.True(t, result) 740 }) 741 742 t.Run("has terminating task", func(t *testing.T) { 743 result := hasGatewayTaskForService(&structs.TaskGroup{ 744 Name: "group", 745 Tasks: []*structs.Task{{ 746 Name: "terminating-gateway-my-service", 747 Kind: structs.NewTaskKind(structs.ConnectTerminatingPrefix, "my-service"), 748 }}, 749 }, "my-service") 750 require.True(t, result) 751 }) 752 753 t.Run("has mesh task", func(t *testing.T) { 754 result := hasGatewayTaskForService(&structs.TaskGroup{ 755 Name: "group", 756 Tasks: []*structs.Task{{ 757 Name: "mesh-gateway-my-service", 758 Kind: structs.NewTaskKind(structs.ConnectMeshPrefix, "my-service"), 759 }}, 760 }, "my-service") 761 require.True(t, result) 762 }) 763 } 764 765 func TestJobEndpointConnect_gatewayProxyIsDefault(t *testing.T) { 766 ci.Parallel(t) 767 768 t.Run("nil", func(t *testing.T) { 769 result := gatewayProxyIsDefault(nil) 770 require.True(t, result) 771 }) 772 773 t.Run("unrelated fields set", func(t *testing.T) { 774 result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{ 775 ConnectTimeout: pointer.Of(2 * time.Second), 776 Config: map[string]interface{}{"foo": 1}, 777 }) 778 require.True(t, result) 779 }) 780 781 t.Run("no-bind set", func(t *testing.T) { 782 result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{ 783 EnvoyGatewayNoDefaultBind: true, 784 }) 785 require.False(t, result) 786 }) 787 788 t.Run("bind-tagged set", func(t *testing.T) { 789 result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{ 790 EnvoyGatewayBindTaggedAddresses: true, 791 }) 792 require.False(t, result) 793 }) 794 795 t.Run("bind-addresses set", func(t *testing.T) { 796 result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{ 797 EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{ 798 "listener1": { 799 Address: "1.1.1.1", 800 Port: 9000, 801 }, 802 }, 803 }) 804 require.False(t, result) 805 }) 806 } 807 808 func TestJobEndpointConnect_gatewayBindAddressesForBridge(t *testing.T) { 809 ci.Parallel(t) 810 811 t.Run("nil", func(t *testing.T) { 812 813 result := gatewayBindAddressesIngressForBridge(nil) 814 require.Empty(t, result) 815 }) 816 817 t.Run("no listeners", func(t *testing.T) { 818 result := gatewayBindAddressesIngressForBridge(&structs.ConsulIngressConfigEntry{Listeners: nil}) 819 require.Empty(t, result) 820 }) 821 822 t.Run("simple", func(t *testing.T) { 823 result := gatewayBindAddressesIngressForBridge(&structs.ConsulIngressConfigEntry{ 824 Listeners: []*structs.ConsulIngressListener{{ 825 Port: 3000, 826 Protocol: "tcp", 827 Services: []*structs.ConsulIngressService{{ 828 Name: "service1", 829 }}, 830 }}, 831 }) 832 require.Equal(t, map[string]*structs.ConsulGatewayBindAddress{ 833 "service1": { 834 Address: "0.0.0.0", 835 Port: 3000, 836 }, 837 }, result) 838 }) 839 840 t.Run("complex", func(t *testing.T) { 841 result := gatewayBindAddressesIngressForBridge(&structs.ConsulIngressConfigEntry{ 842 Listeners: []*structs.ConsulIngressListener{{ 843 Port: 3000, 844 Protocol: "tcp", 845 Services: []*structs.ConsulIngressService{{ 846 Name: "service1", 847 }, { 848 Name: "service2", 849 }}, 850 }, { 851 Port: 3001, 852 Protocol: "http", 853 Services: []*structs.ConsulIngressService{{ 854 Name: "service3", 855 }}, 856 }}, 857 }) 858 require.Equal(t, map[string]*structs.ConsulGatewayBindAddress{ 859 "service1": { 860 Address: "0.0.0.0", 861 Port: 3000, 862 }, 863 "service2": { 864 Address: "0.0.0.0", 865 Port: 3000, 866 }, 867 "service3": { 868 Address: "0.0.0.0", 869 Port: 3001, 870 }, 871 }, result) 872 }) 873 } 874 875 func TestJobEndpointConnect_gatewayProxy(t *testing.T) { 876 ci.Parallel(t) 877 878 t.Run("nil", func(t *testing.T) { 879 result := gatewayProxy(nil, "bridge") 880 require.Nil(t, result) 881 }) 882 883 t.Run("nil proxy", func(t *testing.T) { 884 result := gatewayProxy(&structs.ConsulGateway{ 885 Ingress: &structs.ConsulIngressConfigEntry{ 886 Listeners: []*structs.ConsulIngressListener{{ 887 Port: 3000, 888 Protocol: "tcp", 889 Services: []*structs.ConsulIngressService{{ 890 Name: "service1", 891 }}, 892 }}, 893 }, 894 }, "bridge") 895 require.Equal(t, &structs.ConsulGatewayProxy{ 896 ConnectTimeout: pointer.Of(defaultConnectTimeout), 897 EnvoyGatewayNoDefaultBind: true, 898 EnvoyGatewayBindTaggedAddresses: false, 899 EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{ 900 "service1": { 901 Address: "0.0.0.0", 902 Port: 3000, 903 }}, 904 }, result) 905 }) 906 907 t.Run("ingress set defaults", func(t *testing.T) { 908 result := gatewayProxy(&structs.ConsulGateway{ 909 Proxy: &structs.ConsulGatewayProxy{ 910 ConnectTimeout: pointer.Of(2 * time.Second), 911 Config: map[string]interface{}{"foo": 1}, 912 }, 913 Ingress: &structs.ConsulIngressConfigEntry{ 914 Listeners: []*structs.ConsulIngressListener{{ 915 Port: 3000, 916 Protocol: "tcp", 917 Services: []*structs.ConsulIngressService{{ 918 Name: "service1", 919 }}, 920 }}, 921 }, 922 }, "bridge") 923 require.Equal(t, &structs.ConsulGatewayProxy{ 924 ConnectTimeout: pointer.Of(2 * time.Second), 925 Config: map[string]interface{}{"foo": 1}, 926 EnvoyGatewayNoDefaultBind: true, 927 EnvoyGatewayBindTaggedAddresses: false, 928 EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{ 929 "service1": { 930 Address: "0.0.0.0", 931 Port: 3000, 932 }}, 933 }, result) 934 }) 935 936 t.Run("ingress leave as-is", func(t *testing.T) { 937 result := gatewayProxy(&structs.ConsulGateway{ 938 Proxy: &structs.ConsulGatewayProxy{ 939 Config: map[string]interface{}{"foo": 1}, 940 EnvoyGatewayBindTaggedAddresses: true, 941 }, 942 Ingress: &structs.ConsulIngressConfigEntry{ 943 Listeners: []*structs.ConsulIngressListener{{ 944 Port: 3000, 945 Protocol: "tcp", 946 Services: []*structs.ConsulIngressService{{ 947 Name: "service1", 948 }}, 949 }}, 950 }, 951 }, "bridge") 952 require.Equal(t, &structs.ConsulGatewayProxy{ 953 ConnectTimeout: nil, 954 Config: map[string]interface{}{"foo": 1}, 955 EnvoyGatewayNoDefaultBind: false, 956 EnvoyGatewayBindTaggedAddresses: true, 957 EnvoyGatewayBindAddresses: nil, 958 }, result) 959 }) 960 961 t.Run("terminating set defaults", func(t *testing.T) { 962 result := gatewayProxy(&structs.ConsulGateway{ 963 Proxy: &structs.ConsulGatewayProxy{ 964 ConnectTimeout: pointer.Of(2 * time.Second), 965 EnvoyDNSDiscoveryType: "STRICT_DNS", 966 }, 967 Terminating: &structs.ConsulTerminatingConfigEntry{ 968 Services: []*structs.ConsulLinkedService{{ 969 Name: "service1", 970 CAFile: "/cafile.pem", 971 CertFile: "/certfile.pem", 972 KeyFile: "/keyfile.pem", 973 SNI: "", 974 }}, 975 }, 976 }, "bridge") 977 require.Equal(t, &structs.ConsulGatewayProxy{ 978 ConnectTimeout: pointer.Of(2 * time.Second), 979 EnvoyGatewayNoDefaultBind: true, 980 EnvoyGatewayBindTaggedAddresses: false, 981 EnvoyDNSDiscoveryType: "STRICT_DNS", 982 EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{ 983 "default": { 984 Address: "0.0.0.0", 985 Port: -1, 986 }, 987 }, 988 }, result) 989 }) 990 991 t.Run("terminating leave as-is", func(t *testing.T) { 992 result := gatewayProxy(&structs.ConsulGateway{ 993 Proxy: &structs.ConsulGatewayProxy{ 994 Config: map[string]interface{}{"foo": 1}, 995 EnvoyGatewayBindTaggedAddresses: true, 996 }, 997 Terminating: &structs.ConsulTerminatingConfigEntry{ 998 Services: []*structs.ConsulLinkedService{{ 999 Name: "service1", 1000 }}, 1001 }, 1002 }, "bridge") 1003 require.Equal(t, &structs.ConsulGatewayProxy{ 1004 ConnectTimeout: nil, 1005 Config: map[string]interface{}{"foo": 1}, 1006 EnvoyGatewayNoDefaultBind: false, 1007 EnvoyGatewayBindTaggedAddresses: true, 1008 EnvoyGatewayBindAddresses: nil, 1009 }, result) 1010 }) 1011 1012 t.Run("mesh set defaults in bridge", func(t *testing.T) { 1013 result := gatewayProxy(&structs.ConsulGateway{ 1014 Proxy: &structs.ConsulGatewayProxy{ 1015 ConnectTimeout: pointer.Of(2 * time.Second), 1016 }, 1017 Mesh: &structs.ConsulMeshConfigEntry{ 1018 // nothing 1019 }, 1020 }, "bridge") 1021 require.Equal(t, &structs.ConsulGatewayProxy{ 1022 ConnectTimeout: pointer.Of(2 * time.Second), 1023 EnvoyGatewayNoDefaultBind: true, 1024 EnvoyGatewayBindTaggedAddresses: false, 1025 EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{ 1026 "lan": { 1027 Address: "0.0.0.0", 1028 Port: -1, 1029 }, 1030 "wan": { 1031 Address: "0.0.0.0", 1032 Port: -1, 1033 }, 1034 }, 1035 }, result) 1036 }) 1037 1038 t.Run("mesh set defaults in host", func(t *testing.T) { 1039 result := gatewayProxy(&structs.ConsulGateway{ 1040 Proxy: &structs.ConsulGatewayProxy{ 1041 ConnectTimeout: pointer.Of(2 * time.Second), 1042 }, 1043 Mesh: &structs.ConsulMeshConfigEntry{ 1044 // nothing 1045 }, 1046 }, "host") 1047 require.Equal(t, &structs.ConsulGatewayProxy{ 1048 ConnectTimeout: pointer.Of(2 * time.Second), 1049 }, result) 1050 }) 1051 1052 t.Run("mesh leave as-is", func(t *testing.T) { 1053 result := gatewayProxy(&structs.ConsulGateway{ 1054 Proxy: &structs.ConsulGatewayProxy{ 1055 Config: map[string]interface{}{"foo": 1}, 1056 EnvoyGatewayBindTaggedAddresses: true, 1057 }, 1058 Mesh: &structs.ConsulMeshConfigEntry{ 1059 // nothing 1060 }, 1061 }, "bridge") 1062 require.Equal(t, &structs.ConsulGatewayProxy{ 1063 ConnectTimeout: nil, 1064 Config: map[string]interface{}{"foo": 1}, 1065 EnvoyGatewayNoDefaultBind: false, 1066 EnvoyGatewayBindTaggedAddresses: true, 1067 EnvoyGatewayBindAddresses: nil, 1068 }, result) 1069 }) 1070 }