mirror of https://github.com/hashicorp/consul
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.
364 lines
8.6 KiB
364 lines
8.6 KiB
package validate |
|
|
|
import ( |
|
"testing" |
|
|
|
envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" |
|
envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" |
|
envoy_aggregate_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3" |
|
"github.com/hashicorp/consul/api" |
|
"github.com/hashicorp/consul/envoyextensions/extensioncommon" |
|
"github.com/stretchr/testify/require" |
|
"google.golang.org/protobuf/types/known/anypb" |
|
) |
|
|
|
func TestErrors(t *testing.T) { |
|
cases := map[string]struct { |
|
validate func() *Validate |
|
endpointValidator EndpointValidator |
|
err string |
|
}{ |
|
"success": { |
|
validate: func() *Validate { |
|
return &Validate{ |
|
envoyID: "db", |
|
snis: map[string]struct{}{ |
|
"db-sni": {}, |
|
}, |
|
listener: true, |
|
usesRDS: true, |
|
route: true, |
|
resources: map[string]*resource{ |
|
"db-sni": { |
|
required: true, |
|
cluster: true, |
|
}, |
|
}, |
|
} |
|
}, |
|
endpointValidator: func(r *resource, s string, clusters *envoy_admin_v3.Clusters) { |
|
r.loadAssignment = true |
|
r.endpoints = 1 |
|
}, |
|
}, |
|
"no clusters for listener or route": { |
|
validate: func() *Validate { |
|
return &Validate{ |
|
envoyID: "db", |
|
snis: map[string]struct{}{ |
|
"db-sni": {}, |
|
}, |
|
listener: true, |
|
usesRDS: true, |
|
route: true, |
|
resources: map[string]*resource{ |
|
"db-sni": { |
|
required: false, |
|
cluster: true, |
|
}, |
|
}, |
|
} |
|
}, |
|
endpointValidator: func(r *resource, s string, clusters *envoy_admin_v3.Clusters) { |
|
r.loadAssignment = true |
|
r.endpoints = 1 |
|
}, |
|
err: "No clusters found on route or listener", |
|
}, |
|
"no healthy endpoints": { |
|
validate: func() *Validate { |
|
return &Validate{ |
|
envoyID: "db", |
|
snis: map[string]struct{}{ |
|
"db-sni": {}, |
|
}, |
|
listener: true, |
|
usesRDS: true, |
|
route: true, |
|
resources: map[string]*resource{ |
|
"db-sni": { |
|
required: true, |
|
cluster: true, |
|
}, |
|
}, |
|
} |
|
}, |
|
endpointValidator: func(r *resource, s string, clusters *envoy_admin_v3.Clusters) { |
|
r.loadAssignment = true |
|
}, |
|
err: "No healthy endpoints for cluster \"db-sni\" for upstream \"db\"", |
|
}, |
|
"success: aggregate cluster with one target with endpoints": { |
|
validate: func() *Validate { |
|
return &Validate{ |
|
envoyID: "db", |
|
snis: map[string]struct{}{ |
|
"db-sni": {}, |
|
"db-fail-1-sni": {}, |
|
"db-fail-2-sni": {}, |
|
}, |
|
listener: true, |
|
usesRDS: true, |
|
route: true, |
|
resources: map[string]*resource{ |
|
"db-sni": { |
|
required: true, |
|
cluster: true, |
|
aggregateCluster: true, |
|
aggregateClusterChildren: []string{ |
|
"db-fail-1-sni", |
|
"db-fail-2-sni", |
|
}, |
|
}, |
|
"db-fail-1-sni": { |
|
required: true, |
|
cluster: true, |
|
parentCluster: "db-sni", |
|
// This doesn't usually get set here, but this tests that at least one child cluster has |
|
// healthy endpoints case. |
|
endpoints: 1, |
|
}, |
|
"db-fail-2-sni": { |
|
required: true, |
|
cluster: true, |
|
parentCluster: "db-sni", |
|
}, |
|
}, |
|
} |
|
}, |
|
endpointValidator: func(r *resource, s string, clusters *envoy_admin_v3.Clusters) { |
|
r.loadAssignment = true |
|
}, |
|
}, |
|
"aggregate cluster no healthy endpoints": { |
|
validate: func() *Validate { |
|
return &Validate{ |
|
envoyID: "db", |
|
snis: map[string]struct{}{ |
|
"db-sni": {}, |
|
"db-fail-1-sni": {}, |
|
"db-fail-2-sni": {}, |
|
}, |
|
listener: true, |
|
usesRDS: true, |
|
route: true, |
|
resources: map[string]*resource{ |
|
"db-sni": { |
|
required: true, |
|
cluster: true, |
|
aggregateCluster: true, |
|
aggregateClusterChildren: []string{ |
|
"db-fail-1-sni", |
|
"db-fail-2-sni", |
|
}, |
|
}, |
|
"db-fail-1-sni": { |
|
required: true, |
|
cluster: true, |
|
parentCluster: "db-sni", |
|
}, |
|
"db-fail-2-sni": { |
|
required: true, |
|
cluster: true, |
|
parentCluster: "db-sni", |
|
}, |
|
}, |
|
} |
|
}, |
|
endpointValidator: func(r *resource, s string, clusters *envoy_admin_v3.Clusters) { |
|
r.loadAssignment = true |
|
r.endpoints = 0 |
|
}, |
|
err: "No healthy endpoints for aggregate cluster \"db-sni\" for upstream \"db\"", |
|
}, |
|
"success: passthrough cluster doesn't error even though there are zero endpoints": { |
|
validate: func() *Validate { |
|
return &Validate{ |
|
envoyID: "db", |
|
snis: map[string]struct{}{ |
|
"passthrough~db-sni": {}, |
|
}, |
|
listener: true, |
|
usesRDS: true, |
|
route: true, |
|
resources: map[string]*resource{ |
|
"passthrough~db-sni": { |
|
required: true, |
|
cluster: true, |
|
}, |
|
}, |
|
} |
|
}, |
|
endpointValidator: func(r *resource, s string, clusters *envoy_admin_v3.Clusters) { |
|
r.loadAssignment = true |
|
}, |
|
}, |
|
} |
|
|
|
for n, tc := range cases { |
|
t.Run(n, func(t *testing.T) { |
|
v := tc.validate() |
|
messages := v.GetMessages(true, tc.endpointValidator, nil) |
|
|
|
var outputErrors string |
|
for _, msgError := range messages.Errors() { |
|
outputErrors += msgError.Message |
|
for _, action := range msgError.PossibleActions { |
|
outputErrors += action |
|
} |
|
} |
|
if tc.err == "" { |
|
require.True(t, messages.Success()) |
|
} else { |
|
require.Contains(t, outputErrors, tc.err) |
|
} |
|
|
|
}) |
|
} |
|
|
|
} |
|
|
|
func TestIsAggregateCluster(t *testing.T) { |
|
aggregateClusterConfig, err := anypb.New(&envoy_aggregate_cluster_v3.ClusterConfig{ |
|
Clusters: []string{"c1", "c2"}, |
|
}) |
|
require.NoError(t, err) |
|
|
|
cases := map[string]struct { |
|
input *envoy_cluster_v3.Cluster |
|
expectedAggregateCluster *envoy_aggregate_cluster_v3.ClusterConfig |
|
expectedOk bool |
|
}{ |
|
"non-aggregate cluster": { |
|
input: &envoy_cluster_v3.Cluster{ |
|
Name: "foo", |
|
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{Type: envoy_cluster_v3.Cluster_LOGICAL_DNS}, |
|
}, |
|
expectedOk: false, |
|
}, |
|
"valid aggregate cluster": { |
|
input: &envoy_cluster_v3.Cluster{ |
|
Name: "foo", |
|
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_ClusterType{ |
|
ClusterType: &envoy_cluster_v3.Cluster_CustomClusterType{ |
|
Name: "foo", |
|
TypedConfig: aggregateClusterConfig, |
|
}, |
|
}, |
|
}, |
|
expectedOk: true, |
|
expectedAggregateCluster: &envoy_aggregate_cluster_v3.ClusterConfig{Clusters: []string{"c1", "c2"}}, |
|
}, |
|
} |
|
for n, tc := range cases { |
|
t.Run(n, func(t *testing.T) { |
|
ac, ok := isAggregateCluster(tc.input) |
|
require.Equal(t, tc.expectedOk, ok) |
|
|
|
if tc.expectedOk { |
|
require.Equal(t, tc.expectedAggregateCluster.Clusters, ac.Clusters) |
|
} |
|
|
|
}) |
|
} |
|
} |
|
|
|
func TestMakeValidate(t *testing.T) { |
|
|
|
cases := map[string]struct { |
|
extensionName string |
|
arguments map[string]interface{} |
|
expected *Validate |
|
snis map[string]struct{} |
|
ok bool |
|
}{ |
|
"with no arguments": { |
|
arguments: nil, |
|
ok: false, |
|
}, |
|
"with an invalid name": { |
|
arguments: map[string]interface{}{ |
|
"envoyID": "id", |
|
}, |
|
snis: map[string]struct{}{ |
|
"sni1": {}, |
|
"sni2": {}, |
|
}, |
|
extensionName: "bad", |
|
ok: false, |
|
}, |
|
"empty envoy ID": { |
|
arguments: map[string]interface{}{"envoyID": ""}, |
|
snis: map[string]struct{}{ |
|
"sni1": {}, |
|
"sni2": {}, |
|
}, |
|
ok: false, |
|
}, |
|
"missing snis": { |
|
arguments: map[string]interface{}{ |
|
"envoyID": "id", |
|
}, |
|
expected: &Validate{ |
|
envoyID: "id", |
|
resources: map[string]*resource{}, |
|
snis: map[string]struct{}{ |
|
"sni1": {}, |
|
"sni2": {}, |
|
}, |
|
}, |
|
ok: false, |
|
}, |
|
"valid everything": { |
|
arguments: map[string]interface{}{ |
|
"envoyID": "id", |
|
}, |
|
snis: map[string]struct{}{ |
|
"sni1": {}, |
|
"sni2": {}, |
|
}, |
|
expected: &Validate{ |
|
envoyID: "id", |
|
resources: map[string]*resource{}, |
|
snis: map[string]struct{}{ |
|
"sni1": {}, |
|
"sni2": {}, |
|
}, |
|
}, |
|
ok: true, |
|
}, |
|
} |
|
|
|
for n, tc := range cases { |
|
t.Run(n, func(t *testing.T) { |
|
|
|
extensionName := api.BuiltinValidateExtension |
|
if tc.extensionName != "" { |
|
extensionName = tc.extensionName |
|
} |
|
|
|
svc := api.CompoundServiceName{Name: "svc"} |
|
ext := extensioncommon.RuntimeConfig{ |
|
ServiceName: svc, |
|
EnvoyExtension: api.EnvoyExtension{ |
|
Name: extensionName, |
|
Arguments: tc.arguments, |
|
}, |
|
Upstreams: map[api.CompoundServiceName]*extensioncommon.UpstreamData{ |
|
svc: { |
|
SNI: tc.snis, |
|
}, |
|
}, |
|
} |
|
|
|
patcher, err := MakeValidate(ext) |
|
|
|
if tc.ok { |
|
require.NoError(t, err) |
|
require.Equal(t, tc.expected, patcher) |
|
} else { |
|
require.Error(t, err) |
|
} |
|
}) |
|
} |
|
}
|
|
|