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 }