Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

478 lines
13 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package agent
import (
"fmt"
"testing"
"time"
"github.com/hashicorp/consul/acl"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent/structs"
)
func TestAgent_sidecarServiceFromNodeService(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
tests := []struct {
name string
sd *structs.ServiceDefinition
token string
wantNS *structs.NodeService
wantChecks []*structs.CheckType
wantToken string
wantErr string
}{
{
name: "no sidecar",
sd: &structs.ServiceDefinition{
Name: "web",
Port: 1111,
},
token: "foo",
wantNS: nil,
wantChecks: nil,
wantToken: "",
wantErr: "", // Should NOT error
},
{
name: "all the defaults",
sd: &structs.ServiceDefinition{
ID: "web1",
Name: "web",
Port: 1111,
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{},
},
},
token: "foo",
wantNS: &structs.NodeService{
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Kind: structs.ServiceKindConnectProxy,
ID: "web1-sidecar-proxy",
Service: "web-sidecar-proxy",
Port: 0,
LocallyRegisteredAsSidecar: true,
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "web",
DestinationServiceID: "web1",
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 1111,
},
},
wantChecks: nil,
wantToken: "foo",
},
{
name: "all the allowed overrides",
sd: &structs.ServiceDefinition{
ID: "web1",
Name: "web",
Port: 1111,
Tags: []string{"baz"},
Meta: map[string]string{"foo": "baz"},
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{
Name: "motorbike1",
Port: 3333,
Tags: []string{"foo", "bar"},
Address: "127.127.127.127",
Meta: map[string]string{"foo": "bar"},
Check: structs.CheckType{
ScriptArgs: []string{"sleep", "1"},
Interval: 999 * time.Second,
},
Token: "custom-token",
EnableTagOverride: true,
Proxy: &structs.ConnectProxyConfig{
DestinationServiceName: "web",
DestinationServiceID: "web1",
LocalServiceAddress: "127.0.127.0",
LocalServicePort: 9999,
Config: map[string]interface{}{"baz": "qux"},
Upstreams: structs.TestUpstreams(t, false),
},
},
},
},
token: "foo",
wantNS: &structs.NodeService{
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Kind: structs.ServiceKindConnectProxy,
ID: "web1-sidecar-proxy",
Service: "motorbike1",
Port: 3333,
Tags: []string{"foo", "bar"},
Address: "127.127.127.127",
Meta: map[string]string{
"foo": "bar",
},
LocallyRegisteredAsSidecar: true,
EnableTagOverride: true,
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "web",
DestinationServiceID: "web1",
LocalServiceAddress: "127.0.127.0",
LocalServicePort: 9999,
Config: map[string]interface{}{"baz": "qux"},
Upstreams: structs.TestAddDefaultsToUpstreams(t, structs.TestUpstreams(t, false),
*structs.DefaultEnterpriseMetaInDefaultPartition()),
},
},
wantChecks: []*structs.CheckType{
{
ScriptArgs: []string{"sleep", "1"},
Interval: 999 * time.Second,
},
},
wantToken: "custom-token",
},
{
name: "inherit locality, tags and meta",
sd: &structs.ServiceDefinition{
ID: "web1",
Name: "web",
Port: 1111,
Tags: []string{"foo"},
Meta: map[string]string{"foo": "bar"},
Locality: &structs.Locality{
Region: "us-east-1",
Zone: "us-east-1a",
},
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{},
},
},
wantNS: &structs.NodeService{
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Kind: structs.ServiceKindConnectProxy,
ID: "web1-sidecar-proxy",
Service: "web-sidecar-proxy",
Port: 0,
Tags: []string{"foo"},
Meta: map[string]string{"foo": "bar"},
Locality: &structs.Locality{
Region: "us-east-1",
Zone: "us-east-1a",
},
LocallyRegisteredAsSidecar: true,
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "web",
DestinationServiceID: "web1",
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 1111,
},
},
wantChecks: nil,
},
{
name: "retain locality, tags and meta if explicitly configured",
sd: &structs.ServiceDefinition{
ID: "web1",
Name: "web",
Port: 1111,
Tags: []string{"foo"},
Meta: map[string]string{"foo": "bar"},
Locality: &structs.Locality{
Region: "us-east-1",
Zone: "us-east-1a",
},
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{
Tags: []string{"bar"},
Meta: map[string]string{"baz": "qux"},
Locality: &structs.Locality{
Region: "us-east-2",
Zone: "us-east-2a",
},
},
},
},
wantNS: &structs.NodeService{
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Kind: structs.ServiceKindConnectProxy,
ID: "web1-sidecar-proxy",
Service: "web-sidecar-proxy",
Port: 0,
Tags: []string{"bar"},
Meta: map[string]string{"baz": "qux"},
Locality: &structs.Locality{
Region: "us-east-2",
Zone: "us-east-2a",
},
LocallyRegisteredAsSidecar: true,
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "web",
DestinationServiceID: "web1",
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 1111,
},
},
wantChecks: nil,
},
{
name: "invalid check type",
sd: &structs.ServiceDefinition{
ID: "web1",
Name: "web",
Port: 1111,
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{
Check: structs.CheckType{
TCP: "foo",
// Invalid since no interval specified
},
},
},
},
token: "foo",
wantErr: "Interval must be > 0",
},
{
name: "invalid meta",
sd: &structs.ServiceDefinition{
ID: "web1",
Name: "web",
Port: 1111,
Connect: &structs.ServiceConnect{
SidecarService: &structs.ServiceDefinition{
Meta: map[string]string{
"consul-reserved-key-should-be-rejected": "true",
},
},
},
},
token: "foo",
wantErr: "reserved for internal use",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ns := tt.sd.NodeService()
err := ns.Validate()
require.NoError(t, err, "Invalid test case - NodeService must validate")
gotNS, gotChecks, gotToken, err := sidecarServiceFromNodeService(ns, tt.token)
if tt.wantErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
require.Equal(t, tt.wantNS, gotNS)
require.Equal(t, tt.wantChecks, gotChecks)
require.Equal(t, tt.wantToken, gotToken)
})
}
}
func TestAgent_SidecarPortFromServiceID(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
tests := []struct {
name string
autoPortsDisabled bool
enterpriseMeta acl.EnterpriseMeta
maxPort int
port int
preRegister []*structs.ServiceDefinition
serviceID string
wantPort int
wantErr string
}{
{
name: "use auto ports",
serviceID: "web1",
wantPort: 2222,
},
{
name: "re-registering same sidecar with no port should pick same one",
// Allow multiple ports to be sure we get the right one
maxPort: 2500,
// Pre register the main service and sidecar we want
preRegister: []*structs.ServiceDefinition{{
Kind: structs.ServiceKindConnectProxy,
ID: "web1",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Name: "web",
Port: 2221,
Proxy: &structs.ConnectProxyConfig{
DestinationServiceName: "web",
DestinationServiceID: "web1",
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 1110,
},
}, {
Kind: structs.ServiceKindConnectProxy,
ID: "web1-sidecar-proxy",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Name: "web-sidecar-proxy",
Port: 2222,
Proxy: &structs.ConnectProxyConfig{
DestinationServiceName: "web",
DestinationServiceID: "web1",
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 1111,
},
}},
// Register same sidecar again
serviceID: "web1-sidecar-proxy",
enterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
wantPort: 2222, // Should claim the same port as before
},
{
name: "all auto ports already taken",
// register another service with sidecar consuming our 1 and only allocated auto port.
preRegister: []*structs.ServiceDefinition{{
Kind: structs.ServiceKindConnectProxy,
ID: "api1",
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
Name: "api",
Port: 2221,
Proxy: &structs.ConnectProxyConfig{
DestinationServiceName: "api",
DestinationServiceID: "api1",
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 1110,
},
}, {
Kind: structs.ServiceKindConnectProxy,
Name: "api-proxy-sidecar",
Port: 2222, // Consume the one available auto-port
Proxy: &structs.ConnectProxyConfig{
DestinationServiceID: "api1",
DestinationServiceName: "api",
},
}},
wantErr: "none left in the configured range [2222, 2222]",
},
{
name: "auto ports disabled",
autoPortsDisabled: true,
wantErr: "auto-assignment disabled in config",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set port range to be tiny (one available) to test consuming all of it.
// This allows a single assigned port at 2222 thanks to being inclusive at
// both ends.
if tt.maxPort == 0 {
tt.maxPort = 2222
}
hcl := fmt.Sprintf(`
ports {
sidecar_min_port = 2222
sidecar_max_port = %d
}
`, tt.maxPort)
if tt.autoPortsDisabled {
hcl = `
ports {
sidecar_min_port = 0
sidecar_max_port = 0
}
`
}
a := NewTestAgent(t, hcl)
defer a.Shutdown()
if len(tt.preRegister) > 0 {
for _, s := range tt.preRegister {
err := a.addServiceFromSource(s.NodeService(), nil, false, "", ConfigSourceLocal)
require.NoError(t, err)
}
}
gotPort, err := a.sidecarPortFromServiceIDLocked(structs.ServiceID{ID: tt.serviceID, EnterpriseMeta: tt.enterpriseMeta})
if tt.wantErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
require.Equal(t, tt.wantPort, gotPort)
})
}
}
func TestAgent_SidecarDefaultChecks(t *testing.T) {
tests := []struct {
name string
serviceID string
svcAddress string
proxyLocalSvcAddress string
port int
wantChecks []*structs.CheckType
}{{
name: "uses proxy address for check",
serviceID: "web1-1-sidecar-proxy",
svcAddress: "123.123.123.123",
proxyLocalSvcAddress: "255.255.255.255",
port: 2222,
wantChecks: []*structs.CheckType{
{
Name: "Connect Sidecar Listening",
TCP: "123.123.123.123:2222",
Interval: 10 * time.Second,
},
{
Name: "Connect Sidecar Aliasing web1-1",
AliasService: "web1-1",
},
},
},
{
name: "uses proxy.local_service_address for check if proxy address is empty",
serviceID: "web1-1-sidecar-proxy",
proxyLocalSvcAddress: "1.2.3.4",
port: 2222,
wantChecks: []*structs.CheckType{
{
Name: "Connect Sidecar Listening",
TCP: "1.2.3.4:2222",
Interval: 10 * time.Second,
},
{
Name: "Connect Sidecar Aliasing web1-1",
AliasService: "web1-1",
},
},
},
{
name: "redundant name",
serviceID: "1-sidecar-proxy-web1-sidecar-proxy",
svcAddress: "123.123.123.123",
proxyLocalSvcAddress: "255.255.255.255",
port: 2222,
wantChecks: []*structs.CheckType{
{
Name: "Connect Sidecar Listening",
TCP: "123.123.123.123:2222",
Interval: 10 * time.Second,
},
{
Name: "Connect Sidecar Aliasing 1-sidecar-proxy-web1",
AliasService: "1-sidecar-proxy-web1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotChecks := sidecarDefaultChecks(tt.serviceID, tt.svcAddress, tt.proxyLocalSvcAddress, tt.port)
require.Equal(t, tt.wantChecks, gotChecks)
})
}
}