github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/gateway.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 k8serrors "k8s.io/apimachinery/pkg/api/errors" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/runtime" 14 "k8s.io/apimachinery/pkg/types" 15 ctrl "sigs.k8s.io/controller-runtime" 16 "sigs.k8s.io/controller-runtime/pkg/builder" 17 "sigs.k8s.io/controller-runtime/pkg/client" 18 "sigs.k8s.io/controller-runtime/pkg/handler" 19 "sigs.k8s.io/controller-runtime/pkg/predicate" 20 "sigs.k8s.io/controller-runtime/pkg/reconcile" 21 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 22 gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" 23 gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" 24 25 "github.com/cilium/cilium/operator/pkg/gateway-api/helpers" 26 "github.com/cilium/cilium/operator/pkg/model/translation" 27 ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 28 "github.com/cilium/cilium/pkg/logging/logfields" 29 ) 30 31 const ( 32 // Deprecated: owningGatewayLabel will be removed later in favour of gatewayNameLabel 33 owningGatewayLabel = "io.cilium.gateway/owning-gateway" 34 35 lastTransitionTime = "LastTransitionTime" 36 ) 37 38 // gatewayReconciler reconciles a Gateway object 39 type gatewayReconciler struct { 40 client.Client 41 Scheme *runtime.Scheme 42 translator translation.Translator 43 } 44 45 func newGatewayReconciler(mgr ctrl.Manager, translator translation.Translator) *gatewayReconciler { 46 return &gatewayReconciler{ 47 Client: mgr.GetClient(), 48 Scheme: mgr.GetScheme(), 49 translator: translator, 50 } 51 } 52 53 // SetupWithManager sets up the controller with the Manager. 54 // The reconciler will be triggered by Gateway, or any cilium-managed GatewayClass events 55 func (r *gatewayReconciler) SetupWithManager(mgr ctrl.Manager) error { 56 hasMatchingControllerFn := hasMatchingController(context.Background(), r.Client, controllerName) 57 return ctrl.NewControllerManagedBy(mgr). 58 // Watch its own resource 59 For(&gatewayv1.Gateway{}, 60 builder.WithPredicates(predicate.NewPredicateFuncs(hasMatchingControllerFn))). 61 // Watch GatewayClass resources, which are linked to Gateway 62 Watches(&gatewayv1.GatewayClass{}, 63 r.enqueueRequestForOwningGatewayClass(), 64 builder.WithPredicates(predicate.NewPredicateFuncs(matchesControllerName(controllerName)))). 65 // Watch related LB service for status 66 Watches(&corev1.Service{}, 67 r.enqueueRequestForOwningResource(), 68 builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { 69 _, found := object.GetLabels()[owningGatewayLabel] 70 return found 71 }))). 72 // Watch HTTP Route status changes, there is one assumption that any change in spec will 73 // always update status always at least for observedGeneration value. 74 Watches(&gatewayv1.HTTPRoute{}, 75 r.enqueueRequestForOwningHTTPRoute(), 76 builder.WithPredicates(onlyStatusChanged())). 77 // Watch TLS Route status changes, there is one assumption that any change in spec will 78 // always update status always at least for observedGeneration value. 79 Watches(&gatewayv1alpha2.TLSRoute{}, 80 r.enqueueRequestForOwningTLSRoute(), 81 builder.WithPredicates(onlyStatusChanged())). 82 // Watch GRPCRoute status changes, there is one assumption that any change in spec will 83 // always update status always at least for observedGeneration value. 84 Watches(&gatewayv1.GRPCRoute{}, 85 r.enqueueRequestForOwningGRPCRoute(), 86 builder.WithPredicates(onlyStatusChanged())). 87 // Watch related secrets used to configure TLS 88 Watches(&corev1.Secret{}, 89 r.enqueueRequestForTLSSecret(), 90 builder.WithPredicates(predicate.NewPredicateFuncs(r.usedInGateway))). 91 // Watch related namespace in allowed namespaces 92 Watches(&corev1.Namespace{}, 93 r.enqueueRequestForAllowedNamespace()). 94 // Watch for changes to Reference Grants 95 Watches(&gatewayv1beta1.ReferenceGrant{}, r.enqueueRequestForReferenceGrant()). 96 // Watch created and owned resources 97 Owns(&ciliumv2.CiliumEnvoyConfig{}). 98 Owns(&corev1.Service{}). 99 Owns(&corev1.Endpoints{}). 100 Complete(r) 101 } 102 103 // enqueueRequestForOwningGatewayClass returns an event handler for all Gateway objects 104 // belonging to the given GatewayClass. 105 func (r *gatewayReconciler) enqueueRequestForOwningGatewayClass() handler.EventHandler { 106 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 107 scopedLog := log.WithFields(logrus.Fields{ 108 logfields.Controller: gateway, 109 logfields.Resource: a.GetName(), 110 }) 111 var reqs []reconcile.Request 112 gwList := &gatewayv1.GatewayList{} 113 if err := r.Client.List(ctx, gwList); err != nil { 114 scopedLog.Error("Unable to list Gateways") 115 return nil 116 } 117 118 for _, gw := range gwList.Items { 119 if gw.Spec.GatewayClassName != gatewayv1.ObjectName(a.GetName()) { 120 continue 121 } 122 req := reconcile.Request{ 123 NamespacedName: types.NamespacedName{ 124 Namespace: gw.Namespace, 125 Name: gw.Name, 126 }, 127 } 128 reqs = append(reqs, req) 129 scopedLog.WithFields(logrus.Fields{ 130 logfields.K8sNamespace: gw.GetNamespace(), 131 logfields.Resource: gw.GetName(), 132 }).Info("Queueing gateway") 133 } 134 return reqs 135 }) 136 } 137 138 // enqueueRequestForOwningResource returns an event handler for all Gateway objects having 139 // owningGatewayLabel 140 func (r *gatewayReconciler) enqueueRequestForOwningResource() handler.EventHandler { 141 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 142 scopedLog := log.WithFields(logrus.Fields{ 143 logfields.Controller: "gateway", 144 logfields.Resource: a.GetName(), 145 }) 146 147 key, found := a.GetLabels()[owningGatewayLabel] 148 if !found { 149 return nil 150 } 151 152 scopedLog.WithFields(logrus.Fields{ 153 logfields.K8sNamespace: a.GetNamespace(), 154 logfields.Resource: a.GetName(), 155 "gateway": key, 156 }).Info("Enqueued gateway for owning service") 157 158 return []reconcile.Request{ 159 { 160 NamespacedName: types.NamespacedName{ 161 Namespace: a.GetNamespace(), 162 Name: key, 163 }, 164 }, 165 } 166 }) 167 } 168 169 // enqueueRequestForOwningHTTPRoute returns an event handler for any changes with HTTP Routes 170 // belonging to the given Gateway 171 func (r *gatewayReconciler) enqueueRequestForOwningHTTPRoute() handler.EventHandler { 172 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 173 hr, ok := a.(*gatewayv1.HTTPRoute) 174 if !ok { 175 return nil 176 } 177 178 return getReconcileRequestsForRoute(context.Background(), r.Client, a, hr.Spec.CommonRouteSpec) 179 }) 180 } 181 182 // enqueueRequestForOwningTLSRoute returns an event handler for any changes with TLS Routes 183 // belonging to the given Gateway 184 func (r *gatewayReconciler) enqueueRequestForOwningTLSRoute() handler.EventHandler { 185 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 186 hr, ok := a.(*gatewayv1alpha2.TLSRoute) 187 if !ok { 188 return nil 189 } 190 191 return getReconcileRequestsForRoute(context.Background(), r.Client, a, hr.Spec.CommonRouteSpec) 192 }) 193 } 194 195 // enqueueRequestForOwningGRPCRoute returns an event handler for any changes with GRPC Routes 196 // belonging to the given Gateway 197 func (r *gatewayReconciler) enqueueRequestForOwningGRPCRoute() handler.EventHandler { 198 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 199 gr, ok := a.(*gatewayv1.GRPCRoute) 200 if !ok { 201 return nil 202 } 203 204 return getReconcileRequestsForRoute(ctx, r.Client, a, gr.Spec.CommonRouteSpec) 205 }) 206 } 207 208 func getReconcileRequestsForRoute(ctx context.Context, c client.Client, object metav1.Object, route gatewayv1.CommonRouteSpec) []reconcile.Request { 209 var reqs []reconcile.Request 210 211 scopedLog := log.WithFields(logrus.Fields{ 212 logfields.Controller: gateway, 213 logfields.Resource: types.NamespacedName{ 214 Namespace: object.GetNamespace(), 215 Name: object.GetName(), 216 }, 217 }) 218 219 for _, parent := range route.ParentRefs { 220 if !helpers.IsGateway(parent) { 221 continue 222 } 223 224 ns := helpers.NamespaceDerefOr(parent.Namespace, object.GetNamespace()) 225 226 gw := &gatewayv1.Gateway{} 227 if err := c.Get(ctx, types.NamespacedName{ 228 Namespace: ns, 229 Name: string(parent.Name), 230 }, gw); err != nil { 231 if !k8serrors.IsNotFound(err) { 232 scopedLog.WithError(err).Error("Failed to get Gateway") 233 } 234 continue 235 } 236 237 if !hasMatchingController(ctx, c, controllerName)(gw) { 238 scopedLog.Debug("Gateway does not have matching controller, skipping") 239 continue 240 } 241 242 scopedLog.WithFields(logrus.Fields{ 243 logfields.K8sNamespace: ns, 244 logfields.Resource: parent.Name, 245 logfields.Route: object.GetName(), 246 }).Info("Enqueued gateway for Route") 247 248 reqs = append(reqs, reconcile.Request{ 249 NamespacedName: types.NamespacedName{ 250 Namespace: ns, 251 Name: string(parent.Name), 252 }, 253 }) 254 } 255 256 return reqs 257 } 258 259 // enqueueRequestForOwningTLSCertificate returns an event handler for any changes with TLS secrets 260 func (r *gatewayReconciler) enqueueRequestForTLSSecret() handler.EventHandler { 261 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 262 gateways := getGatewaysForSecret(ctx, r.Client, a) 263 reqs := make([]reconcile.Request, 0, len(gateways)) 264 for _, gw := range gateways { 265 reqs = append(reqs, reconcile.Request{ 266 NamespacedName: types.NamespacedName{ 267 Namespace: gw.GetNamespace(), 268 Name: gw.GetName(), 269 }, 270 }) 271 } 272 return reqs 273 }) 274 } 275 276 // enqueueRequestForAllowedNamespace returns an event handler for any changes 277 // with allowed namespaces 278 func (r *gatewayReconciler) enqueueRequestForAllowedNamespace() handler.EventHandler { 279 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, ns client.Object) []reconcile.Request { 280 gateways := getGatewaysForNamespace(ctx, r.Client, ns) 281 reqs := make([]reconcile.Request, 0, len(gateways)) 282 for _, gw := range gateways { 283 reqs = append(reqs, reconcile.Request{ 284 NamespacedName: gw, 285 }) 286 } 287 return reqs 288 }) 289 } 290 291 func (r *gatewayReconciler) usedInGateway(obj client.Object) bool { 292 return len(getGatewaysForSecret(context.Background(), r.Client, obj)) > 0 293 } 294 295 func (r *gatewayReconciler) enqueueRequestForReferenceGrant() handler.EventHandler { 296 return handler.EnqueueRequestsFromMapFunc(r.enqueueAll()) 297 } 298 299 func (r *gatewayReconciler) enqueueAll() handler.MapFunc { 300 return func(ctx context.Context, o client.Object) []reconcile.Request { 301 scopedLog := log.WithFields(logrus.Fields{ 302 logfields.Controller: "gateway", 303 logfields.Resource: client.ObjectKeyFromObject(o), 304 }) 305 list := &gatewayv1.GatewayList{} 306 307 if err := r.Client.List(ctx, list, &client.ListOptions{}); err != nil { 308 scopedLog.WithError(err).Error("Failed to list Gateway") 309 return []reconcile.Request{} 310 } 311 312 requests := make([]reconcile.Request, 0, len(list.Items)) 313 for _, item := range list.Items { 314 gw := client.ObjectKey{ 315 Namespace: item.GetNamespace(), 316 Name: item.GetName(), 317 } 318 requests = append(requests, reconcile.Request{ 319 NamespacedName: gw, 320 }) 321 scopedLog.Info("Enqueued Gateway for resource", gateway, gw) 322 } 323 return requests 324 } 325 }