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.
706 lines
16 KiB
706 lines
16 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package structs |
|
|
|
import ( |
|
"encoding/json" |
|
"testing" |
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/hashicorp/consul/api" |
|
) |
|
|
|
func TestConnectProxyConfig_ToAPI(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
in ConnectProxyConfig |
|
want *api.AgentServiceConnectProxyConfig |
|
}{ |
|
{ |
|
name: "service", |
|
in: ConnectProxyConfig{ |
|
DestinationServiceName: "web", |
|
DestinationServiceID: "web1", |
|
LocalServiceAddress: "127.0.0.2", |
|
LocalServicePort: 5555, |
|
Config: map[string]interface{}{ |
|
"foo": "bar", |
|
}, |
|
MeshGateway: MeshGatewayConfig{ |
|
Mode: MeshGatewayModeLocal, |
|
}, |
|
Upstreams: Upstreams{ |
|
{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
LocalBindPort: 1234, |
|
MeshGateway: MeshGatewayConfig{ |
|
Mode: MeshGatewayModeLocal, |
|
}, |
|
}, |
|
{ |
|
DestinationType: UpstreamDestTypePreparedQuery, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
LocalBindPort: 2345, |
|
LocalBindAddress: "127.10.10.10", |
|
}, |
|
}, |
|
Mode: ProxyModeTransparent, |
|
TransparentProxy: TransparentProxyConfig{ |
|
OutboundListenerPort: 808, |
|
}, |
|
}, |
|
want: &api.AgentServiceConnectProxyConfig{ |
|
DestinationServiceName: "web", |
|
DestinationServiceID: "web1", |
|
LocalServiceAddress: "127.0.0.2", |
|
LocalServicePort: 5555, |
|
Config: map[string]interface{}{ |
|
"foo": "bar", |
|
}, |
|
MeshGateway: api.MeshGatewayConfig{ |
|
Mode: api.MeshGatewayModeLocal, |
|
}, |
|
Upstreams: []api.Upstream{ |
|
{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
LocalBindPort: 1234, |
|
MeshGateway: api.MeshGatewayConfig{ |
|
Mode: api.MeshGatewayModeLocal, |
|
}, |
|
}, |
|
{ |
|
DestinationType: UpstreamDestTypePreparedQuery, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
LocalBindPort: 2345, |
|
LocalBindAddress: "127.10.10.10", |
|
}, |
|
}, |
|
Mode: api.ProxyModeTransparent, |
|
TransparentProxy: &api.TransparentProxyConfig{ |
|
OutboundListenerPort: 808, |
|
}, |
|
}, |
|
}, |
|
} |
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
require.Equal(t, tt.want, tt.in.ToAPI()) |
|
}) |
|
} |
|
} |
|
|
|
func TestConnectProxyConfig_MarshalJSON(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
in ConnectProxyConfig |
|
want string |
|
wantErr bool |
|
}{ |
|
{ |
|
name: "direct proxy", |
|
in: ConnectProxyConfig{ |
|
DestinationServiceName: "api", |
|
DestinationServiceID: "api-1", |
|
LocalServiceAddress: "127.0.0.1", |
|
LocalServicePort: 8080, |
|
Mode: ProxyModeDirect, |
|
Config: map[string]interface{}{ |
|
"connect_timeout_ms": 5000, |
|
}, |
|
Upstreams: Upstreams{ |
|
Upstream{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationName: "db", |
|
Datacenter: "dc1", |
|
LocalBindPort: 1234, |
|
}, |
|
}, |
|
MeshGateway: MeshGatewayConfig{Mode: MeshGatewayModeLocal}, |
|
Expose: ExposeConfig{Checks: true}, |
|
|
|
// No transparent proxy config, since proxy is set to "direct" mode. |
|
// Field should be omitted from json output. |
|
// TransparentProxy: TransparentProxyConfig{}, |
|
|
|
// No access logs config provided, so this should be omitted. |
|
// AccessLogs: AccessLogsConfig{}, |
|
}, |
|
want: `{ |
|
"DestinationServiceName": "api", |
|
"DestinationServiceID": "api-1", |
|
"LocalServiceAddress": "127.0.0.1", |
|
"LocalServicePort": 8080, |
|
"Mode": "direct", |
|
"Config": { |
|
"connect_timeout_ms": 5000 |
|
}, |
|
"Upstreams": [ |
|
{ |
|
"DestinationType": "service", |
|
"DestinationName": "db", |
|
"Datacenter": "dc1", |
|
"LocalBindPort": 1234, |
|
"MeshGateway": {} |
|
} |
|
], |
|
"MeshGateway": { |
|
"Mode": "local" |
|
}, |
|
"Expose": { |
|
"Checks": true |
|
} |
|
}`, |
|
wantErr: false, |
|
}, |
|
{ |
|
name: "transparent proxy", |
|
in: ConnectProxyConfig{ |
|
DestinationServiceName: "billing", |
|
DestinationServiceID: "billing-1", |
|
LocalServiceAddress: "127.0.0.1", |
|
LocalServicePort: 8080, |
|
Mode: ProxyModeTransparent, |
|
Config: map[string]interface{}{ |
|
"connect_timeout_ms": 5000, |
|
}, |
|
MeshGateway: MeshGatewayConfig{Mode: MeshGatewayModeLocal}, |
|
Expose: ExposeConfig{Checks: true}, |
|
TransparentProxy: TransparentProxyConfig{ |
|
DialedDirectly: true, |
|
OutboundListenerPort: 16001, |
|
}, |
|
AccessLogs: AccessLogsConfig{ |
|
Enabled: true, |
|
DisableListenerLogs: true, |
|
Type: FileLogSinkType, |
|
Path: "/var/log/access.log", |
|
}, |
|
}, |
|
want: `{ |
|
"DestinationServiceName": "billing", |
|
"DestinationServiceID": "billing-1", |
|
"LocalServiceAddress": "127.0.0.1", |
|
"LocalServicePort": 8080, |
|
"Mode": "transparent", |
|
"Config": { |
|
"connect_timeout_ms": 5000 |
|
}, |
|
"TransparentProxy": { |
|
"DialedDirectly": true, |
|
"OutboundListenerPort": 16001 |
|
}, |
|
"MeshGateway": { |
|
"Mode": "local" |
|
}, |
|
"Expose": { |
|
"Checks": true |
|
}, |
|
"AccessLogs": { |
|
"Enabled": true, |
|
"DisableListenerLogs": true, |
|
"Type": "file", |
|
"Path": "/var/log/access.log" |
|
} |
|
}`, |
|
wantErr: false, |
|
}, |
|
} |
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
got, err := tt.in.MarshalJSON() |
|
if tt.wantErr { |
|
require.Error(t, err) |
|
return |
|
} |
|
require.NoError(t, err) |
|
require.JSONEq(t, tt.want, string(got)) |
|
}) |
|
} |
|
} |
|
|
|
func TestUpstream_MarshalJSON(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
in Upstream |
|
want string |
|
wantErr bool |
|
}{ |
|
{ |
|
name: "service", |
|
in: Upstream{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
LocalBindPort: 1234, |
|
// Test IngressHosts does not marshal |
|
IngressHosts: []string{"test.example.com"}, |
|
}, |
|
want: `{ |
|
"DestinationType": "service", |
|
"DestinationName": "foo", |
|
"Datacenter": "dc1", |
|
"LocalBindPort": 1234, |
|
"MeshGateway": {} |
|
}`, |
|
wantErr: false, |
|
}, |
|
{ |
|
name: "pq", |
|
in: Upstream{ |
|
DestinationType: UpstreamDestTypePreparedQuery, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
LocalBindPort: 1234, |
|
}, |
|
want: `{ |
|
"DestinationType": "prepared_query", |
|
"DestinationName": "foo", |
|
"Datacenter": "dc1", |
|
"LocalBindPort": 1234, |
|
"MeshGateway": {} |
|
}`, |
|
wantErr: false, |
|
}, |
|
} |
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
got, err := json.Marshal(tt.in) |
|
if tt.wantErr { |
|
require.Error(t, err) |
|
return |
|
} |
|
require.NoError(t, err) |
|
require.JSONEq(t, tt.want, string(got)) |
|
}) |
|
} |
|
} |
|
|
|
func TestUpstream_UnmarshalJSON(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
json string |
|
jsonSnake string |
|
want Upstream |
|
wantErr bool |
|
}{ |
|
{ |
|
name: "service", |
|
json: `{ |
|
"DestinationType": "service", |
|
"DestinationName": "foo", |
|
"Datacenter": "dc1" |
|
}`, |
|
want: Upstream{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
}, |
|
wantErr: false, |
|
}, |
|
{ |
|
name: "pq", |
|
json: `{ |
|
"DestinationType": "prepared_query", |
|
"DestinationName": "foo", |
|
"Datacenter": "dc1" |
|
}`, |
|
want: Upstream{ |
|
DestinationType: UpstreamDestTypePreparedQuery, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
}, |
|
wantErr: false, |
|
}, |
|
{ |
|
name: "ingress-hosts-do-not-unmarshal", |
|
json: `{ |
|
"DestinationType": "service", |
|
"DestinationName": "foo", |
|
"Datacenter": "dc1", |
|
"IngressHosts": ["asdf"] |
|
}`, |
|
want: Upstream{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationName: "foo", |
|
Datacenter: "dc1", |
|
IngressHosts: nil, // Make sure this doesn't get parsed |
|
}, |
|
wantErr: true, |
|
}, |
|
{ |
|
name: "kitchen sink", |
|
json: ` |
|
{ |
|
"DestinationType": "service", |
|
"DestinationNamespace": "default", |
|
"DestinationName": "bar1", |
|
"Datacenter": "dc1", |
|
"LocalBindAddress": "127.0.0.2", |
|
"LocalBindPort": 6060, |
|
"Config": { |
|
"x": "y", |
|
"z": -2 |
|
}, |
|
"MeshGateway": { |
|
"Mode": "local" |
|
} |
|
} |
|
`, |
|
jsonSnake: ` |
|
{ |
|
"destination_type": "service", |
|
"destination_namespace": "default", |
|
"destination_name": "bar1", |
|
"datacenter": "dc1", |
|
"local_bind_address": "127.0.0.2", |
|
"local_bind_port": 6060, |
|
"config": { |
|
"x": "y", |
|
"z": -2 |
|
}, |
|
"mesh_gateway": { |
|
"mode": "local" |
|
} |
|
} |
|
`, |
|
want: Upstream{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationNamespace: "default", |
|
DestinationName: "bar1", |
|
Datacenter: "dc1", |
|
LocalBindAddress: "127.0.0.2", |
|
LocalBindPort: 6060, |
|
Config: map[string]interface{}{ |
|
"x": "y", |
|
"z": float64(-2), |
|
}, |
|
MeshGateway: MeshGatewayConfig{ |
|
Mode: "local", |
|
}, |
|
}, |
|
}, |
|
} |
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
t.Run("camel", func(t *testing.T) { |
|
var got Upstream |
|
err := json.Unmarshal([]byte(tt.json), &got) |
|
if tt.wantErr { |
|
require.Error(t, err) |
|
} else { |
|
require.NoError(t, err) |
|
require.Equal(t, tt.want, got, "%+v", got) |
|
} |
|
}) |
|
|
|
if tt.jsonSnake != "" { |
|
t.Run("snake", func(t *testing.T) { |
|
var got Upstream |
|
err := json.Unmarshal([]byte(tt.jsonSnake), &got) |
|
if tt.wantErr { |
|
require.Error(t, err) |
|
} else { |
|
require.NoError(t, err) |
|
require.Equal(t, tt.want, got) |
|
} |
|
}) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestConnectProxyConfig_UnmarshalJSON(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
json string |
|
jsonSnake string |
|
want ConnectProxyConfig |
|
wantErr bool |
|
}{ |
|
{ |
|
name: "kitchen sink", |
|
json: ` |
|
{ |
|
"DestinationServiceName": "foo-name", |
|
"DestinationServiceID": "foo-id", |
|
"LocalServiceAddress": "127.0.0.1", |
|
"LocalServicePort": 5050, |
|
"Config": { |
|
"a": "b", |
|
"v": 42 |
|
}, |
|
"Upstreams": [ |
|
{ |
|
"DestinationType": "service", |
|
"DestinationNamespace": "default", |
|
"DestinationName": "bar1", |
|
"Datacenter": "dc1", |
|
"LocalBindAddress": "127.0.0.2", |
|
"LocalBindPort": 6060, |
|
"Config": { |
|
"x": "y", |
|
"z": -2 |
|
}, |
|
"MeshGateway": { |
|
"Mode": "local" |
|
} |
|
}, |
|
{ |
|
"DestinationType": "service", |
|
"DestinationNamespace": "default", |
|
"DestinationName": "bar2", |
|
"Datacenter": "dc2", |
|
"LocalBindAddress": "127.0.0.2", |
|
"LocalBindPort": 6161 |
|
} |
|
], |
|
"MeshGateway": { |
|
"Mode": "remote" |
|
}, |
|
"Expose": { |
|
"Checks": true, |
|
"Paths": [ |
|
{ |
|
"ListenerPort": 8080, |
|
"Path": "/foo", |
|
"LocalPathPort": 7070, |
|
"Protocol": "http2", |
|
"ParsedFromCheck": true |
|
}, |
|
{ |
|
"ListenerPort": 8181, |
|
"Path": "/foo2", |
|
"LocalPathPort": 7171, |
|
"Protocol": "http", |
|
"ParsedFromCheck": false |
|
} |
|
] |
|
} |
|
} |
|
`, |
|
jsonSnake: ` |
|
{ |
|
"destination_service_name": "foo-name", |
|
"destination_service_id": "foo-id", |
|
"local_service_address": "127.0.0.1", |
|
"local_service_port": 5050, |
|
"config": { |
|
"a": "b", |
|
"v": 42 |
|
}, |
|
"upstreams": [ |
|
{ |
|
"destination_type": "service", |
|
"destination_namespace": "default", |
|
"destination_name": "bar1", |
|
"datacenter": "dc1", |
|
"local_bind_address": "127.0.0.2", |
|
"local_bind_port": 6060, |
|
"config": { |
|
"x": "y", |
|
"z": -2 |
|
}, |
|
"mesh_gateway": { |
|
"mode": "local" |
|
} |
|
}, |
|
{ |
|
"destination_type": "service", |
|
"destination_namespace": "default", |
|
"destination_name": "bar2", |
|
"datacenter": "dc2", |
|
"local_bind_address": "127.0.0.2", |
|
"local_bind_port": 6161 |
|
} |
|
], |
|
"mesh_gateway": { |
|
"mode": "remote" |
|
}, |
|
"expose": { |
|
"checks": true, |
|
"paths": [ |
|
{ |
|
"listener_port": 8080, |
|
"path": "/foo", |
|
"local_path_port": 7070, |
|
"protocol": "http2", |
|
"parsed_from_check": true |
|
}, |
|
{ |
|
"listener_port": 8181, |
|
"path": "/foo2", |
|
"local_path_port": 7171, |
|
"protocol": "http", |
|
"parsed_from_check": false |
|
} |
|
] |
|
} |
|
} |
|
`, |
|
want: ConnectProxyConfig{ |
|
DestinationServiceName: "foo-name", |
|
DestinationServiceID: "foo-id", |
|
LocalServiceAddress: "127.0.0.1", |
|
LocalServicePort: 5050, |
|
Config: map[string]interface{}{ |
|
"a": "b", |
|
"v": float64(42), |
|
}, |
|
Upstreams: []Upstream{ |
|
{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationNamespace: "default", |
|
DestinationName: "bar1", |
|
Datacenter: "dc1", |
|
LocalBindAddress: "127.0.0.2", |
|
LocalBindPort: 6060, |
|
Config: map[string]interface{}{ |
|
"x": "y", |
|
"z": float64(-2), |
|
}, |
|
MeshGateway: MeshGatewayConfig{ |
|
Mode: "local", |
|
}, |
|
}, |
|
|
|
{ |
|
DestinationType: UpstreamDestTypeService, |
|
DestinationNamespace: "default", |
|
DestinationName: "bar2", |
|
Datacenter: "dc2", |
|
LocalBindAddress: "127.0.0.2", |
|
LocalBindPort: 6161, |
|
}, |
|
}, |
|
|
|
MeshGateway: MeshGatewayConfig{ |
|
Mode: "remote", |
|
}, |
|
Expose: ExposeConfig{ |
|
Checks: true, |
|
Paths: []ExposePath{ |
|
{ |
|
ListenerPort: 8080, |
|
Path: "/foo", |
|
LocalPathPort: 7070, |
|
Protocol: "http2", |
|
ParsedFromCheck: true, |
|
}, |
|
{ |
|
ListenerPort: 8181, |
|
Path: "/foo2", |
|
LocalPathPort: 7171, |
|
Protocol: "http", |
|
ParsedFromCheck: false, |
|
}, |
|
}, |
|
}, |
|
}, |
|
}, |
|
} |
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
t.Run("camel", func(t *testing.T) { |
|
// |
|
var got ConnectProxyConfig |
|
err := json.Unmarshal([]byte(tt.json), &got) |
|
if tt.wantErr { |
|
require.Error(t, err) |
|
} else { |
|
require.NoError(t, err) |
|
require.Equal(t, tt.want, got) |
|
} |
|
}) |
|
if tt.jsonSnake != "" { |
|
t.Run("snake", func(t *testing.T) { |
|
// |
|
var got ConnectProxyConfig |
|
err := json.Unmarshal([]byte(tt.json), &got) |
|
if tt.wantErr { |
|
require.Error(t, err) |
|
} else { |
|
require.NoError(t, err) |
|
require.Equal(t, tt.want, got) |
|
} |
|
}) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestValidateMeshGatewayMode(t *testing.T) { |
|
for _, tc := range []struct { |
|
modeConstant string |
|
modeExplicit string |
|
expect MeshGatewayMode |
|
ok bool |
|
}{ |
|
{string(MeshGatewayModeNone), "none", MeshGatewayModeNone, true}, |
|
{string(MeshGatewayModeDefault), "", MeshGatewayModeDefault, true}, |
|
{string(MeshGatewayModeLocal), "local", MeshGatewayModeLocal, true}, |
|
{string(MeshGatewayModeRemote), "remote", MeshGatewayModeRemote, true}, |
|
} { |
|
tc := tc |
|
|
|
t.Run(tc.modeConstant+" (constant)", func(t *testing.T) { |
|
got, err := ValidateMeshGatewayMode(tc.modeConstant) |
|
if tc.ok { |
|
require.NoError(t, err) |
|
require.Equal(t, tc.expect, got) |
|
} else { |
|
require.Error(t, err) |
|
} |
|
}) |
|
t.Run(tc.modeExplicit+" (explicit)", func(t *testing.T) { |
|
got, err := ValidateMeshGatewayMode(tc.modeExplicit) |
|
if tc.ok { |
|
require.NoError(t, err) |
|
require.Equal(t, tc.expect, got) |
|
} else { |
|
require.Error(t, err) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestValidateProxyMode(t *testing.T) { |
|
for _, tc := range []struct { |
|
modeConstant string |
|
modeExplicit string |
|
expect ProxyMode |
|
ok bool |
|
}{ |
|
{string(ProxyModeDefault), "", ProxyModeDefault, true}, |
|
{string(ProxyModeDirect), "direct", ProxyModeDirect, true}, |
|
{string(ProxyModeTransparent), "transparent", ProxyModeTransparent, true}, |
|
} { |
|
tc := tc |
|
|
|
t.Run(tc.modeConstant+" (constant)", func(t *testing.T) { |
|
got, err := ValidateProxyMode(tc.modeConstant) |
|
if tc.ok { |
|
require.NoError(t, err) |
|
require.Equal(t, tc.expect, got) |
|
} else { |
|
require.Error(t, err) |
|
} |
|
}) |
|
t.Run(tc.modeExplicit+" (explicit)", func(t *testing.T) { |
|
got, err := ValidateProxyMode(tc.modeExplicit) |
|
if tc.ok { |
|
require.NoError(t, err) |
|
require.Equal(t, tc.expect, got) |
|
} else { |
|
require.Error(t, err) |
|
} |
|
}) |
|
} |
|
}
|
|
|