2ef7a280b0
Co-authored-by: James Rasell <jrasell@users.noreply.github.com>
2077 lines
53 KiB
Go
2077 lines
53 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package structs
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/pointer"
|
|
"github.com/shoenig/test/must"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestServiceCheck_Hash(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
original := &ServiceCheck{
|
|
Name: "check",
|
|
SuccessBeforePassing: 3,
|
|
FailuresBeforeCritical: 4,
|
|
}
|
|
|
|
type sc = ServiceCheck
|
|
type tweaker = func(check *sc)
|
|
|
|
hash := func(c *sc) string {
|
|
return c.Hash("ServiceID")
|
|
}
|
|
|
|
t.Run("reflexive", func(t *testing.T) {
|
|
require.Equal(t, hash(original), hash(original))
|
|
})
|
|
|
|
// these tests use tweaker to modify 1 field and make the false assertion
|
|
// on comparing the resulting hash output
|
|
|
|
try := func(t *testing.T, tweak tweaker) {
|
|
originalHash := hash(original)
|
|
modifiable := original.Copy()
|
|
tweak(modifiable)
|
|
tweakedHash := hash(modifiable)
|
|
require.NotEqual(t, originalHash, tweakedHash)
|
|
}
|
|
|
|
t.Run("name", func(t *testing.T) {
|
|
try(t, func(s *sc) { s.Name = "newName" })
|
|
})
|
|
|
|
t.Run("success_before_passing", func(t *testing.T) {
|
|
try(t, func(s *sc) { s.SuccessBeforePassing = 99 })
|
|
})
|
|
|
|
t.Run("failures_before_critical", func(t *testing.T) {
|
|
try(t, func(s *sc) { s.FailuresBeforeCritical = 99 })
|
|
})
|
|
}
|
|
|
|
func TestServiceCheck_Canonicalize(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("defaults", func(t *testing.T) {
|
|
sc := &ServiceCheck{
|
|
Args: []string{},
|
|
Header: make(map[string][]string),
|
|
Method: "",
|
|
OnUpdate: "",
|
|
}
|
|
sc.Canonicalize("MyService", "task1")
|
|
must.Nil(t, sc.Args)
|
|
must.Nil(t, sc.Header)
|
|
must.Eq(t, `service: "MyService" check`, sc.Name)
|
|
must.Eq(t, "", sc.Method)
|
|
must.Eq(t, OnUpdateRequireHealthy, sc.OnUpdate)
|
|
})
|
|
|
|
t.Run("check name set", func(t *testing.T) {
|
|
sc := &ServiceCheck{
|
|
Name: "Some Check",
|
|
}
|
|
sc.Canonicalize("MyService", "task1")
|
|
must.Eq(t, "Some Check", sc.Name)
|
|
})
|
|
|
|
t.Run("on_update is set", func(t *testing.T) {
|
|
sc := &ServiceCheck{
|
|
OnUpdate: OnUpdateIgnore,
|
|
}
|
|
sc.Canonicalize("MyService", "task1")
|
|
must.Eq(t, OnUpdateIgnore, sc.OnUpdate)
|
|
})
|
|
}
|
|
|
|
func TestServiceCheck_validate_PassingTypes(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("valid", func(t *testing.T) {
|
|
for _, checkType := range []string{"tcp", "http", "grpc"} {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: checkType,
|
|
Path: "/path",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
SuccessBeforePassing: 3,
|
|
}).validateConsul()
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: "script",
|
|
Command: "/nothing",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
SuccessBeforePassing: 3,
|
|
}).validateConsul()
|
|
require.EqualError(t, err, `success_before_passing not supported for check of type "script"`)
|
|
})
|
|
}
|
|
|
|
func TestServiceCheck_validate_FailingTypes(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("valid", func(t *testing.T) {
|
|
for _, checkType := range []string{"tcp", "http", "grpc"} {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: checkType,
|
|
Path: "/path",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
FailuresBeforeCritical: 3,
|
|
}).validateConsul()
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: "script",
|
|
Command: "/nothing",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
SuccessBeforePassing: 0,
|
|
FailuresBeforeCritical: 3,
|
|
}).validateConsul()
|
|
require.EqualError(t, err, `failures_before_critical not supported for check of type "script"`)
|
|
})
|
|
}
|
|
|
|
func TestServiceCheck_validate_PassFailZero_on_scripts(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: "script",
|
|
Command: "/nothing",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
SuccessBeforePassing: 0, // script checks should still pass validation
|
|
FailuresBeforeCritical: 0, // script checks should still pass validation
|
|
}).validateConsul()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestServiceCheck_validate_OnUpdate_CheckRestart_Conflict(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: "script",
|
|
Command: "/nothing",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
CheckRestart: &CheckRestart{
|
|
IgnoreWarnings: false,
|
|
Limit: 3,
|
|
Grace: 5 * time.Second,
|
|
},
|
|
OnUpdate: OnUpdateIgnoreWarn,
|
|
}).validateConsul()
|
|
require.EqualError(t, err, `on_update value "ignore_warnings" not supported with check_restart ignore_warnings value "false"`)
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: "script",
|
|
Command: "/nothing",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
CheckRestart: &CheckRestart{
|
|
IgnoreWarnings: false,
|
|
Limit: 3,
|
|
Grace: 5 * time.Second,
|
|
},
|
|
OnUpdate: OnUpdateIgnore,
|
|
}).validateConsul()
|
|
require.EqualError(t, err, `on_update value "ignore" is not compatible with check_restart`)
|
|
})
|
|
|
|
t.Run("valid", func(t *testing.T) {
|
|
err := (&ServiceCheck{
|
|
Name: "check",
|
|
Type: "script",
|
|
Command: "/nothing",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 2 * time.Second,
|
|
CheckRestart: &CheckRestart{
|
|
IgnoreWarnings: true,
|
|
Limit: 3,
|
|
Grace: 5 * time.Second,
|
|
},
|
|
OnUpdate: OnUpdateIgnoreWarn,
|
|
}).validateConsul()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestServiceCheck_validateNomad(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
sc *ServiceCheck
|
|
exp string
|
|
}{
|
|
{name: "grpc", sc: &ServiceCheck{Type: ServiceCheckGRPC}, exp: `invalid check type ("grpc"), must be one of tcp, http`},
|
|
{name: "script", sc: &ServiceCheck{Type: ServiceCheckScript}, exp: `invalid check type ("script"), must be one of tcp, http`},
|
|
{
|
|
name: "expose",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckTCP,
|
|
Expose: true, // consul only
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
},
|
|
exp: `expose may only be set for Consul service checks`,
|
|
}, {
|
|
name: "on_update ignore_warnings",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckTCP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
OnUpdate: OnUpdateIgnoreWarn,
|
|
},
|
|
exp: `on_update may only be set to ignore_warnings for Consul service checks`,
|
|
},
|
|
{
|
|
name: "success_before_passing",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckTCP,
|
|
SuccessBeforePassing: 3, // consul only
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
},
|
|
exp: `success_before_passing may only be set for Consul service checks`,
|
|
},
|
|
{
|
|
name: "failures_before_critical",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckTCP,
|
|
FailuresBeforeCritical: 3, // consul only
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
},
|
|
exp: `failures_before_critical may only be set for Consul service checks`,
|
|
},
|
|
{
|
|
name: "check_restart",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckTCP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
CheckRestart: new(CheckRestart),
|
|
},
|
|
},
|
|
{
|
|
name: "check_restart ignore_warnings",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckTCP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
CheckRestart: &CheckRestart{
|
|
IgnoreWarnings: true,
|
|
},
|
|
},
|
|
exp: `ignore_warnings on check_restart only supported for Consul service checks`,
|
|
},
|
|
{
|
|
name: "address mode driver",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckTCP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
AddressMode: "driver",
|
|
},
|
|
exp: `address_mode = driver may only be set for Consul service checks`,
|
|
},
|
|
{
|
|
name: "http non GET",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckHTTP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
Path: "/health",
|
|
Method: "HEAD",
|
|
},
|
|
},
|
|
{
|
|
name: "http unknown method type",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckHTTP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
Path: "/health",
|
|
Method: "Invalid",
|
|
},
|
|
exp: `method type "Invalid" not supported in Nomad http check`,
|
|
},
|
|
{
|
|
name: "http with headers",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckHTTP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
Path: "/health",
|
|
Method: "GET",
|
|
Header: map[string][]string{
|
|
"foo": {"bar"},
|
|
"baz": nil,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "http with body",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckHTTP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
Path: "/health",
|
|
Method: "POST",
|
|
Body: "this is a request payload!",
|
|
},
|
|
},
|
|
{
|
|
name: "http with tls_server_name",
|
|
sc: &ServiceCheck{
|
|
Type: ServiceCheckHTTP,
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
Path: "/health",
|
|
TLSServerName: "foo",
|
|
},
|
|
exp: `tls_server_name may only be set for Consul service checks`,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
err := testCase.sc.validateNomad()
|
|
if testCase.exp == "" {
|
|
must.NoError(t, err)
|
|
} else {
|
|
must.EqError(t, err, testCase.exp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestService_Hash(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
original := &Service{
|
|
Name: "myService",
|
|
PortLabel: "portLabel",
|
|
// AddressMode: "bridge", // not hashed (used internally by Nomad)
|
|
Tags: []string{"original", "tags"},
|
|
CanaryTags: []string{"canary", "tags"},
|
|
// Checks: nil, // not hashed (managed independently)
|
|
Connect: &ConsulConnect{
|
|
// Native: false, // not hashed
|
|
SidecarService: &ConsulSidecarService{
|
|
Tags: []string{"original", "sidecar", "tags"},
|
|
Port: "9000",
|
|
Proxy: &ConsulProxy{
|
|
LocalServiceAddress: "127.0.0.1",
|
|
LocalServicePort: 24000,
|
|
Config: map[string]any{"foo": "bar"},
|
|
Upstreams: []ConsulUpstream{{
|
|
DestinationName: "upstream1",
|
|
DestinationNamespace: "ns2",
|
|
LocalBindPort: 29000,
|
|
Config: map[string]any{"foo": "bar"},
|
|
}},
|
|
},
|
|
Meta: map[string]string{
|
|
"test-key": "test-value",
|
|
},
|
|
},
|
|
// SidecarTask: nil // not hashed
|
|
}}
|
|
|
|
type svc = Service
|
|
type tweaker = func(service *svc)
|
|
|
|
hash := func(s *svc, canary bool) string {
|
|
return s.Hash("AllocID", "TaskName", canary)
|
|
}
|
|
|
|
t.Run("matching and is canary", func(t *testing.T) {
|
|
require.Equal(t, hash(original, true), hash(original, true))
|
|
})
|
|
|
|
t.Run("matching and is not canary", func(t *testing.T) {
|
|
require.Equal(t, hash(original, false), hash(original, false))
|
|
})
|
|
|
|
t.Run("matching mod canary", func(t *testing.T) {
|
|
require.NotEqual(t, hash(original, true), hash(original, false))
|
|
})
|
|
|
|
try := func(t *testing.T, tweak tweaker) {
|
|
originalHash := hash(original, true)
|
|
modifiable := original.Copy()
|
|
tweak(modifiable)
|
|
tweakedHash := hash(modifiable, true)
|
|
require.NotEqual(t, originalHash, tweakedHash)
|
|
}
|
|
|
|
// these tests use tweaker to modify 1 field and make the false assertion
|
|
// on comparing the resulting hash output
|
|
|
|
t.Run("mod address", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Address = "example.com" })
|
|
})
|
|
|
|
t.Run("mod name", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Name = "newName" })
|
|
})
|
|
|
|
t.Run("mod port label", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.PortLabel = "newPortLabel" })
|
|
})
|
|
|
|
t.Run("mod tags", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Tags = []string{"new", "tags"} })
|
|
})
|
|
|
|
t.Run("mod canary tags", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.CanaryTags = []string{"new", "tags"} })
|
|
})
|
|
|
|
t.Run("mod enable tag override", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.EnableTagOverride = true })
|
|
})
|
|
|
|
t.Run("mod connect sidecar tags", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Tags = []string{"new", "tags"} })
|
|
})
|
|
|
|
t.Run("mod connect sidecar port", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Port = "9090" })
|
|
})
|
|
|
|
t.Run("mod connect sidecar proxy local service address", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.LocalServiceAddress = "1.1.1.1" })
|
|
})
|
|
|
|
t.Run("mod connect sidecar proxy local service port", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.LocalServicePort = 9999 })
|
|
})
|
|
|
|
t.Run("mod connect sidecar proxy config", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Config = map[string]interface{}{"foo": "baz"} })
|
|
})
|
|
|
|
t.Run("mod connect sidecar proxy upstream destination name", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].DestinationName = "dest2" })
|
|
})
|
|
|
|
t.Run("mod connect sidecar proxy upstream destination namespace", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].DestinationNamespace = "ns3" })
|
|
})
|
|
|
|
t.Run("mod connect sidecar proxy upstream destination local bind port", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].LocalBindPort = 29999 })
|
|
})
|
|
|
|
t.Run("mod connect sidecar proxy upstream config", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].Config = map[string]any{"foo": "baz"} })
|
|
})
|
|
}
|
|
|
|
func TestConsulConnect_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := &ConsulConnect{}
|
|
|
|
// An empty Connect block is invalid
|
|
require.Error(t, c.Validate())
|
|
|
|
c.Native = true
|
|
require.NoError(t, c.Validate())
|
|
|
|
// Native=true + Sidecar!=nil is invalid
|
|
c.SidecarService = &ConsulSidecarService{}
|
|
require.Error(t, c.Validate())
|
|
|
|
c.Native = false
|
|
require.NoError(t, c.Validate())
|
|
}
|
|
|
|
func TestConsulConnect_CopyEqual(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := &ConsulConnect{
|
|
SidecarService: &ConsulSidecarService{
|
|
Tags: []string{"tag1", "tag2"},
|
|
Port: "9001",
|
|
Proxy: &ConsulProxy{
|
|
LocalServiceAddress: "127.0.0.1",
|
|
LocalServicePort: 8080,
|
|
Upstreams: []ConsulUpstream{
|
|
{
|
|
DestinationName: "up1",
|
|
DestinationNamespace: "ns2",
|
|
LocalBindPort: 9002,
|
|
},
|
|
{
|
|
DestinationName: "up2",
|
|
DestinationNamespace: "ns2",
|
|
LocalBindPort: 9003,
|
|
},
|
|
},
|
|
Config: map[string]interface{}{
|
|
"foo": 1,
|
|
},
|
|
},
|
|
Meta: map[string]string{
|
|
"test-key": "test-value",
|
|
},
|
|
},
|
|
}
|
|
|
|
require.NoError(t, c.Validate())
|
|
|
|
// Copies should be equivalent
|
|
o := c.Copy()
|
|
require.True(t, c.Equal(o))
|
|
|
|
o.SidecarService.Proxy.Upstreams = nil
|
|
require.False(t, c.Equal(o))
|
|
}
|
|
|
|
func TestConsulConnect_GatewayProxy_CopyEqual(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := &ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
EnvoyGatewayBindTaggedAddresses: false,
|
|
EnvoyGatewayBindAddresses: make(map[string]*ConsulGatewayBindAddress),
|
|
}
|
|
|
|
require.NoError(t, c.Validate())
|
|
|
|
// Copies should be equivalent
|
|
o := c.Copy()
|
|
require.Equal(t, c, o)
|
|
require.True(t, c.Equal(o))
|
|
}
|
|
|
|
func TestSidecarTask_MergeIntoTask(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
task := MockJob().TaskGroups[0].Tasks[0]
|
|
sTask := &SidecarTask{
|
|
Name: "sidecar",
|
|
Driver: "sidecar",
|
|
User: "test",
|
|
Config: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
Resources: &Resources{
|
|
CPU: 10000,
|
|
MemoryMB: 10000,
|
|
},
|
|
Env: map[string]string{
|
|
"sidecar": "proxy",
|
|
},
|
|
Meta: map[string]string{
|
|
"abc": "123",
|
|
},
|
|
KillTimeout: pointer.Of(15 * time.Second),
|
|
LogConfig: &LogConfig{
|
|
MaxFiles: 3,
|
|
},
|
|
ShutdownDelay: pointer.Of(5 * time.Second),
|
|
KillSignal: "SIGABRT",
|
|
}
|
|
|
|
expected := task.Copy()
|
|
expected.Name = "sidecar"
|
|
expected.Driver = "sidecar"
|
|
expected.User = "test"
|
|
expected.Config = map[string]interface{}{
|
|
"foo": "bar",
|
|
}
|
|
expected.Resources.CPU = 10000
|
|
expected.Resources.MemoryMB = 10000
|
|
expected.Env["sidecar"] = "proxy"
|
|
expected.Meta["abc"] = "123"
|
|
expected.KillTimeout = 15 * time.Second
|
|
expected.LogConfig.MaxFiles = 3
|
|
expected.ShutdownDelay = 5 * time.Second
|
|
expected.KillSignal = "SIGABRT"
|
|
|
|
sTask.MergeIntoTask(task)
|
|
require.Exactly(t, expected, task)
|
|
|
|
// Check that changing just driver config doesn't replace map
|
|
sTask.Config["abc"] = 123
|
|
expected.Config["abc"] = 123
|
|
|
|
sTask.MergeIntoTask(task)
|
|
require.Exactly(t, expected, task)
|
|
}
|
|
|
|
func TestSidecarTask_Equal(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
original := &SidecarTask{
|
|
Name: "sidecar-task-1",
|
|
Driver: "docker",
|
|
User: "nobody",
|
|
Config: map[string]interface{}{"foo": 1},
|
|
Env: map[string]string{"color": "blue"},
|
|
Resources: &Resources{MemoryMB: 300},
|
|
Meta: map[string]string{"index": "1"},
|
|
KillTimeout: pointer.Of(2 * time.Second),
|
|
LogConfig: &LogConfig{
|
|
MaxFiles: 2,
|
|
MaxFileSizeMB: 300,
|
|
},
|
|
ShutdownDelay: pointer.Of(10 * time.Second),
|
|
KillSignal: "SIGTERM",
|
|
}
|
|
|
|
t.Run("unmodified", func(t *testing.T) {
|
|
duplicate := original.Copy()
|
|
require.True(t, duplicate.Equal(original))
|
|
})
|
|
|
|
type st = SidecarTask
|
|
type tweaker = func(task *st)
|
|
|
|
try := func(t *testing.T, tweak tweaker) {
|
|
modified := original.Copy()
|
|
tweak(modified)
|
|
require.NotEqual(t, original, modified)
|
|
}
|
|
|
|
t.Run("mod name", func(t *testing.T) {
|
|
try(t, func(s *st) { s.Name = "sidecar-task-2" })
|
|
})
|
|
|
|
t.Run("mod driver", func(t *testing.T) {
|
|
try(t, func(s *st) { s.Driver = "exec" })
|
|
})
|
|
|
|
t.Run("mod user", func(t *testing.T) {
|
|
try(t, func(s *st) { s.User = "root" })
|
|
})
|
|
|
|
t.Run("mod config", func(t *testing.T) {
|
|
try(t, func(s *st) { s.Config = map[string]interface{}{"foo": 2} })
|
|
})
|
|
|
|
t.Run("mod env", func(t *testing.T) {
|
|
try(t, func(s *st) { s.Env = map[string]string{"color": "red"} })
|
|
})
|
|
|
|
t.Run("mod resources", func(t *testing.T) {
|
|
try(t, func(s *st) { s.Resources = &Resources{MemoryMB: 200} })
|
|
})
|
|
|
|
t.Run("mod meta", func(t *testing.T) {
|
|
try(t, func(s *st) { s.Meta = map[string]string{"index": "2"} })
|
|
})
|
|
|
|
t.Run("mod kill timeout", func(t *testing.T) {
|
|
try(t, func(s *st) { s.KillTimeout = pointer.Of(3 * time.Second) })
|
|
})
|
|
|
|
t.Run("mod log config", func(t *testing.T) {
|
|
try(t, func(s *st) { s.LogConfig = &LogConfig{MaxFiles: 3} })
|
|
})
|
|
|
|
t.Run("mod shutdown delay", func(t *testing.T) {
|
|
try(t, func(s *st) { s.ShutdownDelay = pointer.Of(20 * time.Second) })
|
|
})
|
|
|
|
t.Run("mod kill signal", func(t *testing.T) {
|
|
try(t, func(s *st) { s.KillSignal = "SIGHUP" })
|
|
})
|
|
}
|
|
|
|
func TestConsulUpstream_upstreamEqual(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
up := func(name string, port int) ConsulUpstream {
|
|
return ConsulUpstream{
|
|
DestinationName: name,
|
|
LocalBindPort: port,
|
|
}
|
|
}
|
|
|
|
t.Run("size mismatch", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000)}
|
|
b := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("bar", 9000)}
|
|
b := []ConsulUpstream{up("foo", 8000)}
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different namespace", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000)}
|
|
a[0].DestinationNamespace = "ns1"
|
|
|
|
b := []ConsulUpstream{up("foo", 8000)}
|
|
b[0].DestinationNamespace = "ns2"
|
|
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different dest peer", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000)}
|
|
a[0].DestinationPeer = "10.0.0.1:6379"
|
|
|
|
b := []ConsulUpstream{up("foo", 8000)}
|
|
b[0].DestinationPeer = "10.0.0.1:6375"
|
|
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different dest type", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000)}
|
|
a[0].DestinationType = "tcp"
|
|
|
|
b := []ConsulUpstream{up("foo", 8000)}
|
|
b[0].DestinationType = "udp"
|
|
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different socket path", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000)}
|
|
a[0].LocalBindSocketPath = "/var/run/mysocket.sock"
|
|
|
|
b := []ConsulUpstream{up("foo", 8000)}
|
|
b[0].LocalBindSocketPath = "/var/run/testsocket.sock"
|
|
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different socket mode", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000)}
|
|
a[0].LocalBindSocketMode = "0666"
|
|
|
|
b := []ConsulUpstream{up("foo", 8000)}
|
|
b[0].LocalBindSocketMode = "0600"
|
|
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different mesh_gateway", func(t *testing.T) {
|
|
a := []ConsulUpstream{{DestinationName: "foo", MeshGateway: ConsulMeshGateway{Mode: "local"}}}
|
|
b := []ConsulUpstream{{DestinationName: "foo", MeshGateway: ConsulMeshGateway{Mode: "remote"}}}
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different opaque config", func(t *testing.T) {
|
|
a := []ConsulUpstream{{Config: map[string]any{"foo": 1}}}
|
|
b := []ConsulUpstream{{Config: map[string]any{"foo": 2}}}
|
|
must.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("identical", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
|
|
b := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
|
|
a[0].DestinationPeer = "10.0.0.1:6379"
|
|
a[0].DestinationType = "tcp"
|
|
a[0].LocalBindSocketPath = "/var/run/mysocket.sock"
|
|
a[0].LocalBindSocketMode = "0666"
|
|
b[0].DestinationPeer = "10.0.0.1:6379"
|
|
b[0].DestinationType = "tcp"
|
|
b[0].LocalBindSocketPath = "/var/run/mysocket.sock"
|
|
b[0].LocalBindSocketMode = "0666"
|
|
must.True(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("unsorted", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
|
|
b := []ConsulUpstream{up("bar", 9000), up("foo", 8000)}
|
|
must.True(t, upstreamsEquals(a, b))
|
|
})
|
|
}
|
|
|
|
func TestConsulExposePath_exposePathsEqual(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
expose := func(path, protocol, listen string, local int) ConsulExposePath {
|
|
return ConsulExposePath{
|
|
Path: path,
|
|
Protocol: protocol,
|
|
LocalPathPort: local,
|
|
ListenerPort: listen,
|
|
}
|
|
}
|
|
|
|
t.Run("size mismatch", func(t *testing.T) {
|
|
a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
|
|
b := []ConsulExposePath{expose("/1", "http", "myPort", 8000), expose("/2", "http", "myPort", 8000)}
|
|
require.False(t, exposePathsEqual(a, b))
|
|
})
|
|
|
|
t.Run("different", func(t *testing.T) {
|
|
a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
|
|
b := []ConsulExposePath{expose("/2", "http", "myPort", 8000)}
|
|
require.False(t, exposePathsEqual(a, b))
|
|
})
|
|
|
|
t.Run("identical", func(t *testing.T) {
|
|
a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
|
|
b := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
|
|
require.True(t, exposePathsEqual(a, b))
|
|
})
|
|
|
|
t.Run("unsorted", func(t *testing.T) {
|
|
a := []ConsulExposePath{expose("/2", "http", "myPort", 8000), expose("/1", "http", "myPort", 8000)}
|
|
b := []ConsulExposePath{expose("/1", "http", "myPort", 8000), expose("/2", "http", "myPort", 8000)}
|
|
require.True(t, exposePathsEqual(a, b))
|
|
})
|
|
}
|
|
|
|
func TestConsulExposeConfig_Copy(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require.Nil(t, (*ConsulExposeConfig)(nil).Copy())
|
|
require.Equal(t, &ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}, (&ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}).Copy())
|
|
}
|
|
|
|
func TestConsulExposeConfig_Equal(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require.True(t, (*ConsulExposeConfig)(nil).Equal(nil))
|
|
require.True(t, (&ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}).Equal(&ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}))
|
|
}
|
|
|
|
func TestConsulSidecarService_Copy(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
s := (*ConsulSidecarService)(nil)
|
|
result := s.Copy()
|
|
require.Nil(t, result)
|
|
})
|
|
|
|
t.Run("not nil", func(t *testing.T) {
|
|
s := &ConsulSidecarService{
|
|
Tags: []string{"foo", "bar"},
|
|
Port: "port1",
|
|
Proxy: &ConsulProxy{LocalServiceAddress: "10.0.0.1"},
|
|
Meta: map[string]string{
|
|
"test-key": "test-value",
|
|
},
|
|
}
|
|
result := s.Copy()
|
|
require.Equal(t, &ConsulSidecarService{
|
|
Tags: []string{"foo", "bar"},
|
|
Port: "port1",
|
|
Proxy: &ConsulProxy{LocalServiceAddress: "10.0.0.1"},
|
|
Meta: map[string]string{
|
|
"test-key": "test-value",
|
|
},
|
|
}, result)
|
|
})
|
|
}
|
|
|
|
var (
|
|
consulIngressGateway1 = &ConsulGateway{
|
|
Proxy: &ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
EnvoyGatewayBindTaggedAddresses: true,
|
|
EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
|
|
"listener1": {Address: "10.0.0.1", Port: 2001},
|
|
"listener2": {Address: "10.0.0.1", Port: 2002},
|
|
},
|
|
EnvoyGatewayNoDefaultBind: true,
|
|
Config: map[string]interface{}{
|
|
"foo": 1,
|
|
},
|
|
},
|
|
Ingress: &ConsulIngressConfigEntry{
|
|
TLS: &ConsulGatewayTLSConfig{
|
|
Enabled: true,
|
|
},
|
|
Listeners: []*ConsulIngressListener{{
|
|
Port: 3000,
|
|
Protocol: "http",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service1",
|
|
Hosts: []string{"10.0.0.1", "10.0.0.1:3000"},
|
|
}, {
|
|
Name: "service2",
|
|
Hosts: []string{"10.0.0.2", "10.0.0.2:3000"},
|
|
}},
|
|
}, {
|
|
Port: 3001,
|
|
Protocol: "tcp",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service3",
|
|
}},
|
|
}},
|
|
},
|
|
}
|
|
|
|
consulTerminatingGateway1 = &ConsulGateway{
|
|
Proxy: &ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
EnvoyDNSDiscoveryType: "STRICT_DNS",
|
|
EnvoyGatewayBindAddresses: nil,
|
|
},
|
|
Terminating: &ConsulTerminatingConfigEntry{
|
|
Services: []*ConsulLinkedService{{
|
|
Name: "linked-service1",
|
|
CAFile: "ca.pem",
|
|
CertFile: "cert.pem",
|
|
KeyFile: "key.pem",
|
|
SNI: "service1.consul",
|
|
}, {
|
|
Name: "linked-service2",
|
|
}},
|
|
},
|
|
}
|
|
|
|
consulMeshGateway1 = &ConsulGateway{
|
|
Proxy: &ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
},
|
|
Mesh: &ConsulMeshConfigEntry{
|
|
// nothing
|
|
},
|
|
}
|
|
)
|
|
|
|
func TestConsulGateway_Prefix(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("ingress", func(t *testing.T) {
|
|
result := (&ConsulGateway{Ingress: new(ConsulIngressConfigEntry)}).Prefix()
|
|
require.Equal(t, ConnectIngressPrefix, result)
|
|
})
|
|
|
|
t.Run("terminating", func(t *testing.T) {
|
|
result := (&ConsulGateway{Terminating: new(ConsulTerminatingConfigEntry)}).Prefix()
|
|
require.Equal(t, ConnectTerminatingPrefix, result)
|
|
})
|
|
|
|
t.Run("mesh", func(t *testing.T) {
|
|
result := (&ConsulGateway{Mesh: new(ConsulMeshConfigEntry)}).Prefix()
|
|
require.Equal(t, ConnectMeshPrefix, result)
|
|
})
|
|
}
|
|
|
|
func TestConsulGateway_Copy(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
g := (*ConsulGateway)(nil)
|
|
result := g.Copy()
|
|
require.Nil(t, result)
|
|
})
|
|
|
|
t.Run("as ingress", func(t *testing.T) {
|
|
result := consulIngressGateway1.Copy()
|
|
require.Equal(t, consulIngressGateway1, result)
|
|
require.True(t, result.Equal(consulIngressGateway1))
|
|
require.True(t, consulIngressGateway1.Equal(result))
|
|
})
|
|
|
|
t.Run("as terminating", func(t *testing.T) {
|
|
result := consulTerminatingGateway1.Copy()
|
|
require.Equal(t, consulTerminatingGateway1, result)
|
|
require.True(t, result.Equal(consulTerminatingGateway1))
|
|
require.True(t, consulTerminatingGateway1.Equal(result))
|
|
})
|
|
|
|
t.Run("as mesh", func(t *testing.T) {
|
|
result := consulMeshGateway1.Copy()
|
|
require.Equal(t, consulMeshGateway1, result)
|
|
require.True(t, result.Equal(consulMeshGateway1))
|
|
require.True(t, consulMeshGateway1.Equal(result))
|
|
})
|
|
}
|
|
|
|
func TestConsulGateway_Equal_mesh(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
a := (*ConsulGateway)(nil)
|
|
b := (*ConsulGateway)(nil)
|
|
require.True(t, a.Equal(b))
|
|
require.False(t, a.Equal(consulMeshGateway1))
|
|
require.False(t, consulMeshGateway1.Equal(a))
|
|
})
|
|
|
|
t.Run("reflexive", func(t *testing.T) {
|
|
require.True(t, consulMeshGateway1.Equal(consulMeshGateway1))
|
|
})
|
|
}
|
|
|
|
func TestConsulGateway_Equal_ingress(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
a := (*ConsulGateway)(nil)
|
|
b := (*ConsulGateway)(nil)
|
|
require.True(t, a.Equal(b))
|
|
require.False(t, a.Equal(consulIngressGateway1))
|
|
require.False(t, consulIngressGateway1.Equal(a))
|
|
})
|
|
|
|
original := consulIngressGateway1.Copy()
|
|
|
|
type cg = ConsulGateway
|
|
type tweaker = func(g *cg)
|
|
|
|
t.Run("reflexive", func(t *testing.T) {
|
|
require.True(t, original.Equal(original))
|
|
})
|
|
|
|
try := func(t *testing.T, tweak tweaker) {
|
|
modifiable := original.Copy()
|
|
tweak(modifiable)
|
|
require.False(t, original.Equal(modifiable))
|
|
require.False(t, modifiable.Equal(original))
|
|
require.True(t, modifiable.Equal(modifiable))
|
|
}
|
|
|
|
// proxy block equality checks
|
|
|
|
t.Run("mod gateway timeout", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Proxy.ConnectTimeout = pointer.Of(9 * time.Second) })
|
|
})
|
|
|
|
t.Run("mod gateway envoy_gateway_bind_tagged_addresses", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Proxy.EnvoyGatewayBindTaggedAddresses = false })
|
|
})
|
|
|
|
t.Run("mod gateway envoy_gateway_bind_addresses", func(t *testing.T) {
|
|
try(t, func(g *cg) {
|
|
g.Proxy.EnvoyGatewayBindAddresses = map[string]*ConsulGatewayBindAddress{
|
|
"listener3": {Address: "9.9.9.9", Port: 9999},
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("mod gateway envoy_gateway_no_default_bind", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Proxy.EnvoyGatewayNoDefaultBind = false })
|
|
})
|
|
|
|
t.Run("mod gateway config", func(t *testing.T) {
|
|
try(t, func(g *cg) {
|
|
g.Proxy.Config = map[string]interface{}{
|
|
"foo": 2,
|
|
}
|
|
})
|
|
})
|
|
|
|
// ingress config entry equality checks
|
|
|
|
t.Run("mod ingress tls", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.TLS = nil })
|
|
try(t, func(g *cg) { g.Ingress.TLS.Enabled = false })
|
|
})
|
|
|
|
t.Run("mod ingress listeners count", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.Listeners = g.Ingress.Listeners[:1] })
|
|
})
|
|
|
|
t.Run("mod ingress listeners port", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.Listeners[0].Port = 7777 })
|
|
})
|
|
|
|
t.Run("mod ingress listeners protocol", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.Listeners[0].Protocol = "tcp" })
|
|
})
|
|
|
|
t.Run("mod ingress listeners services count", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.Listeners[0].Services = g.Ingress.Listeners[0].Services[:1] })
|
|
})
|
|
|
|
t.Run("mod ingress listeners services name", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Name = "serviceX" })
|
|
})
|
|
|
|
t.Run("mod ingress listeners services hosts count", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Hosts = g.Ingress.Listeners[0].Services[0].Hosts[:1] })
|
|
})
|
|
|
|
t.Run("mod ingress listeners services hosts content", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Hosts[0] = "255.255.255.255" })
|
|
})
|
|
}
|
|
|
|
func TestConsulGateway_Equal_terminating(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
original := consulTerminatingGateway1.Copy()
|
|
|
|
type cg = ConsulGateway
|
|
type tweaker = func(c *cg)
|
|
|
|
t.Run("reflexive", func(t *testing.T) {
|
|
require.True(t, original.Equal(original))
|
|
})
|
|
|
|
try := func(t *testing.T, tweak tweaker) {
|
|
modifiable := original.Copy()
|
|
tweak(modifiable)
|
|
require.False(t, original.Equal(modifiable))
|
|
require.False(t, modifiable.Equal(original))
|
|
require.True(t, modifiable.Equal(modifiable))
|
|
}
|
|
|
|
// proxy block equality checks
|
|
|
|
t.Run("mod dns discovery type", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Proxy.EnvoyDNSDiscoveryType = "LOGICAL_DNS" })
|
|
})
|
|
|
|
// terminating config entry equality checks
|
|
|
|
t.Run("mod terminating services count", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Terminating.Services = g.Terminating.Services[:1] })
|
|
})
|
|
|
|
t.Run("mod terminating services name", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Terminating.Services[0].Name = "foo" })
|
|
})
|
|
|
|
t.Run("mod terminating services ca_file", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Terminating.Services[0].CAFile = "foo.pem" })
|
|
})
|
|
|
|
t.Run("mod terminating services cert_file", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Terminating.Services[0].CertFile = "foo.pem" })
|
|
})
|
|
|
|
t.Run("mod terminating services key_file", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Terminating.Services[0].KeyFile = "foo.pem" })
|
|
})
|
|
|
|
t.Run("mod terminating services sni", func(t *testing.T) {
|
|
try(t, func(g *cg) { g.Terminating.Services[0].SNI = "foo.consul" })
|
|
})
|
|
}
|
|
|
|
func TestConsulGateway_ingressServicesEqual(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
igs1 := []*ConsulIngressService{{
|
|
Name: "service1",
|
|
Hosts: []string{"host1", "host2"},
|
|
}, {
|
|
Name: "service2",
|
|
Hosts: []string{"host3"},
|
|
}}
|
|
|
|
require.False(t, ingressServicesEqual(igs1, nil))
|
|
require.True(t, ingressServicesEqual(igs1, igs1))
|
|
|
|
reversed := []*ConsulIngressService{
|
|
igs1[1], igs1[0], // services reversed
|
|
}
|
|
|
|
require.True(t, ingressServicesEqual(igs1, reversed))
|
|
|
|
hostOrder := []*ConsulIngressService{{
|
|
Name: "service1",
|
|
Hosts: []string{"host2", "host1"}, // hosts reversed
|
|
}, {
|
|
Name: "service2",
|
|
Hosts: []string{"host3"},
|
|
}}
|
|
|
|
require.True(t, ingressServicesEqual(igs1, hostOrder))
|
|
}
|
|
|
|
func TestConsulGateway_ingressListenersEqual(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
ils1 := []*ConsulIngressListener{{
|
|
Port: 2000,
|
|
Protocol: "http",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service1",
|
|
Hosts: []string{"host1", "host2"},
|
|
}},
|
|
}, {
|
|
Port: 2001,
|
|
Protocol: "tcp",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service2",
|
|
}},
|
|
}}
|
|
|
|
require.False(t, ingressListenersEqual(ils1, nil))
|
|
|
|
reversed := []*ConsulIngressListener{
|
|
ils1[1], ils1[0],
|
|
}
|
|
|
|
require.True(t, ingressListenersEqual(ils1, reversed))
|
|
}
|
|
|
|
func TestConsulGateway_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("bad proxy", func(t *testing.T) {
|
|
err := (&ConsulGateway{
|
|
Proxy: &ConsulGatewayProxy{
|
|
ConnectTimeout: nil,
|
|
},
|
|
Ingress: nil,
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Gateway Proxy connection_timeout must be set")
|
|
})
|
|
|
|
t.Run("bad ingress config entry", func(t *testing.T) {
|
|
err := (&ConsulGateway{
|
|
Ingress: &ConsulIngressConfigEntry{
|
|
Listeners: nil,
|
|
},
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Ingress Gateway requires at least one listener")
|
|
})
|
|
|
|
t.Run("bad terminating config entry", func(t *testing.T) {
|
|
err := (&ConsulGateway{
|
|
Terminating: &ConsulTerminatingConfigEntry{
|
|
Services: nil,
|
|
},
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Terminating Gateway requires at least one service")
|
|
})
|
|
|
|
t.Run("no config entry set", func(t *testing.T) {
|
|
err := (&ConsulGateway{
|
|
Ingress: nil,
|
|
Terminating: nil,
|
|
Mesh: nil,
|
|
}).Validate()
|
|
require.EqualError(t, err, "One Consul Gateway Configuration must be set")
|
|
})
|
|
|
|
t.Run("multiple config entries set", func(t *testing.T) {
|
|
err := (&ConsulGateway{
|
|
Ingress: &ConsulIngressConfigEntry{
|
|
Listeners: []*ConsulIngressListener{{
|
|
Port: 1111,
|
|
Protocol: "tcp",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service1",
|
|
}},
|
|
}},
|
|
},
|
|
Terminating: &ConsulTerminatingConfigEntry{
|
|
Services: []*ConsulLinkedService{{
|
|
Name: "linked-service1",
|
|
}},
|
|
},
|
|
}).Validate()
|
|
require.EqualError(t, err, "One Consul Gateway Configuration must be set")
|
|
})
|
|
|
|
t.Run("ok mesh", func(t *testing.T) {
|
|
err := (&ConsulGateway{
|
|
Mesh: new(ConsulMeshConfigEntry),
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConsulGatewayBindAddress_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("no address", func(t *testing.T) {
|
|
err := (&ConsulGatewayBindAddress{
|
|
Address: "",
|
|
Port: 2000,
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Gateway Bind Address must be set")
|
|
})
|
|
|
|
t.Run("invalid port", func(t *testing.T) {
|
|
err := (&ConsulGatewayBindAddress{
|
|
Address: "10.0.0.1",
|
|
Port: 0,
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Gateway Bind Address must set valid Port")
|
|
})
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
err := (&ConsulGatewayBindAddress{
|
|
Address: "10.0.0.1",
|
|
Port: 2000,
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConsulGatewayProxy_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("no timeout", func(t *testing.T) {
|
|
err := (&ConsulGatewayProxy{
|
|
ConnectTimeout: nil,
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Gateway Proxy connection_timeout must be set")
|
|
})
|
|
|
|
t.Run("invalid bind address", func(t *testing.T) {
|
|
err := (&ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
|
|
"service1": {
|
|
Address: "10.0.0.1",
|
|
Port: 0,
|
|
}},
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Gateway Bind Address must set valid Port")
|
|
})
|
|
|
|
t.Run("invalid dns discovery type", func(t *testing.T) {
|
|
err := (&ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
EnvoyDNSDiscoveryType: "RANDOM_DNS",
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Gateway Proxy Envoy DNS Discovery type must be STRICT_DNS or LOGICAL_DNS")
|
|
})
|
|
|
|
t.Run("ok with nothing set", func(t *testing.T) {
|
|
err := (&ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("ok with everything set", func(t *testing.T) {
|
|
err := (&ConsulGatewayProxy{
|
|
ConnectTimeout: pointer.Of(1 * time.Second),
|
|
EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
|
|
"service1": {
|
|
Address: "10.0.0.1",
|
|
Port: 2000,
|
|
}},
|
|
EnvoyGatewayBindTaggedAddresses: true,
|
|
EnvoyGatewayNoDefaultBind: true,
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConsulIngressService_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("invalid name", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "",
|
|
}).Validate("http")
|
|
must.EqError(t, err, "Consul Ingress Service requires a name")
|
|
})
|
|
|
|
t.Run("tcp extraneous hosts", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "service1",
|
|
Hosts: []string{"host1"},
|
|
}).Validate("tcp")
|
|
must.EqError(t, err, `Consul Ingress Service doesn't support associating hosts to a service for the "tcp" protocol`)
|
|
})
|
|
|
|
t.Run("tcp ok", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "service1",
|
|
}).Validate("tcp")
|
|
must.NoError(t, err)
|
|
})
|
|
|
|
t.Run("tcp with wildcard service", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "*",
|
|
}).Validate("tcp")
|
|
must.EqError(t, err, `Consul Ingress Service doesn't support wildcard name for "tcp" protocol`)
|
|
})
|
|
|
|
// non-"tcp" protocols should be all treated the same.
|
|
for _, proto := range []string{"http", "http2", "grpc"} {
|
|
t.Run(proto+" ok", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "service1",
|
|
Hosts: []string{"host1"},
|
|
}).Validate(proto)
|
|
must.NoError(t, err)
|
|
})
|
|
|
|
t.Run(proto+" without hosts", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "service1",
|
|
}).Validate(proto)
|
|
must.NoError(t, err, must.Sprintf(`"%s" protocol should not require hosts`, proto))
|
|
})
|
|
|
|
t.Run(proto+" wildcard service", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "*",
|
|
}).Validate(proto)
|
|
must.NoError(t, err, must.Sprintf(`"%s" protocol should allow wildcard service`, proto))
|
|
})
|
|
|
|
t.Run(proto+" wildcard service and host", func(t *testing.T) {
|
|
err := (&ConsulIngressService{
|
|
Name: "*",
|
|
Hosts: []string{"any"},
|
|
}).Validate(proto)
|
|
must.EqError(t, err, `Consul Ingress Service with a wildcard "*" service name can not also specify hosts`)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConsulIngressListener_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("invalid port", func(t *testing.T) {
|
|
err := (&ConsulIngressListener{
|
|
Port: 0,
|
|
Protocol: "tcp",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service1",
|
|
}},
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Ingress Listener requires valid Port")
|
|
})
|
|
|
|
t.Run("invalid protocol", func(t *testing.T) {
|
|
err := (&ConsulIngressListener{
|
|
Port: 2000,
|
|
Protocol: "gopher",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service1",
|
|
}},
|
|
}).Validate()
|
|
require.EqualError(t, err, `Consul Ingress Listener requires protocol of tcp, http, http2, grpc, got "gopher"`)
|
|
})
|
|
|
|
t.Run("no services", func(t *testing.T) {
|
|
err := (&ConsulIngressListener{
|
|
Port: 2000,
|
|
Protocol: "tcp",
|
|
Services: nil,
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Ingress Listener requires one or more services")
|
|
})
|
|
|
|
t.Run("invalid service", func(t *testing.T) {
|
|
err := (&ConsulIngressListener{
|
|
Port: 2000,
|
|
Protocol: "tcp",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "",
|
|
}},
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Ingress Service requires a name")
|
|
})
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
err := (&ConsulIngressListener{
|
|
Port: 2000,
|
|
Protocol: "tcp",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service1",
|
|
}},
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConsulIngressConfigEntry_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("no listeners", func(t *testing.T) {
|
|
err := (&ConsulIngressConfigEntry{}).Validate()
|
|
require.EqualError(t, err, "Consul Ingress Gateway requires at least one listener")
|
|
})
|
|
|
|
t.Run("invalid listener", func(t *testing.T) {
|
|
err := (&ConsulIngressConfigEntry{
|
|
Listeners: []*ConsulIngressListener{{
|
|
Port: 9000,
|
|
Protocol: "tcp",
|
|
}},
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Ingress Listener requires one or more services")
|
|
})
|
|
|
|
t.Run("full", func(t *testing.T) {
|
|
err := (&ConsulIngressConfigEntry{
|
|
TLS: &ConsulGatewayTLSConfig{
|
|
Enabled: true,
|
|
},
|
|
Listeners: []*ConsulIngressListener{{
|
|
Port: 9000,
|
|
Protocol: "tcp",
|
|
Services: []*ConsulIngressService{{
|
|
Name: "service1",
|
|
}},
|
|
}},
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConsulLinkedService_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
err := (*ConsulLinkedService)(nil).Validate()
|
|
require.Nil(t, err)
|
|
})
|
|
|
|
t.Run("missing name", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{}).Validate()
|
|
require.EqualError(t, err, "Consul Linked Service requires Name")
|
|
})
|
|
|
|
t.Run("missing ca_file", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
CertFile: "cert_file.pem",
|
|
KeyFile: "key_file.pem",
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Linked Service TLS requires CAFile")
|
|
})
|
|
|
|
t.Run("mutual cert key", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
CAFile: "ca_file.pem",
|
|
CertFile: "cert_file.pem",
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Linked Service TLS Cert and Key must both be set")
|
|
})
|
|
|
|
t.Run("sni without ca_file", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
SNI: "service.consul",
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Linked Service TLS SNI requires CAFile")
|
|
})
|
|
|
|
t.Run("minimal", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("tls minimal", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
CAFile: "ca_file.pem",
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("tls mutual", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
CAFile: "ca_file.pem",
|
|
CertFile: "cert_file.pem",
|
|
KeyFile: "key_file.pem",
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("tls sni", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
CAFile: "ca_file.pem",
|
|
SNI: "linked-service.consul",
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("tls complete", func(t *testing.T) {
|
|
err := (&ConsulLinkedService{
|
|
Name: "linked-service1",
|
|
CAFile: "ca_file.pem",
|
|
CertFile: "cert_file.pem",
|
|
KeyFile: "key_file.pem",
|
|
SNI: "linked-service.consul",
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConsulLinkedService_Copy(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require.Nil(t, (*ConsulLinkedService)(nil).Copy())
|
|
require.Equal(t, &ConsulLinkedService{
|
|
Name: "service1",
|
|
CAFile: "ca.pem",
|
|
CertFile: "cert.pem",
|
|
KeyFile: "key.pem",
|
|
SNI: "service1.consul",
|
|
}, (&ConsulLinkedService{
|
|
Name: "service1",
|
|
CAFile: "ca.pem",
|
|
CertFile: "cert.pem",
|
|
KeyFile: "key.pem",
|
|
SNI: "service1.consul",
|
|
}).Copy())
|
|
}
|
|
|
|
func TestConsulLinkedService_linkedServicesEqual(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
services := []*ConsulLinkedService{{
|
|
Name: "service1",
|
|
CAFile: "ca.pem",
|
|
}, {
|
|
Name: "service2",
|
|
CAFile: "ca.pem",
|
|
}}
|
|
|
|
require.False(t, linkedServicesEqual(services, nil))
|
|
require.True(t, linkedServicesEqual(services, services))
|
|
|
|
reversed := []*ConsulLinkedService{
|
|
services[1], services[0], // reversed
|
|
}
|
|
|
|
require.True(t, linkedServicesEqual(services, reversed))
|
|
|
|
different := []*ConsulLinkedService{
|
|
services[0], {
|
|
Name: "service2",
|
|
CAFile: "ca.pem",
|
|
SNI: "service2.consul",
|
|
},
|
|
}
|
|
|
|
require.False(t, linkedServicesEqual(services, different))
|
|
}
|
|
|
|
func TestConsulTerminatingConfigEntry_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
err := (*ConsulTerminatingConfigEntry)(nil).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("no services", func(t *testing.T) {
|
|
err := (&ConsulTerminatingConfigEntry{
|
|
Services: make([]*ConsulLinkedService, 0),
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Terminating Gateway requires at least one service")
|
|
})
|
|
|
|
t.Run("service invalid", func(t *testing.T) {
|
|
err := (&ConsulTerminatingConfigEntry{
|
|
Services: []*ConsulLinkedService{{
|
|
Name: "",
|
|
}},
|
|
}).Validate()
|
|
require.EqualError(t, err, "Consul Linked Service requires Name")
|
|
})
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
err := (&ConsulTerminatingConfigEntry{
|
|
Services: []*ConsulLinkedService{{
|
|
Name: "service1",
|
|
}},
|
|
}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestConsulMeshGateway_Copy(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require.Nil(t, (*ConsulMeshGateway)(nil))
|
|
require.Equal(t, &ConsulMeshGateway{
|
|
Mode: "remote",
|
|
}, &ConsulMeshGateway{
|
|
Mode: "remote",
|
|
})
|
|
}
|
|
|
|
func TestConsulMeshGateway_Equal(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := ConsulMeshGateway{Mode: "local"}
|
|
require.False(t, c.Equal(ConsulMeshGateway{}))
|
|
require.True(t, c.Equal(c))
|
|
|
|
o := ConsulMeshGateway{Mode: "remote"}
|
|
require.False(t, c.Equal(o))
|
|
}
|
|
|
|
func TestConsulMeshGateway_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
err := (*ConsulMeshGateway)(nil).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("mode invalid", func(t *testing.T) {
|
|
err := (&ConsulMeshGateway{Mode: "banana"}).Validate()
|
|
require.EqualError(t, err, `Connect mesh_gateway mode "banana" not supported`)
|
|
})
|
|
|
|
t.Run("ok", func(t *testing.T) {
|
|
err := (&ConsulMeshGateway{Mode: "local"}).Validate()
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestService_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
input *Service
|
|
expErr bool
|
|
expErrStr string
|
|
name string
|
|
}{
|
|
{
|
|
name: "base service",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Native Connect without task name",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
Connect: &ConsulConnect{
|
|
Native: true,
|
|
},
|
|
},
|
|
expErr: false, // gets set automatically
|
|
},
|
|
{
|
|
name: "Native Connect with task name",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
TaskName: "testtask",
|
|
Connect: &ConsulConnect{
|
|
Native: true,
|
|
},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Native Connect with Sidecar",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
TaskName: "testtask",
|
|
Connect: &ConsulConnect{
|
|
Native: true,
|
|
SidecarService: &ConsulSidecarService{},
|
|
},
|
|
},
|
|
expErr: true,
|
|
expErrStr: "Consul Connect must be exclusively native",
|
|
},
|
|
{
|
|
name: "provider nomad with checks",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
Provider: "nomad",
|
|
PortLabel: "port",
|
|
Checks: []*ServiceCheck{
|
|
{
|
|
Name: "servicecheck",
|
|
Type: "http",
|
|
Path: "/",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 3 * time.Second,
|
|
},
|
|
{
|
|
Name: "servicecheck",
|
|
Type: "tcp",
|
|
Interval: 1 * time.Second,
|
|
Timeout: 3 * time.Second,
|
|
},
|
|
},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "provider nomad with invalid check type",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
Provider: "nomad",
|
|
Checks: []*ServiceCheck{
|
|
{
|
|
Name: "servicecheck",
|
|
Type: "script",
|
|
},
|
|
},
|
|
},
|
|
expErr: true,
|
|
},
|
|
{
|
|
name: "provider nomad with tls skip verify",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
Provider: "nomad",
|
|
Checks: []*ServiceCheck{
|
|
{
|
|
Name: "servicecheck",
|
|
Type: "http",
|
|
TLSSkipVerify: true,
|
|
},
|
|
},
|
|
},
|
|
expErr: true,
|
|
},
|
|
{
|
|
name: "provider nomad with connect",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
Provider: "nomad",
|
|
Connect: &ConsulConnect{
|
|
Native: true,
|
|
},
|
|
},
|
|
expErr: true,
|
|
expErrStr: "Service with provider nomad cannot include Connect blocks",
|
|
},
|
|
{
|
|
name: "provider nomad valid",
|
|
input: &Service{
|
|
Name: "testservice",
|
|
Provider: "nomad",
|
|
},
|
|
expErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc.input.Canonicalize("testjob", "testgroup", "testtask", "testnamespace")
|
|
err := tc.input.Validate()
|
|
if tc.expErr {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tc.expErrStr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestService_Validate_Address(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
try := func(mode, advertise string, exp error) {
|
|
s := &Service{Name: "s1", Provider: "consul", AddressMode: mode, Address: advertise}
|
|
result := s.Validate()
|
|
if exp == nil {
|
|
require.NoError(t, result)
|
|
} else {
|
|
// would be nice if multierror worked with errors.Is
|
|
require.Contains(t, result.Error(), exp.Error())
|
|
}
|
|
}
|
|
|
|
// advertise not set
|
|
try("", "", nil)
|
|
try("auto", "", nil)
|
|
try("host", "", nil)
|
|
try("alloc", "", nil)
|
|
try("driver", "", nil)
|
|
|
|
// advertise is set
|
|
try("", "example.com", nil)
|
|
try("auto", "example.com", nil)
|
|
try("host", "example.com", errors.New(`Service address_mode must be "auto" if address is set`))
|
|
try("alloc", "example.com", errors.New(`Service address_mode must be "auto" if address is set`))
|
|
try("driver", "example.com", errors.New(`Service address_mode must be "auto" if address is set`))
|
|
}
|
|
|
|
func TestService_Equal(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
s := Service{
|
|
Name: "testservice",
|
|
TaggedAddresses: make(map[string]string),
|
|
}
|
|
|
|
s.Canonicalize("testjob", "testgroup", "testtask", "default")
|
|
|
|
o := s.Copy()
|
|
|
|
// Base service should be equal to copy of itself
|
|
require.True(t, s.Equal(o))
|
|
|
|
// create a helper to assert a diff and reset the struct
|
|
assertDiff := func() {
|
|
require.False(t, s.Equal(o))
|
|
o = s.Copy()
|
|
require.True(t, s.Equal(o), "bug in copy")
|
|
}
|
|
|
|
// Changing any field should cause inequality
|
|
o.Name = "diff"
|
|
assertDiff()
|
|
|
|
o.Address = "diff"
|
|
assertDiff()
|
|
|
|
o.PortLabel = "diff"
|
|
assertDiff()
|
|
|
|
o.AddressMode = AddressModeDriver
|
|
assertDiff()
|
|
|
|
o.Tags = []string{"diff"}
|
|
assertDiff()
|
|
|
|
o.CanaryTags = []string{"diff"}
|
|
assertDiff()
|
|
|
|
o.Checks = []*ServiceCheck{{Name: "diff"}}
|
|
assertDiff()
|
|
|
|
o.Connect = &ConsulConnect{Native: true}
|
|
assertDiff()
|
|
|
|
o.EnableTagOverride = true
|
|
assertDiff()
|
|
|
|
o.Provider = "nomad"
|
|
assertDiff()
|
|
|
|
o.TaggedAddresses = map[string]string{"foo": "bar"}
|
|
assertDiff()
|
|
}
|
|
|
|
func TestService_validateNomadService(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
inputService *Service
|
|
inputErr *multierror.Error
|
|
expectedOutputErrors []error
|
|
name string
|
|
}{
|
|
{
|
|
inputService: &Service{
|
|
Name: "webapp",
|
|
PortLabel: "http",
|
|
Namespace: "default",
|
|
Provider: "nomad",
|
|
},
|
|
inputErr: &multierror.Error{},
|
|
expectedOutputErrors: nil,
|
|
name: "valid service",
|
|
},
|
|
{
|
|
inputService: &Service{
|
|
Name: "webapp",
|
|
PortLabel: "http",
|
|
Namespace: "default",
|
|
Provider: "nomad",
|
|
Checks: []*ServiceCheck{{
|
|
Name: "webapp",
|
|
Type: ServiceCheckHTTP,
|
|
Method: "GET",
|
|
Path: "/health",
|
|
Interval: 3 * time.Second,
|
|
Timeout: 1 * time.Second,
|
|
}},
|
|
},
|
|
inputErr: &multierror.Error{},
|
|
expectedOutputErrors: nil,
|
|
name: "valid service with checks",
|
|
},
|
|
{
|
|
inputService: &Service{
|
|
Name: "webapp",
|
|
PortLabel: "http",
|
|
Namespace: "default",
|
|
Provider: "nomad",
|
|
Connect: &ConsulConnect{
|
|
Native: true,
|
|
},
|
|
},
|
|
inputErr: &multierror.Error{},
|
|
expectedOutputErrors: []error{errors.New("Service with provider nomad cannot include Connect blocks")},
|
|
name: "invalid service due to connect",
|
|
},
|
|
{
|
|
inputService: &Service{
|
|
Name: "webapp",
|
|
PortLabel: "http",
|
|
Namespace: "default",
|
|
Provider: "nomad",
|
|
Checks: []*ServiceCheck{
|
|
{Name: "some-check"},
|
|
},
|
|
},
|
|
inputErr: &multierror.Error{},
|
|
expectedOutputErrors: []error{
|
|
errors.New(`invalid check type (""), must be one of tcp, http`),
|
|
},
|
|
name: "bad nomad check",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc.inputService.validateNomadService(tc.inputErr)
|
|
must.Eq(t, tc.expectedOutputErrors, tc.inputErr.Errors)
|
|
})
|
|
}
|
|
}
|