github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/grpcroute_reconcile.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package gateway_api
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/google/go-cmp/cmp/cmpopts"
    12  	"github.com/sirupsen/logrus"
    13  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	ctrl "sigs.k8s.io/controller-runtime"
    16  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    17  	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
    18  
    19  	controllerruntime "github.com/cilium/cilium/operator/pkg/controller-runtime"
    20  	"github.com/cilium/cilium/operator/pkg/gateway-api/routechecks"
    21  	"github.com/cilium/cilium/pkg/logging/logfields"
    22  )
    23  
    24  // Reconcile is part of the main kubernetes reconciliation loop which aims to
    25  // move the current state of the cluster closer to the desired state.
    26  //
    27  // For more details, check Reconcile and its Result here:
    28  // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
    29  //
    30  // The reconciliation loop for GRPCRoute mainly performs checks to make sure that
    31  // the resource is valid and accepted. The Accepted resources will be then included
    32  // in parent Gateway for further processing.
    33  func (r *grpcRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    34  	scopedLog := log.WithContext(ctx).WithFields(logrus.Fields{
    35  		logfields.Controller: grpcRoute,
    36  		logfields.Resource:   req.NamespacedName,
    37  	})
    38  	scopedLog.Info("Reconciling GRPCRoute")
    39  
    40  	// Fetch the GRPCRoute instance
    41  	original := &gatewayv1.GRPCRoute{}
    42  	if err := r.Client.Get(ctx, req.NamespacedName, original); err != nil {
    43  		if k8serrors.IsNotFound(err) {
    44  			return controllerruntime.Success()
    45  		}
    46  		scopedLog.WithError(err).Error("Unable to fetch GRPCRoute")
    47  		return controllerruntime.Fail(err)
    48  	}
    49  
    50  	// Ignore deleted GRPCRoute, this can happen when foregroundDeletion is enabled
    51  	if original.GetDeletionTimestamp() != nil {
    52  		return controllerruntime.Success()
    53  	}
    54  
    55  	gr := original.DeepCopy()
    56  
    57  	// check if the backend is allowed
    58  	grants := &gatewayv1beta1.ReferenceGrantList{}
    59  	if err := r.Client.List(ctx, grants); err != nil {
    60  		return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to retrieve reference grants: %w", err), original, gr)
    61  	}
    62  
    63  	// input for the validators
    64  	i := &routechecks.GRPCRouteInput{
    65  		Ctx:       ctx,
    66  		Logger:    scopedLog.WithField(logfields.Resource, gr),
    67  		Client:    r.Client,
    68  		Grants:    grants,
    69  		GRPCRoute: gr,
    70  	}
    71  
    72  	// gateway validators
    73  	for _, parent := range gr.Spec.ParentRefs {
    74  		// set acceptance to okay, this wil be overwritten in checks if needed
    75  		i.SetParentCondition(parent, metav1.Condition{
    76  			Type:    string(gatewayv1.RouteConditionAccepted),
    77  			Status:  metav1.ConditionTrue,
    78  			Reason:  string(gatewayv1.RouteReasonAccepted),
    79  			Message: "Accepted GRPCRoute",
    80  		})
    81  
    82  		// set status to okay, this wil be overwritten in checks if needed
    83  		i.SetAllParentCondition(metav1.Condition{
    84  			Type:    string(gatewayv1beta1.RouteConditionResolvedRefs),
    85  			Status:  metav1.ConditionTrue,
    86  			Reason:  string(gatewayv1beta1.RouteReasonResolvedRefs),
    87  			Message: "Service reference is valid",
    88  		})
    89  
    90  		// run the actual validators
    91  		for _, fn := range []routechecks.CheckParentFunc{
    92  			routechecks.CheckGatewayAllowedForNamespace,
    93  			routechecks.CheckGatewayRouteKindAllowed,
    94  			routechecks.CheckGatewayMatchingPorts,
    95  			routechecks.CheckGatewayMatchingHostnames,
    96  			routechecks.CheckGatewayMatchingSection,
    97  		} {
    98  			continueCheck, err := fn(i, parent)
    99  			if err != nil {
   100  				return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Gateway check: %w", err), original, gr)
   101  			}
   102  
   103  			if !continueCheck {
   104  				break
   105  			}
   106  		}
   107  	}
   108  
   109  	for _, fn := range []routechecks.CheckRuleFunc{
   110  		routechecks.CheckAgainstCrossNamespaceBackendReferences,
   111  		routechecks.CheckBackend,
   112  		routechecks.CheckHasServiceImportSupport,
   113  		routechecks.CheckBackendIsExistingService,
   114  	} {
   115  		if continueCheck, err := fn(i); err != nil || !continueCheck {
   116  			return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Backend check: %w", err), original, gr)
   117  		}
   118  	}
   119  
   120  	if err := r.updateStatus(ctx, original, gr); err != nil {
   121  		return ctrl.Result{}, fmt.Errorf("failed to update GRPCRoute status: %w", err)
   122  	}
   123  
   124  	scopedLog.Info("Successfully reconciled GRPCRoute")
   125  	return controllerruntime.Success()
   126  }
   127  
   128  func (r *grpcRouteReconciler) updateStatus(ctx context.Context, original *gatewayv1.GRPCRoute, new *gatewayv1.GRPCRoute) error {
   129  	oldStatus := original.Status.DeepCopy()
   130  	newStatus := new.Status.DeepCopy()
   131  
   132  	opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime")
   133  	if cmp.Equal(oldStatus, newStatus, opts) {
   134  		return nil
   135  	}
   136  	return r.Client.Status().Update(ctx, new)
   137  }
   138  
   139  func (r *grpcRouteReconciler) handleReconcileErrorWithStatus(ctx context.Context, reconcileErr error, original *gatewayv1.GRPCRoute, modified *gatewayv1.GRPCRoute) (ctrl.Result, error) {
   140  	if err := r.updateStatus(ctx, original, modified); err != nil {
   141  		return controllerruntime.Fail(fmt.Errorf("failed to update GRPCRoute status while handling the reconcile error: %w: %w", reconcileErr, err))
   142  	}
   143  
   144  	return controllerruntime.Fail(reconcileErr)
   145  }