From 915a72ae154c31744fe9835e7242ea16167b9352 Mon Sep 17 00:00:00 2001 From: Bartosz Majsak Date: Wed, 20 Mar 2024 18:31:36 +0100 Subject: [PATCH] chore: splits feature tests to separate files (#924) As the number of test cases (grouped as `Describe`) started to grow, we can split a single file into several groups per functionality. Follow-up for #906. --- .../integration/features/cleanup_int_test.go | 111 ++++ .../integration/features/features_int_test.go | 522 ------------------ tests/integration/features/fixtures/const.go | 10 + .../features/fixtures/file_sys_fixtures.go | 7 + .../features/manifests_int_test.go | 176 ++++++ .../features/preconditions_int_test.go | 100 ++++ .../features/serverless_feature_test.go | 8 +- .../features/servicemesh_feature_test.go | 8 +- .../integration/features/tracker_int_test.go | 172 ++++++ 9 files changed, 584 insertions(+), 530 deletions(-) create mode 100644 tests/integration/features/cleanup_int_test.go delete mode 100644 tests/integration/features/features_int_test.go create mode 100644 tests/integration/features/fixtures/const.go create mode 100644 tests/integration/features/manifests_int_test.go create mode 100644 tests/integration/features/preconditions_int_test.go create mode 100644 tests/integration/features/tracker_int_test.go diff --git a/tests/integration/features/cleanup_int_test.go b/tests/integration/features/cleanup_int_test.go new file mode 100644 index 00000000000..ee7cfe140b9 --- /dev/null +++ b/tests/integration/features/cleanup_int_test.go @@ -0,0 +1,111 @@ +package features_test + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/envtestutil" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/integration/features/fixtures" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" +) + +var _ = Describe("feature cleanup", func() { + + Context("using FeatureTracker and ownership as cleanup strategy", Ordered, func() { + + const ( + featureName = "create-secret" + secretName = "test-secret" + ) + + var ( + dsci *dsciv1.DSCInitialization + namespace string + featuresHandler *feature.FeaturesHandler + ) + + BeforeAll(func() { + namespace = envtestutil.AppendRandomNameTo("test-secret-ownership") + dsci = fixtures.NewDSCInitialization(namespace) + featuresHandler = feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + secretCreationErr := feature.CreateFeature(featureName). + For(handler). + UsingConfig(envTest.Config). + PreConditions( + feature.CreateNamespaceIfNotExists(namespace), + ). + WithResources(fixtures.CreateSecret(secretName, namespace)). + Load() + + Expect(secretCreationErr).ToNot(HaveOccurred()) + + return nil + }) + + }) + + It("should successfully create resource and associated feature tracker", func() { + // when + Expect(featuresHandler.Apply()).Should(Succeed()) + + // then + Eventually(createdSecretHasOwnerReferenceToOwningFeature(namespace, secretName)). + WithTimeout(fixtures.Timeout). + WithPolling(fixtures.Interval). + Should(Succeed()) + }) + + It("should remove feature tracker on clean-up", func() { + // when + Expect(featuresHandler.Delete()).To(Succeed()) + + // then + Eventually(createdSecretHasOwnerReferenceToOwningFeature(namespace, secretName)). + WithTimeout(fixtures.Timeout). + WithPolling(fixtures.Interval). + Should(WithTransform(errors.IsNotFound, BeTrue())) + }) + + }) + +}) + +func createdSecretHasOwnerReferenceToOwningFeature(namespace, secretName string) func() error { + return func() error { + secret, err := envTestClientset.CoreV1(). + Secrets(namespace). + Get(context.TODO(), secretName, metav1.GetOptions{}) + + if err != nil { + return err + } + + Expect(secret.OwnerReferences).Should( + ContainElement( + MatchFields(IgnoreExtras, Fields{"Kind": Equal("FeatureTracker")}), + ), + ) + + trackerName := "" + for _, ownerRef := range secret.OwnerReferences { + if ownerRef.Kind == "FeatureTracker" { + trackerName = ownerRef.Name + break + } + } + + tracker := &featurev1.FeatureTracker{} + return envTestClient.Get(context.Background(), client.ObjectKey{ + Name: trackerName, + }, tracker) + } +} diff --git a/tests/integration/features/features_int_test.go b/tests/integration/features/features_int_test.go deleted file mode 100644 index 341b7117ba9..00000000000 --- a/tests/integration/features/features_int_test.go +++ /dev/null @@ -1,522 +0,0 @@ -package features_test - -import ( - "context" - "embed" - "fmt" - "os" - "path" - "time" - - conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" - featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" - "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" - "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" - "github.com/opendatahub-io/opendatahub-operator/v2/tests/envtestutil" - "github.com/opendatahub-io/opendatahub-operator/v2/tests/integration/features/fixtures" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" -) - -//go:embed fixtures/templates -var testEmbeddedFiles embed.FS - -const ( - timeout = 5 * time.Second - interval = 250 * time.Millisecond -) - -var _ = Describe("feature preconditions", func() { - - Context("namespace existence", func() { - - var ( - objectCleaner *envtestutil.Cleaner - namespace string - dsci *dsciv1.DSCInitialization - ) - - BeforeEach(func() { - objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, timeout, interval) - - testFeatureName := "test-ns-creation" - namespace = envtestutil.AppendRandomNameTo(testFeatureName) - dsci = fixtures.NewDSCInitialization(namespace) - }) - - It("should create namespace if it does not exist", func() { - // given - _, err := fixtures.GetNamespace(envTestClient, namespace) - Expect(errors.IsNotFound(err)).To(BeTrue()) - defer objectCleaner.DeleteAll(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) - - // when - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - testFeatureErr := feature.CreateFeature("create-new-ns"). - For(handler). - PreConditions(feature.CreateNamespaceIfNotExists(namespace)). - UsingConfig(envTest.Config). - Load() - - Expect(testFeatureErr).ToNot(HaveOccurred()) - - return nil - }) - - // then - Expect(featuresHandler.Apply()).To(Succeed()) - - // and - Eventually(func() error { - _, err := fixtures.GetNamespace(envTestClient, namespace) - return err - }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) - }) - - It("should not try to create namespace if it does already exist", func() { - // given - ns := fixtures.NewNamespace(namespace) - Expect(envTestClient.Create(context.Background(), ns)).To(Succeed()) - Eventually(func() error { - _, err := fixtures.GetNamespace(envTestClient, namespace) - return err - }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) // wait for ns to actually get created - - defer objectCleaner.DeleteAll(ns) - - // when - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - testFeatureErr := feature.CreateFeature("create-new-ns"). - For(handler). - PreConditions(feature.CreateNamespaceIfNotExists(namespace)). - UsingConfig(envTest.Config). - Load() - - Expect(testFeatureErr).ToNot(HaveOccurred()) - - return nil - }) - - // then - Expect(featuresHandler.Apply()).To(Succeed()) - - }) - - }) - -}) - -var _ = Describe("feature cleanup", func() { - - Context("using FeatureTracker and ownership as cleanup strategy", Ordered, func() { - - const ( - featureName = "create-secret" - secretName = "test-secret" - ) - - var ( - dsci *dsciv1.DSCInitialization - namespace string - featuresHandler *feature.FeaturesHandler - ) - - BeforeAll(func() { - namespace = envtestutil.AppendRandomNameTo("test-secret-ownership") - dsci = fixtures.NewDSCInitialization(namespace) - featuresHandler = feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - secretCreationErr := feature.CreateFeature(featureName). - For(handler). - UsingConfig(envTest.Config). - PreConditions( - feature.CreateNamespaceIfNotExists(namespace), - ). - WithResources(fixtures.CreateSecret(secretName, namespace)). - Load() - - Expect(secretCreationErr).ToNot(HaveOccurred()) - - return nil - }) - - }) - - It("should successfully create resource and associated feature tracker", func() { - // when - Expect(featuresHandler.Apply()).Should(Succeed()) - - // then - Eventually(createdSecretHasOwnerReferenceToOwningFeature(secretName, namespace)). - WithTimeout(timeout). - WithPolling(interval). - Should(Succeed()) - }) - - It("should remove feature tracker on clean-up", func() { - // when - Expect(featuresHandler.Delete()).To(Succeed()) - - // then - Eventually(createdSecretHasOwnerReferenceToOwningFeature(secretName, namespace)). - WithTimeout(timeout). - WithPolling(interval). - Should(WithTransform(errors.IsNotFound, BeTrue())) - }) - - }) - - var _ = Describe("feature trackers", func() { - Context("ensuring feature trackers indicate status and phase", func() { - - const appNamespace = "default" - - var ( - dsci *dsciv1.DSCInitialization - ) - - BeforeEach(func() { - dsci = fixtures.NewDSCInitialization("default") - }) - - It("should indicate successful installation in FeatureTracker", func() { - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - verificationFeatureErr := feature.CreateFeature("always-working-feature"). - For(handler). - UsingConfig(envTest.Config). - Load() - - Expect(verificationFeatureErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "always-working-feature") - Expect(err).ToNot(HaveOccurred()) - Expect(*featureTracker.Status.Conditions).To(ContainElement( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(conditionsv1.ConditionAvailable), - "Status": Equal(v1.ConditionTrue), - "Reason": Equal(string(featurev1.FeatureCreated)), - }), - )) - }) - - It("should indicate failure in preconditions", func() { - // given - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - verificationFeatureErr := feature.CreateFeature("precondition-fail"). - For(handler). - UsingConfig(envTest.Config). - PreConditions(func(f *feature.Feature) error { - return fmt.Errorf("during test always fail") - }). - Load() - - Expect(verificationFeatureErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).ToNot(Succeed()) - - // then - featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "precondition-fail") - Expect(err).ToNot(HaveOccurred()) - Expect(*featureTracker.Status.Conditions).To(ContainElement( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(conditionsv1.ConditionDegraded), - "Status": Equal(v1.ConditionTrue), - "Reason": Equal(string(featurev1.PreConditions)), - }), - )) - }) - - It("should indicate failure in post-conditions", func() { - // given - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - verificationFeatureErr := feature.CreateFeature("post-condition-failure"). - For(handler). - UsingConfig(envTest.Config). - PostConditions(func(f *feature.Feature) error { - return fmt.Errorf("during test always fail") - }). - Load() - - Expect(verificationFeatureErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).ToNot(Succeed()) - - // then - featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "post-condition-failure") - Expect(err).ToNot(HaveOccurred()) - Expect(*featureTracker.Status.Conditions).To(ContainElement( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(conditionsv1.ConditionDegraded), - "Status": Equal(v1.ConditionTrue), - "Reason": Equal(string(featurev1.PostConditions)), - }), - )) - }) - - It("should correctly indicate source in the feature tracker", func() { - // given - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - emptyFeatureErr := feature.CreateFeature("always-working-feature"). - For(handler). - UsingConfig(envTest.Config). - Load() - - Expect(emptyFeatureErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "always-working-feature") - Expect(err).ToNot(HaveOccurred()) - Expect(featureTracker.Spec.Source).To( - MatchFields(IgnoreExtras, Fields{ - "Name": Equal("default-dsci"), - "Type": Equal(featurev1.DSCIType), - }), - ) - }) - - It("should correctly indicate app namespace in the feature tracker", func() { - // given - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - emptyFeatureErr := feature.CreateFeature("empty-feature"). - For(handler). - UsingConfig(envTest.Config). - Load() - - Expect(emptyFeatureErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "empty-feature") - Expect(err).ToNot(HaveOccurred()) - Expect(featureTracker.Spec.AppNamespace).To(Equal("default")) - }) - - }) - - }) - - var _ = Describe("Manifest sources", func() { - Context("using various manifest sources", func() { - - var ( - objectCleaner *envtestutil.Cleaner - dsci *dsciv1.DSCInitialization - namespace *v1.Namespace - ) - - BeforeEach(func() { - objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, timeout, interval) - nsName := envtestutil.AppendRandomNameTo("smcp-ns") - - var err error - namespace, err = cluster.CreateNamespace(envTestClient, nsName) - Expect(err).ToNot(HaveOccurred()) - - dsci = fixtures.NewDSCInitialization(nsName) - dsci.Spec.ServiceMesh.ControlPlane.Namespace = namespace.Name - }) - - AfterEach(func() { - objectCleaner.DeleteAll(namespace) - }) - - It("should be able to process an embedded template from the default location", func() { - // given - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - createServiceErr := feature.CreateFeature("create-local-gw-svc"). - For(handler). - UsingConfig(envTest.Config). - Manifests(path.Join(feature.ServerlessDir, "serving-istio-gateways", "local-gateway-svc.tmpl")). - Load() - - Expect(createServiceErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - service, err := fixtures.GetService(envTestClient, namespace.Name, "knative-local-gateway") - Expect(err).ToNot(HaveOccurred()) - Expect(service.Name).To(Equal("knative-local-gateway")) - }) - - It("should be able to process an embedded YAML file from the default location", func() { - // given - knativeNs, nsErr := cluster.CreateNamespace(envTestClient, "knative-serving") - Expect(nsErr).ToNot(HaveOccurred()) - defer objectCleaner.DeleteAll(knativeNs) - - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - createGatewayErr := feature.CreateFeature("create-local-gateway"). - For(handler). - UsingConfig(envTest.Config). - Manifests(path.Join(feature.ServerlessDir, "serving-istio-gateways", "istio-local-gateway.yaml")). - Load() - - Expect(createGatewayErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - gateway, err := getGateway(envTest.Config, "knative-serving", "knative-local-gateway") - Expect(err).ToNot(HaveOccurred()) - Expect(gateway).ToNot(BeNil()) - }) - - It("should be able to process an embedded file from a non default location", func() { - // given - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - createNamespaceErr := feature.CreateFeature("create-namespace"). - For(handler). - UsingConfig(envTest.Config). - ManifestSource(testEmbeddedFiles). - Manifests(path.Join("fixtures", feature.BaseDir, "namespace.yaml")). - Load() - - Expect(createNamespaceErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - embeddedNs, err := fixtures.GetNamespace(envTestClient, "embedded-test-ns") - defer objectCleaner.DeleteAll(embeddedNs) - Expect(err).ToNot(HaveOccurred()) - Expect(embeddedNs.Name).To(Equal("embedded-test-ns")) - }) - - const nsYAML = `apiVersion: v1 -kind: Namespace -metadata: - name: real-file-test-ns` - - It("should source manifests from a specified temporary directory within the file system", func() { - // given - tempDir := GinkgoT().TempDir() - - Expect(fixtures.CreateFile(tempDir, "namespace.yaml", nsYAML)).To(Succeed()) - - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - createServiceErr := feature.CreateFeature("create-namespace"). - For(handler). - UsingConfig(envTest.Config). - ManifestSource(os.DirFS(tempDir)). - Manifests(path.Join("namespace.yaml")). // must be relative to root DirFS defined above - Load() - - Expect(createServiceErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - realNs, err := fixtures.GetNamespace(envTestClient, "real-file-test-ns") - defer objectCleaner.DeleteAll(realNs) - Expect(err).ToNot(HaveOccurred()) - Expect(realNs.Name).To(Equal("real-file-test-ns")) - }) - - It("should process kustomization manifests directly from the file system", func() { - // given - featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { - createCfgMapErr := feature.CreateFeature("create-cfg-map"). - For(handler). - UsingConfig(envTest.Config). - Manifests(path.Join("fixtures", feature.BaseDir, "fake-kust-dir")). - Load() - - Expect(createCfgMapErr).ToNot(HaveOccurred()) - - return nil - }) - - // when - Expect(featuresHandler.Apply()).To(Succeed()) - - // then - cfgMap, err := fixtures.GetConfigMap(envTestClient, featuresHandler.ApplicationsNamespace, "my-configmap") - Expect(err).ToNot(HaveOccurred()) - Expect(cfgMap.Name).To(Equal("my-configmap")) - Expect(cfgMap.Data["key"]).To(Equal("value")) - }) - }) - }) - -}) - -func createdSecretHasOwnerReferenceToOwningFeature(secretName, namespace string) func() error { - return func() error { - secret, err := envTestClientset.CoreV1(). - Secrets(namespace). - Get(context.TODO(), secretName, metav1.GetOptions{}) - - if err != nil { - return err - } - - Expect(secret.OwnerReferences).Should( - ContainElement( - MatchFields(IgnoreExtras, Fields{"Kind": Equal("FeatureTracker")}), - ), - ) - - trackerName := "" - for _, ownerRef := range secret.OwnerReferences { - if ownerRef.Kind == "FeatureTracker" { - trackerName = ownerRef.Name - break - } - } - - tracker := &featurev1.FeatureTracker{} - return envTestClient.Get(context.Background(), client.ObjectKey{ - Name: trackerName, - }, tracker) - } -} diff --git a/tests/integration/features/fixtures/const.go b/tests/integration/features/fixtures/const.go new file mode 100644 index 00000000000..1a5367d4212 --- /dev/null +++ b/tests/integration/features/fixtures/const.go @@ -0,0 +1,10 @@ +package fixtures + +import "time" + +const ( + // Timeout is the default timeout for waiting for a condition to be met. + Timeout = 5 * time.Second + // Interval is the default interval for polling for a condition to be met. + Interval = 250 * time.Millisecond +) diff --git a/tests/integration/features/fixtures/file_sys_fixtures.go b/tests/integration/features/fixtures/file_sys_fixtures.go index e759a56f748..f116d4d2bab 100644 --- a/tests/integration/features/fixtures/file_sys_fixtures.go +++ b/tests/integration/features/fixtures/file_sys_fixtures.go @@ -1,10 +1,17 @@ package fixtures import ( + "embed" "os" "path/filepath" ) +// TestEmbeddedFiles is an embedded filesystem that contains templates used specifically in tests files +// +//go:embed templates +var TestEmbeddedFiles embed.FS + +// CreateFile creates a file with the given content in the specified directory. func CreateFile(dir, filename, content string) error { filePath := filepath.Join(dir, filename) file, err := os.Create(filePath) diff --git a/tests/integration/features/manifests_int_test.go b/tests/integration/features/manifests_int_test.go new file mode 100644 index 00000000000..e7a002e363f --- /dev/null +++ b/tests/integration/features/manifests_int_test.go @@ -0,0 +1,176 @@ +package features_test + +import ( + "os" + "path" + + corev1 "k8s.io/api/core/v1" + + dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/envtestutil" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/integration/features/fixtures" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Manifest sources", func() { + + var ( + objectCleaner *envtestutil.Cleaner + dsci *dsciv1.DSCInitialization + namespace *corev1.Namespace + ) + + BeforeEach(func() { + objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, fixtures.Timeout, fixtures.Interval) + nsName := envtestutil.AppendRandomNameTo("smcp-ns") + + var err error + namespace, err = cluster.CreateNamespace(envTestClient, nsName) + Expect(err).ToNot(HaveOccurred()) + + dsci = fixtures.NewDSCInitialization(nsName) + dsci.Spec.ServiceMesh.ControlPlane.Namespace = namespace.Name + }) + + AfterEach(func() { + objectCleaner.DeleteAll(namespace) + }) + + It("should be able to process an embedded template from the default location", func() { + // given + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + createServiceErr := feature.CreateFeature("create-local-gw-svc"). + For(handler). + UsingConfig(envTest.Config). + Manifests(path.Join(feature.ServerlessDir, "serving-istio-gateways", "local-gateway-svc.tmpl")). + Load() + + Expect(createServiceErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + service, err := fixtures.GetService(envTestClient, namespace.Name, "knative-local-gateway") + Expect(err).ToNot(HaveOccurred()) + Expect(service.Name).To(Equal("knative-local-gateway")) + }) + + It("should be able to process an embedded YAML file from the default location", func() { + // given + knativeNs, nsErr := cluster.CreateNamespace(envTestClient, "knative-serving") + Expect(nsErr).ToNot(HaveOccurred()) + defer objectCleaner.DeleteAll(knativeNs) + + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + createGatewayErr := feature.CreateFeature("create-local-gateway"). + For(handler). + UsingConfig(envTest.Config). + Manifests(path.Join(feature.ServerlessDir, "serving-istio-gateways", "istio-local-gateway.yaml")). + Load() + + Expect(createGatewayErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + gateway, err := getGateway(envTest.Config, "knative-serving", "knative-local-gateway") + Expect(err).ToNot(HaveOccurred()) + Expect(gateway).ToNot(BeNil()) + }) + + It("should be able to process an embedded file from a non default location", func() { + // given + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + createNamespaceErr := feature.CreateFeature("create-namespace"). + For(handler). + UsingConfig(envTest.Config). + ManifestSource(fixtures.TestEmbeddedFiles). + Manifests(path.Join(feature.BaseDir, "namespace.yaml")). + Load() + + Expect(createNamespaceErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + embeddedNs, err := fixtures.GetNamespace(envTestClient, "embedded-test-ns") + defer objectCleaner.DeleteAll(embeddedNs) + Expect(err).ToNot(HaveOccurred()) + Expect(embeddedNs.Name).To(Equal("embedded-test-ns")) + }) + + const nsYAML = `apiVersion: v1 +kind: Namespace +metadata: + name: real-file-test-ns` + + It("should source manifests from a specified temporary directory within the file system", func() { + // given + tempDir := GinkgoT().TempDir() + + Expect(fixtures.CreateFile(tempDir, "namespace.yaml", nsYAML)).To(Succeed()) + + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + createServiceErr := feature.CreateFeature("create-namespace"). + For(handler). + UsingConfig(envTest.Config). + ManifestSource(os.DirFS(tempDir)). + Manifests(path.Join("namespace.yaml")). // must be relative to root DirFS defined above + Load() + + Expect(createServiceErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + realNs, err := fixtures.GetNamespace(envTestClient, "real-file-test-ns") + defer objectCleaner.DeleteAll(realNs) + Expect(err).ToNot(HaveOccurred()) + Expect(realNs.Name).To(Equal("real-file-test-ns")) + }) + + It("should process kustomization manifests directly from the file system", func() { + // given + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + createCfgMapErr := feature.CreateFeature("create-cfg-map"). + For(handler). + UsingConfig(envTest.Config). + Manifests(path.Join("fixtures", feature.BaseDir, "fake-kust-dir")). + Load() + + Expect(createCfgMapErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + cfgMap, err := fixtures.GetConfigMap(envTestClient, featuresHandler.ApplicationsNamespace, "my-configmap") + Expect(err).ToNot(HaveOccurred()) + Expect(cfgMap.Name).To(Equal("my-configmap")) + Expect(cfgMap.Data["key"]).To(Equal("value")) + }) + +}) diff --git a/tests/integration/features/preconditions_int_test.go b/tests/integration/features/preconditions_int_test.go new file mode 100644 index 00000000000..32d2942c0c3 --- /dev/null +++ b/tests/integration/features/preconditions_int_test.go @@ -0,0 +1,100 @@ +package features_test + +import ( + "context" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/envtestutil" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/integration/features/fixtures" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("feature preconditions", func() { + + Context("namespace existence", func() { + + var ( + objectCleaner *envtestutil.Cleaner + namespace string + dsci *dsciv1.DSCInitialization + ) + + BeforeEach(func() { + objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, fixtures.Timeout, fixtures.Interval) + + testFeatureName := "test-ns-creation" + namespace = envtestutil.AppendRandomNameTo(testFeatureName) + dsci = fixtures.NewDSCInitialization(namespace) + }) + + It("should create namespace if it does not exist", func() { + // given + _, err := fixtures.GetNamespace(envTestClient, namespace) + Expect(errors.IsNotFound(err)).To(BeTrue()) + defer objectCleaner.DeleteAll(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) + + // when + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + testFeatureErr := feature.CreateFeature("create-new-ns"). + For(handler). + PreConditions(feature.CreateNamespaceIfNotExists(namespace)). + UsingConfig(envTest.Config). + Load() + + Expect(testFeatureErr).ToNot(HaveOccurred()) + + return nil + }) + + // then + Expect(featuresHandler.Apply()).To(Succeed()) + + // and + Eventually(func() error { + _, err := fixtures.GetNamespace(envTestClient, namespace) + return err + }). + WithTimeout(fixtures.Timeout). + WithPolling(fixtures.Interval). + Should(Succeed()) + }) + + It("should not try to create namespace if it does already exist", func() { + // given + ns := fixtures.NewNamespace(namespace) + Expect(envTestClient.Create(context.Background(), ns)).To(Succeed()) + Eventually(func() error { + _, err := fixtures.GetNamespace(envTestClient, namespace) + return err + }).WithTimeout(fixtures.Timeout).WithPolling(fixtures.Interval).Should(Succeed()) // wait for ns to actually get created + + defer objectCleaner.DeleteAll(ns) + + // when + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + testFeatureErr := feature.CreateFeature("create-new-ns"). + For(handler). + PreConditions(feature.CreateNamespaceIfNotExists(namespace)). + UsingConfig(envTest.Config). + Load() + + Expect(testFeatureErr).ToNot(HaveOccurred()) + + return nil + }) + + // then + Expect(featuresHandler.Apply()).To(Succeed()) + + }) + + }) + +}) diff --git a/tests/integration/features/serverless_feature_test.go b/tests/integration/features/serverless_feature_test.go index 6bcd1e0f543..32690e2d0b6 100644 --- a/tests/integration/features/serverless_feature_test.go +++ b/tests/integration/features/serverless_feature_test.go @@ -36,7 +36,7 @@ var _ = Describe("Serverless feature", func() { // TODO rework c, err := client.New(envTest.Config, client.Options{}) Expect(err).ToNot(HaveOccurred()) - objectCleaner = envtestutil.CreateCleaner(c, envTest.Config, timeout, interval) + objectCleaner = envtestutil.CreateCleaner(c, envTest.Config, fixtures.Timeout, fixtures.Interval) dsci = fixtures.NewDSCInitialization("default") kserveComponent = &kserve.Kserve{} @@ -83,7 +83,7 @@ var _ = Describe("Serverless feature", func() { Expect(err).ToNot(HaveOccurred()) Expect(c.Create(context.TODO(), knativeServingCrdObj)).To(Succeed()) - crdOptions := envtest.CRDInstallOptions{PollInterval: interval, MaxTime: timeout} + crdOptions := envtest.CRDInstallOptions{PollInterval: fixtures.Interval, MaxTime: fixtures.Timeout} err = envtest.WaitForCRDs(envTest.Config, []*apiextensionsv1.CustomResourceDefinition{knativeServingCrdObj}, crdOptions) Expect(err).ToNot(HaveOccurred()) }) @@ -271,7 +271,7 @@ var _ = Describe("Serverless feature", func() { } return nil - }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) + }).WithTimeout(fixtures.Timeout).WithPolling(fixtures.Interval).Should(Succeed()) }) It("should not create any TLS secret if certificate is user provided", func() { @@ -306,7 +306,7 @@ var _ = Describe("Serverless feature", func() { } return nil - }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) + }).WithTimeout(fixtures.Timeout).WithPolling(fixtures.Interval).Should(Succeed()) }) }) diff --git a/tests/integration/features/servicemesh_feature_test.go b/tests/integration/features/servicemesh_feature_test.go index 4443c7a80a0..8c090fc31f7 100644 --- a/tests/integration/features/servicemesh_feature_test.go +++ b/tests/integration/features/servicemesh_feature_test.go @@ -36,7 +36,7 @@ var _ = Describe("Service Mesh setup", func() { BeforeEach(func() { c, err := client.New(envTest.Config, client.Options{}) Expect(err).ToNot(HaveOccurred()) - objectCleaner = envtestutil.CreateCleaner(c, envTest.Config, timeout, interval) + objectCleaner = envtestutil.CreateCleaner(c, envTest.Config, fixtures.Timeout, fixtures.Interval) namespace := envtestutil.AppendRandomNameTo("service-mesh-settings") @@ -175,7 +175,7 @@ var _ = Describe("Service Mesh setup", func() { BeforeEach(func() { smcpCrdObj = installServiceMeshCRD() - objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, timeout, interval) + objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, fixtures.Timeout, fixtures.Interval) dsci = fixtures.NewDSCInitialization(namespace) serviceMeshSpec = &dsci.Spec.ServiceMesh @@ -243,7 +243,7 @@ var _ = Describe("Service Mesh setup", func() { Expect(found).To(BeTrue()) return extensionProviders - }).WithTimeout(timeout).WithPolling(interval).Should(BeEmpty()) + }).WithTimeout(fixtures.Timeout).WithPolling(fixtures.Interval).Should(BeEmpty()) }) }) @@ -260,7 +260,7 @@ func installServiceMeshCRD() *apiextensionsv1.CustomResourceDefinition { Expect(yaml.Unmarshal([]byte(fixtures.ServiceMeshControlPlaneCRD), smcpCrdObj)).ToNot(HaveOccurred()) Expect(envTestClient.Create(context.TODO(), smcpCrdObj)).ToNot(HaveOccurred()) - crdOptions := envtest.CRDInstallOptions{PollInterval: interval, MaxTime: timeout} + crdOptions := envtest.CRDInstallOptions{PollInterval: fixtures.Interval, MaxTime: fixtures.Timeout} Expect(envtest.WaitForCRDs(envTest.Config, []*apiextensionsv1.CustomResourceDefinition{smcpCrdObj}, crdOptions)).To(Succeed()) return smcpCrdObj diff --git a/tests/integration/features/tracker_int_test.go b/tests/integration/features/tracker_int_test.go new file mode 100644 index 00000000000..8298e8327ef --- /dev/null +++ b/tests/integration/features/tracker_int_test.go @@ -0,0 +1,172 @@ +package features_test + +import ( + "fmt" + + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + corev1 "k8s.io/api/core/v1" + + dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/integration/features/fixtures" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" +) + +var _ = Describe("Feature tracking capability - for keeping relationship between features enabled by operator and resources created in the cluster", func() { + Context("ensuring feature trackers indicate status and phase", func() { + + const appNamespace = "default" + + var ( + dsci *dsciv1.DSCInitialization + ) + + BeforeEach(func() { + dsci = fixtures.NewDSCInitialization("default") + }) + + It("should indicate successful installation in FeatureTracker", func() { + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + verificationFeatureErr := feature.CreateFeature("always-working-feature"). + For(handler). + UsingConfig(envTest.Config). + Load() + + Expect(verificationFeatureErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "always-working-feature") + Expect(err).ToNot(HaveOccurred()) + Expect(*featureTracker.Status.Conditions).To(ContainElement( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(conditionsv1.ConditionAvailable), + "Status": Equal(corev1.ConditionTrue), + "Reason": Equal(string(featurev1.FeatureCreated)), + }), + )) + }) + + It("should indicate failure in preconditions", func() { + // given + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + verificationFeatureErr := feature.CreateFeature("precondition-fail"). + For(handler). + UsingConfig(envTest.Config). + PreConditions(func(f *feature.Feature) error { + return fmt.Errorf("during test always fail") + }). + Load() + + Expect(verificationFeatureErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).ToNot(Succeed()) + + // then + featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "precondition-fail") + Expect(err).ToNot(HaveOccurred()) + Expect(*featureTracker.Status.Conditions).To(ContainElement( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(conditionsv1.ConditionDegraded), + "Status": Equal(corev1.ConditionTrue), + "Reason": Equal(string(featurev1.PreConditions)), + }), + )) + }) + + It("should indicate failure in post-conditions", func() { + // given + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + verificationFeatureErr := feature.CreateFeature("post-condition-failure"). + For(handler). + UsingConfig(envTest.Config). + PostConditions(func(f *feature.Feature) error { + return fmt.Errorf("during test always fail") + }). + Load() + + Expect(verificationFeatureErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).ToNot(Succeed()) + + // then + featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "post-condition-failure") + Expect(err).ToNot(HaveOccurred()) + Expect(*featureTracker.Status.Conditions).To(ContainElement( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(conditionsv1.ConditionDegraded), + "Status": Equal(corev1.ConditionTrue), + "Reason": Equal(string(featurev1.PostConditions)), + }), + )) + }) + + It("should correctly indicate source in the feature tracker", func() { + // given + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + emptyFeatureErr := feature.CreateFeature("always-working-feature"). + For(handler). + UsingConfig(envTest.Config). + Load() + + Expect(emptyFeatureErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "always-working-feature") + Expect(err).ToNot(HaveOccurred()) + Expect(featureTracker.Spec.Source).To( + MatchFields(IgnoreExtras, Fields{ + "Name": Equal("default-dsci"), + "Type": Equal(featurev1.DSCIType), + }), + ) + }) + + It("should correctly indicate app namespace in the feature tracker", func() { + // given + featuresHandler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + emptyFeatureErr := feature.CreateFeature("empty-feature"). + For(handler). + UsingConfig(envTest.Config). + Load() + + Expect(emptyFeatureErr).ToNot(HaveOccurred()) + + return nil + }) + + // when + Expect(featuresHandler.Apply()).To(Succeed()) + + // then + featureTracker, err := fixtures.GetFeatureTracker(envTestClient, appNamespace, "empty-feature") + Expect(err).ToNot(HaveOccurred()) + Expect(featureTracker.Spec.AppNamespace).To(Equal("default")) + }) + + }) + +})