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

140 lines
4.8 KiB
Go

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
}