github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/httproute.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  
     9  	"github.com/sirupsen/logrus"
    10  	corev1 "k8s.io/api/core/v1"
    11  	"k8s.io/apimachinery/pkg/fields"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  	"k8s.io/apimachinery/pkg/types"
    14  	ctrl "sigs.k8s.io/controller-runtime"
    15  	"sigs.k8s.io/controller-runtime/pkg/builder"
    16  	"sigs.k8s.io/controller-runtime/pkg/client"
    17  	"sigs.k8s.io/controller-runtime/pkg/handler"
    18  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    19  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    20  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    21  	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
    22  	mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    23  
    24  	"github.com/cilium/cilium/operator/pkg/gateway-api/helpers"
    25  	"github.com/cilium/cilium/pkg/logging/logfields"
    26  )
    27  
    28  // httpRouteReconciler reconciles a HTTPRoute object
    29  type httpRouteReconciler struct {
    30  	client.Client
    31  	Scheme *runtime.Scheme
    32  }
    33  
    34  func newHTTPRouteReconciler(mgr ctrl.Manager) *httpRouteReconciler {
    35  	return &httpRouteReconciler{
    36  		Client: mgr.GetClient(),
    37  		Scheme: mgr.GetScheme(),
    38  	}
    39  }
    40  
    41  // SetupWithManager sets up the controller with the Manager.
    42  func (r *httpRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
    43  	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &gatewayv1.HTTPRoute{}, backendServiceIndex,
    44  		func(rawObj client.Object) []string {
    45  			route, ok := rawObj.(*gatewayv1.HTTPRoute)
    46  			if !ok {
    47  				return nil
    48  			}
    49  			var backendServices []string
    50  			for _, rule := range route.Spec.Rules {
    51  				for _, backend := range rule.BackendRefs {
    52  					namespace := helpers.NamespaceDerefOr(backend.Namespace, route.Namespace)
    53  					backendServiceName, err := helpers.GetBackendServiceName(r.Client, namespace, backend.BackendObjectReference)
    54  					if err != nil {
    55  						log.WithFields(logrus.Fields{
    56  							logfields.Controller: "httpRoute",
    57  							logfields.Resource:   client.ObjectKeyFromObject(rawObj),
    58  						}).WithError(err).Error("Failed to get backend service name")
    59  						continue
    60  					}
    61  					backendServices = append(backendServices,
    62  						types.NamespacedName{
    63  							Namespace: helpers.NamespaceDerefOr(backend.Namespace, route.Namespace),
    64  							Name:      backendServiceName,
    65  						}.String(),
    66  					)
    67  				}
    68  			}
    69  			return backendServices
    70  		},
    71  	); err != nil {
    72  		return err
    73  	}
    74  
    75  	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &gatewayv1beta1.HTTPRoute{}, backendServiceImportIndex,
    76  		func(rawObj client.Object) []string {
    77  			hr, ok := rawObj.(*gatewayv1beta1.HTTPRoute)
    78  			if !ok {
    79  				return nil
    80  			}
    81  			var backendServiceImports []string
    82  			for _, rule := range hr.Spec.Rules {
    83  				for _, backend := range rule.BackendRefs {
    84  					if !helpers.IsServiceImport(backend.BackendObjectReference) {
    85  						continue
    86  					}
    87  					backendServiceImports = append(backendServiceImports,
    88  						types.NamespacedName{
    89  							Namespace: helpers.NamespaceDerefOr(backend.Namespace, hr.Namespace),
    90  							Name:      string(backend.Name),
    91  						}.String(),
    92  					)
    93  				}
    94  			}
    95  			return backendServiceImports
    96  		},
    97  	); err != nil {
    98  		return err
    99  	}
   100  
   101  	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &gatewayv1.HTTPRoute{}, gatewayIndex,
   102  		func(rawObj client.Object) []string {
   103  			hr := rawObj.(*gatewayv1.HTTPRoute)
   104  			var gateways []string
   105  			for _, parent := range hr.Spec.ParentRefs {
   106  				if !helpers.IsGateway(parent) {
   107  					continue
   108  				}
   109  				gateways = append(gateways,
   110  					types.NamespacedName{
   111  						Namespace: helpers.NamespaceDerefOr(parent.Namespace, hr.Namespace),
   112  						Name:      string(parent.Name),
   113  					}.String(),
   114  				)
   115  			}
   116  			return gateways
   117  		},
   118  	); err != nil {
   119  		return err
   120  	}
   121  
   122  	builder := ctrl.NewControllerManagedBy(mgr).
   123  		// Watch for changes to HTTPRoute
   124  		For(&gatewayv1.HTTPRoute{},
   125  			builder.WithPredicates(predicate.NewPredicateFuncs(r.hasGatewayParent()))).
   126  		// Watch for changes to Backend services
   127  		Watches(&corev1.Service{}, r.enqueueRequestForBackendService()).
   128  		// Watch for changes to Reference Grants
   129  		Watches(&gatewayv1beta1.ReferenceGrant{}, r.enqueueRequestForReferenceGrant()).
   130  		// Watch for changes to Gateways and enqueue HTTPRoutes that reference them
   131  		Watches(&gatewayv1.Gateway{}, r.enqueueRequestForGateway(),
   132  			builder.WithPredicates(
   133  				predicate.NewPredicateFuncs(hasMatchingController(context.Background(), mgr.GetClient(), controllerName))))
   134  
   135  	if helpers.HasServiceImportSupport(r.Client.Scheme()) {
   136  		// Watch for changes to Backend Service Imports
   137  		builder = builder.Watches(&mcsapiv1alpha1.ServiceImport{}, r.enqueueRequestForBackendServiceImport())
   138  	}
   139  
   140  	return builder.Complete(r)
   141  }
   142  
   143  func (r *httpRouteReconciler) hasGatewayParent() func(object client.Object) bool {
   144  	return func(obj client.Object) bool {
   145  		hr, ok := obj.(*gatewayv1.HTTPRoute)
   146  		if !ok {
   147  			return false
   148  		}
   149  
   150  		for _, parent := range hr.Spec.ParentRefs {
   151  			if helpers.IsGateway(parent) {
   152  				return true
   153  			}
   154  		}
   155  
   156  		return false
   157  	}
   158  }
   159  
   160  // enqueueRequestForBackendService makes sure that HTTP Routes are reconciled
   161  // if the backend services are updated.
   162  func (r *httpRouteReconciler) enqueueRequestForBackendService() handler.EventHandler {
   163  	return handler.EnqueueRequestsFromMapFunc(r.enqueueFromIndex(backendServiceIndex))
   164  }
   165  
   166  // enqueueRequestForBackendServiceImport makes sure that HTTP Routes are reconciled
   167  // if the backend Service Imports are updated.
   168  func (r *httpRouteReconciler) enqueueRequestForBackendServiceImport() handler.EventHandler {
   169  	return handler.EnqueueRequestsFromMapFunc(r.enqueueFromIndex(backendServiceImportIndex))
   170  }
   171  
   172  // enqueueRequestForReferenceGrant makes sure that all HTTP Routes are reconciled
   173  // if a ReferenceGrant changes
   174  func (r *httpRouteReconciler) enqueueRequestForReferenceGrant() handler.EventHandler {
   175  	return handler.EnqueueRequestsFromMapFunc(r.enqueueAll())
   176  }
   177  
   178  func (r *httpRouteReconciler) enqueueRequestForGateway() handler.EventHandler {
   179  	return handler.EnqueueRequestsFromMapFunc(r.enqueueFromIndex(gatewayIndex))
   180  }
   181  
   182  func (r *httpRouteReconciler) enqueueFromIndex(index string) handler.MapFunc {
   183  	return func(ctx context.Context, o client.Object) []reconcile.Request {
   184  		scopedLog := log.WithFields(logrus.Fields{
   185  			logfields.Controller: "httpRoute",
   186  			logfields.Resource:   client.ObjectKeyFromObject(o),
   187  		})
   188  		hrList := &gatewayv1.HTTPRouteList{}
   189  
   190  		if err := r.Client.List(ctx, hrList, &client.ListOptions{
   191  			FieldSelector: fields.OneTermEqualSelector(index, client.ObjectKeyFromObject(o).String()),
   192  		}); err != nil {
   193  			scopedLog.WithError(err).Error("Failed to get related HTTPRoutes")
   194  			return []reconcile.Request{}
   195  		}
   196  
   197  		requests := make([]reconcile.Request, 0, len(hrList.Items))
   198  		for _, item := range hrList.Items {
   199  			route := client.ObjectKey{
   200  				Namespace: item.GetNamespace(),
   201  				Name:      item.GetName(),
   202  			}
   203  			requests = append(requests, reconcile.Request{
   204  				NamespacedName: route,
   205  			})
   206  			scopedLog.WithField("httpRoute", route).Info("Enqueued HTTPRoute for resource")
   207  		}
   208  		return requests
   209  	}
   210  }
   211  
   212  func (r *httpRouteReconciler) enqueueAll() handler.MapFunc {
   213  	return func(ctx context.Context, o client.Object) []reconcile.Request {
   214  		scopedLog := log.WithFields(logrus.Fields{
   215  			logfields.Controller: "httpRoute",
   216  			logfields.Resource:   client.ObjectKeyFromObject(o),
   217  		})
   218  		hrList := &gatewayv1.HTTPRouteList{}
   219  
   220  		if err := r.Client.List(ctx, hrList, &client.ListOptions{}); err != nil {
   221  			scopedLog.WithError(err).Error("Failed to get HTTPRoutes")
   222  			return []reconcile.Request{}
   223  		}
   224  
   225  		requests := make([]reconcile.Request, 0, len(hrList.Items))
   226  		for _, item := range hrList.Items {
   227  			route := client.ObjectKey{
   228  				Namespace: item.GetNamespace(),
   229  				Name:      item.GetName(),
   230  			}
   231  			requests = append(requests, reconcile.Request{
   232  				NamespacedName: route,
   233  			})
   234  			scopedLog.WithField("httpRoute", route).Info("Enqueued HTTPRoute for resource")
   235  		}
   236  		return requests
   237  	}
   238  }