diff --git a/internal/message/types.go b/internal/message/types.go index 3eb0e0eaa753..827ce7d53a64 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -18,6 +18,8 @@ import ( // ProviderResources message type ProviderResources struct { + // GatewayAPIResources is a map from a GatewayClass name to + // a group of gateway API resources. GatewayAPIResources watchable.Map[string, *gatewayapi.Resources] GatewayStatuses watchable.Map[types.NamespacedName, *gwapiv1b1.Gateway] diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 2407a2751c1c..2ccf08704fb7 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -120,7 +120,10 @@ func newGatewayAPIController(mgr manager.Manager, cfg *config.Server, su status. } // Watch Service CRUDs and process affected *Route objects. - if err := c.Watch(&source.Kind{Type: &corev1.Service{}}, handler.EnqueueRequestsFromMapFunc(r.processServiceForOwningGateway)); err != nil { + if err := c.Watch( + &source.Kind{Type: &corev1.Service{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateServiceForReconcile)); err != nil { return err } @@ -134,7 +137,10 @@ func newGatewayAPIController(mgr manager.Manager, cfg *config.Server, su status. } // Watch ReferenceGrant CRUDs and process affected Gateways. - if err := c.Watch(&source.Kind{Type: &gwapiv1a2.ReferenceGrant{}}, &handler.EnqueueRequestForObject{}); err != nil { + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.ReferenceGrant{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { return err } if err := addReferenceGrantIndexers(ctx, mgr); err != nil { @@ -142,7 +148,11 @@ func newGatewayAPIController(mgr manager.Manager, cfg *config.Server, su status. } // Watch Deployment CRUDs and process affected Gateways. - if err := c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, handler.EnqueueRequestsFromMapFunc(r.processDeploymentForOwningGateway)); err != nil { + if err := c.Watch( + &source.Kind{Type: &appsv1.Deployment{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateDeploymentForReconcile), + ); err != nil { return err } @@ -368,7 +378,7 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, request reconcile. to := ObjectKindNamespacedName{kind: gatewayapi.KindService, namespace: backendNamespace, name: string(backendRef.Name)} refGrant, err := r.findReferenceGrant(ctx, from, to) if err != nil { - r.log.Error(err, "unable to find ReferenceGrant that links the Service to TLSRoute") + r.log.Error(err, "unable to find ReferenceGrant that links the Service to HTTPRoute") continue } @@ -461,66 +471,6 @@ func (r *gatewayAPIReconciler) getNamespace(ctx context.Context, name string) (* return ns, nil } -// processDeploymentForOwningGateway tries finding the owning Gateway of the Deployment -// if it exists, finds the Gateway's Service, and further updates the Gateway -// status Ready condition. -func (r *gatewayAPIReconciler) processDeploymentForOwningGateway(obj client.Object) (request []reconcile.Request) { - // Process Deployment Reconcile nothing. - ctx := context.Background() - deployment := obj.(*appsv1.Deployment) - if deployment == nil { - return - } - - // Check if the deployment belongs to a Gateway, if so, find the Gateway. - gtw := r.findOwningGateway(ctx, deployment.GetLabels()) - if gtw == nil { - return - } - - // Check if the Service for the Gateway also exists, if it does, proceed with - // the Gateway status update. - svc, err := r.envoyServiceForGateway(ctx, gtw) - if err != nil { - r.log.Info("failed to get Service for gateway", - "namespace", gtw.Namespace, "name", gtw.Name) - return - } - - r.statusUpdateForGateway(gtw, svc, deployment) - return -} - -// processServiceForOwningGateway tries finding the owning Gateway of the Service -// if it exists, finds the Gateway's Deployment, and further updates the Gateway -// status Ready condition. -func (r *gatewayAPIReconciler) processServiceForOwningGateway(obj client.Object) (request []reconcile.Request) { - // Process Service Reconcile nothing. - ctx := context.Background() - svc := obj.(*corev1.Service) - if svc == nil { - return - } - - // Check if the Service belongs to a Gateway, if so, find the Gateway. - gtw := r.findOwningGateway(ctx, svc.GetLabels()) - if gtw == nil { - return - } - - // Check if the Deployment for the Gateway also exists, if it does, proceed with - // the Gateway status update. - deployment, err := r.envoyDeploymentForGateway(ctx, gtw) - if err != nil { - r.log.Info("failed to get Deployment for gateway", - "namespace", gtw.Namespace, "name", gtw.Name) - return - } - - r.statusUpdateForGateway(gtw, svc, deployment) - return -} - func (r gatewayAPIReconciler) findOwningGateway(ctx context.Context, labels map[string]string) *gwapiv1b1.Gateway { gwName, ok := labels[gatewayapi.OwningGatewayNameLabel] if !ok { diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go index 82f7d8981682..7909e0dbfc86 100644 --- a/internal/provider/kubernetes/predicates.go +++ b/internal/provider/kubernetes/predicates.go @@ -10,6 +10,7 @@ import ( "github.com/envoyproxy/gateway/internal/gatewayapi" "github.com/envoyproxy/gateway/internal/provider/utils" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" @@ -64,6 +65,7 @@ func (r *gatewayAPIReconciler) validateGatewayForReconcile(obj client.Object) bo return true } +// validateTLSRouteForReconcile checks whether the HTTPRoute refers any valid Gateway. func (r *gatewayAPIReconciler) validateHTTPRouteForReconcile(obj client.Object) bool { hr, ok := obj.(*gwapiv1b1.HTTPRoute) if !ok { @@ -75,6 +77,7 @@ func (r *gatewayAPIReconciler) validateHTTPRouteForReconcile(obj client.Object) return r.validateRouteParentReferences(parentReferences, hr.Namespace) } +// validateTLSRouteForReconcile checks whether the TLSRoute refers any valid Gateway. func (r *gatewayAPIReconciler) validateTLSRouteForReconcile(obj client.Object) bool { tr, ok := obj.(*gwapiv1a2.TLSRoute) if !ok { @@ -86,6 +89,8 @@ func (r *gatewayAPIReconciler) validateTLSRouteForReconcile(obj client.Object) b return r.validateRouteParentReferences(parentReferences, tr.Namespace) } +// validateRouteParentReferences checks whether the parent references of a given Route +// object, point to valid Gateways. func (r *gatewayAPIReconciler) validateRouteParentReferences(refs []gwapiv1b1.ParentReference, defaultNamespace string) bool { for _, ref := range refs { if ref.Kind != nil && *ref.Kind == gatewayapi.KindGateway { @@ -103,12 +108,17 @@ func (r *gatewayAPIReconciler) validateRouteParentReferences(refs []gwapiv1b1.Pa if !r.validateGatewayForReconcile(gw) { return false } + + // Even if one of the parent references points to a valid Gateway, we + // must reconcile the Route object. + return true } } return true } +// validateSecretForReconcile checks whether the Secret belongs to a valid Gateway. func (r *gatewayAPIReconciler) validateSecretForReconcile(obj client.Object) bool { secret, ok := obj.(*corev1.Secret) if !ok { @@ -133,3 +143,64 @@ func (r *gatewayAPIReconciler) validateSecretForReconcile(obj client.Object) boo return true } + +// validateServiceForReconcile tries finding the owning Gateway of the Service +// if it exists, finds the Gateway's Deployment, and further updates the Gateway +// status Ready condition. All Services are pushed for reconciliation. +func (r *gatewayAPIReconciler) validateServiceForReconcile(obj client.Object) bool { + ctx := context.Background() + svc, ok := obj.(*corev1.Service) + if !ok { + r.log.Info("unexpected object type, bypassing reconciliation", "object", obj) + return false + } + + // Check if the Service belongs to a Gateway, if so, find the Gateway. If + gtw := r.findOwningGateway(ctx, svc.GetLabels()) + if gtw != nil { + // Check if the Deployment for the Gateway also exists, if it does, proceed with + // the Gateway status update. + deployment, err := r.envoyDeploymentForGateway(ctx, gtw) + if err != nil { + r.log.Info("failed to get Deployment for gateway", + "namespace", gtw.Namespace, "name", gtw.Name) + return false + } + + r.statusUpdateForGateway(gtw, svc, deployment) + return true + } + + // TODO: further filter only those services that are referred by HTTPRoutes + return true +} + +// validateDeploymentForReconcile tries finding the owning Gateway of the Deployment +// if it exists, finds the Gateway's Service, and further updates the Gateway +// status Ready condition. No Deployments are pushed for reconciliation. +func (r *gatewayAPIReconciler) validateDeploymentForReconcile(obj client.Object) bool { + ctx := context.Background() + deployment, ok := obj.(*appsv1.Deployment) + if !ok { + r.log.Info("unexpected object type, bypassing reconciliation", "object", obj) + return false + } + + // Check if the deployment belongs to a Gateway, if so, find the Gateway. + gtw := r.findOwningGateway(ctx, deployment.GetLabels()) + if gtw != nil { + // Check if the Service for the Gateway also exists, if it does, proceed with + // the Gateway status update. + svc, err := r.envoyServiceForGateway(ctx, gtw) + if err != nil { + r.log.Info("failed to get Service for gateway", + "namespace", gtw.Namespace, "name", gtw.Name) + return false + } + + r.statusUpdateForGateway(gtw, svc, deployment) + } + + // There is no need to reconcile the Deployment any further. + return false +}