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 }