// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 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/pbcommon" "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 "order-by-locality-failover": cluster1UID := UpstreamID{ Name: "db", Peer: "cluster-01", EnterpriseMeta: acl.NewEnterpriseMetaWithPartition(dbUID.PartitionOrDefault(), ""), } cluster2UID := UpstreamID{ Name: "db", Peer: "cluster-02", EnterpriseMeta: acl.NewEnterpriseMetaWithPartition(dbUID.PartitionOrDefault(), ""), } chainID := makeChainID(structs.DiscoveryTargetOpts{Service: "db-v2"}) events = append(events, UpdateEvent{ CorrelationID: "upstream-target:" + chainID + ":" + dbUID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: TestUpstreamNodesAlternate(t), }, }, 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"}, }, }, }, UpdateEvent{ CorrelationID: "peer-trust-bundle:cluster-02", Result: &pbpeering.TrustBundleReadResponse{ Bundle: &pbpeering.PeeringTrustBundle{ PeerName: "peer2", TrustDomain: "peer2.domain", ExportedPartition: "peer2ap", RootPEMs: []string{"peer2-root-2"}, }, }, }, UpdateEvent{ CorrelationID: "upstream-peer:" + cluster1UID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc1", "cluster-01", "10.40.1.1", false, cluster1UID.EnterpriseMeta)}, }, }, UpdateEvent{ CorrelationID: "upstream-peer:" + cluster2UID.String(), Result: &structs.IndexedCheckServiceNodes{ Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "dc2", "cluster-02", "10.40.1.2", false, cluster2UID.EnterpriseMeta)}, }, }, ) 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": default: t.Fatalf("unexpected variation: %q", variation) return nil } 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 "order-by-locality-failover": peers = append(peers, &pbpeering.Peering{ Name: "cluster-01", Remote: &pbpeering.RemoteInfo{ Locality: &pbcommon.Locality{Region: "us-west-1"}, }, }, &pbpeering.Peering{ Name: "cluster-02", Remote: &pbpeering.RemoteInfo{ Locality: &pbcommon.Locality{Region: "us-west-2"}, }, }) cluster1Target := structs.ServiceResolverFailoverTarget{ Peer: "cluster-01", } cluster2Target := structs.ServiceResolverFailoverTarget{ Peer: "cluster-02", } 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{ "*": { Policy: &structs.ServiceResolverFailoverPolicy{ Mode: "order-by-locality", Regions: []string{"us-west-2", "us-west-1"}, }, Targets: []structs.ServiceResolverFailoverTarget{ cluster1Target, cluster2Target, {Service: "db-v2"}, }, }, }, }, ) 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, 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: 33 * time.Second, RequestTimeout: 33 * time.Second, }, &structs.ProxyConfigEntry{ Kind: structs.ProxyDefaults, Name: structs.ProxyConfigGlobal, EnterpriseMeta: entMeta, Config: map[string]interface{}{ "protocol": "http", }, }, &structs.ServiceSplitterConfigEntry{ Kind: structs.ServiceSplitter, Name: "db", EnterpriseMeta: entMeta, Splits: []structs.ServiceSplit{ { 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: 4, 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 "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, 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, 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, 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", }, { SourceIP: true, Terminal: true, }, }, }, }, ) default: t.Fatalf("unexpected variation: %q", variation) return nil } 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} }