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  }