Skip to content

Commit

Permalink
combine services across different httproutes
Browse files Browse the repository at this point in the history
  • Loading branch information
randmonkey committed Nov 25, 2024
1 parent 792ce60 commit 01ab6bc
Show file tree
Hide file tree
Showing 9 changed files with 1,708 additions and 34 deletions.
38 changes: 31 additions & 7 deletions internal/dataplane/translator/backendref.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ import (
func backendRefsToKongStateBackends(
logger logr.Logger,
storer store.Storer,
route client.Object,
routes []client.Object,
backendRefs []gatewayapi.BackendRef,
allowed map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo,
) kongstate.ServiceBackends {
backends := kongstate.ServiceBackends{}

for _, backendRef := range backendRefs {
logger := loggerForBackendRef(logger, route, backendRef)
logger := loggerForBackendRef(logger, routes[0], backendRef)

nn := client.ObjectKey{
Name: string(backendRef.Name),
Namespace: route.GetNamespace(),
Namespace: routes[0].GetNamespace(),
}
if backendRef.Namespace != nil {
nn.Namespace = string(*backendRef.Namespace)
Expand Down Expand Up @@ -64,14 +64,12 @@ func backendRefsToKongStateBackends(
continue
}

if !util.IsBackendRefGroupKindSupported(backendRef.Group, backendRef.Kind) ||
!gatewayapi.NewRefCheckerForRoute(logger, route, backendRef).IsRefAllowedByGrant(allowed) {
if err := backendRefAllowedForRoutes(logger, routes, backendRef, allowed); err != nil {
// we log impermissible refs rather than failing the entire rule. while we cannot actually route to
// these, we do not want a single impermissible ref to take the entire rule offline. in the case of edits,
// failing the entire rule could potentially delete routes that were previously online and in use, and
// that remain viable (because they still have some permissible backendRefs)
logger.Error(nil, "Object requested backendRef to target, but no ReferenceGrant permits it, skipping...")
continue
logger.Error(err, "Object requested backendRef to target, but no ReferenceGrant permits it, skipping...")
}

port := int32(-1)
Expand All @@ -98,6 +96,32 @@ func backendRefsToKongStateBackends(
return backends
}

func backendRefAllowedForRoutes(
logger logr.Logger,
routes []client.Object,
backendRef gatewayapi.BackendRef,
allowed map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo,
) error {
if !util.IsBackendRefGroupKindSupported(backendRef.Group, backendRef.Kind) {
group := "core"
if backendRef.Group != nil || *backendRef.Group == "" {
group = string(*backendRef.Group)
}
return fmt.Errorf("backendRef kind does is not supported: %s/%s", group, *backendRef.Kind)
}
for _, route := range routes {
if !gatewayapi.NewRefCheckerForRoute(logger, route, backendRef).IsRefAllowedByGrant(allowed) {
return fmt.Errorf("No ReferenceGrant found from %s/%s %s/%s to the backend",
route.GetObjectKind().GroupVersionKind().Group,
route.GetObjectKind().GroupVersionKind().Kind,
route.GetNamespace(),
route.GetName(),
)
}
}
return nil
}

func loggerForBackendRef(logger logr.Logger, route client.Object, backendRef gatewayapi.BackendRef) logr.Logger {
var (
namespace = route.GetNamespace()
Expand Down
2 changes: 1 addition & 1 deletion internal/dataplane/translator/backendref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func TestBackendRefsToKongStateBackends(t *testing.T) {
fakestore, err := store.NewFakeStore(tc.objects)
require.NoError(t, err)
logger := logr.Discard()
ret := backendRefsToKongStateBackends(logger, fakestore, tc.route, tc.backendRefs, tc.allowed)
ret := backendRefsToKongStateBackends(logger, fakestore, []client.Object{tc.route}, tc.backendRefs, tc.allowed)
require.Equal(t, tc.expected, ret)
})
}
Expand Down
156 changes: 156 additions & 0 deletions internal/dataplane/translator/subtranslator/backendref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package subtranslator

import (
"errors"
"fmt"

"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
)

// backendRefsToKongStateBackends takes a list of BackendRefs and returns a list of ServiceBackends.
// The backendRefs are checked for the following conditions. If any of these conditions are met, the BackendRef is
// not included in the returned list:
// - If a BackendRef is not permitted by the provided ReferenceGrantTo set,
// - If a BackendRef is not found,
// - If a BackendRef Group & Kind pair is not supported (currently only Service is supported),
// - If a BackendRef is missing a port.
// The provided client is used to retrieve the Backend referenced by the BackendRef
// to check if it exists.
func backendRefsToKongStateBackends(
logger logr.Logger,
storer store.Storer,
routes []client.Object,
backendRefs []gatewayapi.BackendRef,
aggregatedAllowedMap map[gatewayapi.ReferenceGrantFrom]map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo,
) kongstate.ServiceBackends {
backends := kongstate.ServiceBackends{}

for _, backendRef := range backendRefs {
logger := loggerForBackendRef(logger, routes[0], backendRef)

nn := client.ObjectKey{
Name: string(backendRef.Name),
Namespace: routes[0].GetNamespace(),
}
if backendRef.Namespace != nil {
nn.Namespace = string(*backendRef.Namespace)
}

if backendRef.Kind == nil {
// This should never happen as the default value defined by Gateway API is 'Service'. Checking just in case.
logger.Error(nil, "Object requested backendRef to target, but no Kind was specified, skipping...")
continue
}

var err error
switch *backendRef.Kind {
case "Service":
_, err = storer.GetService(nn.Namespace, nn.Name)
default:
err = fmt.Errorf("unsupported kind %q, only 'Service' is supported", *backendRef.Kind)
}
if err != nil {
if errors.As(err, &store.NotFoundError{}) {
logger.Error(err, "Object requested backendRef to target, but it does not exist, skipping...")
} else {
logger.Error(err, "Object requested backendRef to target, but an error occurred, skipping...")
}
continue
}

if err := backendRefAllowedForRoutes(logger, routes, backendRef, aggregatedAllowedMap); err != nil {
// we log impermissible refs rather than failing the entire rule. while we cannot actually route to
// these, we do not want a single impermissible ref to take the entire rule offline. in the case of edits,
// failing the entire rule could potentially delete routes that were previously online and in use, and
// that remain viable (because they still have some permissible backendRefs)
logger.Error(err, "Object requested backendRef to target, but no ReferenceGrant permits it, skipping...")
continue
}

port := int32(-1)
if backendRef.Port != nil {
port = int32(*backendRef.Port)
}
backend, err := kongstate.NewServiceBackendForService(
nn,
kongstate.PortDef{
Mode: kongstate.PortModeByNumber,
Number: port,
},
)
if err != nil {
logger.Error(err, "failed to create ServiceBackend for backendRef")
continue
}
if backendRef.Weight != nil {
backend.SetWeight(*backendRef.Weight)
}
backends = append(backends, backend)
}

return backends
}

func backendRefAllowedForRoutes(
logger logr.Logger,
routes []client.Object,
backendRef gatewayapi.BackendRef,
aggregatewAllowedMap map[gatewayapi.ReferenceGrantFrom]map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo,
) error {
if !util.IsBackendRefGroupKindSupported(backendRef.Group, backendRef.Kind) {
group := "core"
if backendRef.Group != nil || *backendRef.Group == "" {
group = string(*backendRef.Group)
}
return fmt.Errorf("backendRef kind does is not supported: %s/%s", group, *backendRef.Kind)
}
for _, route := range routes {
grantFrom := gatewayapi.ReferenceGrantFrom{
Group: gatewayapi.Group(route.GetObjectKind().GroupVersionKind().Group),
Kind: gatewayapi.Kind(route.GetObjectKind().GroupVersionKind().Kind),
Namespace: gatewayapi.Namespace(route.GetNamespace()),
}
allowed, ok := aggregatewAllowedMap[grantFrom]
if !ok ||
!gatewayapi.NewRefCheckerForRoute(logger, route, backendRef).
IsRefAllowedByGrant(allowed) {
return fmt.Errorf("No ReferenceGrant found from %s/%s %s/%s to the backend",
route.GetObjectKind().GroupVersionKind().Group,
route.GetObjectKind().GroupVersionKind().Kind,
route.GetNamespace(),
route.GetName(),
)
}
}
return nil
}

func loggerForBackendRef(logger logr.Logger, route client.Object, backendRef gatewayapi.BackendRef) logr.Logger {
var (
namespace = route.GetNamespace()
kind = "unknown"
)
if backendRef.Namespace != nil {
namespace = string(*backendRef.Namespace)
}
if backendRef.Kind != nil {
kind = string(*backendRef.Kind)
}

objName := fmt.Sprintf("%s %s/%s",
route.GetObjectKind().GroupVersionKind().String(),
route.GetNamespace(),
route.GetName())
return logger.WithValues(
"object_name", objName,
"target_kind", kind,
"target_namespace", namespace,
"target_name", backendRef.Name,
)
}
Loading

0 comments on commit 01ab6bc

Please sign in to comment.