f61f801e77
Upcoming work to instrument the rate of RPC requests by consumer (and eventually rate limit) requires that we thread the `RPCContext` through all RPC handlers so that we can access the underlying connection. This changeset adds the context to everywhere we intend to initially support it and intentionally excludes streaming RPCs and client RPCs. To improve the ergonomics of adding the context everywhere its needed and to clarify the requirements of dynamic vs static handlers, I've also done a good bit of refactoring here: * canonicalized the RPC handler fields so they're as close to identical as possible without introducing unused fields (i.e. I didn't add loggers if the handler doesn't use them already). * canonicalized the imports in the handler files. * added a `NewExampleEndpoint` function for each handler that ensures we're constructing the handlers with the required arguments. * reordered the registration in server.go to match the order of the files (to make it easier to see if we've missed one), and added a bunch of commentary there as to what the difference between static and dynamic handlers is.
1004 lines
29 KiB
Go
1004 lines
29 KiB
Go
package nomad
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"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/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_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"),
|
|
newConnectSidecarTask("admin"),
|
|
}
|
|
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)
|
|
})
|
|
}
|