d5a2eb677f
Transparent proxies can set up filter chains that allow direct connections to upstream service instances. Services that can be dialed directly are stored in the PassthroughUpstreams map of the proxycfg snapshot. Previously these addresses were not being cleaned up based on new service health data. The list of addresses associated with an upstream service would only ever grow. As services scale up and down, eventually they will have instances assigned to an IP that was previously assigned to a different service. When IP addresses are duplicated across filter chain match rules the listener config will be rejected by Envoy. This commit updates the proxycfg snapshot management so that passthrough addresses can get cleaned up when no longer associated with a given upstream. There is still the possibility of a race condition here where due to timing an address is shared between multiple passthrough upstreams. That concern is mitigated by #12195, but will be further addressed in a follow-up.
233 lines
4.3 KiB
Go
233 lines
4.3 KiB
Go
package proxycfg
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
)
|
|
|
|
// TODO(freddy): Needs enterprise test
|
|
func TestUpstreamIDFromTargetID(t *testing.T) {
|
|
type testcase struct {
|
|
tid string
|
|
expect UpstreamID
|
|
}
|
|
run := func(t *testing.T, tc testcase) {
|
|
tc.expect.EnterpriseMeta.Normalize()
|
|
|
|
got := NewUpstreamIDFromTargetID(tc.tid)
|
|
require.Equal(t, tc.expect, got)
|
|
}
|
|
|
|
cases := map[string]testcase{
|
|
"with subset": {
|
|
tid: "v1.foo.default.default.dc2",
|
|
expect: UpstreamID{
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
},
|
|
"without subset": {
|
|
tid: "foo.default.default.dc2",
|
|
expect: UpstreamID{
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
run(t, tc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpstreamIDFromString(t *testing.T) {
|
|
type testcase struct {
|
|
id string
|
|
expect UpstreamID
|
|
}
|
|
run := func(t *testing.T, tc testcase) {
|
|
tc.expect.EnterpriseMeta.Normalize()
|
|
|
|
got := UpstreamIDFromString(tc.id)
|
|
require.Equal(t, tc.expect, got)
|
|
}
|
|
|
|
prefix := ""
|
|
if structs.DefaultEnterpriseMetaInDefaultPartition().PartitionOrEmpty() != "" {
|
|
prefix = "default/default/"
|
|
}
|
|
|
|
cases := map[string]testcase{
|
|
"prepared query": {
|
|
"prepared_query:" + prefix + "foo",
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypePreparedQuery,
|
|
Name: "foo",
|
|
},
|
|
},
|
|
"prepared query dc": {
|
|
"prepared_query:" + prefix + "foo?dc=dc2",
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypePreparedQuery,
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
},
|
|
"normal": {
|
|
prefix + "foo",
|
|
UpstreamID{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
"normal dc": {
|
|
prefix + "foo?dc=dc2",
|
|
UpstreamID{
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
run(t, tc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpstreamID_String(t *testing.T) {
|
|
type testcase struct {
|
|
u UpstreamID
|
|
expect string
|
|
}
|
|
run := func(t *testing.T, tc testcase) {
|
|
got := tc.u.String()
|
|
require.Equal(t, tc.expect, got)
|
|
}
|
|
|
|
prefix := ""
|
|
if structs.DefaultEnterpriseMetaInDefaultPartition().PartitionOrEmpty() != "" {
|
|
prefix = "default/default/"
|
|
}
|
|
|
|
cases := map[string]testcase{
|
|
"prepared query": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypePreparedQuery,
|
|
Name: "foo",
|
|
},
|
|
"prepared_query:" + prefix + "foo",
|
|
},
|
|
"prepared query dc": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypePreparedQuery,
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
"prepared_query:" + prefix + "foo?dc=dc2",
|
|
},
|
|
"normal implicit": {
|
|
UpstreamID{
|
|
Name: "foo",
|
|
},
|
|
prefix + "foo",
|
|
},
|
|
"normal implicit dc": {
|
|
UpstreamID{
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
prefix + "foo?dc=dc2",
|
|
},
|
|
"normal explicit": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypeService,
|
|
Name: "foo",
|
|
},
|
|
prefix + "foo",
|
|
},
|
|
"normal explicit dc": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypeService,
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
prefix + "foo?dc=dc2",
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
run(t, tc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpstreamID_EnvoyID(t *testing.T) {
|
|
type testcase struct {
|
|
u UpstreamID
|
|
expect string
|
|
}
|
|
run := func(t *testing.T, tc testcase) {
|
|
got := tc.u.EnvoyID()
|
|
require.Equal(t, tc.expect, got)
|
|
}
|
|
|
|
cases := map[string]testcase{
|
|
"prepared query": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypePreparedQuery,
|
|
Name: "foo",
|
|
},
|
|
"prepared_query:foo",
|
|
},
|
|
"prepared query dc": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypePreparedQuery,
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
"prepared_query:foo?dc=dc2",
|
|
},
|
|
"normal implicit": {
|
|
UpstreamID{
|
|
Name: "foo",
|
|
},
|
|
"foo",
|
|
},
|
|
"normal implicit dc": {
|
|
UpstreamID{
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
"foo?dc=dc2",
|
|
},
|
|
"normal explicit": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypeService,
|
|
Name: "foo",
|
|
},
|
|
"foo",
|
|
},
|
|
"normal explicit dc": {
|
|
UpstreamID{
|
|
Type: structs.UpstreamDestTypeService,
|
|
Name: "foo",
|
|
Datacenter: "dc2",
|
|
},
|
|
"foo?dc=dc2",
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
run(t, tc)
|
|
})
|
|
}
|
|
}
|