open-nomad/nomad/job_endpoint_hook_connect_test.go
Seth Hoenig fff2eec625
connect: use heuristic to detect sidecar task driver (#17065)
* connect: use heuristic to detect sidecar task driver

This PR adds a heuristic to detect whether to use the podman task driver
for the connect sidecar proxy. The podman driver will be selected if there
is at least one task in the task group configured to use podman, and there
are zero tasks in the group configured to use docker. In all other cases
the task driver defaults to docker.

After this change, we should be able to run typical Connect jobspecs
(e.g. nomad job init [-short] -connect) on Clusters configured with the
podman task driver, without modification to the job files.

Closes #17042

* golf: cleanup driver detection logic
2023-05-05 10:19:30 -05:00

1071 lines
31 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package nomad
import (
"fmt"
"testing"
"time"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/require"
)
func TestJobEndpointConnect_isSidecarForService(t *testing.T) {
ci.Parallel(t)
cases := []struct {
task *structs.Task
sidecar string
exp bool
}{
{
&structs.Task{},
"api",
false,
},
{
&structs.Task{
Kind: "connect-proxy:api",
},
"api",
true,
},
{
&structs.Task{
Kind: "connect-proxy:api",
},
"db",
false,
},
{
&structs.Task{
Kind: "api",
},
"api",
false,
},
}
for _, c := range cases {
require.Equal(t, c.exp, isSidecarForService(c.task, c.sidecar))
}
}
func TestJobEndpointConnect_groupConnectGuessTaskDriver(t *testing.T) {
ci.Parallel(t)
cases := []struct {
name string
drivers []string
exp string
}{
{
name: "none",
drivers: nil,
exp: "docker",
},
{
name: "neither",
drivers: []string{"exec", "raw_exec", "rkt"},
exp: "docker",
},
{
name: "docker only",
drivers: []string{"docker"},
exp: "docker",
},
{
name: "podman only",
drivers: []string{"podman"},
exp: "podman",
},
{
name: "mix with docker",
drivers: []string{"podman", "docker", "exec"},
exp: "docker",
},
{
name: "mix without docker",
drivers: []string{"exec", "podman", "raw_exec"},
exp: "podman",
},
}
for _, tc := range cases {
tasks := helper.ConvertSlice(tc.drivers, func(driver string) *structs.Task {
return &structs.Task{Driver: driver}
})
tg := &structs.TaskGroup{Tasks: tasks}
result := groupConnectGuessTaskDriver(tg)
must.Eq(t, tc.exp, result)
}
}
func TestJobEndpointConnect_newConnectSidecarTask(t *testing.T) {
ci.Parallel(t)
task := newConnectSidecarTask("redis", "podman")
must.Eq(t, "connect-proxy-redis", task.Name)
must.Eq(t, "podman", task.Driver)
task2 := newConnectSidecarTask("db", "docker")
must.Eq(t, "connect-proxy-db", task2.Name)
must.Eq(t, "docker", task2.Driver)
}
func TestJobEndpointConnect_groupConnectHook(t *testing.T) {
ci.Parallel(t)
// Test that connect-proxy task is inserted for backend service
job := mock.Job()
job.Meta = map[string]string{
"backend_name": "backend",
"admin_name": "admin",
}
job.TaskGroups[0] = &structs.TaskGroup{
Networks: structs.Networks{{
Mode: "bridge",
}},
Services: []*structs.Service{{
Name: "${NOMAD_META_backend_name}",
PortLabel: "8080",
Connect: &structs.ConsulConnect{
SidecarService: &structs.ConsulSidecarService{},
},
}, {
Name: "${NOMAD_META_admin_name}",
PortLabel: "9090",
Connect: &structs.ConsulConnect{
SidecarService: &structs.ConsulSidecarService{},
},
}},
}
// Expected tasks
tgExp := job.TaskGroups[0].Copy()
tgExp.Tasks = []*structs.Task{
newConnectSidecarTask("backend", "docker"),
newConnectSidecarTask("admin", "docker"),
}
tgExp.Services[0].Name = "backend"
tgExp.Services[1].Name = "admin"
// Expect sidecar tasks to be in canonical form.
tgExp.Tasks[0].Canonicalize(job, tgExp)
tgExp.Tasks[1].Canonicalize(job, tgExp)
tgExp.Networks[0].DynamicPorts = []structs.Port{{
Label: fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, "backend"),
To: -1,
}, {
Label: fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, "admin"),
To: -1,
}}
tgExp.Networks[0].Canonicalize()
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, tgExp, job.TaskGroups[0])
// Test that hook is idempotent
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, tgExp, job.TaskGroups[0])
}
func TestJobEndpointConnect_groupConnectHook_IngressGateway_BridgeNetwork(t *testing.T) {
ci.Parallel(t)
// Test that the connect ingress gateway task is inserted if a gateway service
// exists and since this is a bridge network, will rewrite the default gateway proxy
// block with correct configuration.
job := mock.ConnectIngressGatewayJob("bridge", false)
job.Meta = map[string]string{
"gateway_name": "my-gateway",
}
job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}"
job.TaskGroups[0].Services[0].Connect.Gateway.Ingress.TLS = &structs.ConsulGatewayTLSConfig{
Enabled: true,
TLSMinVersion: "TLSv1_2",
}
// setup expectations
expTG := job.TaskGroups[0].Copy()
expTG.Tasks = []*structs.Task{
// inject the gateway task
newConnectGatewayTask(structs.ConnectIngressPrefix, "my-gateway", false, true),
}
expTG.Services[0].Name = "my-gateway"
expTG.Tasks[0].Canonicalize(job, expTG)
expTG.Networks[0].Canonicalize()
// rewrite the service gateway proxy configuration
expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge")
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
// Test that the hook is idempotent
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
// Test that the hook populates the correct constraint for customized tls
require.Contains(t, job.TaskGroups[0].Tasks[0].Constraints, &structs.Constraint{
LTarget: "${attr.consul.version}",
RTarget: ">= 1.11.2",
Operand: structs.ConstraintSemver,
})
}
func TestJobEndpointConnect_groupConnectHook_IngressGateway_HostNetwork(t *testing.T) {
ci.Parallel(t)
// Test that the connect ingress gateway task is inserted if a gateway service
// exists. In host network mode, the default values are used.
job := mock.ConnectIngressGatewayJob("host", false)
job.Meta = map[string]string{
"gateway_name": "my-gateway",
}
job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}"
// setup expectations
expTG := job.TaskGroups[0].Copy()
expTG.Tasks = []*structs.Task{
// inject the gateway task
newConnectGatewayTask(structs.ConnectIngressPrefix, "my-gateway", true, false),
}
expTG.Services[0].Name = "my-gateway"
expTG.Tasks[0].Canonicalize(job, expTG)
expTG.Networks[0].Canonicalize()
// rewrite the service gateway proxy configuration
expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "host")
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
// Test that the hook is idempotent
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
}
func TestJobEndpointConnect_groupConnectHook_IngressGateway_CustomTask(t *testing.T) {
ci.Parallel(t)
// Test that the connect gateway task is inserted if a gateway service exists
// and since this is a bridge network, will rewrite the default gateway proxy
// block with correct configuration.
job := mock.ConnectIngressGatewayJob("bridge", false)
job.Meta = map[string]string{
"gateway_name": "my-gateway",
}
job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}"
job.TaskGroups[0].Services[0].Connect.SidecarTask = &structs.SidecarTask{
Driver: "raw_exec",
User: "sidecars",
Config: map[string]interface{}{
"command": "/bin/sidecar",
"args": []string{"a", "b"},
},
Resources: &structs.Resources{
CPU: 400,
// Memory: inherit 128
},
KillSignal: "SIGHUP",
}
// setup expectations
expTG := job.TaskGroups[0].Copy()
expTG.Tasks = []*structs.Task{
// inject merged gateway task
{
Name: "connect-ingress-my-gateway",
Kind: structs.NewTaskKind(structs.ConnectIngressPrefix, "my-gateway"),
Driver: "raw_exec",
User: "sidecars",
Config: map[string]interface{}{
"command": "/bin/sidecar",
"args": []string{"a", "b"},
},
Resources: &structs.Resources{
CPU: 400,
MemoryMB: 128,
},
LogConfig: &structs.LogConfig{
MaxFiles: 2,
MaxFileSizeMB: 2,
},
ShutdownDelay: 5 * time.Second,
KillSignal: "SIGHUP",
Constraints: structs.Constraints{
connectGatewayVersionConstraint(),
connectListenerConstraint(),
},
},
}
expTG.Services[0].Name = "my-gateway"
expTG.Tasks[0].Canonicalize(job, expTG)
expTG.Networks[0].Canonicalize()
// rewrite the service gateway proxy configuration
expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge")
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
// Test that the hook is idempotent
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
}
func TestJobEndpointConnect_groupConnectHook_TerminatingGateway(t *testing.T) {
ci.Parallel(t)
// Tests that the connect terminating gateway task is inserted if a gateway
// service exists and since this is a bridge network, will rewrite the default
// gateway proxy block with correct configuration.
job := mock.ConnectTerminatingGatewayJob("bridge", false)
job.Meta = map[string]string{
"gateway_name": "my-gateway",
}
job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}"
// setup expectations
expTG := job.TaskGroups[0].Copy()
expTG.Tasks = []*structs.Task{
// inject the gateway task
newConnectGatewayTask(structs.ConnectTerminatingPrefix, "my-gateway", false, false),
}
expTG.Services[0].Name = "my-gateway"
expTG.Tasks[0].Canonicalize(job, expTG)
expTG.Networks[0].Canonicalize()
// rewrite the service gateway proxy configuration
expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge")
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
// Test that the hook is idempotent
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
}
func TestJobEndpointConnect_groupConnectHook_MeshGateway(t *testing.T) {
ci.Parallel(t)
// Test that the connect mesh gateway task is inserted if a gateway service
// exists and since this is a bridge network, will rewrite the default gateway
// proxy block with correct configuration, injecting a dynamic port for use
// by the envoy lan listener.
job := mock.ConnectMeshGatewayJob("bridge", false)
job.Meta = map[string]string{
"gateway_name": "my-gateway",
}
job.TaskGroups[0].Services[0].Name = "${NOMAD_META_gateway_name}"
// setup expectations
expTG := job.TaskGroups[0].Copy()
expTG.Tasks = []*structs.Task{
// inject the gateway task
newConnectGatewayTask(structs.ConnectMeshPrefix, "my-gateway", false, false),
}
expTG.Services[0].Name = "my-gateway"
expTG.Services[0].PortLabel = "public_port"
expTG.Networks[0].DynamicPorts = []structs.Port{{
Label: "connect-mesh-my-gateway-lan",
Value: 0,
To: -1,
HostNetwork: "default",
}}
expTG.Tasks[0].Canonicalize(job, expTG)
expTG.Networks[0].Canonicalize()
// rewrite the service gateway proxy configuration
expTG.Services[0].Connect.Gateway.Proxy = gatewayProxy(expTG.Services[0].Connect.Gateway, "bridge")
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
// Test that the hook is idempotent
require.NoError(t, groupConnectHook(job, job.TaskGroups[0]))
require.Exactly(t, expTG, job.TaskGroups[0])
}
// TestJobEndpoint_ConnectInterpolation asserts that when a Connect sidecar
// proxy task is being created for a group service with an interpolated name,
// the service name is interpolated *before the task is created.
//
// See https://github.com/hashicorp/nomad/issues/6853
func TestJobEndpointConnect_ConnectInterpolation(t *testing.T) {
ci.Parallel(t)
server := &Server{logger: testlog.HCLogger(t)}
jobEndpoint := NewJobEndpoints(server, nil)
j := mock.ConnectJob()
j.TaskGroups[0].Services[0].Name = "${JOB}-api"
j, warnings, err := jobEndpoint.admissionMutators(j)
require.NoError(t, err)
require.Nil(t, warnings)
require.Len(t, j.TaskGroups[0].Tasks, 2)
require.Equal(t, "connect-proxy-my-job-api", j.TaskGroups[0].Tasks[1].Name)
}
func TestJobEndpointConnect_groupConnectSidecarValidate(t *testing.T) {
ci.Parallel(t)
// network validation
makeService := func(name string) *structs.Service {
return &structs.Service{Name: name, Connect: &structs.ConsulConnect{
SidecarService: new(structs.ConsulSidecarService),
}}
}
t.Run("sidecar 0 networks", func(t *testing.T) {
require.EqualError(t, groupConnectSidecarValidate(&structs.TaskGroup{
Name: "g1",
Networks: nil,
}, makeService("connect-service")), `Consul Connect sidecars require exactly 1 network, found 0 in group "g1"`)
})
t.Run("sidecar non bridge", func(t *testing.T) {
require.EqualError(t, groupConnectSidecarValidate(&structs.TaskGroup{
Name: "g2",
Networks: structs.Networks{{
Mode: "host",
}},
}, makeService("connect-service")), `Consul Connect sidecar requires bridge network, found "host" in group "g2"`)
})
t.Run("sidecar okay", func(t *testing.T) {
require.NoError(t, groupConnectSidecarValidate(&structs.TaskGroup{
Name: "g3",
Networks: structs.Networks{{
Mode: "bridge",
}},
}, makeService("connect-service")))
})
// group and service name validation
t.Run("non-connect service contains uppercase characters", func(t *testing.T) {
err := groupConnectValidate(&structs.TaskGroup{
Name: "group",
Networks: structs.Networks{{Mode: "bridge"}},
Services: []*structs.Service{{
Name: "Other-Service",
}},
})
require.NoError(t, err)
})
t.Run("connect service contains uppercase characters", func(t *testing.T) {
err := groupConnectValidate(&structs.TaskGroup{
Name: "group",
Networks: structs.Networks{{Mode: "bridge"}},
Services: []*structs.Service{{
Name: "Other-Service",
}, makeService("Connect-Service")},
})
require.EqualError(t, err, `Consul Connect service name "Connect-Service" in group "group" must not contain uppercase characters`)
})
t.Run("non-connect group contains uppercase characters", func(t *testing.T) {
err := groupConnectValidate(&structs.TaskGroup{
Name: "Other-Group",
Networks: structs.Networks{{Mode: "bridge"}},
Services: []*structs.Service{{
Name: "other-service",
}},
})
require.NoError(t, err)
})
t.Run("connect-group contains uppercase characters", func(t *testing.T) {
err := groupConnectValidate(&structs.TaskGroup{
Name: "Connect-Group",
Networks: structs.Networks{{Mode: "bridge"}},
Services: []*structs.Service{{
Name: "other-service",
}, makeService("connect-service")},
})
require.EqualError(t, err, `Consul Connect group "Connect-Group" with service "connect-service" must not contain uppercase characters`)
})
t.Run("connect group and service lowercase", func(t *testing.T) {
err := groupConnectValidate(&structs.TaskGroup{
Name: "connect-group",
Networks: structs.Networks{{Mode: "bridge"}},
Services: []*structs.Service{{
Name: "other-service",
}, makeService("connect-service")},
})
require.NoError(t, err)
})
t.Run("connect group overlap upstreams", func(t *testing.T) {
s1 := makeService("s1")
s2 := makeService("s2")
s1.Connect.SidecarService.Proxy = &structs.ConsulProxy{
Upstreams: []structs.ConsulUpstream{{
LocalBindPort: 8999,
}},
}
s2.Connect.SidecarService.Proxy = &structs.ConsulProxy{
Upstreams: []structs.ConsulUpstream{{
LocalBindPort: 8999,
}},
}
err := groupConnectValidate(&structs.TaskGroup{
Name: "connect-group",
Networks: structs.Networks{{Mode: "bridge"}},
Services: []*structs.Service{s1, s2},
})
require.EqualError(t, err, `Consul Connect services "s2" and "s1" in group "connect-group" using same address for upstreams (:8999)`)
})
}
func TestJobEndpointConnect_groupConnectUpstreamsValidate(t *testing.T) {
ci.Parallel(t)
t.Run("no connect services", func(t *testing.T) {
err := groupConnectUpstreamsValidate("group",
[]*structs.Service{{Name: "s1"}, {Name: "s2"}})
require.NoError(t, err)
})
t.Run("connect services no overlap", func(t *testing.T) {
err := groupConnectUpstreamsValidate("group",
[]*structs.Service{
{
Name: "s1",
Connect: &structs.ConsulConnect{
SidecarService: &structs.ConsulSidecarService{
Proxy: &structs.ConsulProxy{
Upstreams: []structs.ConsulUpstream{{
LocalBindAddress: "127.0.0.1",
LocalBindPort: 9001,
}, {
LocalBindAddress: "127.0.0.1",
LocalBindPort: 9002,
}},
},
},
},
},
{
Name: "s2",
Connect: &structs.ConsulConnect{
SidecarService: &structs.ConsulSidecarService{
Proxy: &structs.ConsulProxy{
Upstreams: []structs.ConsulUpstream{{
LocalBindAddress: "10.0.0.1",
LocalBindPort: 9001,
}, {
LocalBindAddress: "127.0.0.1",
LocalBindPort: 9003,
}},
},
},
},
},
})
require.NoError(t, err)
})
t.Run("connect services overlap port", func(t *testing.T) {
err := groupConnectUpstreamsValidate("group",
[]*structs.Service{
{
Name: "s1",
Connect: &structs.ConsulConnect{
SidecarService: &structs.ConsulSidecarService{
Proxy: &structs.ConsulProxy{
Upstreams: []structs.ConsulUpstream{{
LocalBindAddress: "127.0.0.1",
LocalBindPort: 9001,
}, {
LocalBindAddress: "127.0.0.1",
LocalBindPort: 9002,
}},
},
},
},
},
{
Name: "s2",
Connect: &structs.ConsulConnect{
SidecarService: &structs.ConsulSidecarService{
Proxy: &structs.ConsulProxy{
Upstreams: []structs.ConsulUpstream{{
LocalBindAddress: "127.0.0.1",
LocalBindPort: 9002,
}, {
LocalBindAddress: "127.0.0.1",
LocalBindPort: 9003,
}},
},
},
},
},
})
require.EqualError(t, err, `Consul Connect services "s2" and "s1" in group "group" using same address for upstreams (127.0.0.1:9002)`)
})
}
func TestJobEndpointConnect_getNamedTaskForNativeService(t *testing.T) {
ci.Parallel(t)
t.Run("named exists", func(t *testing.T) {
task, err := getNamedTaskForNativeService(&structs.TaskGroup{
Name: "g1",
Tasks: []*structs.Task{{Name: "t1"}, {Name: "t2"}},
}, "s1", "t2")
require.NoError(t, err)
require.Equal(t, "t2", task.Name)
})
t.Run("infer exists", func(t *testing.T) {
task, err := getNamedTaskForNativeService(&structs.TaskGroup{
Name: "g1",
Tasks: []*structs.Task{{Name: "t2"}},
}, "s1", "")
require.NoError(t, err)
require.Equal(t, "t2", task.Name)
})
t.Run("infer ambiguous", func(t *testing.T) {
task, err := getNamedTaskForNativeService(&structs.TaskGroup{
Name: "g1",
Tasks: []*structs.Task{{Name: "t1"}, {Name: "t2"}},
}, "s1", "")
require.EqualError(t, err, "task for Consul Connect Native service g1->s1 is ambiguous and must be set")
require.Nil(t, task)
})
t.Run("named absent", func(t *testing.T) {
task, err := getNamedTaskForNativeService(&structs.TaskGroup{
Name: "g1",
Tasks: []*structs.Task{{Name: "t1"}, {Name: "t2"}},
}, "s1", "t3")
require.EqualError(t, err, "task t3 named by Consul Connect Native service g1->s1 does not exist")
require.Nil(t, task)
})
}
func TestJobEndpointConnect_groupConnectGatewayValidate(t *testing.T) {
ci.Parallel(t)
t.Run("no group network", func(t *testing.T) {
err := groupConnectGatewayValidate(&structs.TaskGroup{
Name: "g1",
Networks: nil,
})
require.EqualError(t, err, `Consul Connect gateways require exactly 1 network, found 0 in group "g1"`)
})
t.Run("bad network mode", func(t *testing.T) {
err := groupConnectGatewayValidate(&structs.TaskGroup{
Name: "g1",
Networks: structs.Networks{{
Mode: "",
}},
})
require.EqualError(t, err, `Consul Connect Gateway service requires Task Group with network mode of type "bridge" or "host"`)
})
}
func TestJobEndpointConnect_newConnectGatewayTask_host(t *testing.T) {
ci.Parallel(t)
t.Run("ingress", func(t *testing.T) {
task := newConnectGatewayTask(structs.ConnectIngressPrefix, "foo", true, false)
require.Equal(t, "connect-ingress-foo", task.Name)
require.Equal(t, "connect-ingress:foo", string(task.Kind))
require.Equal(t, ">= 1.8.0", task.Constraints[0].RTarget)
require.Equal(t, "host", task.Config["network_mode"])
require.Nil(t, task.Lifecycle)
})
t.Run("terminating", func(t *testing.T) {
task := newConnectGatewayTask(structs.ConnectTerminatingPrefix, "bar", true, false)
require.Equal(t, "connect-terminating-bar", task.Name)
require.Equal(t, "connect-terminating:bar", string(task.Kind))
require.Equal(t, ">= 1.8.0", task.Constraints[0].RTarget)
require.Equal(t, "host", task.Config["network_mode"])
require.Nil(t, task.Lifecycle)
})
}
func TestJobEndpointConnect_newConnectGatewayTask_bridge(t *testing.T) {
ci.Parallel(t)
task := newConnectGatewayTask(structs.ConnectIngressPrefix, "service1", false, false)
require.NotContains(t, task.Config, "network_mode")
}
func TestJobEndpointConnect_hasGatewayTaskForService(t *testing.T) {
ci.Parallel(t)
t.Run("no gateway task", func(t *testing.T) {
result := hasGatewayTaskForService(&structs.TaskGroup{
Name: "group",
Tasks: []*structs.Task{{
Name: "task1",
Kind: "",
}},
}, "my-service")
require.False(t, result)
})
t.Run("has ingress task", func(t *testing.T) {
result := hasGatewayTaskForService(&structs.TaskGroup{
Name: "group",
Tasks: []*structs.Task{{
Name: "ingress-gateway-my-service",
Kind: structs.NewTaskKind(structs.ConnectIngressPrefix, "my-service"),
}},
}, "my-service")
require.True(t, result)
})
t.Run("has terminating task", func(t *testing.T) {
result := hasGatewayTaskForService(&structs.TaskGroup{
Name: "group",
Tasks: []*structs.Task{{
Name: "terminating-gateway-my-service",
Kind: structs.NewTaskKind(structs.ConnectTerminatingPrefix, "my-service"),
}},
}, "my-service")
require.True(t, result)
})
t.Run("has mesh task", func(t *testing.T) {
result := hasGatewayTaskForService(&structs.TaskGroup{
Name: "group",
Tasks: []*structs.Task{{
Name: "mesh-gateway-my-service",
Kind: structs.NewTaskKind(structs.ConnectMeshPrefix, "my-service"),
}},
}, "my-service")
require.True(t, result)
})
}
func TestJobEndpointConnect_gatewayProxyIsDefault(t *testing.T) {
ci.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := gatewayProxyIsDefault(nil)
require.True(t, result)
})
t.Run("unrelated fields set", func(t *testing.T) {
result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
Config: map[string]interface{}{"foo": 1},
})
require.True(t, result)
})
t.Run("no-bind set", func(t *testing.T) {
result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{
EnvoyGatewayNoDefaultBind: true,
})
require.False(t, result)
})
t.Run("bind-tagged set", func(t *testing.T) {
result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{
EnvoyGatewayBindTaggedAddresses: true,
})
require.False(t, result)
})
t.Run("bind-addresses set", func(t *testing.T) {
result := gatewayProxyIsDefault(&structs.ConsulGatewayProxy{
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"listener1": {
Address: "1.1.1.1",
Port: 9000,
},
},
})
require.False(t, result)
})
}
func TestJobEndpointConnect_gatewayBindAddressesForBridge(t *testing.T) {
ci.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := gatewayBindAddressesIngressForBridge(nil)
require.Empty(t, result)
})
t.Run("no listeners", func(t *testing.T) {
result := gatewayBindAddressesIngressForBridge(&structs.ConsulIngressConfigEntry{Listeners: nil})
require.Empty(t, result)
})
t.Run("simple", func(t *testing.T) {
result := gatewayBindAddressesIngressForBridge(&structs.ConsulIngressConfigEntry{
Listeners: []*structs.ConsulIngressListener{{
Port: 3000,
Protocol: "tcp",
Services: []*structs.ConsulIngressService{{
Name: "service1",
}},
}},
})
require.Equal(t, map[string]*structs.ConsulGatewayBindAddress{
"service1": {
Address: "0.0.0.0",
Port: 3000,
},
}, result)
})
t.Run("complex", func(t *testing.T) {
result := gatewayBindAddressesIngressForBridge(&structs.ConsulIngressConfigEntry{
Listeners: []*structs.ConsulIngressListener{{
Port: 3000,
Protocol: "tcp",
Services: []*structs.ConsulIngressService{{
Name: "service1",
}, {
Name: "service2",
}},
}, {
Port: 3001,
Protocol: "http",
Services: []*structs.ConsulIngressService{{
Name: "service3",
}},
}},
})
require.Equal(t, map[string]*structs.ConsulGatewayBindAddress{
"service1": {
Address: "0.0.0.0",
Port: 3000,
},
"service2": {
Address: "0.0.0.0",
Port: 3000,
},
"service3": {
Address: "0.0.0.0",
Port: 3001,
},
}, result)
})
}
func TestJobEndpointConnect_gatewayProxy(t *testing.T) {
ci.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := gatewayProxy(nil, "bridge")
require.Nil(t, result)
})
t.Run("nil proxy", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Ingress: &structs.ConsulIngressConfigEntry{
Listeners: []*structs.ConsulIngressListener{{
Port: 3000,
Protocol: "tcp",
Services: []*structs.ConsulIngressService{{
Name: "service1",
}},
}},
},
}, "bridge")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(defaultConnectTimeout),
EnvoyGatewayNoDefaultBind: true,
EnvoyGatewayBindTaggedAddresses: false,
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"service1": {
Address: "0.0.0.0",
Port: 3000,
}},
}, result)
})
t.Run("ingress set defaults", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
Config: map[string]interface{}{"foo": 1},
},
Ingress: &structs.ConsulIngressConfigEntry{
Listeners: []*structs.ConsulIngressListener{{
Port: 3000,
Protocol: "tcp",
Services: []*structs.ConsulIngressService{{
Name: "service1",
}},
}},
},
}, "bridge")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
Config: map[string]interface{}{"foo": 1},
EnvoyGatewayNoDefaultBind: true,
EnvoyGatewayBindTaggedAddresses: false,
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"service1": {
Address: "0.0.0.0",
Port: 3000,
}},
}, result)
})
t.Run("ingress leave as-is", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
Config: map[string]interface{}{"foo": 1},
EnvoyGatewayBindTaggedAddresses: true,
},
Ingress: &structs.ConsulIngressConfigEntry{
Listeners: []*structs.ConsulIngressListener{{
Port: 3000,
Protocol: "tcp",
Services: []*structs.ConsulIngressService{{
Name: "service1",
}},
}},
},
}, "bridge")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: nil,
Config: map[string]interface{}{"foo": 1},
EnvoyGatewayNoDefaultBind: false,
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: nil,
}, result)
})
t.Run("terminating set defaults", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
EnvoyDNSDiscoveryType: "STRICT_DNS",
},
Terminating: &structs.ConsulTerminatingConfigEntry{
Services: []*structs.ConsulLinkedService{{
Name: "service1",
CAFile: "/cafile.pem",
CertFile: "/certfile.pem",
KeyFile: "/keyfile.pem",
SNI: "",
}},
},
}, "bridge")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
EnvoyGatewayNoDefaultBind: true,
EnvoyGatewayBindTaggedAddresses: false,
EnvoyDNSDiscoveryType: "STRICT_DNS",
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"default": {
Address: "0.0.0.0",
Port: -1,
},
},
}, result)
})
t.Run("terminating leave as-is", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
Config: map[string]interface{}{"foo": 1},
EnvoyGatewayBindTaggedAddresses: true,
},
Terminating: &structs.ConsulTerminatingConfigEntry{
Services: []*structs.ConsulLinkedService{{
Name: "service1",
}},
},
}, "bridge")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: nil,
Config: map[string]interface{}{"foo": 1},
EnvoyGatewayNoDefaultBind: false,
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: nil,
}, result)
})
t.Run("mesh set defaults in bridge", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
},
Mesh: &structs.ConsulMeshConfigEntry{
// nothing
},
}, "bridge")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
EnvoyGatewayNoDefaultBind: true,
EnvoyGatewayBindTaggedAddresses: false,
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"lan": {
Address: "0.0.0.0",
Port: -1,
},
"wan": {
Address: "0.0.0.0",
Port: -1,
},
},
}, result)
})
t.Run("mesh set defaults in host", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
},
Mesh: &structs.ConsulMeshConfigEntry{
// nothing
},
}, "host")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(2 * time.Second),
}, result)
})
t.Run("mesh leave as-is", func(t *testing.T) {
result := gatewayProxy(&structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
Config: map[string]interface{}{"foo": 1},
EnvoyGatewayBindTaggedAddresses: true,
},
Mesh: &structs.ConsulMeshConfigEntry{
// nothing
},
}, "bridge")
require.Equal(t, &structs.ConsulGatewayProxy{
ConnectTimeout: nil,
Config: map[string]interface{}{"foo": 1},
EnvoyGatewayNoDefaultBind: false,
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: nil,
}, result)
})
}