0266f056b8
Enable configuration of HTTP and gRPC endpoints which should be exposed by the Connect sidecar proxy. This changeset is the first "non-magical" pass that lays the groundwork for enabling Consul service checks for tasks running in a network namespace because they are Connect-enabled. The changes here provide for full configuration of the connect { sidecar_service { proxy { expose { paths = [{ path = <exposed endpoint> protocol = <http or grpc> local_path_port = <local endpoint port> listener_port = <inbound mesh port> }, ... ] } } } stanza. Everything from `expose` and below is new, and partially implements the precedent set by Consul: https://www.consul.io/docs/connect/registration/service-registration.html#expose-paths-configuration-reference Combined with a task-group level network port-mapping in the form: port "exposeExample" { to = -1 } it is now possible to "punch a hole" through the network namespace to a specific HTTP or gRPC path, with the anticipated use case of creating Consul checks on Connect enabled services. A future PR may introduce more automagic behavior, where we can do things like 1) auto-fill the 'expose.path.local_path_port' with the default value of the 'service.port' value for task-group level connect-enabled services. 2) automatically generate a port-mapping 3) enable an 'expose.checks' flag which automatically creates exposed endpoints for every compatible consul service check (http/grpc checks on connect enabled services).
327 lines
8.6 KiB
Go
327 lines
8.6 KiB
Go
package structs
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/helper"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestService_Hash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
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]interface{}{"foo": "bar"},
|
|
Upstreams: []ConsulUpstream{{
|
|
DestinationName: "upstream1",
|
|
LocalBindPort: 29000,
|
|
}},
|
|
},
|
|
},
|
|
// 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 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 dest 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 dest local bind port", func(t *testing.T) {
|
|
try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].LocalBindPort = 29999 })
|
|
})
|
|
}
|
|
|
|
func TestConsulConnect_Validate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := &ConsulConnect{}
|
|
|
|
// An empty Connect stanza is invalid
|
|
require.Error(t, c.Validate())
|
|
|
|
// Native=true is valid
|
|
c.Native = true
|
|
require.NoError(t, c.Validate())
|
|
|
|
// Native=true + Sidecar!=nil is invalid
|
|
c.SidecarService = &ConsulSidecarService{}
|
|
require.Error(t, c.Validate())
|
|
|
|
// Native=false + Sidecar!=nil is valid
|
|
c.Native = false
|
|
require.NoError(t, c.Validate())
|
|
}
|
|
|
|
func TestConsulConnect_CopyEquals(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := &ConsulConnect{
|
|
SidecarService: &ConsulSidecarService{
|
|
Tags: []string{"tag1", "tag2"},
|
|
Port: "9001",
|
|
Proxy: &ConsulProxy{
|
|
LocalServiceAddress: "127.0.0.1",
|
|
LocalServicePort: 8080,
|
|
Upstreams: []ConsulUpstream{
|
|
{
|
|
DestinationName: "up1",
|
|
LocalBindPort: 9002,
|
|
},
|
|
{
|
|
DestinationName: "up2",
|
|
LocalBindPort: 9003,
|
|
},
|
|
},
|
|
Config: map[string]interface{}{
|
|
"foo": 1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
require.NoError(t, c.Validate())
|
|
|
|
// Copies should be equivalent
|
|
o := c.Copy()
|
|
require.True(t, c.Equals(o))
|
|
|
|
o.SidecarService.Proxy.Upstreams = nil
|
|
require.False(t, c.Equals(o))
|
|
}
|
|
|
|
func TestSidecarTask_MergeIntoTask(t *testing.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: helper.TimeToPtr(15 * time.Second),
|
|
LogConfig: &LogConfig{
|
|
MaxFiles: 3,
|
|
},
|
|
ShutdownDelay: helper.TimeToPtr(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 TestConsulUpstream_upstreamEquals(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
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)}
|
|
require.False(t, upstreamsEquals(a, b))
|
|
})
|
|
|
|
t.Run("different", func(t *testing.T) {
|
|
a := []ConsulUpstream{up("bar", 9000)}
|
|
b := []ConsulUpstream{up("foo", 8000)}
|
|
require.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)}
|
|
require.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)}
|
|
require.True(t, upstreamsEquals(a, b))
|
|
})
|
|
}
|
|
|
|
func TestConsulExposePath_exposePathsEqual(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
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) {
|
|
require.Nil(t, (*ConsulExposeConfig)(nil).Copy())
|
|
require.Equal(t, &ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}, (&ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}).Copy())
|
|
}
|
|
|
|
func TestConsulExposeConfig_Equals(t *testing.T) {
|
|
require.True(t, (*ConsulExposeConfig)(nil).Equals(nil))
|
|
require.True(t, (&ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}).Equals(&ConsulExposeConfig{
|
|
Paths: []ConsulExposePath{{
|
|
Path: "/health",
|
|
}},
|
|
}))
|
|
}
|