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.
consul/agent/sidecar_service_test.go

479 lines
13 KiB

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
1 year ago
// SPDX-License-Identifier: BUSL-1.1
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)
})
}
}