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:
Thomas Eckert 2023-01-27 09:41:03 -05:00 committed by GitHub
parent 782aaee69a
commit e69e7fd1f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1347 additions and 579 deletions

View File

@ -21,7 +21,7 @@ type gatewayRefs = map[configentry.KindName][]structs.ResourceReference
// The function returns a list of references to the modified BoundAPIGatewayConfigEntry objects, // 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 // a map of resource references to errors that occurred when they were attempted to be
// bound to a gateway. // 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)) modified := make([]*structs.BoundAPIGatewayConfigEntry, 0, len(gateways))
// errored stores the errors from events where a resource reference failed to bind to a gateway. // 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. // Iterate over all BoundAPIGateway config entries and try to bind them to the route if they are a parent.
for _, gateway := range gateways { for _, gateway := range gateways {
references, routeReferencesGateway := gatewayRefs[configentry.NewKindNameForEntry(gateway)] references, routeReferencesGateway := gatewayRefs[configentry.NewKindNameForEntry(gateway.BoundGateway)]
if routeReferencesGateway { if routeReferencesGateway {
didUpdate, errors := gateway.UpdateRouteBinding(references, route) didUpdate, errors := gateway.updateRouteBinding(references, route)
if didUpdate { if didUpdate {
modified = append(modified, gateway) modified = append(modified, gateway.BoundGateway)
} }
for ref, err := range errors { for ref, err := range errors {
errored[ref] = err errored[ref] = err
@ -44,10 +44,8 @@ func BindRoutesToGateways(gateways []*structs.BoundAPIGatewayConfigEntry, routes
for _, ref := range references { for _, ref := range references {
delete(parentRefs, ref) delete(parentRefs, ref)
} }
} else { } else if gateway.unbindRoute(route) {
if gateway.UnbindRoute(route) { modified = append(modified, gateway.BoundGateway)
modified = append(modified, gateway)
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -980,77 +980,6 @@ func (e *BoundAPIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
return &e.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 // BoundAPIGatewayListener is an API gateway listener with information
// about the routes and certificates that have successfully bound to it. // about the routes and certificates that have successfully bound to it.
type BoundAPIGatewayListener struct { type BoundAPIGatewayListener struct {
@ -1061,13 +990,14 @@ type BoundAPIGatewayListener struct {
// BindRoute is used to create or update a route on the listener. // 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. // 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 { func (l *BoundAPIGatewayListener) BindRoute(route BoundRoute) bool {
if l == nil { if l == nil {
return false 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. // Convert the abstract route interface to a ResourceReference.
routeRef := ResourceReference{ routeRef := ResourceReference{
Kind: route.GetKind(), Kind: route.GetKind(),

View File

@ -1,7 +1,6 @@
package structs package structs
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -1343,289 +1342,6 @@ func TestBoundAPIGateway(t *testing.T) {
testConfigEntryNormalizeAndValidate(t, cases) 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) { func TestListenerBindRoute(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
listener BoundAPIGatewayListener listener BoundAPIGatewayListener

View File

@ -11,6 +11,7 @@ import (
type BoundRoute interface { type BoundRoute interface {
ConfigEntry ConfigEntry
GetParents() []ResourceReference GetParents() []ResourceReference
GetProtocol() APIGatewayListenerProtocol
} }
// HTTPRouteConfigEntry manages the configuration for a HTTP route // HTTPRouteConfigEntry manages the configuration for a HTTP route
@ -39,6 +40,18 @@ func (e *HTTPRouteConfigEntry) GetName() string {
return e.Name 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 { func (e *HTTPRouteConfigEntry) Normalize() error {
return nil return nil
} }
@ -115,6 +128,17 @@ func (e *TCPRouteConfigEntry) GetName() string {
return e.Name 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 { func (e *TCPRouteConfigEntry) GetMeta() map[string]string {
if e == nil { if e == nil {
return nil return nil
@ -160,13 +184,6 @@ func (e *TCPRouteConfigEntry) CanWrite(authz acl.Authorizer) error {
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext) return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
} }
func (e *TCPRouteConfigEntry) GetParents() []ResourceReference {
if e == nil {
return []ResourceReference{}
}
return e.Parents
}
func (e *TCPRouteConfigEntry) GetRaftIndex() *RaftIndex { func (e *TCPRouteConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil { if e == nil {
return &RaftIndex{} return &RaftIndex{}