github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/httproute_gamma_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 corev1 "k8s.io/api/core/v1" 14 k8serrors "k8s.io/apimachinery/pkg/api/errors" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 ctrl "sigs.k8s.io/controller-runtime" 17 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 18 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 19 gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" 20 21 controllerruntime "github.com/cilium/cilium/operator/pkg/controller-runtime" 22 "github.com/cilium/cilium/operator/pkg/gateway-api/routechecks" 23 "github.com/cilium/cilium/operator/pkg/model" 24 "github.com/cilium/cilium/operator/pkg/model/ingestion" 25 ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 26 "github.com/cilium/cilium/pkg/logging/logfields" 27 ) 28 29 // Reconcile is part of the main kubernetes reconciliation loop which aims to 30 // move the current state of the cluster closer to the desired state. 31 // 32 // For more details, check Reconcile and its Result here: 33 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile 34 func (r *gammaHttpRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 35 scopedLog := log.WithContext(ctx).WithFields(logrus.Fields{ 36 logfields.Controller: "gammaHttpRoute", 37 logfields.Resource: req.NamespacedName, 38 }) 39 scopedLog.Info("Reconciling GAMMA HTTPRoute") 40 41 // Fetch the HTTPRoute instance 42 original := &gatewayv1.HTTPRoute{} 43 if err := r.Client.Get(ctx, req.NamespacedName, original); err != nil { 44 if k8serrors.IsNotFound(err) { 45 return controllerruntime.Success() 46 } 47 scopedLog.WithError(err).Error("Unable to fetch HTTPRoute") 48 return controllerruntime.Fail(err) 49 } 50 51 // Ignore deleted HTTPRoute, this can happen when foregroundDeletion is enabled 52 if original.GetDeletionTimestamp() != nil { 53 return controllerruntime.Success() 54 } 55 56 hr := original.DeepCopy() 57 58 // Get ReferenceGrants 59 grants := &gatewayv1beta1.ReferenceGrantList{} 60 if err := r.Client.List(ctx, grants); err != nil { 61 return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to retrieve reference grants: %w", err), original, hr) 62 } 63 64 servicesList := &corev1.ServiceList{} 65 if err := r.Client.List(ctx, servicesList); err != nil { 66 scopedLog.WithError(err).Error("Unable to list Services") 67 return r.handleReconcileErrorWithStatus(ctx, err, original, hr) 68 } 69 70 // input for the validators 71 i := &routechecks.HTTPRouteInput{ 72 Ctx: ctx, 73 Logger: scopedLog.WithField(logfields.Resource, hr), 74 Client: r.Client, 75 Grants: grants, 76 HTTPRoute: hr, 77 } 78 79 // gateway validators 80 for _, parent := range hr.Spec.ParentRefs { 81 82 // set acceptance to okay, this wil be overwritten in checks if needed 83 i.SetParentCondition(parent, metav1.Condition{ 84 Type: string(gatewayv1.RouteConditionAccepted), 85 Status: metav1.ConditionTrue, 86 Reason: string(gatewayv1.RouteReasonAccepted), 87 Message: "Accepted HTTPRoute", 88 }) 89 90 // set status to okay, this wil be overwritten in checks if needed 91 i.SetAllParentCondition(metav1.Condition{ 92 Type: string(gatewayv1.RouteConditionResolvedRefs), 93 Status: metav1.ConditionTrue, 94 Reason: string(gatewayv1.RouteReasonResolvedRefs), 95 Message: "Service reference is valid", 96 }) 97 98 for _, fn := range []routechecks.CheckParentFunc{ 99 routechecks.CheckGammaServiceAllowedForNamespace, 100 } { 101 continueCheck, err := fn(i, parent) 102 if err != nil { 103 return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Gateway check: %w", err), original, hr) 104 } 105 106 if !continueCheck { 107 break 108 } 109 } 110 } 111 112 for _, fn := range []routechecks.CheckRuleFunc{ 113 routechecks.CheckAgainstCrossNamespaceBackendReferences, 114 routechecks.CheckBackend, 115 routechecks.CheckBackendIsExistingService, 116 } { 117 continueCheck, err := fn(i) 118 if err != nil { 119 return r.handleReconcileErrorWithStatus(ctx, fmt.Errorf("failed to apply Backend check: %w", err), original, hr) 120 } 121 122 if !continueCheck { 123 break 124 } 125 } 126 127 if err := r.updateStatus(ctx, original, hr); err != nil { 128 return ctrl.Result{}, fmt.Errorf("failed to update HTTPRoute status: %w", err) 129 } 130 131 httpListeners := ingestion.GammaHTTPRoutes(ingestion.GammaInput{ 132 HTTPRoutes: []gatewayv1.HTTPRoute{*hr}, 133 Services: servicesList.Items, 134 ReferenceGrants: grants.Items, 135 }) 136 137 cec, svc, cep, err := r.translator.Translate(&model.Model{HTTP: httpListeners}) 138 if err != nil { 139 scopedLog.WithError(err).Error("Unable to translate resources") 140 return r.handleReconcileErrorWithStatus(ctx, err, original, hr) 141 } 142 143 scopedLog. 144 WithField("service", fmt.Sprintf("%#v", svc)). 145 WithField("endpoints", fmt.Sprintf("%#v", cep)). 146 Debug("GAMMA translation result") 147 148 if err = r.ensureEnvoyConfig(ctx, cec); err != nil { 149 scopedLog.WithError(err).Error("Unable to ensure CiliumEnvoyConfig") 150 return r.handleReconcileErrorWithStatus(ctx, err, original, hr) 151 } 152 153 if err = r.ensureEndpoints(ctx, cep); err != nil { 154 scopedLog.WithError(err).Error("Unable to ensure Endpoints") 155 return r.handleReconcileErrorWithStatus(ctx, err, original, hr) 156 } 157 158 scopedLog.Info("Successfully reconciled HTTPRoute") 159 return controllerruntime.Success() 160 } 161 162 func (r *gammaHttpRouteReconciler) updateStatus(ctx context.Context, original *gatewayv1.HTTPRoute, new *gatewayv1.HTTPRoute) error { 163 oldStatus := original.Status.DeepCopy() 164 newStatus := new.Status.DeepCopy() 165 166 opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") 167 if cmp.Equal(oldStatus, newStatus, opts) { 168 return nil 169 } 170 return r.Client.Status().Update(ctx, new) 171 } 172 173 func (r *gammaHttpRouteReconciler) handleReconcileErrorWithStatus(ctx context.Context, reconcileErr error, original *gatewayv1.HTTPRoute, modified *gatewayv1.HTTPRoute) (ctrl.Result, error) { 174 if err := r.updateStatus(ctx, original, modified); err != nil { 175 return controllerruntime.Fail(fmt.Errorf("failed to update HTTPRoute status while handling the reconcile error: %w: %w", reconcileErr, err)) 176 } 177 178 return controllerruntime.Fail(reconcileErr) 179 } 180 181 func (r *gammaHttpRouteReconciler) ensureEnvoyConfig(ctx context.Context, desired *ciliumv2.CiliumEnvoyConfig) error { 182 cec := desired.DeepCopy() 183 _, err := controllerutil.CreateOrPatch(ctx, r.Client, cec, func() error { 184 cec.Spec = desired.Spec 185 setMergedLabelsAndAnnotations(cec, desired) 186 return nil 187 }) 188 return err 189 } 190 191 func (r *gammaHttpRouteReconciler) ensureEndpoints(ctx context.Context, desired *corev1.Endpoints) error { 192 ep := desired.DeepCopy() 193 _, err := controllerutil.CreateOrPatch(ctx, r.Client, ep, func() error { 194 ep.Subsets = desired.Subsets 195 ep.OwnerReferences = desired.OwnerReferences 196 setMergedLabelsAndAnnotations(ep, desired) 197 return nil 198 }) 199 return err 200 }