fff2eec625
* 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
1071 lines
31 KiB
Go
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)
|
|
})
|
|
}
|