Merge pull request #7323 from hashicorp/f-connect-expose-checks
connect: enable proxy.expose configuration
This commit is contained in:
commit
2d73d92510
|
@ -168,8 +168,9 @@ type SidecarTask struct {
|
|||
|
||||
// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
|
||||
type ConsulProxy struct {
|
||||
LocalServiceAddress string `mapstructure:"local_service_address"`
|
||||
LocalServicePort int `mapstructure:"local_service_port"`
|
||||
LocalServiceAddress string `mapstructure:"local_service_address"`
|
||||
LocalServicePort int `mapstructure:"local_service_port"`
|
||||
ExposeConfig *ConsulExposeConfig `mapstructure:"expose"`
|
||||
Upstreams []*ConsulUpstream
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
@ -179,3 +180,15 @@ type ConsulUpstream struct {
|
|||
DestinationName string `mapstructure:"destination_name"`
|
||||
LocalBindPort int `mapstructure:"local_bind_port"`
|
||||
}
|
||||
|
||||
type ConsulExposeConfig struct {
|
||||
Path []*ConsulExposePath `mapstructure:"path"`
|
||||
// todo(shoenig): add magic for 'checks' option
|
||||
}
|
||||
|
||||
type ConsulExposePath struct {
|
||||
Path string
|
||||
Protocol string
|
||||
LocalPathPort int `mapstructure:"local_path_port"`
|
||||
ListenerPort string `mapstructure:"listener_port"`
|
||||
}
|
||||
|
|
|
@ -86,8 +86,6 @@ func newBridgeNetworkConfigurator(log hclog.Logger, bridgeName, ipRange, cniPath
|
|||
|
||||
// ensureForwardingRules ensures that a forwarding rule is added to iptables
|
||||
// to allow traffic inbound to the bridge network
|
||||
// // ensureForwardingRules ensures that a forwarding rule is added to iptables
|
||||
// to allow traffic inbound to the bridge network
|
||||
func (b *bridgeNetworkConfigurator) ensureForwardingRules() error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
|
@ -154,9 +152,9 @@ func (b *bridgeNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Al
|
|||
return err
|
||||
}
|
||||
|
||||
// Depending on the version of bridge cni plugin used, a known race could occure
|
||||
// Depending on the version of bridge cni plugin (< 0.8.4) a known race could occur
|
||||
// where two alloc attempt to create the nomad bridge at the same time, resulting
|
||||
// in one of them to fail. This rety attempts to overcome any
|
||||
// in one of them to fail. This retry attempts to overcome those erroneous failures.
|
||||
const retry = 3
|
||||
for attempt := 1; ; attempt++ {
|
||||
//TODO eventually returning the IP from the result would be nice to have in the alloc
|
||||
|
|
|
@ -587,7 +587,7 @@ func convertClientConfig(agentConfig *Config) (*clientconfig.Config, error) {
|
|||
conf.ACLTokenTTL = agentConfig.ACL.TokenTTL
|
||||
conf.ACLPolicyTTL = agentConfig.ACL.PolicyTTL
|
||||
|
||||
// Setup networking configration
|
||||
// Setup networking configuration
|
||||
conf.CNIPath = agentConfig.Client.CNIPath
|
||||
conf.BridgeNetworkName = agentConfig.Client.BridgeNetworkName
|
||||
conf.BridgeNetworkAllocSubnet = agentConfig.Client.BridgeNetworkSubnet
|
||||
|
|
|
@ -1280,7 +1280,7 @@ func MakeCheckID(serviceID string, check *structs.ServiceCheck) string {
|
|||
// createCheckReg creates a Check that can be registered with Consul.
|
||||
//
|
||||
// Script checks simply have a TTL set and the caller is responsible for
|
||||
// running the script and heartbeating.
|
||||
// running the script and heart-beating.
|
||||
func createCheckReg(serviceID, checkID string, check *structs.ServiceCheck, host string, port int) (*api.AgentCheckRegistration, error) {
|
||||
chkReg := api.AgentCheckRegistration{
|
||||
ID: checkID,
|
||||
|
@ -1313,8 +1313,8 @@ func createCheckReg(serviceID, checkID string, check *structs.ServiceCheck, host
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
url := base.ResolveReference(relative)
|
||||
chkReg.HTTP = url.String()
|
||||
checkURL := base.ResolveReference(relative)
|
||||
chkReg.HTTP = checkURL.String()
|
||||
chkReg.Method = check.Method
|
||||
chkReg.Header = check.Header
|
||||
|
||||
|
@ -1471,90 +1471,3 @@ func getAddress(addrMode, portLabel string, networks structs.Networks, driverNet
|
|||
return "", 0, fmt.Errorf("invalid address mode %q", addrMode)
|
||||
}
|
||||
}
|
||||
|
||||
// newConnect creates a new Consul AgentServiceConnect struct based on a Nomad
|
||||
// Connect struct. If the nomad Connect struct is nil, nil will be returned to
|
||||
// disable Connect for this service.
|
||||
func newConnect(serviceName string, nc *structs.ConsulConnect, networks structs.Networks) (*api.AgentServiceConnect, error) {
|
||||
if nc == nil {
|
||||
// No Connect stanza, returning nil is fine
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cc := &api.AgentServiceConnect{
|
||||
Native: nc.Native,
|
||||
}
|
||||
|
||||
if nc.SidecarService == nil {
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
net, port, err := getConnectPort(serviceName, networks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Bind to netns IP(s):port
|
||||
proxyConfig := map[string]interface{}{}
|
||||
localServiceAddress := ""
|
||||
localServicePort := 0
|
||||
if nc.SidecarService.Proxy != nil {
|
||||
localServiceAddress = nc.SidecarService.Proxy.LocalServiceAddress
|
||||
localServicePort = nc.SidecarService.Proxy.LocalServicePort
|
||||
if nc.SidecarService.Proxy.Config != nil {
|
||||
proxyConfig = nc.SidecarService.Proxy.Config
|
||||
}
|
||||
}
|
||||
proxyConfig["bind_address"] = "0.0.0.0"
|
||||
proxyConfig["bind_port"] = port.To
|
||||
|
||||
// Advertise host IP:port
|
||||
cc.SidecarService = &api.AgentServiceRegistration{
|
||||
Tags: helper.CopySliceString(nc.SidecarService.Tags),
|
||||
Address: net.IP,
|
||||
Port: port.Value,
|
||||
|
||||
// Automatically configure the proxy to bind to all addresses
|
||||
// within the netns.
|
||||
Proxy: &api.AgentServiceConnectProxyConfig{
|
||||
LocalServiceAddress: localServiceAddress,
|
||||
LocalServicePort: localServicePort,
|
||||
Config: proxyConfig,
|
||||
},
|
||||
}
|
||||
|
||||
// If no further proxy settings were explicitly configured, exit early
|
||||
if nc.SidecarService.Proxy == nil {
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
numUpstreams := len(nc.SidecarService.Proxy.Upstreams)
|
||||
if numUpstreams == 0 {
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
upstreams := make([]api.Upstream, numUpstreams)
|
||||
for i, nu := range nc.SidecarService.Proxy.Upstreams {
|
||||
upstreams[i].DestinationName = nu.DestinationName
|
||||
upstreams[i].LocalBindPort = nu.LocalBindPort
|
||||
}
|
||||
cc.SidecarService.Proxy.Upstreams = upstreams
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// getConnectPort returns the network and port for the Connect proxy sidecar
|
||||
// defined for this service. An error is returned if the network and port
|
||||
// cannot be determined.
|
||||
func getConnectPort(serviceName string, networks structs.Networks) (*structs.NetworkResource, structs.Port, error) {
|
||||
if n := len(networks); n != 1 {
|
||||
return nil, structs.Port{}, fmt.Errorf("Connect only supported with exactly 1 network (found %d)", n)
|
||||
}
|
||||
|
||||
port, ok := networks[0].PortForService(serviceName)
|
||||
if !ok {
|
||||
return nil, structs.Port{}, fmt.Errorf("No Connect port defined for service %q", serviceName)
|
||||
}
|
||||
|
||||
return networks[0], port, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package consul
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/nomad/helper"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
// newConnect creates a new Consul AgentServiceConnect struct based on a Nomad
|
||||
// Connect struct. If the nomad Connect struct is nil, nil will be returned to
|
||||
// disable Connect for this service.
|
||||
func newConnect(serviceName string, nc *structs.ConsulConnect, networks structs.Networks) (*api.AgentServiceConnect, error) {
|
||||
if nc == nil {
|
||||
// no connect stanza means there is no connect service to register
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if nc.Native {
|
||||
return &api.AgentServiceConnect{Native: true}, nil
|
||||
}
|
||||
|
||||
sidecarReg, err := connectSidecarRegistration(serviceName, nc.SidecarService, networks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.AgentServiceConnect{
|
||||
Native: false,
|
||||
SidecarService: sidecarReg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func connectSidecarRegistration(serviceName string, css *structs.ConsulSidecarService, networks structs.Networks) (*api.AgentServiceRegistration, error) {
|
||||
if css == nil {
|
||||
// no sidecar stanza means there is no sidecar service to register
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cNet, cPort, err := connectPort(serviceName, networks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy, err := connectProxy(css.Proxy, cPort.To, networks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.AgentServiceRegistration{
|
||||
Tags: helper.CopySliceString(css.Tags),
|
||||
Port: cPort.Value,
|
||||
Address: cNet.IP,
|
||||
Proxy: proxy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func connectProxy(proxy *structs.ConsulProxy, cPort int, networks structs.Networks) (*api.AgentServiceConnectProxyConfig, error) {
|
||||
if proxy == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
expose, err := connectProxyExpose(proxy.Expose, networks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.AgentServiceConnectProxyConfig{
|
||||
LocalServiceAddress: proxy.LocalServiceAddress,
|
||||
LocalServicePort: proxy.LocalServicePort,
|
||||
Config: connectProxyConfig(proxy.Config, cPort),
|
||||
Upstreams: connectUpstreams(proxy.Upstreams),
|
||||
Expose: expose,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func connectProxyExpose(expose *structs.ConsulExposeConfig, networks structs.Networks) (api.ExposeConfig, error) {
|
||||
if expose == nil {
|
||||
return api.ExposeConfig{}, nil
|
||||
}
|
||||
|
||||
paths, err := connectProxyExposePaths(expose.Paths, networks)
|
||||
if err != nil {
|
||||
return api.ExposeConfig{}, err
|
||||
}
|
||||
|
||||
return api.ExposeConfig{
|
||||
Checks: false,
|
||||
Paths: paths,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func connectProxyExposePaths(in []structs.ConsulExposePath, networks structs.Networks) ([]api.ExposePath, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
paths := make([]api.ExposePath, len(in))
|
||||
for i, path := range in {
|
||||
if _, exposedPort, err := connectExposePathPort(path.ListenerPort, networks); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
paths[i] = api.ExposePath{
|
||||
ListenerPort: exposedPort,
|
||||
Path: path.Path,
|
||||
LocalPathPort: path.LocalPathPort,
|
||||
Protocol: path.Protocol,
|
||||
ParsedFromCheck: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func connectUpstreams(in []structs.ConsulUpstream) []api.Upstream {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
upstreams := make([]api.Upstream, len(in))
|
||||
for i, upstream := range in {
|
||||
upstreams[i] = api.Upstream{
|
||||
DestinationName: upstream.DestinationName,
|
||||
LocalBindPort: upstream.LocalBindPort,
|
||||
}
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
|
||||
func connectProxyConfig(cfg map[string]interface{}, port int) map[string]interface{} {
|
||||
if cfg == nil {
|
||||
cfg = make(map[string]interface{})
|
||||
}
|
||||
cfg["bind_address"] = "0.0.0.0"
|
||||
cfg["bind_port"] = port
|
||||
return cfg
|
||||
}
|
||||
|
||||
func connectNetworkInvariants(networks structs.Networks) error {
|
||||
if n := len(networks); n != 1 {
|
||||
return fmt.Errorf("Connect only supported with exactly 1 network (found %d)", n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// connectPort returns the network and port for the Connect proxy sidecar
|
||||
// defined for this service. An error is returned if the network and port
|
||||
// cannot be determined.
|
||||
func connectPort(serviceName string, networks structs.Networks) (*structs.NetworkResource, structs.Port, error) {
|
||||
if err := connectNetworkInvariants(networks); err != nil {
|
||||
return nil, structs.Port{}, err
|
||||
}
|
||||
|
||||
port, ok := networks[0].PortForService(serviceName)
|
||||
if !ok {
|
||||
return nil, structs.Port{}, fmt.Errorf("No Connect port defined for service %q", serviceName)
|
||||
}
|
||||
|
||||
return networks[0], port, nil
|
||||
}
|
||||
|
||||
// connectExposePathPort returns the port for the exposed path for the exposed
|
||||
// proxy path.
|
||||
func connectExposePathPort(portLabel string, networks structs.Networks) (string, int, error) {
|
||||
if err := connectNetworkInvariants(networks); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
ip, port := networks.Port(portLabel)
|
||||
if port == 0 {
|
||||
return "", 0, fmt.Errorf("No port of label %q defined", portLabel)
|
||||
}
|
||||
|
||||
return ip, port, nil
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
package consul
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
testConnectNetwork = structs.Networks{{
|
||||
Mode: "bridge",
|
||||
Device: "eth0",
|
||||
IP: "192.168.30.1",
|
||||
DynamicPorts: []structs.Port{
|
||||
{Label: "healthPort", Value: 23100, To: 23100},
|
||||
{Label: "metricsPort", Value: 23200, To: 23200},
|
||||
{Label: "connect-proxy-redis", Value: 3000, To: 3000},
|
||||
},
|
||||
}}
|
||||
)
|
||||
|
||||
func TestConnect_newConnect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
asr, err := newConnect("", nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, asr)
|
||||
})
|
||||
|
||||
t.Run("native", func(t *testing.T) {
|
||||
asr, err := newConnect("", &structs.ConsulConnect{
|
||||
Native: true,
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, asr.Native)
|
||||
require.Nil(t, asr.SidecarService)
|
||||
})
|
||||
|
||||
t.Run("with sidecar", func(t *testing.T) {
|
||||
asr, err := newConnect("redis", &structs.ConsulConnect{
|
||||
Native: false,
|
||||
SidecarService: &structs.ConsulSidecarService{
|
||||
Tags: []string{"foo", "bar"},
|
||||
Port: "sidecarPort",
|
||||
},
|
||||
}, testConnectNetwork)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &api.AgentServiceRegistration{
|
||||
Tags: []string{"foo", "bar"},
|
||||
Port: 3000,
|
||||
Address: "192.168.30.1",
|
||||
}, asr.SidecarService)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_connectSidecarRegistration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
sidecarReg, err := connectSidecarRegistration("", nil, testConnectNetwork)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, sidecarReg)
|
||||
})
|
||||
|
||||
t.Run("no service port", func(t *testing.T) {
|
||||
_, err := connectSidecarRegistration("unknown", &structs.ConsulSidecarService{
|
||||
// irrelevant
|
||||
}, testConnectNetwork)
|
||||
require.EqualError(t, err, `No Connect port defined for service "unknown"`)
|
||||
})
|
||||
|
||||
t.Run("bad proxy", func(t *testing.T) {
|
||||
_, err := connectSidecarRegistration("redis", &structs.ConsulSidecarService{
|
||||
Proxy: &structs.ConsulProxy{
|
||||
Expose: &structs.ConsulExposeConfig{
|
||||
Paths: []structs.ConsulExposePath{{
|
||||
ListenerPort: "badPort",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, testConnectNetwork)
|
||||
require.EqualError(t, err, `No port of label "badPort" defined`)
|
||||
})
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
proxy, err := connectSidecarRegistration("redis", &structs.ConsulSidecarService{
|
||||
Tags: []string{"foo", "bar"},
|
||||
Port: "sidecarPort",
|
||||
}, testConnectNetwork)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &api.AgentServiceRegistration{
|
||||
Tags: []string{"foo", "bar"},
|
||||
Port: 3000,
|
||||
Address: "192.168.30.1",
|
||||
}, proxy)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_connectProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
proxy, err := connectProxy(nil, 2000, nil)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, proxy)
|
||||
})
|
||||
|
||||
t.Run("bad proxy", func(t *testing.T) {
|
||||
_, err := connectProxy(&structs.ConsulProxy{
|
||||
LocalServiceAddress: "0.0.0.0",
|
||||
LocalServicePort: 2000,
|
||||
Upstreams: nil,
|
||||
Expose: &structs.ConsulExposeConfig{
|
||||
Paths: []structs.ConsulExposePath{{
|
||||
ListenerPort: "badPort",
|
||||
}},
|
||||
},
|
||||
Config: nil,
|
||||
}, 2000, testConnectNetwork)
|
||||
require.EqualError(t, err, `No port of label "badPort" defined`)
|
||||
})
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
proxy, err := connectProxy(&structs.ConsulProxy{
|
||||
LocalServiceAddress: "0.0.0.0",
|
||||
LocalServicePort: 2000,
|
||||
Upstreams: nil,
|
||||
Expose: &structs.ConsulExposeConfig{
|
||||
Paths: []structs.ConsulExposePath{{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 8000,
|
||||
ListenerPort: "healthPort",
|
||||
}},
|
||||
},
|
||||
Config: nil,
|
||||
}, 2000, testConnectNetwork)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &api.AgentServiceConnectProxyConfig{
|
||||
LocalServiceAddress: "0.0.0.0",
|
||||
LocalServicePort: 2000,
|
||||
Upstreams: nil,
|
||||
Expose: api.ExposeConfig{
|
||||
Paths: []api.ExposePath{{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 8000,
|
||||
ListenerPort: 23100,
|
||||
}},
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"bind_address": "0.0.0.0",
|
||||
"bind_port": 2000,
|
||||
},
|
||||
}, proxy)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_connectProxyExpose(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
exposeConfig, err := connectProxyExpose(nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, api.ExposeConfig{}, exposeConfig)
|
||||
})
|
||||
|
||||
t.Run("bad port", func(t *testing.T) {
|
||||
_, err := connectProxyExpose(&structs.ConsulExposeConfig{
|
||||
Paths: []structs.ConsulExposePath{{
|
||||
ListenerPort: "badPort",
|
||||
}},
|
||||
}, testConnectNetwork)
|
||||
require.EqualError(t, err, `No port of label "badPort" defined`)
|
||||
})
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
expose, err := connectProxyExpose(&structs.ConsulExposeConfig{
|
||||
Paths: []structs.ConsulExposePath{{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 8000,
|
||||
ListenerPort: "healthPort",
|
||||
}},
|
||||
}, testConnectNetwork)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, api.ExposeConfig{
|
||||
Checks: false,
|
||||
Paths: []api.ExposePath{{
|
||||
Path: "/health",
|
||||
ListenerPort: 23100,
|
||||
LocalPathPort: 8000,
|
||||
Protocol: "http",
|
||||
ParsedFromCheck: false,
|
||||
}},
|
||||
}, expose)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_connectProxyExposePaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
upstreams, err := connectProxyExposePaths(nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, upstreams)
|
||||
})
|
||||
|
||||
t.Run("no network", func(t *testing.T) {
|
||||
original := []structs.ConsulExposePath{{Path: "/path"}}
|
||||
_, err := connectProxyExposePaths(original, nil)
|
||||
require.EqualError(t, err, `Connect only supported with exactly 1 network (found 0)`)
|
||||
})
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
original := []structs.ConsulExposePath{{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 8000,
|
||||
ListenerPort: "healthPort",
|
||||
}, {
|
||||
Path: "/metrics",
|
||||
Protocol: "grpc",
|
||||
LocalPathPort: 9500,
|
||||
ListenerPort: "metricsPort",
|
||||
}}
|
||||
exposePaths, err := connectProxyExposePaths(original, testConnectNetwork)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []api.ExposePath{
|
||||
{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 8000,
|
||||
ListenerPort: 23100,
|
||||
ParsedFromCheck: false,
|
||||
},
|
||||
{
|
||||
Path: "/metrics",
|
||||
Protocol: "grpc",
|
||||
LocalPathPort: 9500,
|
||||
ListenerPort: 23200,
|
||||
ParsedFromCheck: false,
|
||||
},
|
||||
}, exposePaths)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_connectUpstreams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
require.Nil(t, connectUpstreams(nil))
|
||||
})
|
||||
|
||||
t.Run("not empty", func(t *testing.T) {
|
||||
require.Equal(t,
|
||||
[]api.Upstream{{
|
||||
DestinationName: "foo",
|
||||
LocalBindPort: 8000,
|
||||
}, {
|
||||
DestinationName: "bar",
|
||||
LocalBindPort: 9000,
|
||||
}},
|
||||
connectUpstreams([]structs.ConsulUpstream{{
|
||||
DestinationName: "foo",
|
||||
LocalBindPort: 8000,
|
||||
}, {
|
||||
DestinationName: "bar",
|
||||
LocalBindPort: 9000,
|
||||
}}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_connectProxyConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil map", func(t *testing.T) {
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"bind_address": "0.0.0.0",
|
||||
"bind_port": 42,
|
||||
}, connectProxyConfig(nil, 42))
|
||||
})
|
||||
|
||||
t.Run("pre-existing map", func(t *testing.T) {
|
||||
require.Equal(t, map[string]interface{}{
|
||||
"bind_address": "0.0.0.0",
|
||||
"bind_port": 42,
|
||||
"foo": "bar",
|
||||
}, connectProxyConfig(map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}, 42))
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_getConnectPort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
networks := structs.Networks{{
|
||||
IP: "192.168.30.1",
|
||||
DynamicPorts: []structs.Port{{
|
||||
Label: "connect-proxy-foo",
|
||||
Value: 23456,
|
||||
To: 23456,
|
||||
}}}}
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
nr, port, err := connectPort("foo", networks)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, structs.Port{
|
||||
Label: "connect-proxy-foo",
|
||||
Value: 23456,
|
||||
To: 23456,
|
||||
}, port)
|
||||
require.Equal(t, "192.168.30.1", nr.IP)
|
||||
})
|
||||
|
||||
t.Run("no such service", func(t *testing.T) {
|
||||
_, _, err := connectPort("other", networks)
|
||||
require.EqualError(t, err, `No Connect port defined for service "other"`)
|
||||
})
|
||||
|
||||
t.Run("no network", func(t *testing.T) {
|
||||
_, _, err := connectPort("foo", nil)
|
||||
require.EqualError(t, err, "Connect only supported with exactly 1 network (found 0)")
|
||||
})
|
||||
|
||||
t.Run("multi network", func(t *testing.T) {
|
||||
_, _, err := connectPort("foo", append(networks, &structs.NetworkResource{
|
||||
Device: "eth1",
|
||||
IP: "10.0.10.0",
|
||||
}))
|
||||
require.EqualError(t, err, "Connect only supported with exactly 1 network (found 2)")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnect_getExposePathPort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
networks := structs.Networks{{
|
||||
Device: "eth0",
|
||||
IP: "192.168.30.1",
|
||||
DynamicPorts: []structs.Port{{
|
||||
Label: "myPort",
|
||||
Value: 23456,
|
||||
To: 23456,
|
||||
}}}}
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
ip, port, err := connectExposePathPort("myPort", networks)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ip, "192.168.30.1")
|
||||
require.Equal(t, 23456, port)
|
||||
})
|
||||
|
||||
t.Run("no such port label", func(t *testing.T) {
|
||||
_, _, err := connectExposePathPort("otherPort", networks)
|
||||
require.EqualError(t, err, `No port of label "otherPort" defined`)
|
||||
})
|
||||
|
||||
t.Run("no network", func(t *testing.T) {
|
||||
_, _, err := connectExposePathPort("myPort", nil)
|
||||
require.EqualError(t, err, "Connect only supported with exactly 1 network (found 0)")
|
||||
})
|
||||
|
||||
t.Run("multi network", func(t *testing.T) {
|
||||
_, _, err := connectExposePathPort("myPort", append(networks, &structs.NetworkResource{
|
||||
Device: "eth1",
|
||||
IP: "10.0.10.0",
|
||||
}))
|
||||
require.EqualError(t, err, "Connect only supported with exactly 1 network (found 2)")
|
||||
})
|
||||
}
|
|
@ -73,22 +73,22 @@ func BuildAllocServices(node *structs.Node, alloc *structs.Allocation, restarter
|
|||
}
|
||||
|
||||
// Copy method for easing tests
|
||||
func (t *WorkloadServices) Copy() *WorkloadServices {
|
||||
func (ws *WorkloadServices) Copy() *WorkloadServices {
|
||||
newTS := new(WorkloadServices)
|
||||
*newTS = *t
|
||||
*newTS = *ws
|
||||
|
||||
// Deep copy Services
|
||||
newTS.Services = make([]*structs.Service, len(t.Services))
|
||||
for i := range t.Services {
|
||||
newTS.Services[i] = t.Services[i].Copy()
|
||||
newTS.Services = make([]*structs.Service, len(ws.Services))
|
||||
for i := range ws.Services {
|
||||
newTS.Services[i] = ws.Services[i].Copy()
|
||||
}
|
||||
return newTS
|
||||
}
|
||||
|
||||
func (w *WorkloadServices) Name() string {
|
||||
if w.Task != "" {
|
||||
return w.Task
|
||||
func (ws *WorkloadServices) Name() string {
|
||||
if ws.Task != "" {
|
||||
return ws.Task
|
||||
}
|
||||
|
||||
return "group-" + w.Group
|
||||
return "group-" + ws.Group
|
||||
}
|
||||
|
|
|
@ -1185,67 +1185,110 @@ func ApiConsulConnectToStructs(in *api.ConsulConnect) *structs.ConsulConnect {
|
|||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := &structs.ConsulConnect{
|
||||
Native: in.Native,
|
||||
return &structs.ConsulConnect{
|
||||
Native: in.Native,
|
||||
SidecarService: apiConnectSidecarServiceToStructs(in.SidecarService),
|
||||
SidecarTask: apiConnectSidecarTaskToStructs(in.SidecarTask),
|
||||
}
|
||||
}
|
||||
|
||||
if in.SidecarService != nil {
|
||||
func apiConnectSidecarServiceToStructs(in *api.ConsulSidecarService) *structs.ConsulSidecarService {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
return &structs.ConsulSidecarService{
|
||||
Port: in.Port,
|
||||
Tags: helper.CopySliceString(in.Tags),
|
||||
Proxy: apiConnectSidecarServiceProxyToStructs(in.Proxy),
|
||||
}
|
||||
}
|
||||
|
||||
out.SidecarService = &structs.ConsulSidecarService{
|
||||
Tags: helper.CopySliceString(in.SidecarService.Tags),
|
||||
Port: in.SidecarService.Port,
|
||||
}
|
||||
func apiConnectSidecarServiceProxyToStructs(in *api.ConsulProxy) *structs.ConsulProxy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
return &structs.ConsulProxy{
|
||||
LocalServiceAddress: in.LocalServiceAddress,
|
||||
LocalServicePort: in.LocalServicePort,
|
||||
Upstreams: apiUpstreamsToStructs(in.Upstreams),
|
||||
Expose: apiConsulExposeConfigToStructs(in.ExposeConfig),
|
||||
Config: in.Config,
|
||||
}
|
||||
}
|
||||
|
||||
if in.SidecarService.Proxy != nil {
|
||||
|
||||
out.SidecarService.Proxy = &structs.ConsulProxy{
|
||||
LocalServiceAddress: in.SidecarService.Proxy.LocalServiceAddress,
|
||||
LocalServicePort: in.SidecarService.Proxy.LocalServicePort,
|
||||
Config: in.SidecarService.Proxy.Config,
|
||||
}
|
||||
|
||||
upstreams := make([]structs.ConsulUpstream, len(in.SidecarService.Proxy.Upstreams))
|
||||
for i, p := range in.SidecarService.Proxy.Upstreams {
|
||||
upstreams[i] = structs.ConsulUpstream{
|
||||
DestinationName: p.DestinationName,
|
||||
LocalBindPort: p.LocalBindPort,
|
||||
}
|
||||
}
|
||||
|
||||
out.SidecarService.Proxy.Upstreams = upstreams
|
||||
func apiUpstreamsToStructs(in []*api.ConsulUpstream) []structs.ConsulUpstream {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
upstreams := make([]structs.ConsulUpstream, len(in))
|
||||
for i, upstream := range in {
|
||||
upstreams[i] = structs.ConsulUpstream{
|
||||
DestinationName: upstream.DestinationName,
|
||||
LocalBindPort: upstream.LocalBindPort,
|
||||
}
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
|
||||
if in.SidecarTask != nil {
|
||||
out.SidecarTask = &structs.SidecarTask{
|
||||
Name: in.SidecarTask.Name,
|
||||
Driver: in.SidecarTask.Driver,
|
||||
Config: in.SidecarTask.Config,
|
||||
User: in.SidecarTask.User,
|
||||
Env: in.SidecarTask.Env,
|
||||
Resources: ApiResourcesToStructs(in.SidecarTask.Resources),
|
||||
Meta: in.SidecarTask.Meta,
|
||||
LogConfig: &structs.LogConfig{},
|
||||
ShutdownDelay: in.SidecarTask.ShutdownDelay,
|
||||
KillSignal: in.SidecarTask.KillSignal,
|
||||
}
|
||||
func apiConsulExposeConfigToStructs(in *api.ConsulExposeConfig) *structs.ConsulExposeConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
return &structs.ConsulExposeConfig{
|
||||
Paths: apiConsulExposePathsToStructs(in.Path),
|
||||
}
|
||||
}
|
||||
|
||||
if in.SidecarTask.KillTimeout != nil {
|
||||
out.SidecarTask.KillTimeout = in.SidecarTask.KillTimeout
|
||||
}
|
||||
if in.SidecarTask.LogConfig != nil {
|
||||
out.SidecarTask.LogConfig = &structs.LogConfig{}
|
||||
if in.SidecarTask.LogConfig.MaxFiles != nil {
|
||||
out.SidecarTask.LogConfig.MaxFiles = *in.SidecarTask.LogConfig.MaxFiles
|
||||
}
|
||||
if in.SidecarTask.LogConfig.MaxFileSizeMB != nil {
|
||||
out.SidecarTask.LogConfig.MaxFileSizeMB = *in.SidecarTask.LogConfig.MaxFileSizeMB
|
||||
}
|
||||
func apiConsulExposePathsToStructs(in []*api.ConsulExposePath) []structs.ConsulExposePath {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
paths := make([]structs.ConsulExposePath, len(in))
|
||||
for i, path := range in {
|
||||
paths[i] = structs.ConsulExposePath{
|
||||
Path: path.Path,
|
||||
Protocol: path.Protocol,
|
||||
LocalPathPort: path.LocalPathPort,
|
||||
ListenerPort: path.ListenerPort,
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
return out
|
||||
func apiConnectSidecarTaskToStructs(in *api.SidecarTask) *structs.SidecarTask {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
return &structs.SidecarTask{
|
||||
Name: in.Name,
|
||||
Driver: in.Driver,
|
||||
User: in.User,
|
||||
Config: in.Config,
|
||||
Env: in.Env,
|
||||
Resources: ApiResourcesToStructs(in.Resources),
|
||||
Meta: in.Meta,
|
||||
ShutdownDelay: in.ShutdownDelay,
|
||||
KillSignal: in.KillSignal,
|
||||
KillTimeout: in.KillTimeout,
|
||||
LogConfig: apiLogConfigToStructs(in.LogConfig),
|
||||
}
|
||||
}
|
||||
|
||||
func apiLogConfigToStructs(in *api.LogConfig) *structs.LogConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
return &structs.LogConfig{
|
||||
MaxFiles: dereferenceInt(in.MaxFiles),
|
||||
MaxFileSizeMB: dereferenceInt(in.MaxFileSizeMB),
|
||||
}
|
||||
}
|
||||
|
||||
func dereferenceInt(in *int) int {
|
||||
if in == nil {
|
||||
return 0
|
||||
}
|
||||
return *in
|
||||
}
|
||||
|
||||
func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint {
|
||||
|
|
|
@ -2542,3 +2542,169 @@ func TestHTTP_JobValidate_SystemMigrate(t *testing.T) {
|
|||
require.Contains(t, resp.Error, `Job type "system" does not allow migrate block`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConversion_dereferenceInt(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, 0, dereferenceInt(nil))
|
||||
require.Equal(t, 42, dereferenceInt(helper.IntToPtr(42)))
|
||||
}
|
||||
|
||||
func TestConversion_apiLogConfigToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, apiLogConfigToStructs(nil))
|
||||
require.Equal(t, &structs.LogConfig{
|
||||
MaxFiles: 2,
|
||||
MaxFileSizeMB: 8,
|
||||
}, apiLogConfigToStructs(&api.LogConfig{
|
||||
MaxFiles: helper.IntToPtr(2),
|
||||
MaxFileSizeMB: helper.IntToPtr(8),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestConversion_apiConnectSidecarTaskToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, apiConnectSidecarTaskToStructs(nil))
|
||||
delay := time.Duration(200)
|
||||
timeout := time.Duration(1000)
|
||||
config := make(map[string]interface{})
|
||||
env := make(map[string]string)
|
||||
meta := make(map[string]string)
|
||||
require.Equal(t, &structs.SidecarTask{
|
||||
Name: "name",
|
||||
Driver: "driver",
|
||||
User: "user",
|
||||
Config: config,
|
||||
Env: env,
|
||||
Resources: &structs.Resources{
|
||||
CPU: 1,
|
||||
MemoryMB: 128,
|
||||
},
|
||||
Meta: meta,
|
||||
KillTimeout: &timeout,
|
||||
LogConfig: &structs.LogConfig{
|
||||
MaxFiles: 2,
|
||||
MaxFileSizeMB: 8,
|
||||
},
|
||||
ShutdownDelay: &delay,
|
||||
KillSignal: "SIGTERM",
|
||||
}, apiConnectSidecarTaskToStructs(&api.SidecarTask{
|
||||
Name: "name",
|
||||
Driver: "driver",
|
||||
User: "user",
|
||||
Config: config,
|
||||
Env: env,
|
||||
Resources: &api.Resources{
|
||||
CPU: helper.IntToPtr(1),
|
||||
MemoryMB: helper.IntToPtr(128),
|
||||
},
|
||||
Meta: meta,
|
||||
KillTimeout: &timeout,
|
||||
LogConfig: &api.LogConfig{
|
||||
MaxFiles: helper.IntToPtr(2),
|
||||
MaxFileSizeMB: helper.IntToPtr(8),
|
||||
},
|
||||
ShutdownDelay: &delay,
|
||||
KillSignal: "SIGTERM",
|
||||
}))
|
||||
}
|
||||
|
||||
func TestConversion_apiConsulExposePathsToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, apiConsulExposePathsToStructs(nil))
|
||||
require.Nil(t, apiConsulExposePathsToStructs(make([]*api.ConsulExposePath, 0)))
|
||||
require.Equal(t, []structs.ConsulExposePath{{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 8080,
|
||||
ListenerPort: "hcPort",
|
||||
}}, apiConsulExposePathsToStructs([]*api.ConsulExposePath{{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 8080,
|
||||
ListenerPort: "hcPort",
|
||||
}}))
|
||||
}
|
||||
|
||||
func TestConversion_apiConsulExposeConfigToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, apiConsulExposeConfigToStructs(nil))
|
||||
require.Equal(t, &structs.ConsulExposeConfig{
|
||||
Paths: []structs.ConsulExposePath{{Path: "/health"}},
|
||||
}, apiConsulExposeConfigToStructs(&api.ConsulExposeConfig{
|
||||
Path: []*api.ConsulExposePath{{Path: "/health"}},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestConversion_apiUpstreamsToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, apiUpstreamsToStructs(nil))
|
||||
require.Nil(t, apiUpstreamsToStructs(make([]*api.ConsulUpstream, 0)))
|
||||
require.Equal(t, []structs.ConsulUpstream{{
|
||||
DestinationName: "upstream",
|
||||
LocalBindPort: 8000,
|
||||
}}, apiUpstreamsToStructs([]*api.ConsulUpstream{{
|
||||
DestinationName: "upstream",
|
||||
LocalBindPort: 8000,
|
||||
}}))
|
||||
}
|
||||
|
||||
func TestConversion_apiConnectSidecarServiceProxyToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, apiConnectSidecarServiceProxyToStructs(nil))
|
||||
config := make(map[string]interface{})
|
||||
require.Equal(t, &structs.ConsulProxy{
|
||||
LocalServiceAddress: "192.168.30.1",
|
||||
LocalServicePort: 9000,
|
||||
Config: config,
|
||||
Upstreams: []structs.ConsulUpstream{{
|
||||
DestinationName: "upstream",
|
||||
}},
|
||||
Expose: &structs.ConsulExposeConfig{
|
||||
Paths: []structs.ConsulExposePath{{Path: "/health"}},
|
||||
},
|
||||
}, apiConnectSidecarServiceProxyToStructs(&api.ConsulProxy{
|
||||
LocalServiceAddress: "192.168.30.1",
|
||||
LocalServicePort: 9000,
|
||||
Config: config,
|
||||
Upstreams: []*api.ConsulUpstream{{
|
||||
DestinationName: "upstream",
|
||||
}},
|
||||
ExposeConfig: &api.ConsulExposeConfig{
|
||||
Path: []*api.ConsulExposePath{{
|
||||
Path: "/health",
|
||||
}},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestConversion_apiConnectSidecarServiceToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, apiConnectSidecarTaskToStructs(nil))
|
||||
require.Equal(t, &structs.ConsulSidecarService{
|
||||
Tags: []string{"foo"},
|
||||
Port: "myPort",
|
||||
Proxy: &structs.ConsulProxy{
|
||||
LocalServiceAddress: "192.168.30.1",
|
||||
},
|
||||
}, apiConnectSidecarServiceToStructs(&api.ConsulSidecarService{
|
||||
Tags: []string{"foo"},
|
||||
Port: "myPort",
|
||||
Proxy: &api.ConsulProxy{
|
||||
LocalServiceAddress: "192.168.30.1",
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestConversion_ApiConsulConnectToStructs(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, ApiConsulConnectToStructs(nil))
|
||||
require.Equal(t, &structs.ConsulConnect{
|
||||
Native: false,
|
||||
SidecarService: &structs.ConsulSidecarService{Port: "myPort"},
|
||||
SidecarTask: &structs.SidecarTask{Name: "task"},
|
||||
}, ApiConsulConnectToStructs(&api.ConsulConnect{
|
||||
Native: false,
|
||||
SidecarService: &api.ConsulSidecarService{Port: "myPort"},
|
||||
SidecarTask: &api.SidecarTask{Name: "task"},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -306,7 +306,7 @@ func parseSidecarTask(item *ast.ObjectItem) (*api.SidecarTask, error) {
|
|||
KillSignal: task.KillSignal,
|
||||
}
|
||||
|
||||
// Parse ShutdownDelay seperatly to get pointer
|
||||
// Parse ShutdownDelay separately to get pointer
|
||||
var m map[string]interface{}
|
||||
if err := hcl.DecodeObject(&m, item.Val); err != nil {
|
||||
return nil, err
|
||||
|
@ -336,6 +336,7 @@ func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) {
|
|||
"local_service_address",
|
||||
"local_service_port",
|
||||
"upstreams",
|
||||
"expose",
|
||||
"config",
|
||||
}
|
||||
|
||||
|
@ -353,15 +354,27 @@ func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) {
|
|||
}
|
||||
|
||||
// Parse the proxy
|
||||
uo := listVal.Filter("upstreams")
|
||||
proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items))
|
||||
for i := range uo.Items {
|
||||
u, err := parseUpstream(uo.Items[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy.Upstreams[i] = u
|
||||
uo := listVal.Filter("upstreams")
|
||||
if len(uo.Items) > 0 {
|
||||
proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items))
|
||||
for i := range uo.Items {
|
||||
u, err := parseUpstream(uo.Items[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxy.Upstreams[i] = u
|
||||
}
|
||||
}
|
||||
|
||||
if eo := listVal.Filter("expose"); len(eo.Items) > 1 {
|
||||
return nil, fmt.Errorf("only 1 expose object supported")
|
||||
} else if len(eo.Items) == 1 {
|
||||
if e, err := parseExpose(eo.Items[0]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
proxy.ExposeConfig = e
|
||||
}
|
||||
}
|
||||
|
||||
// If we have config, then parse that
|
||||
|
@ -389,6 +402,74 @@ func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) {
|
|||
return &proxy, nil
|
||||
}
|
||||
|
||||
func parseExpose(eo *ast.ObjectItem) (*api.ConsulExposeConfig, error) {
|
||||
valid := []string{
|
||||
"path", // an array of path blocks
|
||||
// todo(shoenig) checks boolean
|
||||
}
|
||||
|
||||
if err := helper.CheckHCLKeys(eo.Val, valid); err != nil {
|
||||
return nil, multierror.Prefix(err, "expose ->")
|
||||
}
|
||||
|
||||
var expose api.ConsulExposeConfig
|
||||
|
||||
var listVal *ast.ObjectList
|
||||
if eoType, ok := eo.Val.(*ast.ObjectType); ok {
|
||||
listVal = eoType.List
|
||||
} else {
|
||||
return nil, fmt.Errorf("expose: should be an object")
|
||||
}
|
||||
|
||||
// Parse the expose block
|
||||
|
||||
po := listVal.Filter("path") // array
|
||||
if len(po.Items) > 0 {
|
||||
expose.Path = make([]*api.ConsulExposePath, len(po.Items))
|
||||
for i := range po.Items {
|
||||
p, err := parseExposePath(po.Items[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expose.Path[i] = p
|
||||
}
|
||||
}
|
||||
|
||||
return &expose, nil
|
||||
}
|
||||
|
||||
func parseExposePath(epo *ast.ObjectItem) (*api.ConsulExposePath, error) {
|
||||
valid := []string{
|
||||
"path",
|
||||
"protocol",
|
||||
"local_path_port",
|
||||
"listener_port",
|
||||
}
|
||||
|
||||
if err := helper.CheckHCLKeys(epo.Val, valid); err != nil {
|
||||
return nil, multierror.Prefix(err, "path ->")
|
||||
}
|
||||
|
||||
var path api.ConsulExposePath
|
||||
var m map[string]interface{}
|
||||
if err := hcl.DecodeObject(&m, epo.Val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Result: &path,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dec.Decode(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &path, nil
|
||||
}
|
||||
|
||||
func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) {
|
||||
valid := []string{
|
||||
"destination_name",
|
||||
|
@ -420,6 +501,7 @@ func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) {
|
|||
|
||||
return &upstream, nil
|
||||
}
|
||||
|
||||
func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error {
|
||||
service.Checks = make([]api.ServiceCheck, len(checkObjs.Items))
|
||||
for idx, co := range checkObjs.Items {
|
||||
|
|
|
@ -1117,6 +1117,39 @@ func TestParse(t *testing.T) {
|
|||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tg-service-proxy-expose.hcl",
|
||||
&api.Job{
|
||||
ID: helper.StringToPtr("group_service_proxy_expose"),
|
||||
Name: helper.StringToPtr("group_service_proxy_expose"),
|
||||
TaskGroups: []*api.TaskGroup{{
|
||||
Name: helper.StringToPtr("group"),
|
||||
Services: []*api.Service{{
|
||||
Name: "example",
|
||||
Connect: &api.ConsulConnect{
|
||||
SidecarService: &api.ConsulSidecarService{
|
||||
Proxy: &api.ConsulProxy{
|
||||
ExposeConfig: &api.ConsulExposeConfig{
|
||||
Path: []*api.ConsulExposePath{{
|
||||
Path: "/health",
|
||||
Protocol: "http",
|
||||
LocalPathPort: 2222,
|
||||
ListenerPort: "healthcheck",
|
||||
}, {
|
||||
Path: "/metrics",
|
||||
Protocol: "grpc",
|
||||
LocalPathPort: 3000,
|
||||
ListenerPort: "metrics",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tg-service-enable-tag-override.hcl",
|
||||
&api.Job{
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
job "group_service_proxy_expose" {
|
||||
group "group" {
|
||||
service {
|
||||
name = "example"
|
||||
connect {
|
||||
sidecar_service {
|
||||
proxy {
|
||||
expose {
|
||||
path = {
|
||||
path = "/health"
|
||||
protocol = "http"
|
||||
local_path_port = 2222
|
||||
listener_port = "healthcheck"
|
||||
}
|
||||
|
||||
path = {
|
||||
path = "/metrics"
|
||||
protocol = "grpc"
|
||||
local_path_port = 3000
|
||||
listener_port = "metrics"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,9 +30,9 @@ var (
|
|||
}
|
||||
|
||||
// connectVersionConstraint is used when building the sidecar task to ensure
|
||||
// the proper Consul version is used that supports the nessicary Connect
|
||||
// features. This includes bootstraping envoy with a unix socket for Consul's
|
||||
// grpc xDS api.
|
||||
// the proper Consul version is used that supports the necessary Connect
|
||||
// features. This includes bootstrapping envoy with a unix socket for Consul's
|
||||
// gRPC xDS API.
|
||||
connectVersionConstraint = func() *structs.Constraint {
|
||||
return &structs.Constraint{
|
||||
LTarget: "${attr.consul.version}",
|
||||
|
@ -97,6 +97,8 @@ func isSidecarForService(t *structs.Task, svc string) bool {
|
|||
return t.Kind == structs.TaskKind(fmt.Sprintf("%s:%s", structs.ConnectProxyPrefix, svc))
|
||||
}
|
||||
|
||||
// probably need to hack this up to look for checks on the service, and if they
|
||||
// qualify, configure a port for envoy to use to expose their paths.
|
||||
func groupConnectHook(job *structs.Job, g *structs.TaskGroup) error {
|
||||
for _, service := range g.Services {
|
||||
if service.Connect.HasSidecar() {
|
||||
|
@ -125,29 +127,28 @@ func groupConnectHook(job *structs.Job, g *structs.TaskGroup) error {
|
|||
// Canonicalize task since this mutator runs after job canonicalization
|
||||
task.Canonicalize(job, g)
|
||||
|
||||
// port to be added for the sidecar task's proxy port
|
||||
port := structs.Port{
|
||||
Label: fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, service.Name),
|
||||
|
||||
// -1 is a sentinel value to instruct the
|
||||
// scheduler to map the host's dynamic port to
|
||||
// the same port in the netns.
|
||||
To: -1,
|
||||
}
|
||||
|
||||
// check that port hasn't already been defined before adding it to tg
|
||||
var found bool
|
||||
for _, p := range g.Networks[0].DynamicPorts {
|
||||
if p.Label == port.Label {
|
||||
found = true
|
||||
break
|
||||
makePort := func(label string) {
|
||||
// check that port hasn't already been defined before adding it to tg
|
||||
for _, p := range g.Networks[0].DynamicPorts {
|
||||
if p.Label == label {
|
||||
return
|
||||
}
|
||||
}
|
||||
g.Networks[0].DynamicPorts = append(g.Networks[0].DynamicPorts, structs.Port{
|
||||
Label: label,
|
||||
// -1 is a sentinel value to instruct the
|
||||
// scheduler to map the host's dynamic port to
|
||||
// the same port in the netns.
|
||||
To: -1,
|
||||
})
|
||||
}
|
||||
if !found {
|
||||
g.Networks[0].DynamicPorts = append(g.Networks[0].DynamicPorts, port)
|
||||
}
|
||||
|
||||
// create a port for the sidecar task's proxy port
|
||||
makePort(fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, service.Name))
|
||||
// todo(shoenig) magic port for 'expose.checks'
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -852,6 +852,10 @@ type ConsulProxy struct {
|
|||
// connect to.
|
||||
Upstreams []ConsulUpstream
|
||||
|
||||
// Expose configures the consul proxy.expose stanza to "open up" endpoints
|
||||
// used by task-group level service checks using HTTP or gRPC protocols.
|
||||
Expose *ConsulExposeConfig
|
||||
|
||||
// Config is a proxy configuration. It is opaque to Nomad and passed
|
||||
// directly to Consul.
|
||||
Config map[string]interface{}
|
||||
|
@ -863,9 +867,11 @@ func (p *ConsulProxy) Copy() *ConsulProxy {
|
|||
return nil
|
||||
}
|
||||
|
||||
newP := ConsulProxy{}
|
||||
newP.LocalServiceAddress = p.LocalServiceAddress
|
||||
newP.LocalServicePort = p.LocalServicePort
|
||||
newP := &ConsulProxy{
|
||||
LocalServiceAddress: p.LocalServiceAddress,
|
||||
LocalServicePort: p.LocalServicePort,
|
||||
Expose: p.Expose,
|
||||
}
|
||||
|
||||
if n := len(p.Upstreams); n > 0 {
|
||||
newP.Upstreams = make([]ConsulUpstream, n)
|
||||
|
@ -883,7 +889,7 @@ func (p *ConsulProxy) Copy() *ConsulProxy {
|
|||
}
|
||||
}
|
||||
|
||||
return &newP
|
||||
return newP
|
||||
}
|
||||
|
||||
// Equals returns true if the structs are recursively equal.
|
||||
|
@ -895,24 +901,16 @@ func (p *ConsulProxy) Equals(o *ConsulProxy) bool {
|
|||
if p.LocalServiceAddress != o.LocalServiceAddress {
|
||||
return false
|
||||
}
|
||||
|
||||
if p.LocalServicePort != o.LocalServicePort {
|
||||
return false
|
||||
}
|
||||
if len(p.Upstreams) != len(o.Upstreams) {
|
||||
|
||||
if !p.Expose.Equals(o.Expose) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Order doesn't matter
|
||||
OUTER:
|
||||
for _, up := range p.Upstreams {
|
||||
for _, innerUp := range o.Upstreams {
|
||||
if up.Equals(&innerUp) {
|
||||
// Match; find next upstream
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
|
||||
// No match
|
||||
if !upstreamsEquals(p.Upstreams, o.Upstreams) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -936,7 +934,24 @@ type ConsulUpstream struct {
|
|||
LocalBindPort int
|
||||
}
|
||||
|
||||
// Copy the stanza recursively. Returns nil if nil.
|
||||
func upstreamsEquals(a, b []ConsulUpstream) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
LOOP: // order does not matter
|
||||
for _, upA := range a {
|
||||
for _, upB := range b {
|
||||
if upA.Equals(&upB) {
|
||||
continue LOOP
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy the stanza recursively. Returns nil if u is nil.
|
||||
func (u *ConsulUpstream) Copy() *ConsulUpstream {
|
||||
if u == nil {
|
||||
return nil
|
||||
|
@ -956,3 +971,54 @@ func (u *ConsulUpstream) Equals(o *ConsulUpstream) bool {
|
|||
|
||||
return (*u) == (*o)
|
||||
}
|
||||
|
||||
// ExposeConfig represents a Consul Connect expose jobspec stanza.
|
||||
type ConsulExposeConfig struct {
|
||||
Paths []ConsulExposePath
|
||||
}
|
||||
|
||||
type ConsulExposePath struct {
|
||||
Path string
|
||||
Protocol string
|
||||
LocalPathPort int
|
||||
ListenerPort string
|
||||
}
|
||||
|
||||
func exposePathsEqual(pathsA, pathsB []ConsulExposePath) bool {
|
||||
if len(pathsA) != len(pathsB) {
|
||||
return false
|
||||
}
|
||||
|
||||
LOOP: // order does not matter
|
||||
for _, pathA := range pathsA {
|
||||
for _, pathB := range pathsB {
|
||||
if pathA == pathB {
|
||||
continue LOOP
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy the stanza. Returns nil if e is nil.
|
||||
func (e *ConsulExposeConfig) Copy() *ConsulExposeConfig {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
paths := make([]ConsulExposePath, len(e.Paths))
|
||||
for i := 0; i < len(e.Paths); i++ {
|
||||
paths[i] = e.Paths[i]
|
||||
}
|
||||
return &ConsulExposeConfig{
|
||||
Paths: paths,
|
||||
}
|
||||
}
|
||||
|
||||
// Equals returns true if the structs are recursively equal.
|
||||
func (e *ConsulExposeConfig) Equals(o *ConsulExposeConfig) bool {
|
||||
if e == nil || o == nil {
|
||||
return e == o
|
||||
}
|
||||
return exposePathsEqual(e.Paths, o.Paths)
|
||||
}
|
||||
|
|
|
@ -174,7 +174,6 @@ func TestConsulConnect_CopyEquals(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSidecarTask_MergeIntoTask(t *testing.T) {
|
||||
|
||||
task := MockJob().TaskGroups[0].Tasks[0]
|
||||
sTask := &SidecarTask{
|
||||
Name: "sidecar",
|
||||
|
@ -226,5 +225,102 @@ func TestSidecarTask_MergeIntoTask(t *testing.T) {
|
|||
|
||||
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",
|
||||
}},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -2422,7 +2422,7 @@ type RequestedDevice struct {
|
|||
// to use.
|
||||
Constraints Constraints
|
||||
|
||||
// Affinities are a set of affinites to apply when selecting the device
|
||||
// Affinities are a set of affinities to apply when selecting the device
|
||||
// to use.
|
||||
Affinities Affinities
|
||||
}
|
||||
|
@ -2615,18 +2615,18 @@ func (n *NodeResources) Equals(o *NodeResources) bool {
|
|||
}
|
||||
|
||||
// Equals equates Networks as a set
|
||||
func (n *Networks) Equals(o *Networks) bool {
|
||||
if n == o {
|
||||
func (ns *Networks) Equals(o *Networks) bool {
|
||||
if ns == o {
|
||||
return true
|
||||
}
|
||||
if n == nil || o == nil {
|
||||
if ns == nil || o == nil {
|
||||
return false
|
||||
}
|
||||
if len(*n) != len(*o) {
|
||||
if len(*ns) != len(*o) {
|
||||
return false
|
||||
}
|
||||
SETEQUALS:
|
||||
for _, ne := range *n {
|
||||
for _, ne := range *ns {
|
||||
for _, oe := range *o {
|
||||
if ne.Equals(oe) {
|
||||
continue SETEQUALS
|
||||
|
|
Loading…
Reference in New Issue