diff --git a/controllers/controllers/networking/cfdomain_controller_test.go b/controllers/controllers/networking/cfdomain_controller_test.go deleted file mode 100644 index d5d364a5f..000000000 --- a/controllers/controllers/networking/cfdomain_controller_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package networking_test - -import ( - "context" - - korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" - . "code.cloudfoundry.org/korifi/controllers/controllers/workloads/testutils" - "sigs.k8s.io/controller-runtime/pkg/client" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var _ = Describe("CFDomainReconciler Integration Tests", func() { - var ( - ctx context.Context - testDomainName string - testDomainGUID string - domainNamespace string - route1Namespace string - route2Namespace string - - cfDomain *korifiv1alpha1.CFDomain - ) - - BeforeEach(func() { - ctx = context.Background() - - domainNamespace = GenerateGUID() - Expect(adminClient.Create(ctx, &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: domainNamespace, - }, - })).To(Succeed()) - - route1Namespace = GenerateGUID() - Expect(adminClient.Create(ctx, &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: route1Namespace, - }, - })).To(Succeed()) - - route2Namespace = GenerateGUID() - Expect(adminClient.Create(ctx, &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: route2Namespace, - }, - })).To(Succeed()) - - testDomainGUID = GenerateGUID() - testDomainName = "a" + GenerateGUID() + ".com" - cfDomain = &korifiv1alpha1.CFDomain{ - ObjectMeta: metav1.ObjectMeta{ - Name: testDomainGUID, - Namespace: domainNamespace, - }, - Spec: korifiv1alpha1.CFDomainSpec{ - Name: testDomainName, - }, - } - Expect(adminClient.Create(ctx, cfDomain)).To(Succeed()) - - createValidRoute(ctx, &korifiv1alpha1.CFRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: GenerateGUID(), - Namespace: route1Namespace, - }, - Spec: korifiv1alpha1.CFRouteSpec{ - Host: "test-route-host-1", - Path: "/test/path/1", - Protocol: "http", - DomainRef: corev1.ObjectReference{ - Name: testDomainGUID, - Namespace: domainNamespace, - }, - }, - }) - - createValidRoute(ctx, &korifiv1alpha1.CFRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: GenerateGUID(), - Namespace: route2Namespace, - }, - Spec: korifiv1alpha1.CFRouteSpec{ - Host: "test-route-host-2", - Path: "/test/path/2", - Protocol: "http", - DomainRef: corev1.ObjectReference{ - Name: testDomainGUID, - Namespace: domainNamespace, - }, - }, - }) - }) - - When("a domain is deleted", func() { - JustBeforeEach(func() { - Expect(adminClient.Delete(ctx, cfDomain)).To(Succeed()) - }) - - It("deletes the domain routes", func() { - Eventually(func(g Gomega) { - routes := &korifiv1alpha1.CFRouteList{} - - g.Expect(adminClient.List(ctx, routes, client.InNamespace(route1Namespace))).To(Succeed()) - g.Expect(routes.Items).To(BeEmpty()) - - g.Expect(adminClient.List(ctx, routes, client.InNamespace(route2Namespace))).To(Succeed()) - g.Expect(routes.Items).To(BeEmpty()) - }).Should(Succeed()) - }) - }) -}) - -func createValidRoute(ctx context.Context, route *korifiv1alpha1.CFRoute) { - Expect(adminClient.Create(ctx, route)).To(Succeed()) - Eventually(func(g Gomega) { - createdRoute := &korifiv1alpha1.CFRoute{} - g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(route), createdRoute)).To(Succeed()) - g.Expect(meta.IsStatusConditionTrue(createdRoute.Status.Conditions, "Valid")).To(BeTrue()) - }).Should(Succeed()) -} diff --git a/controllers/controllers/networking/cfdomain_controller.go b/controllers/controllers/networking/domains/controller.go similarity index 76% rename from controllers/controllers/networking/cfdomain_controller.go rename to controllers/controllers/networking/domains/controller.go index 3b53d7c3c..c006af28f 100644 --- a/controllers/controllers/networking/cfdomain_controller.go +++ b/controllers/controllers/networking/domains/controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package networking +package domains import ( "context" @@ -26,7 +26,6 @@ import ( "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -34,22 +33,22 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -type CFDomainReconciler struct { +type Reconciler struct { client client.Client scheme *runtime.Scheme log logr.Logger } -func NewCFDomainReconciler( +func NewReconciler( client client.Client, scheme *runtime.Scheme, log logr.Logger, ) *k8s.PatchingReconciler[korifiv1alpha1.CFDomain, *korifiv1alpha1.CFDomain] { - routeReconciler := CFDomainReconciler{client: client, scheme: scheme, log: log} + routeReconciler := Reconciler{client: client, scheme: scheme, log: log} return k8s.NewPatchingReconciler[korifiv1alpha1.CFDomain, *korifiv1alpha1.CFDomain](log, client, &routeReconciler) } -func (r *CFDomainReconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder { +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder { return ctrl.NewControllerManagedBy(mgr). For(&korifiv1alpha1.CFDomain{}) } @@ -58,9 +57,15 @@ func (r *CFDomainReconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder //+kubebuilder:rbac:groups=korifi.cloudfoundry.org,resources=cfdomains/status,verbs=patch //+kubebuilder:rbac:groups=korifi.cloudfoundry.org,resources=cfdomains/finalizers,verbs=update -func (r *CFDomainReconciler) ReconcileResource(ctx context.Context, cfDomain *korifiv1alpha1.CFDomain) (ctrl.Result, error) { +func (r *Reconciler) ReconcileResource(ctx context.Context, cfDomain *korifiv1alpha1.CFDomain) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) + var err error + readyConditionBuilder := k8s.NewReadyConditionBuilder(cfDomain) + defer func() { + meta.SetStatusCondition(&cfDomain.Status.Conditions, readyConditionBuilder.WithError(err).Build()) + }() + if !cfDomain.GetDeletionTimestamp().IsZero() { return r.finalizeCFDomain(ctx, cfDomain) } @@ -68,18 +73,11 @@ func (r *CFDomainReconciler) ReconcileResource(ctx context.Context, cfDomain *ko cfDomain.Status.ObservedGeneration = cfDomain.Generation log.V(1).Info("set observed generation", "generation", cfDomain.Status.ObservedGeneration) - meta.SetStatusCondition(&cfDomain.Status.Conditions, metav1.Condition{ - Type: "Valid", - Status: metav1.ConditionTrue, - Reason: "Valid", - Message: "Valid Domain", - ObservedGeneration: cfDomain.Generation, - }) - + readyConditionBuilder.Ready() return ctrl.Result{}, nil } -func (r *CFDomainReconciler) finalizeCFDomain(ctx context.Context, cfDomain *korifiv1alpha1.CFDomain) (ctrl.Result, error) { +func (r *Reconciler) finalizeCFDomain(ctx context.Context, cfDomain *korifiv1alpha1.CFDomain) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx).WithName("finalizeCFDomain") if !controllerutil.ContainsFinalizer(cfDomain, korifiv1alpha1.CFDomainFinalizerName) { @@ -92,6 +90,8 @@ func (r *CFDomainReconciler) finalizeCFDomain(ctx context.Context, cfDomain *kor return ctrl.Result{}, err } + log.Info("routes", "len", len(domainRoutes)) + if len(domainRoutes) == 0 { if controllerutil.RemoveFinalizer(cfDomain, korifiv1alpha1.CFDomainFinalizerName) { log.V(1).Info("finalizer removed") @@ -111,7 +111,7 @@ func (r *CFDomainReconciler) finalizeCFDomain(ctx context.Context, cfDomain *kor return ctrl.Result{RequeueAfter: time.Second}, nil } -func (r *CFDomainReconciler) listRoutesForDomain(ctx context.Context, cfDomain *korifiv1alpha1.CFDomain) ([]korifiv1alpha1.CFRoute, error) { +func (r *Reconciler) listRoutesForDomain(ctx context.Context, cfDomain *korifiv1alpha1.CFDomain) ([]korifiv1alpha1.CFRoute, error) { routesList := korifiv1alpha1.CFRouteList{} err := r.client.List(ctx, &routesList, client.MatchingFields{shared.IndexRouteDomainQualifiedName: cfDomain.Namespace + "." + cfDomain.Name}) if err != nil { diff --git a/controllers/controllers/networking/domains/controller_test.go b/controllers/controllers/networking/domains/controller_test.go new file mode 100644 index 000000000..295350969 --- /dev/null +++ b/controllers/controllers/networking/domains/controller_test.go @@ -0,0 +1,111 @@ +package domains_test + +import ( + korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("CFDomainReconciler Integration Tests", func() { + var cfDomain *korifiv1alpha1.CFDomain + + BeforeEach(func() { + domainNamespace := uuid.NewString() + Expect(adminClient.Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: domainNamespace, + }, + })).To(Succeed()) + + cfDomain = &korifiv1alpha1.CFDomain{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: domainNamespace, + Finalizers: []string{ + korifiv1alpha1.CFDomainFinalizerName, + }, + }, + Spec: korifiv1alpha1.CFDomainSpec{ + Name: "a" + uuid.NewString() + ".com", + }, + } + Expect(adminClient.Create(ctx, cfDomain)).To(Succeed()) + }) + + It("sets the domain Ready status", func() { + Eventually(func(g Gomega) { + g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cfDomain), cfDomain)).To(Succeed()) + g.Expect(meta.IsStatusConditionTrue(cfDomain.Status.Conditions, korifiv1alpha1.StatusConditionReady)).To(BeTrue()) + }).Should(Succeed()) + }) + + Describe("finalization", func() { + var ( + route1Namespace string + route2Namespace string + ) + BeforeEach(func() { + route1Namespace = uuid.NewString() + Expect(adminClient.Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: route1Namespace, + }, + })).To(Succeed()) + + route2Namespace = uuid.NewString() + Expect(adminClient.Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: route2Namespace, + }, + })).To(Succeed()) + + Expect(adminClient.Create(ctx, &korifiv1alpha1.CFRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: route1Namespace, + }, + Spec: korifiv1alpha1.CFRouteSpec{ + DomainRef: corev1.ObjectReference{ + Name: cfDomain.Name, + Namespace: cfDomain.Namespace, + }, + }, + })).To(Succeed()) + + Expect(adminClient.Create(ctx, &korifiv1alpha1.CFRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: route2Namespace, + }, + Spec: korifiv1alpha1.CFRouteSpec{ + DomainRef: corev1.ObjectReference{ + Name: cfDomain.Name, + Namespace: cfDomain.Namespace, + }, + }, + })).To(Succeed()) + }) + + JustBeforeEach(func() { + Expect(adminClient.Delete(ctx, cfDomain)).To(Succeed()) + }) + + It("deletes the domain routes", func() { + Eventually(func(g Gomega) { + routes := &korifiv1alpha1.CFRouteList{} + + g.Expect(adminClient.List(ctx, routes, client.InNamespace(route1Namespace))).To(Succeed()) + g.Expect(routes.Items).To(BeEmpty()) + + g.Expect(adminClient.List(ctx, routes, client.InNamespace(route2Namespace))).To(Succeed()) + g.Expect(routes.Items).To(BeEmpty()) + }).Should(Succeed()) + }) + }) +}) diff --git a/controllers/controllers/networking/domains/suite_test.go b/controllers/controllers/networking/domains/suite_test.go new file mode 100644 index 000000000..6e0e10c95 --- /dev/null +++ b/controllers/controllers/networking/domains/suite_test.go @@ -0,0 +1,82 @@ +package domains_test + +import ( + "context" + "path/filepath" + "testing" + "time" + + korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + "code.cloudfoundry.org/korifi/controllers/controllers/networking/domains" + "code.cloudfoundry.org/korifi/controllers/controllers/shared" + "code.cloudfoundry.org/korifi/tests/helpers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + //+kubebuilder:scaffold:imports +) + +var ( + stopManager context.CancelFunc + stopClientCache context.CancelFunc + testEnv *envtest.Environment + adminClient client.Client + ctx context.Context +) + +func TestNetworkingControllers(t *testing.T) { + SetDefaultEventuallyTimeout(10 * time.Second) + SetDefaultEventuallyPollingInterval(250 * time.Millisecond) + + SetDefaultConsistentlyDuration(5 * time.Second) + SetDefaultConsistentlyPollingInterval(250 * time.Millisecond) + + RegisterFailHandler(Fail) + RunSpecs(t, "CFDomain Controller Integration Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "..", "..", "helm", "korifi", "controllers", "crds"), + }, + ErrorIfCRDPathMissing: true, + } + + _, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + + Expect(korifiv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) + + k8sManager := helpers.NewK8sManager(testEnv, filepath.Join("helm", "korifi", "controllers", "role.yaml")) + Expect(shared.SetupIndexWithManager(k8sManager)).To(Succeed()) + + adminClient, stopClientCache = helpers.NewCachedClient(testEnv.Config) + + err = domains.NewReconciler( + k8sManager.GetClient(), + k8sManager.GetScheme(), + ctrl.Log.WithName("controllers").WithName("CFDomain"), + ).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + stopManager = helpers.StartK8sManager(k8sManager) +}) + +var _ = BeforeEach(func() { + ctx = context.Background() +}) + +var _ = AfterSuite(func() { + stopClientCache() + stopManager() + Expect(testEnv.Stop()).To(Succeed()) +}) diff --git a/controllers/controllers/networking/cfroute_controller.go b/controllers/controllers/networking/routes/controller.go similarity index 90% rename from controllers/controllers/networking/cfroute_controller.go rename to controllers/controllers/networking/routes/controller.go index ce3e1ee53..c7b0c5e35 100644 --- a/controllers/controllers/networking/cfroute_controller.go +++ b/controllers/controllers/networking/routes/controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package networking +package routes import ( "context" @@ -45,25 +45,24 @@ import ( gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) -// CFRouteReconciler reconciles a CFRoute object to create Contour resources -type CFRouteReconciler struct { +type Reconciler struct { client client.Client scheme *runtime.Scheme log logr.Logger controllerConfig *config.ControllerConfig } -func NewCFRouteReconciler( +func NewReconciler( client client.Client, scheme *runtime.Scheme, log logr.Logger, controllerConfig *config.ControllerConfig, ) *k8s.PatchingReconciler[korifiv1alpha1.CFRoute, *korifiv1alpha1.CFRoute] { - routeReconciler := CFRouteReconciler{client: client, scheme: scheme, log: log, controllerConfig: controllerConfig} + routeReconciler := Reconciler{client: client, scheme: scheme, log: log, controllerConfig: controllerConfig} return k8s.NewPatchingReconciler[korifiv1alpha1.CFRoute, *korifiv1alpha1.CFRoute](log, client, &routeReconciler) } -func (r *CFRouteReconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder { +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder { return ctrl.NewControllerManagedBy(mgr). For(&korifiv1alpha1.CFRoute{}). Watches( @@ -72,7 +71,7 @@ func (r *CFRouteReconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder ) } -func (r *CFRouteReconciler) enqueueCFAppRequests(ctx context.Context, o client.Object) []reconcile.Request { +func (r *Reconciler) enqueueCFAppRequests(ctx context.Context, o client.Object) []reconcile.Request { var requests []reconcile.Request cfApp, ok := o.(*korifiv1alpha1.CFApp) @@ -112,7 +111,7 @@ func (r *CFRouteReconciler) enqueueCFAppRequests(ctx context.Context, o client.O //+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete -func (r *CFRouteReconciler) ReconcileResource(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) (ctrl.Result, error) { +func (r *Reconciler) ReconcileResource(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) var err error @@ -206,7 +205,7 @@ func setInvalidRouteStatus(log logr.Logger, cfRoute *korifiv1alpha1.CFRoute, des return ctrl.Result{}, err } -func (r *CFRouteReconciler) finalizeCFRoute(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) error { +func (r *Reconciler) finalizeCFRoute(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) error { log := logr.FromContextOrDiscard(ctx).WithName("finalizeCRRoute") if !controllerutil.ContainsFinalizer(cfRoute, korifiv1alpha1.CFRouteFinalizerName) { @@ -220,7 +219,7 @@ func (r *CFRouteReconciler) finalizeCFRoute(ctx context.Context, cfRoute *korifi return nil } -func (r *CFRouteReconciler) createOrPatchServices(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) error { +func (r *Reconciler) createOrPatchServices(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) error { log := logr.FromContextOrDiscard(ctx).WithName("createOrPatchServices") for _, destination := range cfRoute.Status.Destinations { @@ -272,7 +271,7 @@ func (r *CFRouteReconciler) createOrPatchServices(ctx context.Context, cfRoute * return nil } -func (r *CFRouteReconciler) buildEffectiveDestinations(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) ([]korifiv1alpha1.Destination, error) { +func (r *Reconciler) buildEffectiveDestinations(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) ([]korifiv1alpha1.Destination, error) { effectiveDestinations := []korifiv1alpha1.Destination{} for _, dest := range cfRoute.Spec.Destinations { @@ -304,7 +303,7 @@ func (r *CFRouteReconciler) buildEffectiveDestinations(ctx context.Context, cfRo return effectiveDestinations, nil } -func (r *CFRouteReconciler) getAppCurrentDroplet(ctx context.Context, appNamespace, appName string) (*korifiv1alpha1.BuildDropletStatus, error) { +func (r *Reconciler) getAppCurrentDroplet(ctx context.Context, appNamespace, appName string) (*korifiv1alpha1.BuildDropletStatus, error) { cfApp := &korifiv1alpha1.CFApp{ ObjectMeta: metav1.ObjectMeta{ Namespace: appNamespace, @@ -334,7 +333,7 @@ func (r *CFRouteReconciler) getAppCurrentDroplet(ctx context.Context, appNamespa return cfBuild.Status.Droplet, nil } -func (r *CFRouteReconciler) reconcileHTTPRoute(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute, cfDomain *korifiv1alpha1.CFDomain) error { +func (r *Reconciler) reconcileHTTPRoute(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute, cfDomain *korifiv1alpha1.CFDomain) error { fqdn := buildFQDN(cfRoute, cfDomain) log := logr.FromContextOrDiscard(ctx).WithName("createOrPatchHTTPRoute").WithValues("fqdn", fqdn, "path", cfRoute.Spec.Path) @@ -389,7 +388,7 @@ func (r *CFRouteReconciler) reconcileHTTPRoute(ctx context.Context, cfRoute *kor return nil } -func (r *CFRouteReconciler) deleteOrphanedServices(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) error { +func (r *Reconciler) deleteOrphanedServices(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) error { log := logr.FromContextOrDiscard(ctx).WithName("deleteOrphanedServices") matchingLabelSet := map[string]string{ @@ -425,7 +424,7 @@ func (r *CFRouteReconciler) deleteOrphanedServices(ctx context.Context, cfRoute return nil } -func (r *CFRouteReconciler) fetchServicesByMatchingLabels(ctx context.Context, labelSet map[string]string, namespace string) (*corev1.ServiceList, error) { +func (r *Reconciler) fetchServicesByMatchingLabels(ctx context.Context, labelSet map[string]string, namespace string) (*corev1.ServiceList, error) { log := logr.FromContextOrDiscard(ctx).WithName("fetchServicesByMatchingLabels") selector, err := labels.ValidatedSelectorFromSet(labelSet) diff --git a/controllers/controllers/networking/cfroute_controller_test.go b/controllers/controllers/networking/routes/controller_test.go similarity index 86% rename from controllers/controllers/networking/cfroute_controller_test.go rename to controllers/controllers/networking/routes/controller_test.go index 3430889fc..9d0f6d18e 100644 --- a/controllers/controllers/networking/cfroute_controller_test.go +++ b/controllers/controllers/networking/routes/controller_test.go @@ -1,12 +1,10 @@ -package networking_test +package routes_test import ( - "context" "fmt" "strings" korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" - . "code.cloudfoundry.org/korifi/controllers/controllers/workloads/testutils" "code.cloudfoundry.org/korifi/tools" "code.cloudfoundry.org/korifi/tools/k8s" @@ -19,81 +17,49 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) var _ = Describe("CFRouteReconciler Integration Tests", func() { var ( - ctx context.Context - - testNamespace string - testDomainGUID string - testRouteGUID string - testAppGUID string - ns *corev1.Namespace cfDomain *korifiv1alpha1.CFDomain cfRoute *korifiv1alpha1.CFRoute - cfApp *korifiv1alpha1.CFApp ) BeforeEach(func() { - ctx = context.Background() - - testNamespace = GenerateGUID() - ns = &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: testNamespace, + Name: uuid.NewString(), }, } Expect(adminClient.Create(ctx, ns)).To(Succeed()) - testDomainGUID = GenerateGUID() - testRouteGUID = GenerateGUID() - testAppGUID = GenerateGUID() - cfDomain = &korifiv1alpha1.CFDomain{ ObjectMeta: metav1.ObjectMeta{ - Name: testDomainGUID, - Namespace: testNamespace, + Name: uuid.NewString(), + Namespace: ns.Name, }, Spec: korifiv1alpha1.CFDomainSpec{ - Name: "a" + GenerateGUID() + ".com", + Name: "a" + uuid.NewString() + ".com", }, } Expect(adminClient.Create(ctx, cfDomain)).To(Succeed()) - cfApp = &korifiv1alpha1.CFApp{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: testAppGUID, - }, - Spec: korifiv1alpha1.CFAppSpec{ - Lifecycle: korifiv1alpha1.Lifecycle{ - Type: "buildpack", - }, - DesiredState: "STARTED", - DisplayName: testAppGUID, - }, - } - Expect(adminClient.Create(ctx, cfApp)).To(Succeed()) - cfRoute = &korifiv1alpha1.CFRoute{ ObjectMeta: metav1.ObjectMeta{ - Name: testRouteGUID, - Namespace: testNamespace, + Name: uuid.NewString(), + Namespace: ns.Name, }, Spec: korifiv1alpha1.CFRouteSpec{ Host: "test-route-host", Path: "/hello", Protocol: "http", DomainRef: corev1.ObjectReference{ - Name: testDomainGUID, - Namespace: testNamespace, + Name: cfDomain.Name, + Namespace: ns.Name, }, }, } @@ -118,10 +84,6 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { return httpRoute } - AfterEach(func() { - Expect(client.IgnoreNotFound(adminClient.Delete(ctx, ns))).To(Succeed()) - }) - JustBeforeEach(func() { Expect(adminClient.Create(ctx, cfRoute)).To(Succeed()) }) @@ -129,14 +91,14 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { It("does not create a HTTPRoute (as there are no destinations)", func() { Consistently(func(g Gomega) { httpRoutes := &gatewayv1beta1.HTTPRouteList{} - g.Expect(adminClient.List(ctx, httpRoutes, client.InNamespace(testNamespace))).To(Succeed()) + g.Expect(adminClient.List(ctx, httpRoutes, client.InNamespace(ns.Name))).To(Succeed()) g.Expect(httpRoutes.Items).To(BeEmpty()) }).Should(Succeed()) }) It("sets a valid status on the cfroute", func() { Eventually(func(g Gomega) { - g.Expect(adminClient.Get(ctx, types.NamespacedName{Name: testRouteGUID, Namespace: testNamespace}, cfRoute)).To(Succeed()) + g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cfRoute), cfRoute)).To(Succeed()) g.Expect(cfRoute.Status.CurrentStatus).To(Equal(korifiv1alpha1.ValidStatus)) g.Expect(cfRoute.Status.FQDN).To(Equal(getCfRouteFQDN())) g.Expect(cfRoute.Status.URI).To(Equal(getCfRouteFQDN() + "/hello")) @@ -146,12 +108,29 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { }) When("the CFRoute includes destinations", func() { + var cfApp *korifiv1alpha1.CFApp + BeforeEach(func() { + cfApp = &korifiv1alpha1.CFApp{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: uuid.NewString(), + }, + Spec: korifiv1alpha1.CFAppSpec{ + Lifecycle: korifiv1alpha1.Lifecycle{ + Type: "buildpack", + }, + DesiredState: "STARTED", + DisplayName: uuid.NewString(), + }, + } + Expect(adminClient.Create(ctx, cfApp)).To(Succeed()) + cfRoute.Spec.Destinations = []korifiv1alpha1.Destination{ { - GUID: GenerateGUID(), + GUID: uuid.NewString(), AppRef: corev1.LocalObjectReference{ - Name: testAppGUID, + Name: cfApp.Name, }, ProcessType: "web", Port: tools.PtrTo(80), @@ -233,14 +212,14 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { Eventually(func(g Gomega) { var svc corev1.Service - g.Expect(adminClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: testNamespace}, &svc)).To(Succeed()) + g.Expect(adminClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: ns.Name}, &svc)).To(Succeed()) g.Expect(svc.Labels).To(SatisfyAll( HaveKeyWithValue("korifi.cloudfoundry.org/app-guid", cfRoute.Spec.Destinations[0].AppRef.Name), HaveKeyWithValue("korifi.cloudfoundry.org/route-guid", cfRoute.Name), )) g.Expect(svc.Spec.Selector).To(SatisfyAll( HaveLen(2), - HaveKeyWithValue("korifi.cloudfoundry.org/app-guid", testAppGUID), + HaveKeyWithValue("korifi.cloudfoundry.org/app-guid", cfApp.Name), HaveKeyWithValue("korifi.cloudfoundry.org/process-type", "web"), )) g.Expect(svc.ObjectMeta.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ @@ -256,7 +235,7 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { It("sets effective destinations to the cfroute status", func() { Eventually(func(g Gomega) { - g.Expect(adminClient.Get(ctx, types.NamespacedName{Name: testRouteGUID, Namespace: testNamespace}, cfRoute)).To(Succeed()) + g.Expect(adminClient.Get(ctx, client.ObjectKeyFromObject(cfRoute), cfRoute)).To(Succeed()) g.Expect(cfRoute.Status.Destinations).To(ConsistOf(korifiv1alpha1.Destination{ GUID: cfRoute.Spec.Destinations[0].GUID, Port: tools.PtrTo(80), @@ -305,7 +284,7 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { It("does not create a service", func() { serviceName := fmt.Sprintf("s-%s", cfRoute.Spec.Destinations[0].GUID) Consistently(func(g Gomega) { - err := adminClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: testNamespace}, &corev1.Service{}) + err := adminClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: ns.Name}, &corev1.Service{}) g.Expect(err).To(MatchError(ContainSubstring("not found"))) }).Should(Succeed()) }) @@ -313,7 +292,7 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { It("does not create a HTTPRoute", func() { Consistently(func(g Gomega) { httpRoutes := &gatewayv1beta1.HTTPRouteList{} - g.Expect(adminClient.List(ctx, httpRoutes, client.InNamespace(testNamespace))).To(Succeed()) + g.Expect(adminClient.List(ctx, httpRoutes, client.InNamespace(ns.Name))).To(Succeed()) g.Expect(httpRoutes.Items).To(BeEmpty()) }).Should(Succeed()) }) @@ -338,7 +317,7 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { JustBeforeEach(func() { cfBuild = &korifiv1alpha1.CFBuild{ ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, + Namespace: ns.Name, Name: uuid.NewString(), }, Spec: korifiv1alpha1.CFBuildSpec{ @@ -347,7 +326,7 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { Data: korifiv1alpha1.LifecycleData{}, }, AppRef: corev1.LocalObjectReference{ - Name: testAppGUID, + Name: cfApp.Name, }, }, } @@ -430,7 +409,7 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { It("deletes the corresponding service", func() { Eventually(func(g Gomega) { - err := adminClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: testNamespace}, new(corev1.Service)) + err := adminClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: ns.Name}, new(corev1.Service)) g.Expect(errors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) }) @@ -439,7 +418,9 @@ var _ = Describe("CFRouteReconciler Integration Tests", func() { When("a legacy route has a finalizer", func() { BeforeEach(func() { - Expect(controllerutil.AddFinalizer(cfRoute, korifiv1alpha1.CFRouteFinalizerName)).To(BeTrue()) + cfRoute.Finalizers = []string{ + korifiv1alpha1.CFRouteFinalizerName, + } }) JustBeforeEach(func() { diff --git a/controllers/controllers/networking/routes/suite_test.go b/controllers/controllers/networking/routes/suite_test.go new file mode 100644 index 000000000..3ce7254d4 --- /dev/null +++ b/controllers/controllers/networking/routes/suite_test.go @@ -0,0 +1,95 @@ +package routes_test + +import ( + "context" + "path/filepath" + "testing" + "time" + + korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + "code.cloudfoundry.org/korifi/controllers/config" + "code.cloudfoundry.org/korifi/controllers/controllers/networking/routes" + "code.cloudfoundry.org/korifi/controllers/controllers/shared" + "code.cloudfoundry.org/korifi/tests/helpers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + //+kubebuilder:scaffold:imports +) + +var ( + stopManager context.CancelFunc + stopClientCache context.CancelFunc + testEnv *envtest.Environment + adminClient client.Client + ctx context.Context +) + +func TestNetworkingControllers(t *testing.T) { + SetDefaultEventuallyTimeout(10 * time.Second) + SetDefaultEventuallyPollingInterval(250 * time.Millisecond) + + SetDefaultConsistentlyDuration(5 * time.Second) + SetDefaultConsistentlyPollingInterval(250 * time.Millisecond) + + RegisterFailHandler(Fail) + RunSpecs(t, "CFRoute Controller Integration Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "..", "..", "helm", "korifi", "controllers", "crds"), + filepath.Join("..", "..", "..", "..", "tests", "vendor", "gateway-api"), + }, + ErrorIfCRDPathMissing: true, + } + + _, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + + Expect(korifiv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(gatewayv1beta1.Install(scheme.Scheme)).To(Succeed()) + + k8sManager := helpers.NewK8sManager(testEnv, filepath.Join("helm", "korifi", "controllers", "role.yaml")) + Expect(shared.SetupIndexWithManager(k8sManager)).To(Succeed()) + + adminClient, stopClientCache = helpers.NewCachedClient(testEnv.Config) + + Expect(routes.NewReconciler( + k8sManager.GetClient(), + k8sManager.GetScheme(), + ctrl.Log.WithName("controllers").WithName("CFRoute"), + &config.ControllerConfig{ + CFProcessDefaults: config.CFProcessDefaults{ + MemoryMB: 500, + DiskQuotaMB: 512, + }, + Networking: config.Networking{ + GatewayName: "korifi", + GatewayNamespace: "korifi-gateway", + }, + }, + ).SetupWithManager(k8sManager)).To(Succeed()) + + stopManager = helpers.StartK8sManager(k8sManager) +}) + +var _ = BeforeEach(func() { + ctx = context.Background() +}) + +var _ = AfterSuite(func() { + stopClientCache() + stopManager() + Expect(testEnv.Stop()).To(Succeed()) +}) diff --git a/controllers/controllers/networking/suite_test.go b/controllers/controllers/networking/suite_test.go deleted file mode 100644 index dde1d7fe7..000000000 --- a/controllers/controllers/networking/suite_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package networking_test - -import ( - "context" - "path/filepath" - "testing" - "time" - - korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" - "code.cloudfoundry.org/korifi/controllers/config" - . "code.cloudfoundry.org/korifi/controllers/controllers/networking" - "code.cloudfoundry.org/korifi/controllers/controllers/shared" - "code.cloudfoundry.org/korifi/controllers/coordination" - "code.cloudfoundry.org/korifi/controllers/webhooks" - "code.cloudfoundry.org/korifi/controllers/webhooks/finalizer" - "code.cloudfoundry.org/korifi/controllers/webhooks/networking" - "code.cloudfoundry.org/korifi/controllers/webhooks/version" - "code.cloudfoundry.org/korifi/controllers/webhooks/workloads" - "code.cloudfoundry.org/korifi/tests/helpers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - //+kubebuilder:scaffold:imports -) - -const rootNamespace = "cf" - -var ( - stopManager context.CancelFunc - stopClientCache context.CancelFunc - testEnv *envtest.Environment - adminClient client.Client -) - -func TestNetworkingControllers(t *testing.T) { - SetDefaultEventuallyTimeout(10 * time.Second) - SetDefaultEventuallyPollingInterval(250 * time.Millisecond) - - SetDefaultConsistentlyDuration(5 * time.Second) - SetDefaultConsistentlyPollingInterval(250 * time.Millisecond) - - RegisterFailHandler(Fail) - RunSpecs(t, "Networking Controllers Integration Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "..", "helm", "korifi", "controllers", "crds"), - filepath.Join("..", "..", "..", "tests", "vendor", "gateway-api"), - }, - WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("..", "..", "..", "helm", "korifi", "controllers", "manifests.yaml")}, - }, - ErrorIfCRDPathMissing: true, - } - - _, err := testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - - Expect(korifiv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) - Expect(gatewayv1beta1.Install(scheme.Scheme)).To(Succeed()) - - k8sManager := helpers.NewK8sManager(testEnv, filepath.Join("helm", "korifi", "controllers", "role.yaml")) - Expect(shared.SetupIndexWithManager(k8sManager)).To(Succeed()) - - adminClient, stopClientCache = helpers.NewCachedClient(testEnv.Config) - - err = (NewCFRouteReconciler( - k8sManager.GetClient(), - k8sManager.GetScheme(), - ctrl.Log.WithName("controllers").WithName("CFRoute"), - &config.ControllerConfig{ - CFProcessDefaults: config.CFProcessDefaults{ - MemoryMB: 500, - DiskQuotaMB: 512, - }, - Networking: config.Networking{ - GatewayName: "korifi", - GatewayNamespace: "korifi-gateway", - }, - }, - )).SetupWithManager(k8sManager) - Expect(err).ToNot(HaveOccurred()) - - err = (NewCFDomainReconciler( - k8sManager.GetClient(), - k8sManager.GetScheme(), - ctrl.Log.WithName("controllers").WithName("CFDomain"), - )).SetupWithManager(k8sManager) - Expect(err).ToNot(HaveOccurred()) - - finalizer.NewControllersFinalizerWebhook().SetupWebhookWithManager(k8sManager) - version.NewVersionWebhook("some-version").SetupWebhookWithManager(k8sManager) - Expect((&korifiv1alpha1.CFApp{}).SetupWebhookWithManager(k8sManager)).To(Succeed()) - (&workloads.AppRevWebhook{}).SetupWebhookWithManager(k8sManager) - - uncachedClient := helpers.NewUncachedClient(k8sManager.GetConfig()) - Expect(workloads.NewCFAppValidator( - webhooks.NewDuplicateValidator(coordination.NewNameRegistry(uncachedClient, workloads.AppEntityType)), - ).SetupWebhookWithManager(k8sManager)).To(Succeed()) - Expect(networking.NewCFDomainValidator(uncachedClient).SetupWebhookWithManager(k8sManager)).To(Succeed()) - Expect((&korifiv1alpha1.CFRoute{}).SetupWebhookWithManager(k8sManager)).To(Succeed()) - Expect(networking.NewCFRouteValidator( - webhooks.NewDuplicateValidator(coordination.NewNameRegistry(uncachedClient, networking.RouteEntityType)), - rootNamespace, - uncachedClient, - ).SetupWebhookWithManager(k8sManager)).To(Succeed()) - Expect((&korifiv1alpha1.CFBuild{}).SetupWebhookWithManager(k8sManager)).To(Succeed()) - - Expect(adminClient.Create(context.Background(), &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: rootNamespace, - }, - })).To(Succeed()) - - stopManager = helpers.StartK8sManager(k8sManager) -}) - -var _ = AfterSuite(func() { - stopClientCache() - stopManager() - Expect(testEnv.Stop()).To(Succeed()) -}) diff --git a/controllers/main.go b/controllers/main.go index e92947abd..cc7944a5f 100644 --- a/controllers/main.go +++ b/controllers/main.go @@ -27,7 +27,8 @@ import ( korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" "code.cloudfoundry.org/korifi/controllers/cleanup" "code.cloudfoundry.org/korifi/controllers/config" - networkingcontrollers "code.cloudfoundry.org/korifi/controllers/controllers/networking" + "code.cloudfoundry.org/korifi/controllers/controllers/networking/domains" + "code.cloudfoundry.org/korifi/controllers/controllers/networking/routes" "code.cloudfoundry.org/korifi/controllers/controllers/services/bindings" "code.cloudfoundry.org/korifi/controllers/controllers/services/instances" "code.cloudfoundry.org/korifi/controllers/controllers/shared" @@ -277,11 +278,11 @@ func main() { os.Exit(1) } - if err = (networkingcontrollers.NewCFDomainReconciler( + if err = domains.NewReconciler( mgr.GetClient(), mgr.GetScheme(), ctrl.Log.WithName("controllers").WithName("CFDomain"), - )).SetupWithManager(mgr); err != nil { + ).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "CFDomain") os.Exit(1) } @@ -386,12 +387,12 @@ func main() { } } - if err = (networkingcontrollers.NewCFRouteReconciler( + if err = routes.NewReconciler( mgr.GetClient(), mgr.GetScheme(), ctrl.Log.WithName("controllers").WithName("CFRoute"), controllerConfig, - )).SetupWithManager(mgr); err != nil { + ).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "CFRoute") os.Exit(1) }