Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Gateway status on ReferenceGrant changes #2797

Merged
merged 9 commits into from
Aug 11, 2022
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@
[#2781](https:/Kong/kubernetes-ingress-controller/pull/2781)
- Treat status conditions in `Gateway` and `GatewayClass` as snapshots, replace
existing conditions with same type on setting conditions.
[#2791](https:/Kong/kubernetes-ingress-controller/pull/2791)
[#2791](https:/Kong/kubernetes-ingress-controller/pull/2791)
- Update Listener statuses whenever they change, not just on Gateway creation.
[#2797](https:/Kong/kubernetes-ingress-controller/pull/2797)

#### Under the hood

Expand Down
65 changes: 63 additions & 2 deletions internal/controllers/gateway/gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ type GatewayReconciler struct { //nolint:revive,golint
Scheme *runtime.Scheme
DataplaneClient *dataplane.KongClient

PublishService string
WatchNamespaces []string
PublishService string
WatchNamespaces []string
WatchReferenceGrant bool

publishServiceRef types.NamespacedName
}
Expand Down Expand Up @@ -105,6 +106,17 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
return err
}

// watch ReferenceGrants, which may invalidate or allow cross-namespace TLSConfigs
if r.WatchReferenceGrant {
if err := c.Watch(
&source.Kind{Type: &gatewayv1alpha2.ReferenceGrant{}},
handler.EnqueueRequestsFromMapFunc(r.listReferenceGrantsForGateway),
predicate.NewPredicateFuncs(referenceGrantHasGatewayFrom),
); err != nil {
return err
}
}

// start the required gatewayclass controller as well
gwcCTRL := &GatewayClassReconciler{
Client: r.Client,
Expand Down Expand Up @@ -158,6 +170,38 @@ func (r *GatewayReconciler) listGatewaysForGatewayClass(gatewayClass client.Obje
return reconcileGatewaysIfClassMatches(gatewayClass, gateways.Items)
}

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

// listGatewaysForService is a watch predicate which finds all the gateway objects which use
// GatewayClasses supported by this controller and are configured for the same service via
// unmanaged mode and enqueues them for reconciliation. This is generally used to ensure
Expand Down Expand Up @@ -192,6 +236,19 @@ func (r *GatewayReconciler) isGatewayService(obj client.Object) bool {
return fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName()) == r.PublishService
}

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

// -----------------------------------------------------------------------------
// Gateway Controller - Reconciliation
// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -582,6 +639,10 @@ func (r *GatewayReconciler) updateAddressesAndListenersStatus(
setGatewayCondition(gateway, readyCondition)
return true, r.Status().Update(ctx, pruneGatewayStatusConds(gateway))
randmonkey marked this conversation as resolved.
Show resolved Hide resolved
}
if !reflect.DeepEqual(gateway.Status.Listeners, listenerStatuses) {
rainest marked this conversation as resolved.
Show resolved Hide resolved
gateway.Status.Listeners = listenerStatuses
return true, r.Status().Update(ctx, gateway)
}
return false, nil
}

Expand Down
15 changes: 15 additions & 0 deletions internal/controllers/gateway/gateway_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gateway
import (
"fmt"
"reflect"
"sort"
"strings"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -351,6 +352,13 @@ func getListenerStatus(
})
}

// consistent sort statuses to allow equality comparisons
sort.Slice(status.Conditions, func(i, j int) bool {
rainest marked this conversation as resolved.
Show resolved Hide resolved
a := status.Conditions[i]
b := status.Conditions[j]
return fmt.Sprintf("%s%s%s%s", a.Type, a.Status, a.Reason, a.Message) <
fmt.Sprintf("%s%s%s%s", b.Type, b.Status, b.Reason, b.Message)
})
statuses[listener.Name] = status
}

