From 1e33cc3dbf24ac73b95751d767d7bb961c026574 Mon Sep 17 00:00:00 2001 From: Jintao Zhang Date: Fri, 5 Jan 2024 22:31:06 +0800 Subject: [PATCH] feat: add new flag to specific Gateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jintao Zhang Co-authored-by: Grzegorz BurzyƄski Co-authored-by: Tao Yi --- CHANGELOG.md | 3 + docs/cli-arguments.md | 1 + internal/controllers/controller.go | 4 + .../controllers/gateway/gateway_controller.go | 20 +++++ .../gateway/grpcroute_controller.go | 22 +++++- .../gateway/httproute_controller.go | 22 +++++- internal/controllers/gateway/route_utils.go | 13 +++- .../controllers/gateway/route_utils_test.go | 11 +-- .../gateway/tcproute_controller.go | 22 +++++- .../gateway/tlsroute_controller.go | 22 +++++- .../gateway/udproute_controller.go | 22 +++++- internal/manager/config.go | 5 ++ internal/manager/config_validation_test.go | 17 ++++ internal/manager/controllerdef.go | 6 ++ test/envtest/run.go | 14 ++++ test/envtest/specific_gateway_envtest_test.go | 77 +++++++++++++++++++ 16 files changed, 270 insertions(+), 11 deletions(-) create mode 100644 test/envtest/specific_gateway_envtest_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 6890fe3788..adbcc0b3ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,9 @@ Adding a new version? You'll need three changes: [#5354](https://github.com/Kong/kubernetes-ingress-controller/pull/5354) [#5384](https://github.com/Kong/kubernetes-ingress-controller/pull/5384) [#5435](https://github.com/Kong/kubernetes-ingress-controller/pull/5435) +- Added flag `--gateway-to-reconcile` to set KIC to only reconcile + the specified Gateway resource in Kubernetes. + [#5405](https://github.com/Kong/kubernetes-ingress-controller/pull/5405) ### Fixed diff --git a/docs/cli-arguments.md b/docs/cli-arguments.md index 86a0abec43..8742a55421 100644 --- a/docs/cli-arguments.md +++ b/docs/cli-arguments.md @@ -39,6 +39,7 @@ | `--feature-gates` | `list of string=bool` | A set of comma separated key=value pairs that describe feature gates for alpha/beta/experimental features. See the Feature Gates documentation for information and available options: https://github.com/Kong/kubernetes-ingress-controller/blob/main/FEATURE_GATES.md. | | | `--gateway-api-controller-name` | `string` | The controller name to match on Gateway API resources. | `konghq.com/kic-gateway-controller` | | `--gateway-discovery-dns-strategy` | `dns-strategy` | DNS strategy to use when creating Gateway's Admin API addresses. One of: ip, service, pod. | `"ip"` | +| `--gateway-to-reconcile` | `namespaced-name` | Gateway namespaced name in "namespace/name" format. Makes KIC reconcile only the specified Gateway. | | | `--health-probe-bind-address` | `string` | The address the probe endpoint binds to. | `:10254` | | `--ingress-class` | `string` | Name of the ingress class to route through this controller. | `kong` | | `--init-cache-sync-duration` | `duration` | The initial delay to wait for Kubernetes object caches to be synced before the initial configuration. | `5s` | diff --git a/internal/controllers/controller.go b/internal/controllers/controller.go index b1acf57911..5e61b39b05 100644 --- a/internal/controllers/controller.go +++ b/internal/controllers/controller.go @@ -2,6 +2,8 @@ package controllers import ( "github.com/go-logr/logr" + "github.com/samber/mo" + k8stypes "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" ) @@ -9,3 +11,5 @@ type Reconciler interface { SetupWithManager(ctrl.Manager) error SetLogger(logr.Logger) } + +type OptionalNamespacedName = mo.Option[k8stypes.NamespacedName] diff --git a/internal/controllers/gateway/gateway_controller.go b/internal/controllers/gateway/gateway_controller.go index 0a8f2e44bf..4a731ab88a 100644 --- a/internal/controllers/gateway/gateway_controller.go +++ b/internal/controllers/gateway/gateway_controller.go @@ -58,6 +58,10 @@ type GatewayReconciler struct { //nolint:revive // to invalidate or allow cross-namespace TLSConfigs in gateways. // It's resolved on SetupWithManager call. enableReferenceGrant bool + + // If GatewayNN is set, + // only resources managed by the specified Gateway are reconciled. + GatewayNN controllers.OptionalNamespacedName } // SetupWithManager sets up the controller with the Manager. @@ -173,6 +177,15 @@ func (r *GatewayReconciler) gatewayHasMatchingGatewayClass(obj client.Object) bo ) return false } + + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gateway.Namespace || gatewayToReconcile.Name != gateway.Name { + return false + } + } + gatewayClass := &gatewayapi.GatewayClass{} if err := r.Client.Get(context.Background(), client.ObjectKey{Name: string(gateway.Spec.GatewayClassName)}, gatewayClass); err != nil { r.Log.Error(err, "Could not retrieve gatewayclass", "gatewayclass", gateway.Spec.GatewayClassName) @@ -327,6 +340,13 @@ func referenceGrantHasGatewayFrom(obj client.Object) bool { func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("GatewayV1Gateway", req.NamespacedName) + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if req.Namespace != gatewayToReconcile.Namespace || req.Name != gatewayToReconcile.Name { + r.Log.V(util.DebugLevel).Info("The request does not match the specified Gateway and will be skipped.", "gateway", gatewayToReconcile.String()) + return ctrl.Result{}, nil + } + } + // gather the gateway object based on the reconciliation trigger. It's possible for the object // to be gone at this point in which case it will be ignored. gateway := new(gatewayapi.Gateway) diff --git a/internal/controllers/gateway/grpcroute_controller.go b/internal/controllers/gateway/grpcroute_controller.go index f0762693e3..91bfc19b5a 100644 --- a/internal/controllers/gateway/grpcroute_controller.go +++ b/internal/controllers/gateway/grpcroute_controller.go @@ -47,6 +47,10 @@ type GRPCRouteReconciler struct { // If it is false, referencing backend in different namespace will be rejected. EnableReferenceGrant bool CacheSyncTimeout time.Duration + + // If GatewayNN is set, + // only resources managed by the specified Gateway are reconciled. + GatewayNN controllers.OptionalNamespacedName } // SetupWithManager sets up the controller with the Manager. @@ -143,6 +147,14 @@ func (r *GRPCRouteReconciler) listGRPCRoutesForGatewayClass(ctx context.Context, gateways := make(map[string]map[string]struct{}) for _, gateway := range gatewayList.Items { if string(gateway.Spec.GatewayClassName) == gwc.Name { + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gateway.Namespace || gatewayToReconcile.Name != gateway.Name { + continue + } + } + _, ok := gateways[gateway.Namespace] if !ok { gateways[gateway.Namespace] = make(map[string]struct{}) @@ -217,6 +229,14 @@ func (r *GRPCRouteReconciler) listGRPCRoutesForGateway(ctx context.Context, obj return nil } + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gw.Namespace || gatewayToReconcile.Name != gw.Name { + return nil + } + } + // map all GRPCRoute objects grpcrouteList := gatewayapi.GRPCRouteList{} if err := r.Client.List(ctx, &grpcrouteList); err != nil { @@ -293,7 +313,7 @@ func (r *GRPCRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // we need to pull the Gateway parent objects for the grpcroute to verify // routing behavior and ensure compatibility with Gateway configurations. debug(log, grpcroute, "Retrieving GatewayClass and Gateway for route") - gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, grpcroute) + gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, grpcroute, r.GatewayNN) if err != nil { if err.Error() == unsupportedGW { debug(log, grpcroute, "Unsupported route found, processing to verify whether it was ever supported") diff --git a/internal/controllers/gateway/httproute_controller.go b/internal/controllers/gateway/httproute_controller.go index 75b045556d..def05ecc9c 100644 --- a/internal/controllers/gateway/httproute_controller.go +++ b/internal/controllers/gateway/httproute_controller.go @@ -52,6 +52,10 @@ type HTTPRouteReconciler struct { // If it is false, referencing backend in different namespace will be rejected. // It's resolved on SetupWithManager call. enableReferenceGrant bool + + // If GatewayNN is set, + // only resources managed by the specified Gateway are reconciled. + GatewayNN controllers.OptionalNamespacedName } // SetupWithManager sets up the controller with the Manager. @@ -217,6 +221,14 @@ func (r *HTTPRouteReconciler) listHTTPRoutesForGatewayClass(ctx context.Context, gateways := make(map[string]map[string]struct{}) for _, gateway := range gatewayList.Items { if string(gateway.Spec.GatewayClassName) == gwc.Name { + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gateway.Namespace || gatewayToReconcile.Name != gateway.Name { + continue + } + } + _, ok := gateways[gateway.Namespace] if !ok { gateways[gateway.Namespace] = make(map[string]struct{}) @@ -291,6 +303,14 @@ func (r *HTTPRouteReconciler) listHTTPRoutesForGateway(ctx context.Context, obj return nil } + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gw.Namespace || gatewayToReconcile.Name != gw.Name { + return nil + } + } + // map all HTTPRoute objects httprouteList := gatewayapi.HTTPRouteList{} if err := r.Client.List(ctx, &httprouteList); err != nil { @@ -368,7 +388,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // we need to pull the Gateway parent objects for the HTTPRoute to verify // routing behavior and ensure compatibility with Gateway configurations. debug(log, httproute, "Retrieving GatewayClass and Gateway for route") - gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, httproute) + gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, httproute, r.GatewayNN) if err != nil { if err.Error() == unsupportedGW { debug(log, httproute, "Unsupported route found, processing to verify whether it was ever supported") diff --git a/internal/controllers/gateway/route_utils.go b/internal/controllers/gateway/route_utils.go index 736ff6b557..d8a29e00ef 100644 --- a/internal/controllers/gateway/route_utils.go +++ b/internal/controllers/gateway/route_utils.go @@ -17,6 +17,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" "github.com/kong/kubernetes-ingress-controller/v3/internal/util" ) @@ -125,7 +126,9 @@ func parentRefsForRoute[T gatewayapi.RouteT](route T) ([]gatewayapi.ParentRefere // Gateway APIs route object (e.g. HTTPRoute, TCPRoute, e.t.c.) from the provided cached // client if they match this controller. If there are no gateways present for this route // OR the present gateways are references to missing objects, this will return a unsupportedGW error. -func getSupportedGatewayForRoute[T gatewayapi.RouteT](ctx context.Context, logger logr.Logger, mgrc client.Client, route T) ([]supportedGatewayWithCondition, error) { +// +// There is a parameter `specifiedGW` here, which is used to specific the gateway. +func getSupportedGatewayForRoute[T gatewayapi.RouteT](ctx context.Context, logger logr.Logger, mgrc client.Client, route T, specifiedGW controllers.OptionalNamespacedName) ([]supportedGatewayWithCondition, error) { // gather the parentrefs for this route object parentRefs, err := parentRefsForRoute(route) if err != nil { @@ -145,6 +148,14 @@ func getSupportedGatewayForRoute[T gatewayapi.RouteT](ctx context.Context, logge } name := string(parentRef.Name) + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := specifiedGW.Get(); ok { + namespace = gatewayToReconcile.Namespace + name = gatewayToReconcile.Name + + } + // pull the Gateway object from the cached client gateway := gatewayapi.Gateway{} if err := mgrc.Get(ctx, client.ObjectKey{ diff --git a/internal/controllers/gateway/route_utils_test.go b/internal/controllers/gateway/route_utils_test.go index ef2e968a0f..a9df5a5ba9 100644 --- a/internal/controllers/gateway/route_utils_test.go +++ b/internal/controllers/gateway/route_utils_test.go @@ -19,6 +19,7 @@ import ( fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" "github.com/kong/kubernetes-ingress-controller/v3/internal/util" "github.com/kong/kubernetes-ingress-controller/v3/internal/util/builder" @@ -542,7 +543,7 @@ func TestGetSupportedGatewayForRoute(t *testing.T) { WithObjects(tt.objects...). Build() - got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route) + got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route, controllers.OptionalNamespacedName{}) require.NoError(t, err) require.Len(t, got, len(tt.expected)) @@ -813,7 +814,7 @@ func TestGetSupportedGatewayForRoute(t *testing.T) { WithObjects(tt.objects...). Build() - got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route) + got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route, controllers.OptionalNamespacedName{}) require.NoError(t, err) require.Len(t, got, 1) match := got[0] @@ -1048,7 +1049,7 @@ func TestGetSupportedGatewayForRoute(t *testing.T) { WithObjects(tt.objects...). Build() - got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route) + got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route, controllers.OptionalNamespacedName{}) require.NoError(t, err) require.Len(t, got, 1) match := got[0] @@ -1270,7 +1271,7 @@ func TestGetSupportedGatewayForRoute(t *testing.T) { WithObjects(tt.objects...). Build() - got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route) + got, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, tt.route, controllers.OptionalNamespacedName{}) require.NoError(t, err) require.Len(t, got, len(tt.expected)) @@ -1300,7 +1301,7 @@ func TestGetSupportedGatewayForRoute(t *testing.T) { WithScheme(scheme.Scheme). Build() - _, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, bustedParentHTTPRoute) + _, err := getSupportedGatewayForRoute(context.Background(), logr.Discard(), fakeClient, bustedParentHTTPRoute, controllers.OptionalNamespacedName{}) require.Equal(t, fmt.Errorf("unsupported parent kind %s/%s", string(badGroup), string(badKind)), err) }) } diff --git a/internal/controllers/gateway/tcproute_controller.go b/internal/controllers/gateway/tcproute_controller.go index 5318c67aee..343adfd050 100644 --- a/internal/controllers/gateway/tcproute_controller.go +++ b/internal/controllers/gateway/tcproute_controller.go @@ -44,6 +44,10 @@ type TCPRouteReconciler struct { DataplaneClient controllers.DataPlane CacheSyncTimeout time.Duration StatusQueue *status.Queue + + // If GatewayNN is set, + // only resources managed by the specified Gateway are reconciled. + GatewayNN controllers.OptionalNamespacedName } // SetupWithManager sets up the controller with the Manager. @@ -140,6 +144,14 @@ func (r *TCPRouteReconciler) listTCPRoutesForGatewayClass(ctx context.Context, o gateways := make(map[string]map[string]struct{}) for _, gateway := range gatewayList.Items { if string(gateway.Spec.GatewayClassName) == gwc.Name { + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gateway.Namespace || gatewayToReconcile.Name != gateway.Name { + continue + } + } + _, ok := gateways[gateway.Namespace] if !ok { gateways[gateway.Namespace] = make(map[string]struct{}) @@ -214,6 +226,14 @@ func (r *TCPRouteReconciler) listTCPRoutesForGateway(ctx context.Context, obj cl return nil } + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gw.Namespace || gatewayToReconcile.Name != gw.Name { + return nil + } + } + // map all TCPRoute objects tcprouteList := gatewayapi.TCPRouteList{} if err := r.Client.List(ctx, &tcprouteList); err != nil { @@ -290,7 +310,7 @@ func (r *TCPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // we need to pull the Gateway parent objects for the TCPRoute to verify // routing behavior and ensure compatibility with Gateway configurations. debug(log, tcproute, "Retrieving GatewayClass and Gateway for route") - gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, tcproute) + gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, tcproute, r.GatewayNN) if err != nil { if err.Error() == unsupportedGW { debug(log, tcproute, "Unsupported route found, processing to verify whether it was ever supported") diff --git a/internal/controllers/gateway/tlsroute_controller.go b/internal/controllers/gateway/tlsroute_controller.go index 36d9cf6d32..1c91d78149 100644 --- a/internal/controllers/gateway/tlsroute_controller.go +++ b/internal/controllers/gateway/tlsroute_controller.go @@ -43,6 +43,10 @@ type TLSRouteReconciler struct { DataplaneClient controllers.DataPlane CacheSyncTimeout time.Duration StatusQueue *status.Queue + + // If GatewayNN is set, + // only resources managed by the specified Gateway are reconciled. + GatewayNN controllers.OptionalNamespacedName } // SetupWithManager sets up the controller with the Manager. @@ -139,6 +143,14 @@ func (r *TLSRouteReconciler) listTLSRoutesForGatewayClass(ctx context.Context, o gateways := make(map[string]map[string]struct{}) for _, gateway := range gatewayList.Items { if string(gateway.Spec.GatewayClassName) == gwc.Name { + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gateway.Namespace || gatewayToReconcile.Name != gateway.Name { + continue + } + } + _, ok := gateways[gateway.Namespace] if !ok { gateways[gateway.Namespace] = make(map[string]struct{}) @@ -213,6 +225,14 @@ func (r *TLSRouteReconciler) listTLSRoutesForGateway(ctx context.Context, obj cl return nil } + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gw.Namespace || gatewayToReconcile.Name != gw.Name { + return nil + } + } + // map all TLSRoute objects tlsrouteList := gatewayapi.TLSRouteList{} if err := r.Client.List(ctx, &tlsrouteList); err != nil { @@ -289,7 +309,7 @@ func (r *TLSRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // we need to pull the Gateway parent objects for the TLSRoute to verify // routing behavior and ensure compatibility with Gateway configurations. debug(log, tlsroute, "Retrieving GatewayClass and Gateway for route") - gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, tlsroute) + gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, tlsroute, r.GatewayNN) if err != nil { if err.Error() == unsupportedGW { debug(log, tlsroute, "Unsupported route found, processing to verify whether it was ever supported") diff --git a/internal/controllers/gateway/udproute_controller.go b/internal/controllers/gateway/udproute_controller.go index 7f88c284d0..10e646034c 100644 --- a/internal/controllers/gateway/udproute_controller.go +++ b/internal/controllers/gateway/udproute_controller.go @@ -43,6 +43,10 @@ type UDPRouteReconciler struct { DataplaneClient controllers.DataPlane CacheSyncTimeout time.Duration StatusQueue *status.Queue + + // If GatewayNN is set, + // only resources managed by the specified Gateway are reconciled. + GatewayNN controllers.OptionalNamespacedName } // SetupWithManager sets up the controller with the Manager. @@ -139,6 +143,14 @@ func (r *UDPRouteReconciler) listUDPRoutesForGatewayClass(ctx context.Context, o gateways := make(map[string]map[string]struct{}) for _, gateway := range gatewayList.Items { if string(gateway.Spec.GatewayClassName) == gwc.Name { + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gateway.Namespace || gatewayToReconcile.Name != gateway.Name { + continue + } + } + _, ok := gateways[gateway.Namespace] if !ok { gateways[gateway.Namespace] = make(map[string]struct{}) @@ -213,6 +225,14 @@ func (r *UDPRouteReconciler) listUDPRoutesForGateway(ctx context.Context, obj cl return nil } + // If the flag `--gateway-to-reconcile` is set, KIC will only reconcile the specified gateway. + // https://github.com/Kong/kubernetes-ingress-controller/issues/5322 + if gatewayToReconcile, ok := r.GatewayNN.Get(); ok { + if gatewayToReconcile.Namespace != gw.Namespace || gatewayToReconcile.Name != gw.Name { + return nil + } + } + // map all UDPRoute objects udprouteList := gatewayapi.UDPRouteList{} if err := r.Client.List(ctx, &udprouteList); err != nil { @@ -289,7 +309,7 @@ func (r *UDPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // we need to pull the Gateway parent objects for the UDPRoute to verify // routing behavior and ensure compatibility with Gateway configurations. debug(log, udproute, "Retrieving GatewayClass and Gateway for route") - gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, udproute) + gateways, err := getSupportedGatewayForRoute(ctx, log, r.Client, udproute, r.GatewayNN) if err != nil { if err.Error() == unsupportedGW { debug(log, udproute, "Unsupported route found, processing to verify whether it was ever supported") diff --git a/internal/manager/config.go b/internal/manager/config.go index ae40c81b9e..776dbb2b2f 100644 --- a/internal/manager/config.go +++ b/internal/manager/config.go @@ -116,6 +116,9 @@ type Config struct { GatewayAPIHTTPRouteController bool GatewayAPIReferenceGrantController bool + // KIC can only reconciling the specified Gateway. + GatewayToReconcile OptionalNamespacedName + // Admission Webhook server config AdmissionServer admission.ServerConfig @@ -249,6 +252,8 @@ func (c *Config) FlagSet() *pflag.FlagSet { flagSet.BoolVar(&c.GatewayAPIGatewayController, "enable-controller-gwapi-gateway", true, "Enable the Gateway API Gateway controller.") flagSet.BoolVar(&c.GatewayAPIHTTPRouteController, "enable-controller-gwapi-httproute", true, "Enable the Gateway API HTTPRoute controller.") flagSet.BoolVar(&c.GatewayAPIReferenceGrantController, "enable-controller-gwapi-reference-grant", true, "Enable the Gateway API ReferenceGrant controller.") + flagSet.Var(flags.NewValidatedValue(&c.GatewayToReconcile, namespacedNameFromFlagValue, nnTypeNameOverride), "gateway-to-reconcile", + `Gateway namespaced name in "namespace/name" format. Makes KIC reconcile only the specified Gateway.`) flagSet.BoolVar(&c.KongServiceFacadeEnabled, "enable-controller-kong-service-facade", true, "Enable the KongServiceFacade controller.") flagSet.BoolVar(&c.KongVaultEnabled, "enable-controller-kong-vault", true, "Enable the KongVault controller.") diff --git a/internal/manager/config_validation_test.go b/internal/manager/config_validation_test.go index c57224e535..1afe1c325b 100644 --- a/internal/manager/config_validation_test.go +++ b/internal/manager/config_validation_test.go @@ -93,6 +93,23 @@ func TestConfigValidatedVars(t *testing.T) { ExpectedValue: "5ef731c0-6081-49d6-b3ec-d4f85e58b956", }, }, + "--gateway-to-reconcile": { + { + Input: "namespace/gatewayname", + ExtractValueFn: func(c manager.Config) any { + return c.GatewayToReconcile + }, + ExpectedValue: mo.Some(k8stypes.NamespacedName{Namespace: "namespace", Name: "gatewayname"}), + }, + { + Input: "namespace/", + ExpectedErrorContains: "name cannot be empty", + }, + { + Input: "/name", + ExpectedErrorContains: "namespace cannot be empty", + }, + }, } for flag, flagTestCases := range testCasesGroupedByFlag { diff --git a/internal/manager/controllerdef.go b/internal/manager/controllerdef.go index 0eaaadcd6d..f652d0dffe 100644 --- a/internal/manager/controllerdef.go +++ b/internal/manager/controllerdef.go @@ -319,6 +319,7 @@ func setupControllers( WatchNamespaces: c.WatchNamespaces, CacheSyncTimeout: c.CacheSyncTimeout, ReferenceIndexers: referenceIndexers, + GatewayNN: c.GatewayToReconcile, }, }, }, @@ -340,6 +341,7 @@ func setupControllers( DataplaneClient: dataplaneClient, CacheSyncTimeout: c.CacheSyncTimeout, StatusQueue: kubernetesStatusQueue, + GatewayNN: c.GatewayToReconcile, }, }, }, @@ -384,6 +386,7 @@ func setupControllers( DataplaneClient: dataplaneClient, CacheSyncTimeout: c.CacheSyncTimeout, StatusQueue: kubernetesStatusQueue, + GatewayNN: c.GatewayToReconcile, }, }, }, @@ -405,6 +408,7 @@ func setupControllers( DataplaneClient: dataplaneClient, CacheSyncTimeout: c.CacheSyncTimeout, StatusQueue: kubernetesStatusQueue, + GatewayNN: c.GatewayToReconcile, }, }, }, @@ -426,6 +430,7 @@ func setupControllers( DataplaneClient: dataplaneClient, CacheSyncTimeout: c.CacheSyncTimeout, StatusQueue: kubernetesStatusQueue, + GatewayNN: c.GatewayToReconcile, }, }, }, @@ -447,6 +452,7 @@ func setupControllers( DataplaneClient: dataplaneClient, CacheSyncTimeout: c.CacheSyncTimeout, StatusQueue: kubernetesStatusQueue, + GatewayNN: c.GatewayToReconcile, }, }, }, diff --git a/test/envtest/run.go b/test/envtest/run.go index 849d1da732..ab9e53458f 100644 --- a/test/envtest/run.go +++ b/test/envtest/run.go @@ -3,6 +3,7 @@ package envtest import ( "context" "fmt" + "strings" "sync" "testing" "time" @@ -88,6 +89,19 @@ func WithGatewayAPIControllers() func(cfg *manager.Config) { } } +func WithGatewayToReconcile(gatewayNN string) func(cfg *manager.Config) { + parts := strings.SplitN(gatewayNN, "/", 3) + if len(parts) != 2 { + panic("the expected format if namespace/name") + } + return func(cfg *manager.Config) { + cfg.GatewayToReconcile = mo.Some(k8stypes.NamespacedName{ + Namespace: parts[0], + Name: parts[1], + }) + } +} + func WithPublishService(namespace string) func(cfg *manager.Config) { return func(cfg *manager.Config) { cfg.PublishStatusAddress = []string{"127.0.0.1"} diff --git a/test/envtest/specific_gateway_envtest_test.go b/test/envtest/specific_gateway_envtest_test.go new file mode 100644 index 0000000000..432da1f111 --- /dev/null +++ b/test/envtest/specific_gateway_envtest_test.go @@ -0,0 +1,77 @@ +//go:build envtest + +package envtest + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/samber/lo" + "github.com/stretchr/testify/require" + k8stypes "k8s.io/apimachinery/pkg/types" + + "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" +) + +func TestSpecificGatewayNN(t *testing.T) { + t.Parallel() + + const ( + waitTime = time.Minute + tickTime = 500 * time.Millisecond + ) + + scheme := Scheme(t, WithGatewayAPI, WithKong) + envcfg := Setup(t, scheme) + ctrlClient := NewControllerClient(t, scheme, envcfg) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + gw := deployGateway(ctx, t, ctrlClient) + ignoredGW := deployGateway(ctx, t, ctrlClient) + RunManager(ctx, t, envcfg, + AdminAPIOptFns(), + WithPublishService(gw.Namespace), + WithGatewayFeatureEnabled, + WithGatewayAPIControllers(), + WithGatewayToReconcile(fmt.Sprintf("%s/%s", gw.Namespace, gw.Name)), + ) + + createHTTPRoutes(ctx, t, ctrlClient, gw, 1) + createHTTPRoutes(ctx, t, ctrlClient, ignoredGW, 1) + + require.Eventually(t, func() bool { + err := ctrlClient.Get(ctx, k8stypes.NamespacedName{Namespace: gw.Namespace, Name: gw.Name}, &gw) + if err != nil { + t.Logf("Failed to get gateway %s/%s: %v", gw.Namespace, gw.Name, err) + return false + } + httpListener, ok := lo.Find(gw.Status.Listeners, func(listener gatewayapi.ListenerStatus) bool { + return listener.Name == "http" + }) + if !ok { + t.Logf("Failed to find http listener status in gateway %s/%s", gw.Namespace, gw.Name) + return false + } + if httpListener.AttachedRoutes != 1 { + t.Logf("Expected %d routes to be attached to the http listener, got %d", 1, httpListener.AttachedRoutes) + return false + } + + err = ctrlClient.Get(ctx, k8stypes.NamespacedName{Namespace: ignoredGW.Namespace, Name: ignoredGW.Name}, &ignoredGW) + if err != nil { + t.Logf("Failed to get gateway %s/%s: %v", ignoredGW.Namespace, ignoredGW.Name, err) + return false + } + + // ignoredGW.Status.Listeners should be [] + if len(ignoredGW.Status.Listeners) != 0 { + t.Logf("Expected %s/%s gateway should not be processed.", ignoredGW.Namespace, ignoredGW.Name) + return false + } + + return true + }, waitTime, tickTime, "failed to reconcile all HTTPRoutes") +}