Match route and listener protocols when binding (#16057)
* Add GatewayMeta for matching routes to listeners based on protocols
* Add GetGatewayMeta
* Apply suggestions from code review
Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>
* Make GatewayMeta private
* Bound -> BoundGateway
* Document gatewayMeta more
* Simplify conditional
* Parallelize tests and simplify bind conditional
* gofmt
* 💧 getGatewayMeta
---------
Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>
This commit is contained in:
parent
782aaee69a
commit
e69e7fd1f2
|
@ -21,7 +21,7 @@ type gatewayRefs = map[configentry.KindName][]structs.ResourceReference
|
|||
// The function returns a list of references to the modified BoundAPIGatewayConfigEntry objects,
|
||||
// a map of resource references to errors that occurred when they were attempted to be
|
||||
// bound to a gateway.
|
||||
func BindRoutesToGateways(gateways []*structs.BoundAPIGatewayConfigEntry, routes ...structs.BoundRoute) ([]*structs.BoundAPIGatewayConfigEntry, map[structs.ResourceReference]error) {
|
||||
func BindRoutesToGateways(gateways []*gatewayMeta, routes ...structs.BoundRoute) ([]*structs.BoundAPIGatewayConfigEntry, map[structs.ResourceReference]error) {
|
||||
modified := make([]*structs.BoundAPIGatewayConfigEntry, 0, len(gateways))
|
||||
|
||||
// errored stores the errors from events where a resource reference failed to bind to a gateway.
|
||||
|
@ -32,11 +32,11 @@ func BindRoutesToGateways(gateways []*structs.BoundAPIGatewayConfigEntry, routes
|
|||
|
||||
// Iterate over all BoundAPIGateway config entries and try to bind them to the route if they are a parent.
|
||||
for _, gateway := range gateways {
|
||||
references, routeReferencesGateway := gatewayRefs[configentry.NewKindNameForEntry(gateway)]
|
||||
references, routeReferencesGateway := gatewayRefs[configentry.NewKindNameForEntry(gateway.BoundGateway)]
|
||||
if routeReferencesGateway {
|
||||
didUpdate, errors := gateway.UpdateRouteBinding(references, route)
|
||||
didUpdate, errors := gateway.updateRouteBinding(references, route)
|
||||
if didUpdate {
|
||||
modified = append(modified, gateway)
|
||||
modified = append(modified, gateway.BoundGateway)
|
||||
}
|
||||
for ref, err := range errors {
|
||||
errored[ref] = err
|
||||
|
@ -44,10 +44,8 @@ func BindRoutesToGateways(gateways []*structs.BoundAPIGatewayConfigEntry, routes
|
|||
for _, ref := range references {
|
||||
delete(parentRefs, ref)
|
||||
}
|
||||
} else {
|
||||
if gateway.UnbindRoute(route) {
|
||||
modified = append(modified, gateway)
|
||||
}
|
||||
} else if gateway.unbindRoute(route) {
|
||||
modified = append(modified, gateway.BoundGateway)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,139 @@
|
|||
package gateways
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
)
|
||||
|
||||
// gatewayMeta embeds both a BoundAPIGateway and its corresponding APIGateway.
|
||||
// This is used when binding routes to a gateway to ensure that a route's protocol (e.g. http)
|
||||
// matches the protocol of the listener it wants to bind to. The binding modifies the
|
||||
// "bound" gateway, but relies on the "gateway" to determine the protocol of the listener.
|
||||
type gatewayMeta struct {
|
||||
// BoundGateway is the bound-api-gateway config entry for a given gateway.
|
||||
BoundGateway *structs.BoundAPIGatewayConfigEntry
|
||||
// Gateway is the api-gateway config entry for the gateway.
|
||||
Gateway *structs.APIGatewayConfigEntry
|
||||
}
|
||||
|
||||
// updateRouteBinding takes a parent resource reference and a BoundRoute and
|
||||
// modifies the listeners on the BoundAPIGateway config entry in GatewayMeta
|
||||
// to reflect the binding of the route to the gateway.
|
||||
//
|
||||
// If the reference is not valid or the route's protocol does not match the
|
||||
// targeted listener's protocol, a mapping of parent references to associated
|
||||
// errors is returned.
|
||||
func (g *gatewayMeta) updateRouteBinding(refs []structs.ResourceReference, route structs.BoundRoute) (bool, map[structs.ResourceReference]error) {
|
||||
if g.BoundGateway == nil || g.Gateway == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
didUpdate := false
|
||||
errors := make(map[structs.ResourceReference]error)
|
||||
|
||||
if len(g.BoundGateway.Listeners) == 0 {
|
||||
for _, ref := range refs {
|
||||
errors[ref] = fmt.Errorf("route cannot bind because gateway has no listeners")
|
||||
}
|
||||
return false, errors
|
||||
}
|
||||
|
||||
for i, listener := range g.BoundGateway.Listeners {
|
||||
// Unbind to handle any stale route references.
|
||||
didUnbind := listener.UnbindRoute(route)
|
||||
if didUnbind {
|
||||
didUpdate = true
|
||||
}
|
||||
g.BoundGateway.Listeners[i] = listener
|
||||
|
||||
for _, ref := range refs {
|
||||
didBind, err := g.bindRoute(ref, route)
|
||||
if err != nil {
|
||||
errors[ref] = err
|
||||
}
|
||||
if didBind {
|
||||
didUpdate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return didUpdate, errors
|
||||
}
|
||||
|
||||
// bindRoute takes a parent reference and a route and attempts to bind the route to the
|
||||
// bound gateway in the gatewayMeta struct. It returns true if the route was bound and
|
||||
// false if it was not. If the route fails to bind, an error is returned.
|
||||
//
|
||||
// Binding logic binds a route to one or more listeners on the Bound gateway.
|
||||
// For a route to successfully bind it must:
|
||||
// - have a parent reference to the gateway
|
||||
// - have a parent reference with a section name matching the name of a listener
|
||||
// on the gateway. If the section name is `""`, the route will be bound to all
|
||||
// listeners on the gateway whose protocol matches the route's protocol.
|
||||
// - have a protocol that matches the protocol of the listener it is being bound to.
|
||||
func (g *gatewayMeta) bindRoute(ref structs.ResourceReference, route structs.BoundRoute) (bool, error) {
|
||||
if g.BoundGateway == nil || g.Gateway == nil {
|
||||
return false, fmt.Errorf("gateway cannot be found")
|
||||
}
|
||||
|
||||
if ref.Kind != structs.APIGateway || g.Gateway.Name != ref.Name || !g.Gateway.EnterpriseMeta.IsSame(&ref.EnterpriseMeta) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(g.BoundGateway.Listeners) == 0 {
|
||||
return false, fmt.Errorf("route cannot bind because gateway has no listeners")
|
||||
}
|
||||
|
||||
didBind := false
|
||||
for _, listener := range g.Gateway.Listeners {
|
||||
// A route with a section name of "" is bound to all listeners on the gateway.
|
||||
if listener.Name != ref.SectionName && ref.SectionName != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if listener.Protocol == route.GetProtocol() {
|
||||
i, boundListener := g.boundListenerByName(listener.Name)
|
||||
if boundListener != nil && boundListener.BindRoute(route) {
|
||||
didBind = true
|
||||
g.BoundGateway.Listeners[i] = *boundListener
|
||||
}
|
||||
} else if ref.SectionName != "" {
|
||||
// Failure to bind to a specific listener is an error
|
||||
return false, fmt.Errorf("failed to bind route %s to gateway %s: listener %s is not a %s listener", route.GetName(), g.Gateway.Name, listener.Name, route.GetProtocol())
|
||||
}
|
||||
}
|
||||
|
||||
if !didBind {
|
||||
return didBind, fmt.Errorf("failed to bind route %s to gateway %s: no valid listener has name '%s' and uses %s protocol", route.GetName(), g.Gateway.Name, ref.SectionName, route.GetProtocol())
|
||||
}
|
||||
|
||||
return didBind, nil
|
||||
}
|
||||
|
||||
// unbindRoute takes a route and unbinds it from all of the listeners on a gateway.
|
||||
// It returns true if the route was unbound and false if it was not.
|
||||
func (g *gatewayMeta) unbindRoute(route structs.BoundRoute) bool {
|
||||
if g.BoundGateway == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
didUnbind := false
|
||||
for i, listener := range g.BoundGateway.Listeners {
|
||||
if listener.UnbindRoute(route) {
|
||||
didUnbind = true
|
||||
g.BoundGateway.Listeners[i] = listener
|
||||
}
|
||||
}
|
||||
|
||||
return didUnbind
|
||||
}
|
||||
|
||||
func (g *gatewayMeta) boundListenerByName(name string) (int, *structs.BoundAPIGatewayListener) {
|
||||
for i, listener := range g.BoundGateway.Listeners {
|
||||
if listener.Name == name {
|
||||
return i, &listener
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
package gateways
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBoundAPIGatewayBindRoute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := map[string]struct {
|
||||
gateway gatewayMeta
|
||||
route structs.BoundRoute
|
||||
expectedBoundGateway structs.BoundAPIGatewayConfigEntry
|
||||
expectedDidBind bool
|
||||
expectedErr error
|
||||
}{
|
||||
"Bind TCP Route to Gateway": {
|
||||
gateway: gatewayMeta{
|
||||
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
Gateway: &structs.APIGatewayConfigEntry{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
route: &structs.TCPRouteConfigEntry{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
SectionName: "Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDidBind: true,
|
||||
},
|
||||
"Bind TCP Route with wildcard section name to all listeners on Gateway": {
|
||||
gateway: gatewayMeta{
|
||||
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener 1",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
{
|
||||
Name: "Listener 2",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
{
|
||||
Name: "Listener 3",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
Gateway: &structs.APIGatewayConfigEntry{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "Listener 1",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
},
|
||||
{
|
||||
Name: "Listener 2",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
},
|
||||
{
|
||||
Name: "Listener 3",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
route: &structs.TCPRouteConfigEntry{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener 1",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Listener 2",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Listener 3",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDidBind: true,
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because the parent reference kind is not APIGateway": {
|
||||
gateway: gatewayMeta{
|
||||
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{},
|
||||
},
|
||||
Gateway: &structs.APIGatewayConfigEntry{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.APIGatewayListener{},
|
||||
},
|
||||
},
|
||||
route: &structs.TCPRouteConfigEntry{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Name: "Gateway",
|
||||
SectionName: "Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.TerminatingGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because the parent reference name does not match": {
|
||||
gateway: gatewayMeta{
|
||||
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{},
|
||||
},
|
||||
Gateway: &structs.APIGatewayConfigEntry{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.APIGatewayListener{},
|
||||
},
|
||||
},
|
||||
route: &structs.TCPRouteConfigEntry{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Other Gateway",
|
||||
SectionName: "Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because it lacks listeners": {
|
||||
gateway: gatewayMeta{
|
||||
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{},
|
||||
},
|
||||
Gateway: &structs.APIGatewayConfigEntry{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.APIGatewayListener{},
|
||||
},
|
||||
},
|
||||
route: &structs.TCPRouteConfigEntry{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
SectionName: "Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: fmt.Errorf("route cannot bind because gateway has no listeners"),
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because it has an invalid section name": {
|
||||
gateway: gatewayMeta{
|
||||
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
Gateway: &structs.APIGatewayConfigEntry{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.APIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Protocol: structs.ListenerProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
route: &structs.TCPRouteConfigEntry{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.APIGateway,
|
||||
Name: "Gateway",
|
||||
SectionName: "Other Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBoundGateway: structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: fmt.Errorf("failed to bind route Route to gateway Gateway: no valid listener has name 'Other Listener' and uses tcp protocol"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ref := tc.route.GetParents()[0]
|
||||
|
||||
actualDidBind, actualErr := tc.gateway.bindRoute(ref, tc.route)
|
||||
|
||||
require.Equal(t, tc.expectedDidBind, actualDidBind)
|
||||
require.Equal(t, tc.expectedErr, actualErr)
|
||||
require.Equal(t, tc.expectedBoundGateway.Listeners, tc.gateway.BoundGateway.Listeners)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoundAPIGatewayUnbindRoute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := map[string]struct {
|
||||
gateway gatewayMeta
|
||||
route structs.BoundRoute
|
||||
expectedGateway structs.BoundAPIGatewayConfigEntry
|
||||
expectedDidUnbind bool
|
||||
}{
|
||||
"TCP Route unbinds from Gateway": {
|
||||
gateway: gatewayMeta{
|
||||
BoundGateway: &structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Routes: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
route: &structs.TCPRouteConfigEntry{
|
||||
Kind: structs.TCPRoute,
|
||||
Name: "Route",
|
||||
Parents: []structs.ResourceReference{
|
||||
{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
SectionName: "Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: structs.BoundAPIGatewayConfigEntry{
|
||||
Kind: structs.BoundAPIGateway,
|
||||
Name: "Gateway",
|
||||
Listeners: []structs.BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Listener",
|
||||
Routes: []structs.ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDidUnbind: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actualDidUnbind := tc.gateway.unbindRoute(tc.route)
|
||||
|
||||
require.Equal(t, tc.expectedDidUnbind, actualDidUnbind)
|
||||
require.Equal(t, tc.expectedGateway.Listeners, tc.gateway.BoundGateway.Listeners)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -980,77 +980,6 @@ func (e *BoundAPIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
|
|||
return &e.EnterpriseMeta
|
||||
}
|
||||
|
||||
func (e *BoundAPIGatewayConfigEntry) UpdateRouteBinding(refs []ResourceReference, route BoundRoute) (bool, map[ResourceReference]error) {
|
||||
didUpdate := false
|
||||
errors := make(map[ResourceReference]error)
|
||||
|
||||
if len(e.Listeners) == 0 {
|
||||
for _, ref := range refs {
|
||||
errors[ref] = fmt.Errorf("route cannot bind because gateway has no listeners")
|
||||
}
|
||||
return false, errors
|
||||
}
|
||||
|
||||
for i, listener := range e.Listeners {
|
||||
// Unbind to handle any stale route references.
|
||||
didUnbind := listener.UnbindRoute(route)
|
||||
if didUnbind {
|
||||
didUpdate = true
|
||||
}
|
||||
e.Listeners[i] = listener
|
||||
|
||||
for _, ref := range refs {
|
||||
didBind, err := e.BindRoute(ref, route)
|
||||
if err != nil {
|
||||
errors[ref] = err
|
||||
}
|
||||
if didBind {
|
||||
didUpdate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return didUpdate, errors
|
||||
}
|
||||
|
||||
func (e *BoundAPIGatewayConfigEntry) BindRoute(ref ResourceReference, route BoundRoute) (bool, error) {
|
||||
if ref.Kind != APIGateway || e.Name != ref.Name || !e.EnterpriseMeta.IsSame(&ref.EnterpriseMeta) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(e.Listeners) == 0 {
|
||||
return false, fmt.Errorf("route cannot bind because gateway has no listeners")
|
||||
}
|
||||
|
||||
didBind := false
|
||||
for i, listener := range e.Listeners {
|
||||
if listener.Name == ref.SectionName || ref.SectionName == "" {
|
||||
if listener.BindRoute(route) {
|
||||
didBind = true
|
||||
e.Listeners[i] = listener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !didBind {
|
||||
return false, fmt.Errorf("invalid section name: %s", ref.SectionName)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (e *BoundAPIGatewayConfigEntry) UnbindRoute(route BoundRoute) bool {
|
||||
didUnbind := false
|
||||
for i, listener := range e.Listeners {
|
||||
if listener.UnbindRoute(route) {
|
||||
didUnbind = true
|
||||
e.Listeners[i] = listener
|
||||
}
|
||||
}
|
||||
|
||||
return didUnbind
|
||||
}
|
||||
|
||||
// BoundAPIGatewayListener is an API gateway listener with information
|
||||
// about the routes and certificates that have successfully bound to it.
|
||||
type BoundAPIGatewayListener struct {
|
||||
|
@ -1061,13 +990,14 @@ type BoundAPIGatewayListener struct {
|
|||
|
||||
// BindRoute is used to create or update a route on the listener.
|
||||
// It returns true if the route was able to be bound to the listener.
|
||||
// Routes should only bind to listeners with their same section name
|
||||
// and protocol. Be sure to check both of these before attempting
|
||||
// to bind a route to the listener.
|
||||
func (l *BoundAPIGatewayListener) BindRoute(route BoundRoute) bool {
|
||||
if l == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO (t-eckert): Add a check that the listener has the same `protocol` as the route. Fail to bind if the protocols do not match.
|
||||
|
||||
// Convert the abstract route interface to a ResourceReference.
|
||||
routeRef := ResourceReference{
|
||||
Kind: route.GetKind(),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -1343,289 +1342,6 @@ func TestBoundAPIGateway(t *testing.T) {
|
|||
testConfigEntryNormalizeAndValidate(t, cases)
|
||||
}
|
||||
|
||||
func TestBoundAPIGatewayBindRoute(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
gateway BoundAPIGatewayConfigEntry
|
||||
route BoundRoute
|
||||
expectedGateway BoundAPIGatewayConfigEntry
|
||||
expectedDidBind bool
|
||||
expectedErr error
|
||||
}{
|
||||
"Bind TCP Route to Gateway": {
|
||||
gateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Test Listener",
|
||||
Routes: []ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
route: &TCPRouteConfigEntry{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
Parents: []ResourceReference{
|
||||
{
|
||||
Kind: APIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
SectionName: "Test Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Test Listener",
|
||||
Routes: []ResourceReference{
|
||||
{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDidBind: true,
|
||||
},
|
||||
"Bind TCP Route with wildcard section name to all listeners on Gateway": {
|
||||
gateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Test Listener 1",
|
||||
Routes: []ResourceReference{},
|
||||
},
|
||||
{
|
||||
Name: "Test Listener 2",
|
||||
Routes: []ResourceReference{},
|
||||
},
|
||||
{
|
||||
Name: "Test Listener 3",
|
||||
Routes: []ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
route: &TCPRouteConfigEntry{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
Parents: []ResourceReference{
|
||||
{
|
||||
Kind: APIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Test Listener 1",
|
||||
Routes: []ResourceReference{
|
||||
{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Test Listener 2",
|
||||
Routes: []ResourceReference{
|
||||
{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Test Listener 3",
|
||||
Routes: []ResourceReference{
|
||||
{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDidBind: true,
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because the parent reference kind is not APIGateway": {
|
||||
gateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
route: &TCPRouteConfigEntry{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
Parents: []ResourceReference{
|
||||
{
|
||||
Name: "Test Bound API Gateway",
|
||||
SectionName: "Test Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: TerminatingGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because the parent reference name does not match": {
|
||||
gateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
route: &TCPRouteConfigEntry{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
Parents: []ResourceReference{
|
||||
{
|
||||
Kind: APIGateway,
|
||||
Name: "Other Test Bound API Gateway",
|
||||
SectionName: "Test Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: nil,
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because it lacks listeners": {
|
||||
gateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
route: &TCPRouteConfigEntry{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
Parents: []ResourceReference{
|
||||
{
|
||||
Kind: APIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
SectionName: "Test Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: fmt.Errorf("route cannot bind because gateway has no listeners"),
|
||||
},
|
||||
"TCP Route cannot bind to Gateway because it has an invalid section name": {
|
||||
gateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
route: &TCPRouteConfigEntry{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
Parents: []ResourceReference{
|
||||
{
|
||||
Kind: APIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
SectionName: "Other Test Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{},
|
||||
},
|
||||
expectedDidBind: false,
|
||||
expectedErr: fmt.Errorf("route cannot bind because gateway has no listeners"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ref := tc.route.GetParents()[0]
|
||||
|
||||
actualDidBind, actualErr := tc.gateway.BindRoute(ref, tc.route)
|
||||
|
||||
require.Equal(t, tc.expectedDidBind, actualDidBind)
|
||||
require.Equal(t, tc.expectedErr, actualErr)
|
||||
require.Equal(t, tc.expectedGateway.Listeners, tc.gateway.Listeners)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoundAPIGatewayUnbindRoute(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
gateway BoundAPIGatewayConfigEntry
|
||||
route BoundRoute
|
||||
expectedGateway BoundAPIGatewayConfigEntry
|
||||
expectedDidUnbind bool
|
||||
}{
|
||||
"TCP Route unbinds from Gateway": {
|
||||
gateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Test Listener",
|
||||
Routes: []ResourceReference{
|
||||
{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
route: &TCPRouteConfigEntry{
|
||||
Kind: TCPRoute,
|
||||
Name: "Test Route",
|
||||
Parents: []ResourceReference{
|
||||
{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Other Test Bound API Gateway",
|
||||
SectionName: "Test Listener",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGateway: BoundAPIGatewayConfigEntry{
|
||||
Kind: BoundAPIGateway,
|
||||
Name: "Test Bound API Gateway",
|
||||
Listeners: []BoundAPIGatewayListener{
|
||||
{
|
||||
Name: "Test Listener",
|
||||
Routes: []ResourceReference{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDidUnbind: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actualDidUnbind := tc.gateway.UnbindRoute(tc.route)
|
||||
|
||||
require.Equal(t, tc.expectedDidUnbind, actualDidUnbind)
|
||||
require.Equal(t, tc.expectedGateway.Listeners, tc.gateway.Listeners)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerBindRoute(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
listener BoundAPIGatewayListener
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
type BoundRoute interface {
|
||||
ConfigEntry
|
||||
GetParents() []ResourceReference
|
||||
GetProtocol() APIGatewayListenerProtocol
|
||||
}
|
||||
|
||||
// HTTPRouteConfigEntry manages the configuration for a HTTP route
|
||||
|
@ -39,6 +40,18 @@ func (e *HTTPRouteConfigEntry) GetName() string {
|
|||
return e.Name
|
||||
}
|
||||
|
||||
func (e *HTTPRouteConfigEntry) GetParents() []ResourceReference {
|
||||
if e == nil {
|
||||
return []ResourceReference{}
|
||||
}
|
||||
// TODO HTTP Route should have "parents". Andrew will implement this in his work.
|
||||
return []ResourceReference{}
|
||||
}
|
||||
|
||||
func (e *HTTPRouteConfigEntry) GetProtocol() APIGatewayListenerProtocol {
|
||||
return ListenerProtocolHTTP
|
||||
}
|
||||
|
||||
func (e *HTTPRouteConfigEntry) Normalize() error {
|
||||
return nil
|
||||
}
|
||||
|
@ -115,6 +128,17 @@ func (e *TCPRouteConfigEntry) GetName() string {
|
|||
return e.Name
|
||||
}
|
||||
|
||||
func (e *TCPRouteConfigEntry) GetParents() []ResourceReference {
|
||||
if e == nil {
|
||||
return []ResourceReference{}
|
||||
}
|
||||
return e.Parents
|
||||
}
|
||||
|
||||
func (e *TCPRouteConfigEntry) GetProtocol() APIGatewayListenerProtocol {
|
||||
return ListenerProtocolTCP
|
||||
}
|
||||
|
||||
func (e *TCPRouteConfigEntry) GetMeta() map[string]string {
|
||||
if e == nil {
|
||||
return nil
|
||||
|
@ -160,13 +184,6 @@ func (e *TCPRouteConfigEntry) CanWrite(authz acl.Authorizer) error {
|
|||
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
|
||||
}
|
||||
|
||||
func (e *TCPRouteConfigEntry) GetParents() []ResourceReference {
|
||||
if e == nil {
|
||||
return []ResourceReference{}
|
||||
}
|
||||
return e.Parents
|
||||
}
|
||||
|
||||
func (e *TCPRouteConfigEntry) GetRaftIndex() *RaftIndex {
|
||||
if e == nil {
|
||||
return &RaftIndex{}
|
||||
|
|
Loading…
Reference in New Issue