Skip to content

Commit

Permalink
NET-6080 - xds controller golden file inputs into xds resources - des…
Browse files Browse the repository at this point in the history
…tinations (#19244)

* NET-5397 - wire up golden tests from sidecar-proxy controller for xds controller and xdsv2

* WIP

* WIP

* everything matching except leafCerts.  need to mock those

* single port destinations working except mixed destinations

* golden test input to xds controller tests for destinations

* proposed fix for failover group naming errors

* clean up test to use helper.

* clean up test to use helper.

* fix test file

* add docstring for test function.

* add docstring for test function.

* fix linting error

* fixing test after route fix merged into main

* first source test works

* WIP

* modify all source files

* source tests pass

* fixing tests after bug fix in main

* got first destination working.

* adding destinations

* fix docstring for test

* fixing tests after bug fix in main
  • Loading branch information
jmurret authored Oct 24, 2023
1 parent 59d4962 commit abbf858
Show file tree
Hide file tree
Showing 43 changed files with 3,388 additions and 249 deletions.
178 changes: 108 additions & 70 deletions agent/xdsv2/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package xdsv2

import (
"os"
"path/filepath"
"fmt"
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/hashicorp/consul/internal/testing/golden"
"sort"
"testing"

Expand All @@ -23,62 +26,111 @@ import (
"google.golang.org/protobuf/proto"
)

func TestResources_ImplicitDestinations(t *testing.T) {
var testTypeUrlToPrettyName = map[string]string{
xdscommon.ListenerType: "listeners",
xdscommon.RouteType: "routes",
xdscommon.ClusterType: "clusters",
xdscommon.EndpointType: "endpoints",
xdscommon.SecretType: "secrets",
}

cases := map[string]struct {
}{
"l4-single-implicit-destination-tproxy": {},
// TestAllResourcesFromIR_XDSGoldenFileInputs tests the AllResourcesFromIR() by
// using the golden test output/expected files from the XDS controller tests as
// inputs to the XDSV2 resources generation.
func TestAllResourcesFromIR_XDSGoldenFileInputs(t *testing.T) {
inputPath := "../../internal/mesh/internal/controllers/xds"

cases := []string{
// destinations - please add in alphabetical order
"destination/l4-single-destination-ip-port-bind-address",
"destination/l4-single-destination-unix-socket-bind-address",
"destination/l4-single-implicit-destination-tproxy",
"destination/l4-multi-destination",
"destination/l4-multiple-implicit-destinations-tproxy",
"destination/l4-implicit-and-explicit-destinations-tproxy",
"destination/mixed-multi-destination",
"destination/multiport-l4-and-l7-multiple-implicit-destinations-tproxy",
"destination/multiport-l4-and-l7-single-implicit-destination-tproxy",
"destination/multiport-l4-and-l7-single-implicit-destination-with-multiple-workloads-tproxy",

//sources - please add in alphabetical order
//"source/l4-multiple-workload-addresses-with-specific-ports",
//"source/l4-multiple-workload-addresses-without-ports",
//"source/l4-single-workload-address-without-ports",
//"source/l7-expose-paths",
//"source/local-and-inbound-connections",
//"source/multiport-l4-multiple-workload-addresses-with-specific-ports",
//"source/multiport-l4-multiple-workload-addresses-without-ports",
//"source/multiport-l4-workload-with-only-mesh-port",
//"source/multiport-l7-multiple-workload-addresses-with-specific-ports",
//"source/multiport-l7-multiple-workload-addresses-without-ports",
//"source/multiport-l7-multiple-workload-addresses-without-ports",
}

for name := range cases {
goldenValueInput := goldenValueJSON(t, name, "input")

proxyTemplate := jsonToProxyTemplate(t, goldenValueInput)
generator := NewResourceGenerator(testutil.Logger(t))

resources, err := generator.AllResourcesFromIR(&proxytracker.ProxyState{ProxyState: proxyTemplate.ProxyState})
require.NoError(t, err)

verifyClusterResourcesToGolden(t, resources, name)
verifyListenerResourcesToGolden(t, resources, name)

for _, name := range cases {
t.Run(name, func(t *testing.T) {
// Arrange - paths to input and output golden files.
testFile := fmt.Sprintf("%s.golden", name)
inputFilePath := fmt.Sprintf("%s/testdata/%s", inputPath, testFile)
inputValueInput := golden.GetBytesAtFilePath(t, inputFilePath)

// Act.
ps := jsonToProxyState(t, inputValueInput)
generator := NewResourceGenerator(testutil.Logger(t))
resources, err := generator.AllResourcesFromIR(&proxytracker.ProxyState{ProxyState: ps})
require.NoError(t, err)

// Assert.
// Assert all resources were generated.
typeUrls := []string{
xdscommon.ListenerType,
xdscommon.RouteType,
xdscommon.ClusterType,
xdscommon.EndpointType,
// TODO(proxystate): add in future
//xdscommon.SecretType,
}
require.Len(t, resources, len(typeUrls))

// Assert each resource type has actual XDS matching expected XDS.
for _, typeUrl := range typeUrls {
prettyName := testTypeUrlToPrettyName[typeUrl]
t.Run(prettyName, func(t *testing.T) {
items, ok := resources[typeUrl]
require.True(t, ok)

// sort resources so they don't show up as flakey tests as
// ordering in JSON is not guaranteed.
sort.Slice(items, func(i, j int) bool {
switch typeUrl {
case xdscommon.ListenerType:
return items[i].(*envoy_listener_v3.Listener).Name < items[j].(*envoy_listener_v3.Listener).Name
case xdscommon.RouteType:
return items[i].(*envoy_route_v3.RouteConfiguration).Name < items[j].(*envoy_route_v3.RouteConfiguration).Name
case xdscommon.ClusterType:
return items[i].(*envoy_cluster_v3.Cluster).Name < items[j].(*envoy_cluster_v3.Cluster).Name
case xdscommon.EndpointType:
return items[i].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName < items[j].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName
case xdscommon.SecretType:
return items[i].(*envoy_tls_v3.Secret).Name < items[j].(*envoy_tls_v3.Secret).Name
default:
panic("not possible")
}
})

// Compare actual to expected.
resp, err := response.CreateResponse(typeUrl, "00000001", "00000001", items)
require.NoError(t, err)
gotJSON := protoToJSON(t, resp)

expectedJSON := golden.Get(t, gotJSON, fmt.Sprintf("%s/%s", prettyName, testFile))
require.JSONEq(t, expectedJSON, gotJSON)
})
}
})
}
}

func verifyClusterResourcesToGolden(t *testing.T, resources map[string][]proto.Message, testName string) {
clusters := resources[xdscommon.ClusterType]

// The order of clusters returned via CDS isn't relevant, so it's safe
// to sort these for the purposes of test comparisons.
sort.Slice(clusters, func(i, j int) bool {
return clusters[i].(*envoy_cluster_v3.Cluster).Name < clusters[j].(*envoy_cluster_v3.Cluster).Name
})

resp, err := response.CreateResponse(xdscommon.ClusterType, "00000001", "00000001", clusters)
require.NoError(t, err)
gotJSON := protoToJSON(t, resp)

expectedJSON := goldenValue(t, filepath.Join("clusters", testName), "output")
require.JSONEq(t, expectedJSON, gotJSON)
}

func verifyListenerResourcesToGolden(t *testing.T, resources map[string][]proto.Message, testName string) {
listeners := resources[xdscommon.ListenerType]

// The order of clusters returned via CDS isn't relevant, so it's safe
// to sort these for the purposes of test comparisons.
sort.Slice(listeners, func(i, j int) bool {
return listeners[i].(*envoy_listener_v3.Listener).Name < listeners[j].(*envoy_listener_v3.Listener).Name
})

resp, err := response.CreateResponse(xdscommon.ListenerType, "00000001", "00000001", listeners)
require.NoError(t, err)
gotJSON := protoToJSON(t, resp)

expectedJSON := goldenValue(t, filepath.Join("listeners", testName), "output")
require.JSONEq(t, expectedJSON, gotJSON)
}

func protoToJSON(t *testing.T, pb proto.Message) string {
t.Helper()
m := protojson.MarshalOptions{
Expand All @@ -89,25 +141,11 @@ func protoToJSON(t *testing.T, pb proto.Message) string {
return string(gotJSON)
}

func jsonToProxyTemplate(t *testing.T, json []byte) *meshv2beta1.ProxyStateTemplate {
func jsonToProxyState(t *testing.T, json []byte) *meshv2beta1.ProxyState {
t.Helper()
um := protojson.UnmarshalOptions{}
proxyTemplate := &meshv2beta1.ProxyStateTemplate{}
err := um.Unmarshal(json, proxyTemplate)
require.NoError(t, err)
return proxyTemplate
}

func goldenValueJSON(t *testing.T, goldenFile, inputOutput string) []byte {
t.Helper()
goldenPath := filepath.Join("testdata", inputOutput, goldenFile) + ".golden"

content, err := os.ReadFile(goldenPath)
ps := &meshv2beta1.ProxyState{}
err := um.Unmarshal(json, ps)
require.NoError(t, err)
return content
}

func goldenValue(t *testing.T, goldenFile, inputOutput string) string {
t.Helper()
return string(goldenValueJSON(t, goldenFile, inputOutput))
return ps
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "original-destination",
"type": "ORIGINAL_DST",
"connectTimeout": "5s",
"lbPolicy": "CLUSTER_PROVIDED"
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "tcp.api-1.default.dc1.internal.foo.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"commonTlsContext": {
"tlsParams": {},
"tlsCertificates": [
{
"certificateChain": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICDjCCAbWgAwIBAgIBAjAKBggqhkjOPQQDAjAUMRIwEAYDVQQDEwlUZXN0IENB\nIDEwHhcNMjMxMDE2MTYxMzI5WhcNMjMxMDE2MTYyMzI5WjAAMFkwEwYHKoZIzj0C\nAQYIKoZIzj0DAQcDQgAErErAIosDPheZQGbxFQ4hYC/e9Fi4MG9z/zjfCnCq/oK9\nta/bGT+5orZqTmdN/ICsKQDhykxZ2u/Xr6845zhcJaOCAQowggEGMA4GA1UdDwEB\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\nBAIwADApBgNVHQ4EIgQg3ogXVz9cqaK2B6xdiJYMa5NtT0KkYv7BA2dR7h9EcwUw\nKwYDVR0jBCQwIoAgq+C1mPlPoGa4lt7sSft1goN5qPGyBIB/3mUHJZKSFY8wbwYD\nVR0RAQH/BGUwY4Zhc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9hcC9kZWZhdWx0L25zL2RlZmF1bHQvaWRlbnRpdHkv\ndGVzdC1pZGVudGl0eTAKBggqhkjOPQQDAgNHADBEAiB6L+t5bzRrBPhiQYNeA7fF\nUCuLWrdjW4Xbv3SLg0IKMgIgfRC5hEx+DqzQxTCP4sexX3hVWMjKoWmHdwiUcg+K\n/IE=\n-----END CERTIFICATE-----\n"
},
"privateKey": {
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIFIFkTIL1iUV4O/RpveVHzHs7ZzhSkvYIzbdXDttz9EooAoGCCqGSM49\nAwEHoUQDQgAErErAIosDPheZQGbxFQ4hYC/e9Fi4MG9z/zjfCnCq/oK9ta/bGT+5\norZqTmdN/ICsKQDhykxZ2u/Xr6845zhcJQ==\n-----END EC PRIVATE KEY-----\n"
}
}
],
"validationContext": {
"trustedCa": {
"inlineString": "some-root\nsome-other-root\n"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://foo.consul/ap/default/ns/default/identity/api1-identity"
}
]
},
"alpnProtocols": [
"consul~tcp"
]
},
"sni": "api-1.default.dc1.internal.foo.consul"
}
}
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "tcp.api-2.default.dc1.internal.foo.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"commonTlsContext": {
"tlsParams": {},
"tlsCertificates": [
{
"certificateChain": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICDjCCAbWgAwIBAgIBAjAKBggqhkjOPQQDAjAUMRIwEAYDVQQDEwlUZXN0IENB\nIDEwHhcNMjMxMDE2MTYxMzI5WhcNMjMxMDE2MTYyMzI5WjAAMFkwEwYHKoZIzj0C\nAQYIKoZIzj0DAQcDQgAErErAIosDPheZQGbxFQ4hYC/e9Fi4MG9z/zjfCnCq/oK9\nta/bGT+5orZqTmdN/ICsKQDhykxZ2u/Xr6845zhcJaOCAQowggEGMA4GA1UdDwEB\n/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH/\nBAIwADApBgNVHQ4EIgQg3ogXVz9cqaK2B6xdiJYMa5NtT0KkYv7BA2dR7h9EcwUw\nKwYDVR0jBCQwIoAgq+C1mPlPoGa4lt7sSft1goN5qPGyBIB/3mUHJZKSFY8wbwYD\nVR0RAQH/BGUwY4Zhc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9hcC9kZWZhdWx0L25zL2RlZmF1bHQvaWRlbnRpdHkv\ndGVzdC1pZGVudGl0eTAKBggqhkjOPQQDAgNHADBEAiB6L+t5bzRrBPhiQYNeA7fF\nUCuLWrdjW4Xbv3SLg0IKMgIgfRC5hEx+DqzQxTCP4sexX3hVWMjKoWmHdwiUcg+K\n/IE=\n-----END CERTIFICATE-----\n"
},
"privateKey": {
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIFIFkTIL1iUV4O/RpveVHzHs7ZzhSkvYIzbdXDttz9EooAoGCCqGSM49\nAwEHoUQDQgAErErAIosDPheZQGbxFQ4hYC/e9Fi4MG9z/zjfCnCq/oK9ta/bGT+5\norZqTmdN/ICsKQDhykxZ2u/Xr6845zhcJQ==\n-----END EC PRIVATE KEY-----\n"
}
}
],
"validationContext": {
"trustedCa": {
"inlineString": "some-root\nsome-other-root\n"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://foo.consul/ap/default/ns/default/identity/api2-identity"
}
]
},
"alpnProtocols": [
"consul~tcp"
]
},
"sni": "api-2.default.dc1.internal.foo.consul"
}
}
}
],
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"nonce": "00000001"
}
Loading

0 comments on commit abbf858

Please sign in to comment.