open-consul/agent/consul/gateways/bind_test.go

901 lines
22 KiB
Go

package gateways
import (
"fmt"
"testing"
"github.com/hashicorp/consul/agent/structs"
"github.com/stretchr/testify/require"
)
func TestBindRoutesToGateways(t *testing.T) {
t.Parallel()
type testCase struct {
gateways []*gatewayMeta
routes []structs.BoundRoute
expectedBoundAPIGateways []*structs.BoundAPIGatewayConfigEntry
expectedReferenceErrors map[structs.ResourceReference]error
}
cases := map[string]testCase{
"TCP Route binds to gateway": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Kind: structs.TCPRoute,
Name: "TCP Route",
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "Listener",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route unbinds from gateway": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Kind: structs.TCPRoute,
Name: "TCP Route",
Parents: []structs.ResourceReference{},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route binds to multiple gateways": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway 1",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway 1",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway 2",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway 2",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Kind: structs.TCPRoute,
Name: "TCP Route",
Parents: []structs.ResourceReference{
{
Name: "Gateway 1",
Kind: structs.APIGateway,
SectionName: "Listener",
},
{
Name: "Gateway 2",
Kind: structs.APIGateway,
SectionName: "Listener",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway 1",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
{
Name: "Gateway 2",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route binds to a single listener on a gateway with multiple listeners": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolHTTP,
},
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "Listener 2",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route binds to all listeners on a gateway": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolTCP,
},
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route binds to gateway with multiple listeners, one of which is already bound": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolTCP,
},
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route binds to a listener on multiple gateways": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway 1",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway 1",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolTCP,
},
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway 2",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway 2",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolTCP,
},
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway 1",
Kind: structs.APIGateway,
SectionName: "Listener 2",
},
{
Name: "Gateway 2",
Kind: structs.APIGateway,
SectionName: "Listener 2",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway 1",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
{
Name: "Gateway 2",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route swaps from one listener to another on a gateway": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolTCP,
},
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "Listener 2",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"Multiple TCP Routes bind to different gateways": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway 1",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway 1",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway 2",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway 2",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route 1",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway 1",
Kind: structs.APIGateway,
SectionName: "Listener 1",
},
},
},
&structs.TCPRouteConfigEntry{
Name: "TCP Route 2",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway 2",
Kind: structs.APIGateway,
SectionName: "Listener 2",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway 1",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{
{
Name: "TCP Route 1",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
{
Name: "Gateway 2",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route 2",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route cannot be bound to a listener with an HTTP protocol": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener",
Protocol: structs.ListenerProtocolHTTP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "Listener",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{},
expectedReferenceErrors: map[structs.ResourceReference]error{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "Listener",
}: fmt.Errorf("failed to bind route TCP Route to gateway Gateway: listener Listener is not a tcp listener"),
},
},
"If a route/listener protocol mismatch occurs with the wildcard, but a bind to another listener was possible, no error is returned": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener 1",
Protocol: structs.ListenerProtocolHTTP,
},
{
Name: "Listener 2",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{
{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener 1",
Routes: []structs.ResourceReference{},
},
{
Name: "Listener 2",
Routes: []structs.ResourceReference{
{
Name: "TCP Route",
Kind: structs.TCPRoute,
SectionName: "",
},
},
},
},
},
},
expectedReferenceErrors: map[structs.ResourceReference]error{},
},
"TCP Route references a listener that does not exist": {
gateways: []*gatewayMeta{
{
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.BoundAPIGatewayListener{
{
Name: "Listener",
Routes: []structs.ResourceReference{},
},
},
},
Gateway: &structs.APIGatewayConfigEntry{
Name: "Gateway",
Listeners: []structs.APIGatewayListener{
{
Name: "Listener",
Protocol: structs.ListenerProtocolTCP,
},
},
},
},
},
routes: []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "TCP Route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "Non-existent Listener",
},
},
},
},
expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{},
expectedReferenceErrors: map[structs.ResourceReference]error{
{
Name: "Gateway",
Kind: structs.APIGateway,
SectionName: "Non-existent Listener",
}: fmt.Errorf("failed to bind route TCP Route to gateway Gateway: no valid listener has name 'Non-existent Listener' and uses tcp protocol"),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
actualBoundAPIGateways, referenceErrors := BindRoutesToGateways(tc.gateways, tc.routes...)
require.Equal(t, tc.expectedBoundAPIGateways, actualBoundAPIGateways)
require.Equal(t, tc.expectedReferenceErrors, referenceErrors)
})
}
}