consul/agent/xds/serverless_plugin_oss_test.go

196 lines
6.3 KiB
Go

//go:build !consulent
// +build !consulent
package xds
import (
"path/filepath"
"sort"
"testing"
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
"github.com/golang/protobuf/proto"
testinf "github.com/mitchellh/go-testing-interface"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/xds/proxysupport"
"github.com/hashicorp/consul/agent/xds/serverlessplugin"
"github.com/hashicorp/consul/agent/xds/xdscommon"
"github.com/hashicorp/consul/sdk/testutil"
)
func TestServerlessPluginFromSnapshot(t *testing.T) {
// If opposite is true, the returned service defaults config entry will have
// payload-passthrough=true and invocation-mode=asynchronous.
// Otherwise payload-passthrough=false and invocation-mode=synchronous.
// This is used to test all the permutations.
makeServiceDefaults := func(opposite bool) *structs.ServiceConfigEntry {
payloadPassthrough := true
if opposite {
payloadPassthrough = false
}
invocationMode := "synchronous"
if opposite {
invocationMode = "asynchronous"
}
return &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
Protocol: "http",
EnvoyExtensions: []structs.EnvoyExtension{
{
Name: structs.BuiltinAWSLambdaExtension,
Arguments: map[string]interface{}{
"ARN": "lambda-arn",
"PayloadPassthrough": payloadPassthrough,
"InvocationMode": invocationMode,
"Region": "us-east-1",
},
},
},
}
}
tests := []struct {
name string
create func(t testinf.T) *proxycfg.ConfigSnapshot
}{
{
name: "lambda-connect-proxy",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeServiceDefaults(false))
},
},
// Make sure that if the upstream type is different from ExtensionConfiguration.Kind is, that the resources are not patched.
{
name: "lambda-connect-proxy-with-terminating-gateway-upstream",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "register-to-terminating-gateway", nil, nil, makeServiceDefaults(false))
},
},
{
name: "lambda-connect-proxy-opposite-meta",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "default", nil, nil, makeServiceDefaults(true))
},
},
{
name: "lambda-terminating-gateway",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaService(t)
},
},
{
name: "lambda-terminating-gateway-with-service-resolvers",
create: proxycfg.TestConfigSnapshotTerminatingGatewayWithLambdaServiceAndServiceResolvers,
},
}
latestEnvoyVersion := proxysupport.EnvoyVersions[0]
for _, envoyVersion := range proxysupport.EnvoyVersions {
sf, err := determineSupportedProxyFeaturesFromString(envoyVersion)
require.NoError(t, err)
t.Run("envoy-"+envoyVersion, func(t *testing.T) {
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!
setupTLSRootsAndLeaf(t, snap)
g := newResourceGenerator(testutil.Logger(t), nil, false)
g.ProxyFeatures = sf
res, err := g.allResourcesFromSnapshot(snap)
require.NoError(t, err)
indexedResources := indexResources(g.Logger, res)
var newResourceMap *xdscommon.IndexedResources
cfgs := xdscommon.GetExtensionConfigurations(snap)
for _, extensions := range cfgs {
for _, ext := range extensions {
switch ext.EnvoyExtension.Name {
case structs.BuiltinAWSLambdaExtension:
newResourceMap, err = serverlessplugin.Extend(indexedResources, ext)
require.NoError(t, err)
}
}
}
entities := []struct {
name string
key string
sorter func([]proto.Message) func(int, int) bool
}{
{
name: "clusters",
key: xdscommon.ClusterType,
sorter: func(msgs []proto.Message) func(int, int) bool {
return func(i, j int) bool {
return msgs[i].(*envoy_cluster_v3.Cluster).Name < msgs[j].(*envoy_cluster_v3.Cluster).Name
}
},
},
{
name: "listeners",
key: xdscommon.ListenerType,
sorter: func(msgs []proto.Message) func(int, int) bool {
return func(i, j int) bool {
return msgs[i].(*envoy_listener_v3.Listener).Name < msgs[j].(*envoy_listener_v3.Listener).Name
}
},
},
{
name: "routes",
key: xdscommon.RouteType,
sorter: func(msgs []proto.Message) func(int, int) bool {
return func(i, j int) bool {
return msgs[i].(*envoy_route_v3.RouteConfiguration).Name < msgs[j].(*envoy_route_v3.RouteConfiguration).Name
}
},
},
{
name: "endpoints",
key: xdscommon.EndpointType,
sorter: func(msgs []proto.Message) func(int, int) bool {
return func(i, j int) bool {
return msgs[i].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName < msgs[j].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName
}
},
},
}
for _, entity := range entities {
var msgs []proto.Message
for _, e := range newResourceMap.Index[entity.key] {
msgs = append(msgs, e)
}
sort.Slice(msgs, entity.sorter(msgs))
r, err := createResponse(entity.key, "00000001", "00000001", msgs)
require.NoError(t, err)
t.Run(entity.name, func(t *testing.T) {
gotJSON := protoToJSON(t, r)
require.JSONEq(t, goldenEnvoy(t,
filepath.Join("serverless_plugin", entity.name, tt.name),
envoyVersion, latestEnvoyVersion, gotJSON), gotJSON)
})
}
})
}
})
}
}