consul/connect/resolver_test.go

372 lines
8.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package connect
import (
"context"
"testing"
"time"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/api"
"github.com/stretchr/testify/require"
)
func TestStaticResolver_Resolve(t *testing.T) {
type fields struct {
Addr string
CertURI connect.CertURI
}
tests := []struct {
name string
fields fields
}{
{
name: "simples",
fields: fields{"1.2.3.4:80", connect.TestSpiffeIDService(t, "foo")},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sr := StaticResolver{
Addr: tt.fields.Addr,
CertURI: tt.fields.CertURI,
}
addr, certURI, err := sr.Resolve(context.Background())
require.Nil(t, err)
require.Equal(t, sr.Addr, addr)
require.Equal(t, sr.CertURI, certURI)
})
}
}
func TestConsulResolver_Resolve(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
// Setup a local test agent to query
agent := agent.StartTestAgent(t, agent.TestAgent{Name: "test-consul"})
defer agent.Shutdown()
cfg := api.DefaultConfig()
cfg.Address = agent.HTTPAddr()
client, err := api.NewClient(cfg)
require.Nil(t, err)
// Setup a service with a connect proxy instance
regSrv := &api.AgentServiceRegistration{
Name: "web",
Port: 8080,
}
err = client.Agent().ServiceRegister(regSrv)
require.Nil(t, err)
regProxy := &api.AgentServiceRegistration{
Kind: "connect-proxy",
Name: "web-proxy",
Port: 9090,
Proxy: &api.AgentServiceConnectProxyConfig{
DestinationServiceName: "web",
},
Meta: map[string]string{
"MetaKey": "MetaValue",
},
}
err = client.Agent().ServiceRegister(regProxy)
require.Nil(t, err)
// And another proxy so we can test handling with multiple endpoints returned
regProxy.Port = 9091
regProxy.ID = "web-proxy-2"
regProxy.Meta = map[string]string{}
err = client.Agent().ServiceRegister(regProxy)
require.Nil(t, err)
// Add a native service
{
regSrv := &api.AgentServiceRegistration{
Name: "db",
Port: 8080,
Connect: &api.AgentServiceConnect{
Native: true,
},
}
require.NoError(t, client.Agent().ServiceRegister(regSrv))
}
// Add a prepared query
queryId, _, err := client.PreparedQuery().Create(&api.PreparedQueryDefinition{
Name: "test-query",
Service: api.ServiceQuery{
Service: "web",
Connect: true,
},
}, nil)
require.NoError(t, err)
proxyAddrs := []string{
agent.Config.AdvertiseAddrLAN.String() + ":9090",
agent.Config.AdvertiseAddrLAN.String() + ":9091",
}
type fields struct {
Namespace string
Name string
Type int
Datacenter string
Filter string
}
tests := []struct {
name string
fields fields
timeout time.Duration
wantAddr string
wantCertURI connect.CertURI
wantErr bool
addrs []string
}{
{
name: "basic service discovery",
fields: fields{
Namespace: "default",
Name: "web",
Type: ConsulResolverTypeService,
},
// Want empty host since we don't enforce trust domain outside of TLS and
// don't need to load the current one this way.
wantCertURI: connect.TestSpiffeIDServiceWithHost(t, "web", ""),
wantErr: false,
addrs: proxyAddrs,
},
{
name: "basic service with native service",
fields: fields{
Namespace: "default",
Name: "db",
Type: ConsulResolverTypeService,
},
// Want empty host since we don't enforce trust domain outside of TLS and
// don't need to load the current one this way.
wantCertURI: connect.TestSpiffeIDServiceWithHost(t, "db", ""),
wantErr: false,
},
{
name: "service discovery with filter",
fields: fields{
Namespace: "default",
Name: "web",
Type: ConsulResolverTypeService,
Filter: "Service.Meta[`MetaKey`] == `MetaValue`",
},
// Want empty host since we don't enforce trust domain outside of TLS and
// don't need to load the current one this way.
wantCertURI: connect.TestSpiffeIDServiceWithHost(t, "web", ""),
wantErr: false,
addrs: []string{
agent.Config.AdvertiseAddrLAN.String() + ":9090",
},
},
{
name: "service discovery with filter",
fields: fields{
Namespace: "default",
Name: "web",
Type: ConsulResolverTypeService,
Filter: "`AnotherMetaValue` in Service.Meta.MetaKey",
},
wantErr: true,
},
{
name: "Bad Type errors",
fields: fields{
Namespace: "default",
Name: "web",
Type: 123,
},
wantErr: true,
},
{
name: "Non-existent service errors",
fields: fields{
Namespace: "default",
Name: "foo",
Type: ConsulResolverTypeService,
},
wantErr: true,
},
{
name: "timeout errors",
fields: fields{
Namespace: "default",
Name: "web",
Type: ConsulResolverTypeService,
},
timeout: 1 * time.Nanosecond,
wantErr: true,
},
{
name: "prepared query by id",
fields: fields{
Name: queryId,
Type: ConsulResolverTypePreparedQuery,
},
// Want empty host since we don't enforce trust domain outside of TLS and
// don't need to load the current one this way.
wantCertURI: connect.TestSpiffeIDServiceWithHost(t, "web", ""),
wantErr: false,
addrs: proxyAddrs,
},
{
name: "prepared query by name",
fields: fields{
Name: "test-query",
Type: ConsulResolverTypePreparedQuery,
},
// Want empty host since we don't enforce trust domain outside of TLS and
// don't need to load the current one this way.
wantCertURI: connect.TestSpiffeIDServiceWithHost(t, "web", ""),
wantErr: false,
addrs: proxyAddrs,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cr := &ConsulResolver{
Client: client,
Namespace: tt.fields.Namespace,
Name: tt.fields.Name,
Type: tt.fields.Type,
Datacenter: tt.fields.Datacenter,
Filter: tt.fields.Filter,
}
// WithCancel just to have a cancel func in scope to assign in the if
// clause.
ctx, cancel := context.WithCancel(context.Background())
if tt.timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, tt.timeout)
}
defer cancel()
gotAddr, gotCertURI, err := cr.Resolve(ctx)
if tt.wantErr {
require.NotNil(t, err)
return
}
require.Nil(t, err)
require.Equal(t, tt.wantCertURI, gotCertURI)
if len(tt.addrs) > 0 {
require.Contains(t, tt.addrs, gotAddr)
}
})
}
}
func TestConsulResolverFromAddrFunc(t *testing.T) {
// Don't need an actual instance since we don't do the service discovery but
// we do want to assert the client is pass through correctly.
client, err := api.NewClient(api.DefaultConfig())
require.NoError(t, err)
tests := []struct {
name string
addr string
want Resolver
wantErr string
}{
{
name: "service",
addr: "foo.service.consul",
want: &ConsulResolver{
Client: client,
Namespace: "default",
Name: "foo",
Type: ConsulResolverTypeService,
},
},
{
name: "query",
addr: "foo.query.consul",
want: &ConsulResolver{
Client: client,
Namespace: "default",
Name: "foo",
Type: ConsulResolverTypePreparedQuery,
},
},
{
name: "service with dc",
addr: "foo.service.dc2.consul",
want: &ConsulResolver{
Client: client,
Datacenter: "dc2",
Namespace: "default",
Name: "foo",
Type: ConsulResolverTypeService,
},
},
{
name: "query with dc",
addr: "foo.query.dc2.consul",
want: &ConsulResolver{
Client: client,
Datacenter: "dc2",
Namespace: "default",
Name: "foo",
Type: ConsulResolverTypePreparedQuery,
},
},
{
name: "invalid host:port",
addr: "%%%",
wantErr: "invalid Consul DNS domain",
},
{
name: "custom domain",
addr: "foo.service.my-consul.com",
wantErr: "invalid Consul DNS domain",
},
{
name: "unsupported query type",
addr: "foo.connect.consul",
wantErr: "unsupported Consul DNS domain",
},
{
name: "unsupported query type and datacenter",
addr: "foo.connect.dc1.consul",
wantErr: "unsupported Consul DNS domain",
},
{
name: "unsupported query type and datacenter",
addr: "foo.connect.dc1.consul",
wantErr: "unsupported Consul DNS domain",
},
{
name: "unsupported tag filter",
addr: "tag1.foo.service.consul",
wantErr: "unsupported Consul DNS domain",
},
{
name: "unsupported tag filter with DC",
addr: "tag1.foo.service.dc1.consul",
wantErr: "unsupported Consul DNS domain",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fn := ConsulResolverFromAddrFunc(client)
got, gotErr := fn(tt.addr)
if tt.wantErr != "" {
require.Error(t, gotErr)
require.Contains(t, gotErr.Error(), tt.wantErr)
} else {
require.NoError(t, gotErr)
require.Equal(t, tt.want, got)
}
})
}
}