Expand Down Expand Up @@ -459,6 +467,13 @@ func getListenerStatus(
}
if len(newConditions) > 0 {
status := statuses[listener.Name]
// consistent sort statuses to allow equality comparisons
sort.Slice(newConditions, func(i, j int) bool {
a := newConditions[i]
b := newConditions[j]
return fmt.Sprintf("%s%s%s%s", a.Type, a.Status, a.Reason, a.Message) <
fmt.Sprintf("%s%s%s%s", b.Type, b.Status, b.Reason, b.Message)
})
status.Conditions = newConditions
statuses[listener.Name] = status
}
Expand Down
13 changes: 7 additions & 6 deletions internal/manager/controllerdef.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,13 @@ func setupControllers(
},
}.CRDExists,
Controller: &gateway.GatewayReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName(gatewayFeature),
Scheme: mgr.GetScheme(),
DataplaneClient: dataplaneClient,
PublishService: c.PublishService,
WatchNamespaces: c.WatchNamespaces,
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName(gatewayFeature),
Scheme: mgr.GetScheme(),
DataplaneClient: dataplaneClient,
PublishService: c.PublishService,
WatchNamespaces: c.WatchNamespaces,
WatchReferenceGrant: featureGates[gatewayAlphaFeature],
},
},
{
Expand Down
21 changes: 19 additions & 2 deletions test/integration/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
ktfkong "github.com/kong/kubernetes-testing-framework/pkg/clusters/addons/kong"
netv1 "k8s.io/api/networking/v1"
netv1beta1 "k8s.io/api/networking/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/retry"
Expand Down Expand Up @@ -47,7 +48,15 @@ func DeployGatewayClass(ctx context.Context, client *gatewayclient.Clientset, ga
opt(gwc)
}

return client.GatewayV1alpha2().GatewayClasses().Create(ctx, gwc, metav1.CreateOptions{})
result, err := client.GatewayV1alpha2().GatewayClasses().Create(ctx, gwc, metav1.CreateOptions{})
if apierrors.IsAlreadyExists(err) {
err = client.GatewayV1alpha2().GatewayClasses().Delete(ctx, gwc.Name, metav1.DeleteOptions{})
if err != nil {
return result, err
}
result, err = client.GatewayV1alpha2().GatewayClasses().Create(ctx, gwc, metav1.CreateOptions{})
}
return result, err
}

// DeployGateway creates a default gateway, accepts a variadic set of options,
Expand Down Expand Up @@ -76,7 +85,15 @@ func DeployGateway(ctx context.Context, client *gatewayclient.Clientset, namespa
opt(gw)
}

return client.GatewayV1alpha2().Gateways(namespace).Create(ctx, gw, metav1.CreateOptions{})
result, err := client.GatewayV1alpha2().Gateways(namespace).Create(ctx, gw, metav1.CreateOptions{})
if apierrors.IsAlreadyExists(err) {
err = client.GatewayV1alpha2().Gateways(namespace).Delete(ctx, gw.Name, metav1.DeleteOptions{})
if err != nil {
return result, err
}
result, err = client.GatewayV1alpha2().Gateways(namespace).Create(ctx, gw, metav1.CreateOptions{})
}
return result, err
}

// gatewayHealthCheck checks the gateway has been scheduled by KIC. This function is inspired by
Expand Down
15 changes: 15 additions & 0 deletions test/integration/tlsroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,21 @@ func TestTLSRouteReferenceGrant(t *testing.T) {
return err != nil && responded == false
}, ingressWait, waitTick)

t.Log("verifying that a Listener has the invalid ref status condition")
gateway, err = gatewayClient.GatewayV1alpha2().Gateways(ns.Name).Get(ctx, gateway.Name, metav1.GetOptions{})
require.NoError(t, err)
invalid := false
for _, status := range gateway.Status.Listeners {
for _, condition := range status.Conditions {
if condition.Type == string(gatewayv1alpha2.ListenerConditionResolvedRefs) &&
condition.Status == metav1.ConditionFalse &&
condition.Reason == string(gatewayv1alpha2.ListenerReasonInvalidCertificateRef) {
invalid = true
}
}
}
require.True(t, invalid)

t.Log("verifying the certificate returns when using a ReferenceGrant with no name restrictions")
grant.Spec.To[0].Name = nil
_, err = gatewayClient.GatewayV1alpha2().ReferenceGrants(otherNs.Name).Update(ctx, grant, metav1.UpdateOptions{})
Expand Down