consul/agent/xds/validateupstream-test/validateupstream_test.go

321 lines
10 KiB
Go
Raw Normal View History

// 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>
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
package validateupstream_test
import (
"testing"
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/xds"
"github.com/hashicorp/consul/agent/xds/testcommon"
"github.com/hashicorp/consul/envoyextensions/xdscommon"
"github.com/hashicorp/consul/sdk/testutil"
troubleshoot "github.com/hashicorp/consul/troubleshoot/proxy"
testinf "github.com/mitchellh/go-testing-interface"
"github.com/stretchr/testify/require"
)
// TestValidateUpstreams only tests validation for listeners, routes, and clusters. Endpoints validation is done in a
// top level test that can parse the output of the /clusters endpoint.
func TestValidateUpstreams(t *testing.T) {
sni := "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
listenerName := "db:127.0.0.1:9191"
httpServiceDefaults := &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
Protocol: "http",
}
dbUID := proxycfg.NewUpstreamID(&structs.Upstream{
DestinationName: "db",
LocalBindPort: 9191,
})
nodes := proxycfg.TestUpstreamNodes(t, "db")
tests := []struct {
name string
create func(t testinf.T) *proxycfg.ConfigSnapshot
patcher func(*xdscommon.IndexedResources) *xdscommon.IndexedResources
err string
peer string
vip string
envoyID string
}{
{
name: "tcp-success",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil)
},
},
{
name: "tcp-missing-listener",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
delete(ir.Index[xdscommon.ListenerType], listenerName)
return ir
},
err: "No listener for upstream \"db\"",
},
{
name: "tcp-missing-cluster",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
delete(ir.Index[xdscommon.ClusterType], sni)
return ir
},
err: "No cluster \"db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul\" for upstream \"db\"",
},
{
name: "http-success",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, nil, httpServiceDefaults)
},
},
{
name: "http-rds-success",
// RDS, Envoy's Route Discovery Service, is only used for HTTP services with a customized discovery chain, so we
// need to use the test snapshot and add L7 config entries.
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, []proxycfg.UpdateEvent{
// The events ensure there are endpoints for the v1 and v2 subsets.
{
CorrelationID: "upstream-target:v1.db.default.default.dc1:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: nodes,
},
},
{
CorrelationID: "upstream-target:v2.db.default.default.dc1:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: nodes,
},
},
}, configEntriesForDBSplits()...)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
return ir
},
},
{
name: "http-rds-missing-route",
// RDS, Envoy's Route Discovery Service, is only used for HTTP services with a customized discovery chain, so we
// need to use the test snapshot and add L7 config entries.
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", false, nil, []proxycfg.UpdateEvent{
// The events ensure there are endpoints for the v1 and v2 subsets.
{
CorrelationID: "upstream-target:v1.db.default.default.dc1:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: nodes,
},
},
{
CorrelationID: "upstream-target:v2.db.default.default.dc1:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: nodes,
},
},
}, configEntriesForDBSplits()...)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
delete(ir.Index[xdscommon.RouteType], "db")
return ir
},
err: "No route for upstream \"db\"",
},
{
name: "redirect",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "redirect-to-cluster-peer", false, nil, nil)
},
},
{
name: "failover",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover", false, nil, nil)
},
},
{
name: "failover-to-cluster-peer",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
2023-03-22 18:56:18 +00:00
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-to-cluster-peer", false, nil, nil)
},
},
{
name: "non-eds",
create: proxycfg.TestConfigSnapshotPeering,
envoyID: "payments?peer=cloud",
},
{
name: "tproxy-success",
vip: "240.0.0.1",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotTransparentProxyHTTPUpstream(t, nil)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
return ir
},
},
{
name: "tproxy-http-missing-cluster",
vip: "240.0.0.1",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotTransparentProxyHTTPUpstream(t, nil)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
sni := "google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
delete(ir.Index[xdscommon.ClusterType], sni)
return ir
},
err: "No cluster \"google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul\" for upstream \"240.0.0.1\"",
},
{
name: "tproxy-http-redirect-success",
vip: "240.0.0.1",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotTransparentProxyHTTPUpstream(t, nil, configEntriesForGoogleRedirect()...)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
return ir
},
},
{
name: "tproxy-http-split-success",
vip: "240.0.0.1",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotTransparentProxyHTTPUpstream(t, nil, configEntriesForGoogleSplits()...)
},
patcher: func(ir *xdscommon.IndexedResources) *xdscommon.IndexedResources {
return ir
},
},
}
latestEnvoyVersion := xdscommon.EnvoyVersions[0]
sf, err := xdscommon.DetermineSupportedProxyFeaturesFromString(latestEnvoyVersion)
require.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Sanity check default with no overrides first
snap := tt.create(t)
// We need to replace the TLS certs with deterministic ones to make golden
// files workable. Note we don't update these otherwise they'd change
// golden files for every test case and so not be any use!
testcommon.SetupTLSRootsAndLeaf(t, snap)
g := xds.NewResourceGenerator(testutil.Logger(t), nil, false)
g.ProxyFeatures = sf
res, err := g.AllResourcesFromSnapshot(snap)
require.NoError(t, err)
indexedResources := xdscommon.IndexResources(g.Logger, res)
if tt.patcher != nil {
indexedResources = tt.patcher(indexedResources)
}
envoyID := tt.envoyID
vip := tt.vip
if envoyID == "" && vip == "" {
envoyID = "db"
}
// This only tests validation for listeners, routes, and clusters. Endpoints validation is done in a top
// level test that can parse the output of the /clusters endpoint. So for this test, we set clusters to nil.
messages := troubleshoot.Validate(indexedResources, envoyID, vip, false, nil)
var outputErrors string
for _, msgError := range messages.Errors() {
outputErrors += msgError.Message
for _, action := range msgError.PossibleActions {
outputErrors += action
}
}
if len(tt.err) == 0 {
require.True(t, messages.Success())
} else {
require.Contains(t, outputErrors, tt.err)
}
})
}
}
func configEntriesForDBSplits() []structs.ConfigEntry {
httpServiceDefaults := &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
Protocol: "http",
}
splitter := &structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "db",
Splits: []structs.ServiceSplit{
{
Weight: 50,
Service: "db",
ServiceSubset: "v1",
},
{
Weight: 50,
Service: "db",
ServiceSubset: "v2",
},
},
}
resolver := &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {Filter: "Service.Meta.version == v1"},
"v2": {Filter: "Service.Meta.version == v2"},
},
}
return []structs.ConfigEntry{httpServiceDefaults, splitter, resolver}
}
func configEntriesForGoogleSplits() []structs.ConfigEntry {
splitter := &structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "google",
Splits: []structs.ServiceSplit{
{
Weight: 50,
Service: "google",
ServiceSubset: "v1",
},
{
Weight: 50,
Service: "google",
ServiceSubset: "v2",
},
},
}
resolver := &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "google",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {Filter: "Service.Meta.version == v1"},
"v2": {Filter: "Service.Meta.version == v2"},
},
}
return []structs.ConfigEntry{splitter, resolver}
}
func configEntriesForGoogleRedirect() []structs.ConfigEntry {
redirectGoogle := &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "google",
Redirect: &structs.ServiceResolverRedirect{
Service: "google-v2",
},
}
return []structs.ConfigEntry{redirectGoogle}
}