From ba4a242fd7602c88ba9efa9a8927a32cff1511d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Ma=C5=82ek?= Date: Tue, 13 Jun 2023 11:42:04 +0200 Subject: [PATCH] tests: migrate GatewayClass related integration tests to envtest based tests --- .../dataplane_client.go => dataplane.go} | 6 +- .../controllers/gateway/gateway_controller.go | 9 +- .../gateway/gatewayclass_controller.go | 5 + .../gateway/httproute_controller.go | 3 +- internal/controllers/reference/indexer.go | 4 +- internal/controllers/reference/reference.go | 8 +- internal/util/builder/serviceport.go | 65 ++++ test/envtest/controller.go | 27 +- test/envtest/gatewayclass_envtest_test.go | 302 ++++++++++++++++++ test/envtest/httproute_controller_test.go | 4 +- test/integration/gateway_test.go | 127 -------- test/{internal => }/mocks/dataplane.go | 7 + 12 files changed, 421 insertions(+), 146 deletions(-) rename internal/controllers/{gateway/dataplane_client.go => dataplane.go} (84%) create mode 100644 internal/util/builder/serviceport.go create mode 100644 test/envtest/gatewayclass_envtest_test.go rename test/{internal => }/mocks/dataplane.go (84%) diff --git a/internal/controllers/gateway/dataplane_client.go b/internal/controllers/dataplane.go similarity index 84% rename from internal/controllers/gateway/dataplane_client.go rename to internal/controllers/dataplane.go index be06870683..28c9993757 100644 --- a/internal/controllers/gateway/dataplane_client.go +++ b/internal/controllers/dataplane.go @@ -1,6 +1,9 @@ -package gateway +package controllers import ( + "context" + + "github.com/kong/go-kong/kong" "sigs.k8s.io/controller-runtime/pkg/client" k8sobj "github.com/kong/kubernetes-ingress-controller/v2/internal/util/kubernetes/object" @@ -14,6 +17,7 @@ import ( type DataPlane interface { DataPlaneClient + Listeners(ctx context.Context) ([]kong.ProxyListener, []kong.StreamListener, error) AreKubernetesObjectReportsEnabled() bool KubernetesObjectConfigurationStatus(obj client.Object) k8sobj.ConfigurationStatus } diff --git a/internal/controllers/gateway/gateway_controller.go b/internal/controllers/gateway/gateway_controller.go index 92d9ae8e8e..e33de1fbae 100644 --- a/internal/controllers/gateway/gateway_controller.go +++ b/internal/controllers/gateway/gateway_controller.go @@ -28,9 +28,9 @@ import ( gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/kong/kubernetes-ingress-controller/v2/internal/annotations" + "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers" ctrlref "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/reference" ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils" - "github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane" "github.com/kong/kubernetes-ingress-controller/v2/internal/util" ) @@ -53,7 +53,7 @@ type GatewayReconciler struct { //nolint:revive Log logr.Logger Scheme *runtime.Scheme - DataplaneClient *dataplane.KongClient + DataplaneClient controllers.DataPlane WatchNamespaces []string CacheSyncTimeout time.Duration @@ -161,6 +161,11 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error { return gwcCTRL.SetupWithManager(mgr) } +// SetLogger sets the logger. +func (r *GatewayReconciler) SetLogger(l logr.Logger) { + r.Log = l +} + // ----------------------------------------------------------------------------- // Gateway Controller - Watch Predicates // ----------------------------------------------------------------------------- diff --git a/internal/controllers/gateway/gatewayclass_controller.go b/internal/controllers/gateway/gatewayclass_controller.go index 8bcd6db28e..4fd0498abd 100644 --- a/internal/controllers/gateway/gatewayclass_controller.go +++ b/internal/controllers/gateway/gatewayclass_controller.go @@ -148,6 +148,11 @@ func (r *GatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, nil } +// SetLogger sets the logger. +func (r *GatewayClassReconciler) SetLogger(l logr.Logger) { + r.Log = l +} + // ----------------------------------------------------------------------------- // GatewayClass Controller - Private // ----------------------------------------------------------------------------- diff --git a/internal/controllers/gateway/httproute_controller.go b/internal/controllers/gateway/httproute_controller.go index d29ca5e6c2..8d2955d416 100644 --- a/internal/controllers/gateway/httproute_controller.go +++ b/internal/controllers/gateway/httproute_controller.go @@ -24,6 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers" ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils" "github.com/kong/kubernetes-ingress-controller/v2/internal/util" k8sobj "github.com/kong/kubernetes-ingress-controller/v2/internal/util/kubernetes/object" @@ -39,7 +40,7 @@ type HTTPRouteReconciler struct { Log logr.Logger Scheme *runtime.Scheme - DataplaneClient DataPlane + DataplaneClient controllers.DataPlane CacheSyncTimeout time.Duration // If enableReferenceGrant is true, we will check for ReferenceGrant if backend in another diff --git a/internal/controllers/reference/indexer.go b/internal/controllers/reference/indexer.go index f35f8a8eb2..9f886a9720 100644 --- a/internal/controllers/reference/indexer.go +++ b/internal/controllers/reference/indexer.go @@ -6,7 +6,7 @@ import ( "k8s.io/client-go/tools/cache" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane" + "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers" ) const ( @@ -155,7 +155,7 @@ func (c CacheIndexers) DeleteReferencesByReferrer(referrer client.Object) error // DeleteObjectIfNotReferred deletes object from object cach by dataplaneClient // the object is not referenced in reference cache. -func (c CacheIndexers) DeleteObjectIfNotReferred(obj client.Object, dataplaneClient *dataplane.KongClient) error { +func (c CacheIndexers) DeleteObjectIfNotReferred(obj client.Object, dataplaneClient controllers.DataPlaneClient) error { referred, err := c.ObjectReferred(obj) if err != nil { return err diff --git a/internal/controllers/reference/reference.go b/internal/controllers/reference/reference.go index 139ac65164..e694a5d4a7 100644 --- a/internal/controllers/reference/reference.go +++ b/internal/controllers/reference/reference.go @@ -9,7 +9,7 @@ import ( k8stypes "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane" + "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers" ) const ( @@ -22,7 +22,7 @@ const ( // in namespacedNames in record cache. func UpdateReferencesToSecret( ctx context.Context, - c client.Client, indexers CacheIndexers, dataplaneClient *dataplane.KongClient, + c client.Client, indexers CacheIndexers, dataplaneClient controllers.DataPlaneClient, referrer client.Object, referencedSecretNameMap map[k8stypes.NamespacedName]struct{}, ) error { for nsName := range referencedSecretNameMap { @@ -60,7 +60,7 @@ func UpdateReferencesToSecret( // and should be removed from the object cache inside KongClient. func removeOutdatedReferencesToSecret( ctx context.Context, - indexers CacheIndexers, c client.Client, dataplaneClient *dataplane.KongClient, + indexers CacheIndexers, c client.Client, dataplaneClient controllers.DataPlaneClient, referrer client.Object, referredSecretNameMap map[k8stypes.NamespacedName]struct{}, ) error { referents, err := indexers.ListReferredObjects(referrer) @@ -114,7 +114,7 @@ func removeOutdatedReferencesToSecret( // DeleteReferencesByReferrer deletes all reference records with specified referrer // in reference cache. // If the affected secret is not referred by any other objects, it deletes the secret in object cache. -func DeleteReferencesByReferrer(indexers CacheIndexers, dataplaneClient *dataplane.KongClient, referrer client.Object) error { +func DeleteReferencesByReferrer(indexers CacheIndexers, dataplaneClient controllers.DataPlaneClient, referrer client.Object) error { referents, err := indexers.ListReferredObjects(referrer) if err != nil { return err diff --git a/internal/util/builder/serviceport.go b/internal/util/builder/serviceport.go new file mode 100644 index 0000000000..41136e881e --- /dev/null +++ b/internal/util/builder/serviceport.go @@ -0,0 +1,65 @@ +package builder + +import ( + "github.com/samber/lo" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// ServicePortBuilder is a builder for core v1 ServicePort. +// Primarily used for testing. +type ServicePortBuilder struct { + sp corev1.ServicePort +} + +func NewServicePort() *ServicePortBuilder { + return &ServicePortBuilder{ + sp: corev1.ServicePort{}, + } +} + +// WithNodePort sets the target port on the service port. +func (b *ServicePortBuilder) WithNodePort(port int32) *ServicePortBuilder { + b.sp.NodePort = port + return b +} + +// WithTargetPort sets the target port on the service port. +func (b *ServicePortBuilder) WithTargetPort(targetport intstr.IntOrString) *ServicePortBuilder { + b.sp.TargetPort = targetport + return b +} + +// WithPort sets the port on the service port. +func (b *ServicePortBuilder) WithPort(port int32) *ServicePortBuilder { + b.sp.Port = port + return b +} + +// WithAppProtocol sets the app protocol on the service port. +func (b *ServicePortBuilder) WithAppProtocol(appproto string) *ServicePortBuilder { + b.sp.AppProtocol = lo.ToPtr(appproto) + return b +} + +// WithProtocol sets the protocol on the service port. +func (b *ServicePortBuilder) WithProtocol(proto corev1.Protocol) *ServicePortBuilder { + b.sp.Protocol = proto + return b +} + +// WithName sets the name on the service port. +func (b *ServicePortBuilder) WithName(name string) *ServicePortBuilder { + b.sp.Name = name + return b +} + +// Build returns the configured ServicePort. +func (b *ServicePortBuilder) Build() corev1.ServicePort { + return b.sp +} + +// IntoSlice returns the configured ServicePort in a slice. +func (b *ServicePortBuilder) IntoSlice() []corev1.ServicePort { + return []corev1.ServicePort{b.sp} +} diff --git a/test/envtest/controller.go b/test/envtest/controller.go index 7bc3d05a29..775b1f218f 100644 --- a/test/envtest/controller.go +++ b/test/envtest/controller.go @@ -1,6 +1,7 @@ package envtest import ( + "bytes" "context" "sync" "testing" @@ -17,13 +18,18 @@ import ( "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers" ) -// StartReconciler creates a controller manager and starts the provided reconciler +// StartReconcilers creates a controller manager and starts the provided reconciler // as its runnable. // It also adds a t.Cleanup which waits for the maanger to exit so that the test // can be self contained and logs from different tests' managers don't mix up. -func StartReconciler(ctx context.Context, t *testing.T, scheme *runtime.Scheme, cfg *rest.Config, reconciler controllers.Reconciler) { +func StartReconcilers(ctx context.Context, t *testing.T, scheme *runtime.Scheme, cfg *rest.Config, reconcilers ...controllers.Reconciler) { + t.Helper() + + var b bytes.Buffer + log := logrus.New() + log.Out = &b o := manager.Options{ - Logger: logrusr.New(logrus.New()), + Logger: logrusr.New(log), Scheme: scheme, MetricsBindAddress: "0", } @@ -31,9 +37,10 @@ func StartReconciler(ctx context.Context, t *testing.T, scheme *runtime.Scheme, mgr, err := ctrl.NewManager(cfg, o) require.NoError(t, err) - reconciler.SetLogger(mgr.GetLogger()) - - require.NoError(t, reconciler.SetupWithManager(mgr)) + for _, r := range reconcilers { + r.SetLogger(mgr.GetLogger()) + require.NoError(t, r.SetupWithManager(mgr)) + } // This wait group makes it so that we wait for manager to exit. // This way we get clean test logs not mixing between tests. @@ -43,5 +50,11 @@ func StartReconciler(ctx context.Context, t *testing.T, scheme *runtime.Scheme, defer wg.Done() assert.NoError(t, mgr.Start(ctx)) }() - t.Cleanup(func() { wg.Wait() }) + t.Cleanup(func() { + wg.Wait() + + if t.Failed() { + t.Logf("Test %s failed: dumping controller logs\n%s", t.Name(), b.String()) + } + }) } diff --git a/test/envtest/gatewayclass_envtest_test.go b/test/envtest/gatewayclass_envtest_test.go new file mode 100644 index 0000000000..1ce8cdbf4f --- /dev/null +++ b/test/envtest/gatewayclass_envtest_test.go @@ -0,0 +1,302 @@ +//go:build envtest +// +build envtest + +package envtest + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8stypes "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes/scheme" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + gatewayclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" + gatewayclientv1beta1 "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/typed/apis/v1beta1" + + "github.com/kong/kubernetes-ingress-controller/v2/internal/annotations" + "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/gateway" + ctrlref "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/reference" + "github.com/kong/kubernetes-ingress-controller/v2/internal/util/builder" + "github.com/kong/kubernetes-ingress-controller/v2/test/mocks" +) + +func init() { + if err := gatewayv1beta1.Install(scheme.Scheme); err != nil { + panic(err) + } +} + +func TestGatewayClassReconciliation(t *testing.T) { + t.Parallel() + + const ( + // unsupportedControllerName is the name of the controller used for those + // gateways that are not supported by an actual controller (i.e., they won't be scheduled). + unsupportedControllerName gatewayv1beta1.GatewayController = "example.com/unsupported-gateway-controller" + ) + + cfg := Setup(t, scheme.Scheme) + + gatewayClient, err := gatewayclient.NewForConfig(cfg) + require.NoError(t, err) + + client, err := ctrlclient.New(cfg, ctrlclient.Options{ + Scheme: scheme.Scheme, + }) + require.NoError(t, err) + + testcases := []struct { + Name string + GatewayClass gatewayv1beta1.GatewayClass + Gateway gatewayv1beta1.Gateway + Test func(context.Context, *testing.T, gatewayclientv1beta1.GatewayInterface, gatewayv1beta1.GatewayClass, gatewayv1beta1.Gateway) + }{ + { + Name: "unsupported gateway class", + GatewayClass: gatewayv1beta1.GatewayClass{ + Spec: gatewayv1beta1.GatewayClassSpec{ + ControllerName: unsupportedControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "unsupported-gateway-class", + }, + }, + Gateway: gatewayv1beta1.Gateway{ + Spec: gatewayv1beta1.GatewaySpec{ + GatewayClassName: gatewayv1beta1.ObjectName("unsupported-gateway-class"), + Listeners: builder.NewListener("http"). + HTTP(). + WithPort(80). + WithAllowedRoutes(builder.NewAllowedRoutesFromAllNamespaces()). + IntoSlice(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + }, + }, + Test: func(ctx context.Context, t *testing.T, gwClient gatewayclientv1beta1.GatewayInterface, gwc gatewayv1beta1.GatewayClass, gw gatewayv1beta1.Gateway) { + t.Helper() + + t.Logf("deploying gateway class %s", gwc.Name) + require.NoError(t, client.Create(ctx, &gwc)) + t.Cleanup(func() { _ = client.Delete(ctx, &gwc) }) + + t.Logf("verifying that the unsupported Gateway %s does not get Accepted or Programmed by the controller", gw.Name) + for i := 0; i < 100; i++ { + gateway, err := gwClient.Get(ctx, gw.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Len(t, gateway.Status.Conditions, 2) + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Programmed" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find a Programmed condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Accepted" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find a Accepted condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + } + }, + }, + { + Name: "managed gateway class", + GatewayClass: gatewayv1beta1.GatewayClass{ + Spec: gatewayv1beta1.GatewayClassSpec{ + ControllerName: gateway.GetControllerName(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-gateway-class", + }, + }, + Gateway: gatewayv1beta1.Gateway{ + Spec: gatewayv1beta1.GatewaySpec{ + GatewayClassName: gatewayv1beta1.ObjectName("managed-gateway-class"), + Listeners: builder.NewListener("http"). + HTTP(). + WithPort(80). + WithAllowedRoutes(builder.NewAllowedRoutesFromAllNamespaces()). + IntoSlice(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + }, + }, + Test: func(ctx context.Context, t *testing.T, gwClient gatewayclientv1beta1.GatewayInterface, gwc gatewayv1beta1.GatewayClass, gw gatewayv1beta1.Gateway) { + t.Helper() + + t.Logf("verifying that the Gateway %s does not get scheduled by the controller due to missing its GatewayClass", gw.Name) + for i := 0; i < 100; i++ { + gateway, err := gwClient.Get(ctx, gw.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Len(t, gateway.Status.Conditions, 2) + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Programmed" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find a Programmed condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Accepted" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find a Accepted condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + } + + t.Logf("deploying gateway class %s", gwc.Name) + require.NoError(t, client.Create(ctx, &gwc)) + t.Cleanup(func() { _ = client.Delete(ctx, &gwc) }) + + // Let's wait and check that the Gateway hasn't been reconciled by the operator. + t.Log("verifying the Gateway is not reconciled as it is using a managed GatewayClass") + for i := 0; i < 100; i++ { + gateway, err := gwClient.Get(ctx, gw.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Len(t, gateway.Status.Conditions, 2) + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Programmed" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find a Programmed condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Accepted" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find an Accepted condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + } + }, + }, + { + Name: "unmanaged gateway class", + GatewayClass: gatewayv1beta1.GatewayClass{ + Spec: gatewayv1beta1.GatewayClassSpec{ + ControllerName: gateway.GetControllerName(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "unmanaged-gateway-class", + Annotations: map[string]string{ + annotations.GatewayClassUnmanagedAnnotation: annotations.GatewayClassUnmanagedAnnotationValuePlaceholder, + }, + }, + }, + Gateway: gatewayv1beta1.Gateway{ + Spec: gatewayv1beta1.GatewaySpec{ + GatewayClassName: gatewayv1beta1.ObjectName("unmanaged-gateway-class"), + Listeners: builder.NewListener("http"). + HTTP(). + WithPort(80). + WithAllowedRoutes(builder.NewAllowedRoutesFromAllNamespaces()). + IntoSlice(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + }, + }, + Test: func(ctx context.Context, t *testing.T, gwClient gatewayclientv1beta1.GatewayInterface, gwc gatewayv1beta1.GatewayClass, gw gatewayv1beta1.Gateway) { + t.Helper() + + t.Logf("verifying that the Gateway %s does not get scheduled by the controller due to missing its GatewayClass", gw.Name) + for i := 0; i < 100; i++ { + gateway, err := gwClient.Get(ctx, gw.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Len(t, gateway.Status.Conditions, 2) + + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Programmed" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find a Programmed condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + if lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Accepted" && c.Status != metav1.ConditionUnknown + }) { + t.Fatalf("expected not to find an Accepted condition with Reason different than Unknown, got %#v", gateway.Status.Conditions) + } + } + + t.Logf("deploying gateway class %s", gwc.Name) + require.NoError(t, client.Create(ctx, &gwc)) + t.Cleanup(func() { _ = client.Delete(ctx, &gwc) }) + + t.Logf("now that the GatewayClass exists, verifying that the Gateway %s gets Accepted and Programmed", gw.Name) + if !assert.Eventually(t, func() bool { + gateway, err := gwClient.Get(ctx, gw.Name, metav1.GetOptions{}) + require.NoError(t, err) + + if !lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Programmed" && c.Status == metav1.ConditionTrue + }) { + return false + } + if !lo.ContainsBy(gateway.Status.Conditions, func(c metav1.Condition) bool { + return c.Type == "Accepted" && c.Status == metav1.ConditionTrue + }) { + return false + } + + return true + }, 10*time.Second, 10*time.Millisecond) { + t.Logf("expected to find an Accepted and Programmed conditions with Status True, got %#v", gw.Status.Conditions) + } + }, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + // We use a deferred cancel to stop the manager and not wait for its timeout. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ns := CreateNamespace(ctx, t, client) + + svc := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: "publish-svc", + }, + Spec: corev1.ServiceSpec{ + Ports: builder.NewServicePort(). + WithName("http"). + WithPort(80). + WithProtocol(corev1.ProtocolTCP). + WithTargetPort(intstr.FromInt(80)). + IntoSlice(), + }, + } + require.NoError(t, client.Create(ctx, &svc)) + + gwReconciler := &gateway.GatewayReconciler{ + Client: client, + PublishServiceRef: k8stypes.NamespacedName{ + Namespace: ns.Name, + Name: svc.Name, + }, + DataplaneClient: mocks.Dataplane{}, + ReferenceIndexers: ctrlref.NewCacheIndexers(), + } + gwcReconciler := &gateway.GatewayClassReconciler{ + Client: client, + } + StartReconcilers(ctx, t, client.Scheme(), cfg, gwcReconciler, gwReconciler) + + t.Logf("deploying gateway %s using %s gateway class", tc.Gateway.Name, tc.GatewayClass.Name) + tc.Gateway.Namespace = ns.Name + require.NoError(t, client.Create(ctx, &tc.Gateway)) + t.Cleanup(func() { _ = client.Delete(ctx, &tc.Gateway) }) + + gwClient := gatewayClient.GatewayV1beta1().Gateways(ns.Name) + tc.Test(ctx, t, gwClient, tc.GatewayClass, tc.Gateway) + }) + } +} diff --git a/test/envtest/httproute_controller_test.go b/test/envtest/httproute_controller_test.go index 7d3e7a1686..316c092cec 100644 --- a/test/envtest/httproute_controller_test.go +++ b/test/envtest/httproute_controller_test.go @@ -25,7 +25,7 @@ import ( "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/gateway" "github.com/kong/kubernetes-ingress-controller/v2/internal/util/builder" "github.com/kong/kubernetes-ingress-controller/v2/test/helpers" - "github.com/kong/kubernetes-ingress-controller/v2/test/internal/mocks" + "github.com/kong/kubernetes-ingress-controller/v2/test/mocks" ) func init() { @@ -81,7 +81,7 @@ func TestHTTPRouteReconcilerProperlyReactsToReferenceGrant(t *testing.T) { }, } require.NoError(t, client.Create(ctx, &svc)) - StartReconciler(ctx, t, client.Scheme(), cfg, reconciler) + StartReconcilers(ctx, t, client.Scheme(), cfg, reconciler) gwc := gatewayv1beta1.GatewayClass{ Spec: gatewayv1beta1.GatewayClassSpec{ diff --git a/test/integration/gateway_test.go b/test/integration/gateway_test.go index 6f5ae02c26..d237f35415 100644 --- a/test/integration/gateway_test.go +++ b/test/integration/gateway_test.go @@ -359,133 +359,6 @@ func TestGatewayListenerConflicts(t *testing.T) { }, gatewayUpdateWaitTime, time.Second) } -func TestUnmanagedGatewayControllerSupport(t *testing.T) { - ctx := context.Background() - - ns, cleaner := helpers.Setup(ctx, t, env) - - t.Log("generating a gateway kubernetes client") - gatewayClient, err := gatewayclient.NewForConfig(env.Cluster().Config()) - require.NoError(t, err) - - t.Log("deploying an unsupported gatewayclass to the test cluster") - unsupportedGatewayClass, err := DeployGatewayClass(ctx, gatewayClient, uuid.NewString(), func(gc *gatewayv1beta1.GatewayClass) { - gc.Spec.ControllerName = unsupportedControllerName - }) - require.NoError(t, err) - cleaner.Add(unsupportedGatewayClass) - - t.Log("deploying a gateway using the unsupported gateway class") - unsupportedGateway, err := DeployGateway(ctx, gatewayClient, ns.Name, unsupportedGatewayClass.Name) - require.NoError(t, err) - cleaner.Add(unsupportedGateway) - - t.Log("verifying that the unsupported Gateway object does not get scheduled by the controller") - timeout := time.Now().Add(gatewayWaitTimeToVerifyScheduling) - for timeout.After(time.Now()) { - unsupportedGateway, err = gatewayClient.GatewayV1beta1().Gateways(ns.Name).Get(ctx, unsupportedGateway.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Len(t, unsupportedGateway.Status.Conditions, 2) - require.Equal(t, string(gatewayv1beta1.GatewayReasonPending), unsupportedGateway.Status.Conditions[0].Reason) - } -} - -func TestUnmanagedGatewayClass(t *testing.T) { - ctx := context.Background() - - ns, cleaner := helpers.Setup(ctx, t, env) - - t.Log("generating a gateway kubernetes client") - gatewayClient, err := gatewayclient.NewForConfig(env.Cluster().Config()) - require.NoError(t, err) - - t.Log("deploying a gateway to the test cluster using unmanaged mode, but with no valid gatewayclass yet") - gatewayClassName := uuid.NewString() - gatewayName := uuid.NewString() - gateway, err := DeployGateway(ctx, gatewayClient, ns.Name, gatewayClassName, func(gw *gatewayv1beta1.Gateway) { - gw.Name = gatewayName - }) - require.NoError(t, err) - cleaner.Add(gateway) - - t.Log("verifying that the Gateway object does not get scheduled by the controller due to missing its GatewayClass") - timeout := time.Now().Add(gatewayWaitTimeToVerifyScheduling) - for timeout.After(time.Now()) { - gateway, err = gatewayClient.GatewayV1beta1().Gateways(ns.Name).Get(ctx, gateway.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Len(t, gateway.Status.Conditions, 2) - require.Equal(t, string(gatewayv1beta1.GatewayReasonPending), gateway.Status.Conditions[0].Reason) - } - - t.Log("deploying the missing gatewayclass to the test cluster") - gwc, err := DeployGatewayClass(ctx, gatewayClient, gatewayClassName) - require.NoError(t, err) - cleaner.Add(gwc) - - t.Log("now that the gatewayclass exists, verifying that the gateway resource gets resolved") - require.Eventually(t, func() bool { - gateway, err = gatewayClient.GatewayV1beta1().Gateways(ns.Name).Get(ctx, gateway.Name, metav1.GetOptions{}) - require.NoError(t, err) - for _, cond := range gateway.Status.Conditions { - if cond.Reason == string(gatewayv1beta1.GatewayReasonProgrammed) { - return true - } - } - return false - }, gatewayUpdateWaitTime, time.Second) -} - -func TestManagedGatewayClass(t *testing.T) { - ctx := context.Background() - - ns, cleaner := helpers.Setup(ctx, t, env) - - t.Log("generating a gateway kubernetes client") - gatewayClient, err := gatewayclient.NewForConfig(env.Cluster().Config()) - require.NoError(t, err) - - t.Log("deploying a gateway to the test cluster, but with no valid gatewayclass yet") - gatewayClassName := uuid.NewString() - gatewayName := uuid.NewString() - gateway, err := DeployGateway(ctx, gatewayClient, ns.Name, gatewayClassName, func(gw *gatewayv1beta1.Gateway) { - gw.Name = gatewayName - }) - require.NoError(t, err) - cleaner.Add(gateway) - - t.Log("verifying that the Gateway object does not get scheduled by the controller due to missing its GatewayClass") - timeout := time.Now().Add(gatewayWaitTimeToVerifyScheduling) - for timeout.After(time.Now()) { - gateway, err = gatewayClient.GatewayV1beta1().Gateways(ns.Name).Get(ctx, gateway.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Len(t, gateway.Status.Conditions, 2) - require.Equal(t, string(gatewayv1beta1.GatewayReasonPending), gateway.Status.Conditions[0].Reason) - } - - t.Log("deploying a missing managed gatewayclass to the test cluster") - gwc, err := DeployGatewayClass(ctx, gatewayClient, gatewayClassName, func(gc *gatewayv1beta1.GatewayClass) { - gc.Annotations = nil - }) - require.NoError(t, err) - cleaner.Add(gwc) - - finished := make(chan struct{}) - - // Let's wait for one minute and check that the Gateway hasn't reconciled by the operator. It should never get ready. - t.Log("the Gateway must not be reconciled as it is using a managed GatewayClass") - time.AfterFunc(time.Minute, func() { - defer close(finished) - gateway, err = gatewayClient.GatewayV1beta1().Gateways(ns.Name).Get(ctx, gateway.Name, metav1.GetOptions{}) - require.NoError(t, err) - for _, cond := range gateway.Status.Conditions { - if cond.Type == string(gatewayv1beta1.GatewayConditionProgrammed) { - require.Equal(t, cond.Status, metav1.ConditionUnknown) - } - } - }) - <-finished -} - func TestGatewayFilters(t *testing.T) { skipTestForExpressionRouter(t) ctx := context.Background() diff --git a/test/internal/mocks/dataplane.go b/test/mocks/dataplane.go similarity index 84% rename from test/internal/mocks/dataplane.go rename to test/mocks/dataplane.go index 4e2bfaa4da..3c45b5a976 100644 --- a/test/internal/mocks/dataplane.go +++ b/test/mocks/dataplane.go @@ -1,6 +1,9 @@ package mocks import ( + "context" + + "github.com/kong/go-kong/kong" "sigs.k8s.io/controller-runtime/pkg/client" k8sobj "github.com/kong/kubernetes-ingress-controller/v2/internal/util/kubernetes/object" @@ -30,3 +33,7 @@ func (d Dataplane) AreKubernetesObjectReportsEnabled() bool { func (d Dataplane) KubernetesObjectConfigurationStatus(obj client.Object) k8sobj.ConfigurationStatus { return d.ObjectsStatuses[obj.GetNamespace()][obj.GetName()] } + +func (d Dataplane) Listeners(context.Context) ([]kong.ProxyListener, []kong.StreamListener, error) { + return nil, nil, nil +}