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/proxycfg/testing_upstreams.go

1135 lines
32 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package proxycfg
import (
"time"
"github.com/mitchellh/go-testing-interface"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/configentry"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/consul/discoverychain"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/proto/private/pbpeering"
)
func setupTestVariationConfigEntriesAndSnapshot(
t testing.T,
variation string,
enterprise bool,
upstreams structs.Upstreams,
additionalEntries ...structs.ConfigEntry,
) []UpdateEvent {
var (
dbUpstream = upstreams[0]
dbUID = NewUpstreamID(&dbUpstream)
)
dbChain := setupTestVariationDiscoveryChain(t, variation, enterprise, dbUID.EnterpriseMeta, additionalEntries...)
nodes := TestUpstreamNodes(t, "db")
if variation == "register-to-terminating-gateway" {
for _, node := range nodes {
node.Service.Kind = structs.ServiceKindTerminatingGateway
}
}
events := []UpdateEvent{
{
CorrelationID: "discovery-chain:" + dbUID.String(),
Result: &structs.DiscoveryChainResponse{
Chain: dbChain,
},
},
{
CorrelationID: "upstream-target:" + dbChain.ID() + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: nodes,
},
},
}
dbOpts := structs.DiscoveryTargetOpts{
Service: dbUID.Name,
Namespace: dbUID.NamespaceOrDefault(),
Partition: dbUID.PartitionOrDefault(),
Datacenter: "dc1",
}
dbChainID := structs.ChainID(dbOpts)
makeChainID := func(opts structs.DiscoveryTargetOpts) string {
finalOpts := structs.MergeDiscoveryTargetOpts(dbOpts, opts)
return structs.ChainID(finalOpts)
}
switch variation {
case "default":
case "simple-with-overrides":
case "simple":
case "external-sni":
case "failover":
chainID := makeChainID(structs.DiscoveryTargetOpts{Service: "fail"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesAlternate(t),
},
})
case "failover-through-remote-gateway-triggered":
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesInStatus(t, "critical"),
},
})
fallthrough
case "failover-through-remote-gateway":
chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesDC2(t),
},
})
events = append(events, UpdateEvent{
CorrelationID: "mesh-gateway:dc2:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestGatewayNodesDC2(t),
},
})
case "failover-to-cluster-peer":
uid := UpstreamID{
Name: "db",
Peer: "cluster-01",
EnterpriseMeta: acl.NewEnterpriseMetaWithPartition(dbUID.PartitionOrDefault(), ""),
}
events = append(events, UpdateEvent{
CorrelationID: "peer-trust-bundle:cluster-01",
Result: &pbpeering.TrustBundleReadResponse{
Bundle: &pbpeering.PeeringTrustBundle{
PeerName: "peer1",
TrustDomain: "peer1.domain",
ExportedPartition: "peer1ap",
RootPEMs: []string{"peer1-root-1"},
},
},
})
if enterprise {
uid.EnterpriseMeta = acl.NewEnterpriseMetaWithPartition(dbUID.PartitionOrDefault(), "ns9")
}
events = append(events, UpdateEvent{
CorrelationID: "upstream-peer:" + uid.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc2", "cluster-01", "10.40.1.1", false, uid.EnterpriseMeta)},
},
})
case "redirect-to-cluster-peer":
events = append(events, UpdateEvent{
CorrelationID: "peer-trust-bundle:cluster-01",
Result: &pbpeering.TrustBundleReadResponse{
Bundle: &pbpeering.PeeringTrustBundle{
PeerName: "peer1",
TrustDomain: "peer1.domain",
ExportedPartition: "peer1ap",
RootPEMs: []string{"peer1-root-1"},
},
},
})
uid := UpstreamID{
Name: "db",
Peer: "cluster-01",
}
if enterprise {
uid.EnterpriseMeta = acl.NewEnterpriseMetaWithPartition(dbUID.PartitionOrDefault(), "ns9")
}
events = append(events, UpdateEvent{
CorrelationID: "upstream-peer:" + uid.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc2", "cluster-01", "10.40.1.1", false, uid.EnterpriseMeta)},
},
})
case "failover-through-double-remote-gateway-triggered":
chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesInStatus(t, "critical"),
},
},
UpdateEvent{
CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesInStatusDC2(t, "critical"),
},
})
fallthrough
case "failover-through-double-remote-gateway":
chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc3"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesDC2(t),
},
},
UpdateEvent{
CorrelationID: "mesh-gateway:dc2:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestGatewayNodesDC2(t),
},
},
UpdateEvent{
CorrelationID: "mesh-gateway:dc3:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestGatewayNodesDC3(t),
},
})
case "failover-through-local-gateway-triggered":
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesInStatus(t, "critical"),
},
})
fallthrough
case "failover-through-local-gateway":
chainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesDC2(t),
},
},
UpdateEvent{
CorrelationID: "mesh-gateway:dc1:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestGatewayNodesDC1(t),
},
})
case "failover-through-double-local-gateway-triggered":
db2ChainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc2"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + dbChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesInStatus(t, "critical"),
},
},
UpdateEvent{
CorrelationID: "upstream-target:" + db2ChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesInStatusDC2(t, "critical"),
},
})
fallthrough
case "failover-through-double-local-gateway":
db3ChainID := makeChainID(structs.DiscoveryTargetOpts{Datacenter: "dc3"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + db3ChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesDC2(t),
},
})
events = append(events, UpdateEvent{
CorrelationID: "mesh-gateway:dc1:" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestGatewayNodesDC1(t),
},
})
case "splitter-with-resolver-redirect-multidc":
v1ChainID := makeChainID(structs.DiscoveryTargetOpts{ServiceSubset: "v1"})
v2ChainID := makeChainID(structs.DiscoveryTargetOpts{ServiceSubset: "v2", Datacenter: "dc2"})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + v1ChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodes(t, "db"),
},
})
events = append(events, UpdateEvent{
CorrelationID: "upstream-target:" + v2ChainID + ":" + dbUID.String(),
Result: &structs.IndexedCheckServiceNodes{
Nodes: TestUpstreamNodesDC2(t),
},
})
case "chain-and-splitter":
case "grpc-router":
case "chain-and-router":
case "lb-resolver":
case "register-to-terminating-gateway":
case "redirect-to-lb-node":
case "resolver-with-lb":
case "splitter-overweight":
default:
extraEvents := extraUpdateEvents(t, variation, dbUID)
events = append(events, extraEvents...)
}
return events
}
func setupTestVariationDiscoveryChain(
t testing.T,
variation string,
enterprise bool,
entMeta acl.EnterpriseMeta,
additionalEntries ...structs.ConfigEntry,
) *structs.CompiledDiscoveryChain {
// Compile a chain.
var (
peers []*pbpeering.Peering
entries []structs.ConfigEntry
compileSetup func(req *discoverychain.CompileRequest)
)
switch variation {
case "default":
// no config entries
case "register-to-terminating-gateway":
case "simple-with-overrides":
compileSetup = func(req *discoverychain.CompileRequest) {
req.OverrideMeshGateway.Mode = structs.MeshGatewayModeLocal
req.OverrideProtocol = "grpc"
req.OverrideConnectTimeout = 66 * time.Second
}
fallthrough
case "simple":
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
},
)
case "external-sni":
entries = append(entries,
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
EnterpriseMeta: entMeta,
ExternalSNI: "db.some.other.service.mesh",
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
},
)
case "failover":
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
Failover: map[string]structs.ServiceResolverFailover{
"*": {
Service: "fail",
},
},
},
)
case "failover-through-remote-gateway-triggered":
fallthrough
case "failover-through-remote-gateway":
entries = append(entries,
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
EnterpriseMeta: entMeta,
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
Failover: map[string]structs.ServiceResolverFailover{
"*": {
Datacenters: []string{"dc2"},
},
},
},
)
case "failover-to-cluster-peer":
target := structs.ServiceResolverFailoverTarget{
Peer: "cluster-01",
}
if enterprise {
target.Namespace = "ns9"
}
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
Failover: map[string]structs.ServiceResolverFailover{
"*": {
Targets: []structs.ServiceResolverFailoverTarget{target},
},
},
},
)
case "redirect-to-cluster-peer":
redirect := &structs.ServiceResolverRedirect{
Peer: "cluster-01",
}
if enterprise {
redirect.Namespace = "ns9"
}
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
Redirect: redirect,
},
)
case "failover-through-double-remote-gateway-triggered":
fallthrough
case "failover-through-double-remote-gateway":
entries = append(entries,
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
EnterpriseMeta: entMeta,
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
Failover: map[string]structs.ServiceResolverFailover{
"*": {
Datacenters: []string{"dc2", "dc3"},
},
},
},
)
case "failover-through-local-gateway-triggered":
fallthrough
case "failover-through-local-gateway":
entries = append(entries,
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
EnterpriseMeta: entMeta,
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeLocal,
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
Failover: map[string]structs.ServiceResolverFailover{
"*": {
Datacenters: []string{"dc2"},
},
},
},
)
case "failover-through-double-local-gateway-triggered":
fallthrough
case "failover-through-double-local-gateway":
entries = append(entries,
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
EnterpriseMeta: entMeta,
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeLocal,
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
Failover: map[string]structs.ServiceResolverFailover{
"*": {
Datacenters: []string{"dc2", "dc3"},
},
},
},
)
case "splitter-with-resolver-redirect-multidc":
em := acl.NewEnterpriseMetaWithPartition(entMeta.PartitionOrDefault(), acl.NamespaceOrDefault(""))
entries = append(entries,
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: em,
Protocol: "http",
Config: map[string]interface{}{
"protocol": "http",
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
Splits: []structs.ServiceSplit{
{Weight: 50, Service: "db-dc1"},
{Weight: 50, Service: "db-dc2"},
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db-dc1",
EnterpriseMeta: entMeta,
Redirect: &structs.ServiceResolverRedirect{
Service: "db",
ServiceSubset: "v1",
Datacenter: "dc1",
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db-dc2",
EnterpriseMeta: entMeta,
Redirect: &structs.ServiceResolverRedirect{
Service: "db",
ServiceSubset: "v2",
Datacenter: "dc2",
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {
Filter: "Service.Meta.version == v1",
},
"v2": {
Filter: "Service.Meta.version == v2",
},
},
},
)
case "chain-and-splitter":
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 25 * time.Second,
},
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: entMeta,
Protocol: "http",
Config: map[string]interface{}{
"protocol": "http",
},
},
// Adding a ServiceRouter in this case allows testing ServiceRoute.Destination timeouts.
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "db",
EnterpriseMeta: entMeta,
Routes: []structs.ServiceRoute{
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathPrefix: "/big-side",
},
},
Destination: &structs.ServiceRouteDestination{
Service: "big-side",
// Test disabling idle timeout.
IdleTimeout: -1 * time.Second,
// Test a positive value for request timeout.
RequestTimeout: 10 * time.Second,
},
},
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathPrefix: "/lil-bit-side",
},
},
Destination: &structs.ServiceRouteDestination{
Service: "lil-bit-side",
// Test zero values for these timeouts.
IdleTimeout: 0 * time.Second,
RequestTimeout: 0 * time.Second,
},
},
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "db",
EnterpriseMeta: entMeta,
Splits: []structs.ServiceSplit{
{
Weight: 1,
Service: "db",
RequestHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "db"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "db"},
},
},
{
Weight: 95.5,
Service: "big-side",
RequestHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "big"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "big"},
},
},
{
Weight: 3,
Service: "goldilocks-side",
RequestHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "goldilocks"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "goldilocks"},
},
},
{
Weight: 0.5,
Service: "lil-bit-side",
RequestHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "small"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "small"},
},
},
},
},
)
case "splitter-overweight":
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
},
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: entMeta,
Protocol: "http",
Config: map[string]interface{}{
"protocol": "http",
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "db",
EnterpriseMeta: entMeta,
Splits: []structs.ServiceSplit{
{
Weight: 100.0,
Service: "big-side",
RequestHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "big"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "big"},
},
},
{
Weight: 100.0,
Service: "goldilocks-side",
RequestHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "goldilocks"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "goldilocks"},
},
},
{
Weight: 100.0,
Service: "lil-bit-side",
RequestHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "small"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Set: map[string]string{"x-split-leg": "small"},
},
},
},
},
)
case "grpc-router":
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
},
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: entMeta,
Protocol: "grpc",
Config: map[string]interface{}{
"protocol": "grpc",
},
},
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "db",
EnterpriseMeta: entMeta,
Routes: []structs.ServiceRoute{
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathExact: "/fgrpc.PingServer/Ping",
},
},
Destination: &structs.ServiceRouteDestination{
Service: "prefix",
},
},
},
},
)
case "chain-and-router":
entries = append(entries,
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
ConnectTimeout: 33 * time.Second,
RequestTimeout: 33 * time.Second,
},
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: entMeta,
Protocol: "http",
Config: map[string]interface{}{
"protocol": "http",
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "split-3-ways",
EnterpriseMeta: entMeta,
Splits: []structs.ServiceSplit{
{Weight: 95.5, Service: "big-side"},
{Weight: 4, Service: "goldilocks-side"},
{Weight: 0.5, Service: "lil-bit-side"},
},
},
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "db",
EnterpriseMeta: entMeta,
Routes: []structs.ServiceRoute{
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/prefix",
}),
Destination: toService("prefix"),
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathExact: "/exact",
}),
Destination: toService("exact"),
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathRegex: "/regex",
}),
Destination: toService("regex"),
},
{
Match: httpMatchHeader(structs.ServiceRouteHTTPMatchHeader{
Name: "x-debug",
Present: true,
}),
Destination: toService("hdr-present"),
},
{
Match: httpMatchHeader(structs.ServiceRouteHTTPMatchHeader{
Name: "x-debug",
Present: true,
Invert: true,
}),
Destination: toService("hdr-not-present"),
},
{
Match: httpMatchHeader(structs.ServiceRouteHTTPMatchHeader{
Name: "x-debug",
Exact: "exact",
}),
Destination: toService("hdr-exact"),
},
{
Match: httpMatchHeader(structs.ServiceRouteHTTPMatchHeader{
Name: "x-debug",
Prefix: "prefix",
}),
Destination: toService("hdr-prefix"),
},
{
Match: httpMatchHeader(structs.ServiceRouteHTTPMatchHeader{
Name: "x-debug",
Suffix: "suffix",
}),
Destination: toService("hdr-suffix"),
},
{
Match: httpMatchHeader(structs.ServiceRouteHTTPMatchHeader{
Name: "x-debug",
Regex: "regex",
}),
Destination: toService("hdr-regex"),
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
Methods: []string{"GET", "PUT"},
}),
Destination: toService("just-methods"),
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
Header: []structs.ServiceRouteHTTPMatchHeader{
{
Name: "x-debug",
Exact: "exact",
},
},
Methods: []string{"GET", "PUT"},
}),
Destination: toService("hdr-exact-with-method"),
},
{
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
Name: "secretparam1",
Exact: "exact",
}),
Destination: toService("prm-exact"),
},
{
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
Name: "secretparam2",
Regex: "regex",
}),
Destination: toService("prm-regex"),
},
{
Match: httpMatchParam(structs.ServiceRouteHTTPMatchQueryParam{
Name: "secretparam3",
Present: true,
}),
Destination: toService("prm-present"),
},
{
Match: nil,
Destination: toService("nil-match"),
},
{
Match: &structs.ServiceRouteMatch{},
Destination: toService("empty-match-1"),
},
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{},
},
Destination: toService("empty-match-2"),
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/prefix",
}),
Destination: &structs.ServiceRouteDestination{
Service: "prefix-rewrite-1",
PrefixRewrite: "/",
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/prefix",
}),
Destination: &structs.ServiceRouteDestination{
Service: "prefix-rewrite-2",
PrefixRewrite: "/nested/newlocation",
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/timeout",
}),
Destination: &structs.ServiceRouteDestination{
Service: "req-timeout",
RequestTimeout: 33 * time.Second,
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/idle-timeout",
}),
Destination: &structs.ServiceRouteDestination{
Service: "idle-timeout",
IdleTimeout: 33 * time.Second,
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/retry-connect",
}),
Destination: &structs.ServiceRouteDestination{
Service: "retry-connect",
NumRetries: 15,
RetryOnConnectFailure: true,
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/retry-reset",
}),
Destination: &structs.ServiceRouteDestination{
Service: "retry-reset",
NumRetries: 15,
RetryOn: []string{"reset"},
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/retry-codes",
}),
Destination: &structs.ServiceRouteDestination{
Service: "retry-codes",
NumRetries: 15,
RetryOnStatusCodes: []uint32{401, 409, 451},
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/retry-all",
}),
Destination: &structs.ServiceRouteDestination{
Service: "retry-all",
RetryOnConnectFailure: true,
RetryOn: []string{"5xx", "gateway-error", "reset", "connect-failure", "envoy-ratelimited", "retriable-4xx", "refused-stream", "cancelled", "deadline-exceeded", "internal", "resource-exhausted", "unavailable"},
RetryOnStatusCodes: []uint32{401, 409, 451},
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/split-3-ways",
}),
Destination: toService("split-3-ways"),
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathExact: "/header-manip",
}),
Destination: &structs.ServiceRouteDestination{
Service: "header-manip",
RequestHeaders: &structs.HTTPHeaderModifiers{
Add: map[string]string{
"request": "bar",
},
Set: map[string]string{
"bar": "baz",
},
Remove: []string{"qux"},
},
ResponseHeaders: &structs.HTTPHeaderModifiers{
Add: map[string]string{
"response": "bar",
},
Set: map[string]string{
"bar": "baz",
},
Remove: []string{"qux"},
},
},
},
},
},
)
case "lb-resolver":
entries = append(entries,
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: entMeta,
Protocol: "http",
Config: map[string]interface{}{
"protocol": "http",
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "db",
EnterpriseMeta: entMeta,
Splits: []structs.ServiceSplit{
{Weight: 95.5, Service: "something-else"},
{Weight: 4.5, Service: "db"},
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
LoadBalancer: &structs.LoadBalancer{
Policy: "ring_hash",
RingHashConfig: &structs.RingHashConfig{
MinimumRingSize: 20,
MaximumRingSize: 30,
},
HashPolicies: []structs.HashPolicy{
{
Field: "cookie",
FieldValue: "chocolate-chip",
Terminal: true,
},
{
Field: "cookie",
FieldValue: "chocolate-chip",
CookieConfig: &structs.CookieConfig{Session: true},
},
{
Field: "header",
FieldValue: "x-user-id",
},
{
Field: "query_parameter",
FieldValue: "my-pretty-param",
},
{
SourceIP: true,
Terminal: true,
},
},
},
})
case "redirect-to-lb-node":
entries = append(entries,
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: entMeta,
Protocol: "http",
Config: map[string]interface{}{
"protocol": "http",
},
},
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "db",
EnterpriseMeta: entMeta,
Routes: []structs.ServiceRoute{
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/web",
}),
Destination: toService("web"),
},
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "web",
EnterpriseMeta: entMeta,
LoadBalancer: &structs.LoadBalancer{
Policy: "ring_hash",
RingHashConfig: &structs.RingHashConfig{
MinimumRingSize: 20,
MaximumRingSize: 30,
},
},
},
)
case "resolver-with-lb":
entries = append(entries,
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
EnterpriseMeta: entMeta,
Protocol: "http",
Config: map[string]interface{}{
"protocol": "http",
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
EnterpriseMeta: entMeta,
LoadBalancer: &structs.LoadBalancer{
Policy: "ring_hash",
RingHashConfig: &structs.RingHashConfig{
MinimumRingSize: 20,
MaximumRingSize: 30,
},
},
},
)
default:
e, p := extraDiscoChainConfig(t, variation, entMeta)
entries = append(entries, e...)
peers = append(peers, p...)
}
if len(additionalEntries) > 0 {
entries = append(entries, additionalEntries...)
}
set := configentry.NewDiscoveryChainSet()
set.AddEntries(entries...)
set.AddPeers(peers...)
return discoverychain.TestCompileConfigEntries(t, "db", entMeta.NamespaceOrDefault(), entMeta.PartitionOrDefault(), "dc1", connect.TestClusterID+".consul", compileSetup, set)
}
func httpMatch(http *structs.ServiceRouteHTTPMatch) *structs.ServiceRouteMatch {
return &structs.ServiceRouteMatch{HTTP: http}
}
func httpMatchHeader(headers ...structs.ServiceRouteHTTPMatchHeader) *structs.ServiceRouteMatch {
return httpMatch(&structs.ServiceRouteHTTPMatch{
Header: headers,
})
}
func httpMatchParam(params ...structs.ServiceRouteHTTPMatchQueryParam) *structs.ServiceRouteMatch {
return httpMatch(&structs.ServiceRouteHTTPMatch{
QueryParam: params,
})
}
func toService(svc string) *structs.ServiceRouteDestination {
return &structs.ServiceRouteDestination{Service: svc}
}