Skip to content

Commit

Permalink
feat(httproute): add ReferenceGrant watch to controller (#3759)
Browse files Browse the repository at this point in the history
Co-authored-by: Grzegorz Burzyński <czeslavo@gmail.com>
Co-authored-by: Travis Raines <571832+rainest@users.noreply.github.com>
  • Loading branch information
3 people committed Mar 27, 2023
1 parent 6b00bfa commit d11292b
Show file tree
Hide file tree
Showing 14 changed files with 767 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ Adding a new version? You'll need three changes:
- Adding the `konghq.com/tags: csv,of,tags` annotation will add tags to
generated resources.
[#3778](https://github.com/Kong/kubernetes-ingress-controller/pull/3778)
- `HTTPRoute` reconciler now watches relevant `ReferenceGrant`s for changes.
[#3759](https://github.com/Kong/kubernetes-ingress-controller/pull/3759)
- Bumped Kong version in manifests to 3.2.
[#3804](https://github.com/Kong/kubernetes-ingress-controller/pull/3804)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func startKongAdminAPIServiceReconciler(ctx context.Context, t *testing.T, clien

mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Logger: logrusr.New(logrus.New()),
Scheme: scheme.Scheme,
Scheme: client.Scheme(),
SyncPeriod: lo.ToPtr(2 * time.Second),
MetricsBindAddress: "0",
})
Expand Down Expand Up @@ -106,7 +106,7 @@ func TestKongAdminAPIController(t *testing.T) {
// In tests below we use a deferred cancel to stop the manager and not wait
// for its timeout.

cfg := envtest.Setup(t)
cfg := envtest.Setup(t, scheme.Scheme)
client, err := ctrlclient.New(cfg, ctrlclient.Options{})
require.NoError(t, err)

Expand Down
11 changes: 11 additions & 0 deletions internal/controllers/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package controllers

import (
"github.com/go-logr/logr"
ctrl "sigs.k8s.io/controller-runtime"
)

type Reconciler interface {
SetupWithManager(ctrl.Manager) error
SetLogger(logr.Logger)
}
26 changes: 26 additions & 0 deletions internal/controllers/gateway/dataplane_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gateway

import (
"sigs.k8s.io/controller-runtime/pkg/client"

k8sobj "github.com/kong/kubernetes-ingress-controller/v2/internal/util/kubernetes/object"
)

// DataPlane is a common interface that is used by reconcilers to interact
// with the dataplane.
//
// TODO: This can probably be used in other reconcilers as well.
// Related issue: https://github.com/Kong/kubernetes-ingress-controller/issues/3794
type DataPlane interface {
DataPlaneClient

AreKubernetesObjectReportsEnabled() bool
KubernetesObjectConfigurationStatus(obj client.Object) k8sobj.ConfigurationStatus
}

// DataPlaneClient is a common client interface that is used by reconcilers to interact
// with the dataplane to perform CRUD operations on provided objects.
type DataPlaneClient interface {
UpdateObject(obj client.Object) error
DeleteObject(obj client.Object) error
}
32 changes: 32 additions & 0 deletions internal/controllers/gateway/dataplane_mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gateway

import (
"sigs.k8s.io/controller-runtime/pkg/client"

k8sobj "github.com/kong/kubernetes-ingress-controller/v2/internal/util/kubernetes/object"
)

type DataplaneMock struct {
KubernetesObjectReportsEnabled bool
// Mapping namespace to name to status
// Note: this will come in useful when implementing
// https://github.com/Kong/kubernetes-ingress-controller/issues/3793
// which requires the status to be reported for route objects.
ObjectsStatuses map[string]map[string]k8sobj.ConfigurationStatus
}

func (d DataplaneMock) UpdateObject(_ client.Object) error {
return nil
}

func (d DataplaneMock) DeleteObject(_ client.Object) error {
return nil
}

func (d DataplaneMock) AreKubernetesObjectReportsEnabled() bool {
return d.KubernetesObjectReportsEnabled
}

func (d DataplaneMock) KubernetesObjectConfigurationStatus(obj client.Object) k8sobj.ConfigurationStatus {
return d.ObjectsStatuses[obj.GetNamespace()][obj.GetName()]
}
3 changes: 2 additions & 1 deletion internal/controllers/gateway/gateway_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ func isObjectUnmanaged(anns map[string]string) bool {
// isGatewayClassControlledAndUnmanaged returns boolean if the GatewayClass
// is controlled by this controller and is configured for unmanaged mode.
func isGatewayClassControlledAndUnmanaged(gatewayClass *GatewayClass) bool {
return gatewayClass.Spec.ControllerName == GetControllerName() && isObjectUnmanaged(gatewayClass.Annotations)
isUnamanaged := isObjectUnmanaged(gatewayClass.Annotations)
return gatewayClass.Spec.ControllerName == GetControllerName() && isUnamanaged
}

// getRefFromPublishService splits a publish service string in the format namespace/name into a types.NamespacedName
Expand Down
66 changes: 64 additions & 2 deletions internal/controllers/gateway/httproute_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/source"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
k8sobj "github.com/kong/kubernetes-ingress-controller/v2/internal/util/kubernetes/object"
)
Expand All @@ -38,7 +37,7 @@ type HTTPRouteReconciler struct {

Log logr.Logger
Scheme *runtime.Scheme
DataplaneClient *dataplane.KongClient
DataplaneClient DataPlane
// If EnableReferenceGrant is true, we will check for ReferenceGrant if backend in another
// namespace is in backendRefs.
// If it is false, referencing backend in different namespace will be rejected.
Expand Down Expand Up @@ -87,6 +86,16 @@ func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
return err
}

if r.EnableReferenceGrant {
if err := c.Watch(
&source.Kind{Type: &gatewayv1beta1.ReferenceGrant{}},
handler.EnqueueRequestsFromMapFunc(r.listReferenceGrantsForHTTPRoute),
predicate.NewPredicateFuncs(referenceGrantHasHTTPRouteFrom),
); err != nil {
return err
}
}

// because of the additional burden of having to manage reference data-plane
// configurations for HTTPRoute objects in the underlying Kong Gateway, we
// simply reconcile ALL HTTPRoute objects. This allows us to drop the backend
Expand All @@ -102,6 +111,54 @@ func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
// HTTPRoute Controller - Event Handlers
// -----------------------------------------------------------------------------

// listReferenceGrantsForHTTPRoute is a watch predicate which finds all HTTPRoutes
// mentioned in a From clause for a ReferenceGrant.
func (r *HTTPRouteReconciler) listReferenceGrantsForHTTPRoute(obj client.Object) []reconcile.Request {
grant, ok := obj.(*gatewayv1beta1.ReferenceGrant)
if !ok {
r.Log.Error(
fmt.Errorf("unexpected object type"),
"referencegrant watch predicate received unexpected object type",
"expected", "*gatewayv1beta1.ReferenceGrant", "found", reflect.TypeOf(obj),
)
return nil
}
httproutes := &gatewayv1beta1.HTTPRouteList{}
if err := r.Client.List(context.Background(), httproutes); err != nil {
r.Log.Error(err, "failed to list httproutes in watch", "referencegrant", grant.Name)
return nil
}
recs := []reconcile.Request{}
for _, gateway := range httproutes.Items {
for _, from := range grant.Spec.From {
if string(from.Namespace) == gateway.Namespace &&
from.Kind == gatewayv1beta1.Kind("HTTPRoute") &&
from.Group == gatewayv1beta1.Group("gateway.networking.k8s.io") {
recs = append(recs, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: gateway.Namespace,
Name: gateway.Name,
},
})
}
}
}
return recs
}

func referenceGrantHasHTTPRouteFrom(obj client.Object) bool {
grant, ok := obj.(*gatewayv1beta1.ReferenceGrant)
if !ok {
return false
}
for _, from := range grant.Spec.From {
if from.Kind == "HTTPRoute" && from.Group == "gateway.networking.k8s.io" {
return true
}
}
return false
}

// listHTTPRoutesForGatewayClass is a controller-runtime event.Handler which
// produces a list of HTTPRoutes which were bound to a Gateway which is or was
// bound to this GatewayClass. This implementation effectively does a map-reduce
Expand Down Expand Up @@ -634,3 +691,8 @@ func (r *HTTPRouteReconciler) getHTTPRouteRuleReason(ctx context.Context, httpRo
}
return gatewayv1beta1.RouteReasonResolvedRefs, nil
}

// SetLogger sets the logger.
func (r *HTTPRouteReconciler) SetLogger(l logr.Logger) {
r.Log = l
}
Loading

0 comments on commit d11292b

Please sign in to comment.