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