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.
553 lines
15 KiB
553 lines
15 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package configentry |
|
|
|
import ( |
|
"sort" |
|
"testing" |
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/hashicorp/consul/acl" |
|
"github.com/hashicorp/consul/agent/structs" |
|
) |
|
|
|
func Test_ComputeResolvedServiceConfig(t *testing.T) { |
|
type args struct { |
|
scReq *structs.ServiceConfigRequest |
|
entries *ResolvedServiceConfigSet |
|
} |
|
|
|
sid := structs.ServiceID{ |
|
ID: "sid", |
|
EnterpriseMeta: *acl.DefaultEnterpriseMeta(), |
|
} |
|
uid := structs.PeeredServiceName{ |
|
ServiceName: structs.NewServiceName("upstream1", acl.DefaultEnterpriseMeta()), |
|
} |
|
|
|
uids := []structs.PeeredServiceName{uid} |
|
|
|
wildcard := structs.PeeredServiceName{ |
|
ServiceName: structs.NewServiceName(structs.WildcardSpecifier, acl.WildcardEnterpriseMeta()), |
|
} |
|
|
|
localMeshGW := structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal} |
|
remoteMeshGW := structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote} |
|
noneMeshGW := structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeNone} |
|
|
|
tests := []struct { |
|
name string |
|
args args |
|
want *structs.ServiceConfigResponse |
|
}{ |
|
{ |
|
name: "proxy with balanceinboundconnections", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
BalanceInboundConnections: "exact_balance", |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
ProxyConfig: map[string]interface{}{ |
|
"balance_inbound_connections": "exact_balance", |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy with maxinboundsconnections", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
MaxInboundConnections: 20, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
ProxyConfig: map[string]interface{}{ |
|
"max_inbound_connections": 20, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy with local_connect_timeout_ms and local_request_timeout_ms", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
MaxInboundConnections: 20, |
|
LocalConnectTimeoutMs: 20000, |
|
LocalRequestTimeoutMs: 30000, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
ProxyConfig: map[string]interface{}{ |
|
"max_inbound_connections": 20, |
|
"local_connect_timeout_ms": 20000, |
|
"local_request_timeout_ms": 30000, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy upstream mesh-gateway inherits proxy-defaults", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
UpstreamServiceNames: uids, |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
MeshGateway: remoteMeshGW, // applied 1st |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
MeshGateway: remoteMeshGW, |
|
UpstreamConfigs: structs.OpaqueUpstreamConfigs{ |
|
{ |
|
Upstream: wildcard, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": structs.MeshGatewayConfig{ |
|
Mode: structs.MeshGatewayModeRemote, |
|
}, |
|
}, |
|
}, |
|
{ |
|
Upstream: uid, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy inherits kitchen sink from proxy-defaults", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
UpstreamServiceNames: uids, |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
Config: map[string]interface{}{ |
|
"foo": "bar", |
|
}, |
|
Expose: structs.ExposeConfig{ |
|
Checks: true, |
|
Paths: []structs.ExposePath{}, |
|
}, |
|
Mode: structs.ProxyModeTransparent, |
|
MeshGateway: remoteMeshGW, |
|
TransparentProxy: structs.TransparentProxyConfig{ |
|
OutboundListenerPort: 6666, |
|
DialedDirectly: true, |
|
}, |
|
AccessLogs: structs.AccessLogsConfig{ |
|
Enabled: true, |
|
DisableListenerLogs: true, |
|
Type: structs.FileLogSinkType, |
|
Path: "/tmp/accesslog.txt", |
|
JSONFormat: "{ \"custom_start_time\": \"%START_TIME%\" }", |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
ProxyConfig: map[string]interface{}{ |
|
"foo": "bar", |
|
}, |
|
Expose: structs.ExposeConfig{ |
|
Checks: true, |
|
Paths: []structs.ExposePath{}, |
|
}, |
|
Mode: structs.ProxyModeTransparent, |
|
MeshGateway: remoteMeshGW, |
|
TransparentProxy: structs.TransparentProxyConfig{ |
|
OutboundListenerPort: 6666, |
|
DialedDirectly: true, |
|
}, |
|
AccessLogs: structs.AccessLogsConfig{ |
|
Enabled: true, |
|
DisableListenerLogs: true, |
|
Type: structs.FileLogSinkType, |
|
Path: "/tmp/accesslog.txt", |
|
JSONFormat: "{ \"custom_start_time\": \"%START_TIME%\" }", |
|
}, |
|
UpstreamConfigs: structs.OpaqueUpstreamConfigs{ |
|
{ |
|
Upstream: wildcard, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
{ |
|
Upstream: uid, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy upstream mesh-gateway inherits service-defaults", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
UpstreamServiceNames: uids, |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
MeshGateway: localMeshGW, // applied 1st |
|
}, |
|
}, |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
MeshGateway: noneMeshGW, // applied 2nd |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
MeshGateway: noneMeshGW, // service-defaults has a higher precedence. |
|
UpstreamConfigs: structs.OpaqueUpstreamConfigs{ |
|
{ |
|
Upstream: wildcard, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": noneMeshGW, |
|
}, |
|
}, |
|
{ |
|
Upstream: uid, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": noneMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy wildcard upstream mesh-gateway inherits proxy-defaults", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
Mode: structs.ProxyModeTransparent, |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
MeshGateway: localMeshGW, |
|
}, |
|
}, |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
UpstreamConfig: &structs.UpstreamConfiguration{ |
|
Defaults: &structs.UpstreamConfig{ |
|
Protocol: "http", |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
MeshGateway: localMeshGW, |
|
UpstreamConfigs: structs.OpaqueUpstreamConfigs{ |
|
{ |
|
Upstream: wildcard, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": localMeshGW, // From proxy-defaults |
|
"protocol": "http", |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy upstream mesh-gateway inherits upstream defaults", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
UpstreamServiceNames: uids, |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
MeshGateway: localMeshGW, |
|
}, |
|
}, |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
MeshGateway: noneMeshGW, |
|
UpstreamConfig: &structs.UpstreamConfiguration{ |
|
Defaults: &structs.UpstreamConfig{ |
|
MeshGateway: remoteMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
MeshGateway: noneMeshGW, // Merged from proxy-defaults + service-defaults |
|
UpstreamConfigs: structs.OpaqueUpstreamConfigs{ |
|
{ |
|
Upstream: wildcard, |
|
Config: map[string]interface{}{ |
|
// Wildcard stores the values from UpstreamConfig.Defaults directly |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
{ |
|
Upstream: uid, |
|
Config: map[string]interface{}{ |
|
// Upstream-specific config comes from UpstreamConfig.Defaults |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy upstream mesh-gateway inherits value from node-service", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
UpstreamServiceNames: uids, |
|
|
|
// MeshGateway from NodeService is received in the request |
|
MeshGateway: remoteMeshGW, |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
UpstreamConfig: &structs.UpstreamConfiguration{ |
|
Defaults: &structs.UpstreamConfig{ |
|
MeshGateway: noneMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
UpstreamConfigs: structs.OpaqueUpstreamConfigs{ |
|
{ |
|
Upstream: wildcard, |
|
Config: map[string]interface{}{ |
|
// NodeService.Proxy.MeshGateway has a higher precedence than centralized |
|
// UpstreamConfig.Defaults, since it's specific to a service instance. |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
{ |
|
Upstream: uid, |
|
Config: map[string]interface{}{ |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxy upstream mesh-gateway inherits value from service-defaults override", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
UpstreamServiceNames: uids, |
|
MeshGateway: localMeshGW, // applied 2nd |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
UpstreamConfig: &structs.UpstreamConfiguration{ |
|
Defaults: &structs.UpstreamConfig{ |
|
MeshGateway: localMeshGW, // applied 1st |
|
}, |
|
Overrides: []*structs.UpstreamConfig{ |
|
{ |
|
Name: uid.ServiceName.Name, |
|
MeshGateway: remoteMeshGW, // applied 3rd |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
UpstreamConfigs: structs.OpaqueUpstreamConfigs{ |
|
{ |
|
Upstream: wildcard, |
|
Config: map[string]interface{}{ |
|
// Wildcard stores the values from UpstreamConfig.Defaults directly |
|
"mesh_gateway": localMeshGW, |
|
}, |
|
}, |
|
{ |
|
Upstream: uid, |
|
Config: map[string]interface{}{ |
|
// UpstreamConfig.Overrides has a higher precedence than UpstreamConfig.Defaults |
|
"mesh_gateway": remoteMeshGW, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "servicedefaults envoy extension", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
EnvoyExtensions: []structs.EnvoyExtension{ |
|
{ |
|
Name: "sd-ext", |
|
Required: false, |
|
Arguments: map[string]interface{}{"arg": "val"}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
EnvoyExtensions: []structs.EnvoyExtension{ |
|
{ |
|
Name: "sd-ext", |
|
Required: false, |
|
Arguments: map[string]interface{}{"arg": "val"}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "proxydefaults envoy extension appended to servicedefaults extension", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
EnvoyExtensions: []structs.EnvoyExtension{ |
|
{ |
|
Name: "pd-ext", |
|
Required: false, |
|
Arguments: map[string]interface{}{"arg": "val"}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
EnvoyExtensions: []structs.EnvoyExtension{ |
|
{ |
|
Name: "sd-ext", |
|
Required: false, |
|
Arguments: map[string]interface{}{"arg": "val"}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
EnvoyExtensions: []structs.EnvoyExtension{ |
|
{ |
|
Name: "pd-ext", |
|
Required: false, |
|
Arguments: map[string]interface{}{"arg": "val"}, |
|
}, |
|
{ |
|
Name: "sd-ext", |
|
Required: false, |
|
Arguments: map[string]interface{}{"arg": "val"}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "service-defaults inherits mutual_tls_mode from proxy-defaults", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
MutualTLSMode: structs.MutualTLSModePermissive, |
|
}, |
|
}, |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: {}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
MutualTLSMode: structs.MutualTLSModePermissive, |
|
}, |
|
}, |
|
{ |
|
name: "service-defaults overrides mutual_tls_mode in proxy-defaults", |
|
args: args{ |
|
scReq: &structs.ServiceConfigRequest{ |
|
Name: "sid", |
|
}, |
|
entries: &ResolvedServiceConfigSet{ |
|
ProxyDefaults: map[string]*structs.ProxyConfigEntry{ |
|
acl.DefaultEnterpriseMeta().PartitionOrDefault(): { |
|
MutualTLSMode: structs.MutualTLSModeStrict, |
|
}, |
|
}, |
|
ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ |
|
sid: { |
|
MutualTLSMode: structs.MutualTLSModePermissive, |
|
}, |
|
}, |
|
}, |
|
}, |
|
want: &structs.ServiceConfigResponse{ |
|
MutualTLSMode: structs.MutualTLSModePermissive, |
|
}, |
|
}, |
|
} |
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
got, err := ComputeResolvedServiceConfig(tt.args.scReq, tt.args.entries, nil) |
|
require.NoError(t, err) |
|
// This is needed because map iteration is random and determines the order of some outputs. |
|
sort.Slice(got.UpstreamConfigs, func(i, j int) bool { |
|
return got.UpstreamConfigs[i].Upstream.ServiceName.Name < got.UpstreamConfigs[j].Upstream.ServiceName.Name |
|
}) |
|
require.Equal(t, tt.want, got) |
|
}) |
|
} |
|
}
|
|
|