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  }