diff --git a/pkg/api/v1/atlasdatafederation_types.go b/pkg/api/v1/atlasdatafederation_types.go index 9aeb935bb2..6ecbe00b03 100644 --- a/pkg/api/v1/atlasdatafederation_types.go +++ b/pkg/api/v1/atlasdatafederation_types.go @@ -228,3 +228,8 @@ func (c *AtlasDataFederation) WithPrivateEndpoint(endpointID, provider, endpoint }) return c } + +func (c *AtlasDataFederation) WithAnnotations(annotations map[string]string) *AtlasDataFederation { + c.Annotations = annotations + return c +} diff --git a/test/int/datafederation_protect_test.go b/test/int/datafederation_protect_test.go new file mode 100644 index 0000000000..b27e520bb9 --- /dev/null +++ b/test/int/datafederation_protect_test.go @@ -0,0 +1,175 @@ +package int + +import ( + "context" + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.mongodb.org/atlas/mongodbatlas" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/project" + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/status" + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/customresource" + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/testutil" +) + +var _ = Describe("AtlasProject", Label("int", "AtlasDataFederation", "protection-enabled"), func() { + const ( + interval = PollingInterval + dataFederationBaseName = "test-data-federation-%s" + ) + + var ( + connectionSecret corev1.Secret + testProject *mdbv1.AtlasProject + testNamespace *corev1.Namespace + stopManager context.CancelFunc + testDataFederation *mdbv1.AtlasDataFederation + testDataFederationName string + manualDeletion bool + ) + + BeforeEach(func() { + By("Starting the operator", func() { + testNamespace, stopManager = prepareControllers(true) + Expect(testNamespace).ToNot(BeNil()) + Expect(stopManager).ToNot(BeNil()) + }) + + By("Creating project connection secret", func() { + connectionSecret = buildConnectionSecret(fmt.Sprintf("%s-atlas-key", testNamespace.Name)) + Expect(k8sClient.Create(context.Background(), &connectionSecret)).To(Succeed()) + }) + + By("Creating a project in the cluster", func() { + testProject = mdbv1.DefaultProject(testNamespace.Name, connectionSecret.Name). + WithIPAccessList(project.NewIPAccessList(). + WithCIDR("0.0.0.0/0")) + Expect(k8sClient.Create(context.TODO(), testProject, &client.CreateOptions{})).To(Succeed()) + + Eventually(func() bool { + return testutil.CheckCondition(k8sClient, testProject, status.TrueCondition(status.ReadyType)) + }).WithTimeout(15 * time.Minute).WithPolling(PollingInterval).Should(BeTrue()) + }) + + if !manualDeletion { + By("Setting up DataFederation struct", func() { + testDataFederation = &mdbv1.AtlasDataFederation{} + testDataFederationName = fmt.Sprintf(dataFederationBaseName, testNamespace.Name) + }) + } + }) + + AfterEach(func() { + By("Deleting project connection secret", func() { + Expect(k8sClient.Delete(context.Background(), &connectionSecret)).To(Succeed()) + }) + + By("Stopping the operator", func() { + stopManager() + err := k8sClient.Delete(context.Background(), testNamespace) + Expect(err).ToNot(HaveOccurred()) + }) + + By("Removing Atlas Project "+testProject.Status.ID, func() { + Expect(k8sClient.Delete(context.Background(), testProject)).To(Succeed()) + Eventually(checkAtlasProjectRemoved(testProject.Status.ID), 60, interval).Should(BeTrue()) + }) + + By("Removing Atlas DataFederation "+testDataFederationName, func() { + _, err := atlasClient.DataFederation.Delete(context.TODO(), testProject.ID(), testDataFederation.Spec.Name) + Expect(err).To(BeNil()) + }) + }) + + Describe("Operator is running with deletion protection enabled", func() { + It("Creates a data federation and protects it from deletion", func() { + By("Creating a DataFederation instance", func() { + testDataFederation = mdbv1.NewDataFederationInstance(testProject.Name, testDataFederationName, testNamespace.Name) + Expect(k8sClient.Create(context.Background(), testDataFederation)).ShouldNot(HaveOccurred()) + + Eventually(func(g Gomega) { + df, _, err := dataFederationClient.Get(context.Background(), testProject.ID(), testDataFederation.Spec.Name) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(df).NotTo(BeNil()) + }).WithTimeout(20 * time.Minute).WithPolling(15 * time.Second).ShouldNot(HaveOccurred()) + }) + + // nolint:dupl + By("Deleting a data federation instance in cluster doesn't delete it from Atlas", func() { + Expect(k8sClient.Delete(context.TODO(), testDataFederation, &client.DeleteOptions{})).To(Succeed()) + + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(context.TODO(), client.ObjectKeyFromObject(testDataFederation), testDataFederation, &client.GetOptions{})).ToNot(Succeed()) + dataFederation, _, err := atlasClient.DataFederation.Get(context.TODO(), testProject.ID(), testDataFederation.Spec.Name) + g.Expect(err).To(BeNil()) + g.Expect(dataFederation).ToNot(BeNil()) + }).WithTimeout(5 * time.Minute).WithPolling(PollingInterval).Should(Succeed()) + }) + }) + + It("Adds an existing Atlas data federation and protects it from being deleted", func() { + By("Creating a data federation instance in Atlas", func() { + df := &mongodbatlas.DataFederationInstance{ + Name: testDataFederationName, + } + + _, _, err := atlasClient.DataFederation.Create(context.TODO(), testProject.ID(), df) + Expect(err).To(BeNil()) + }) + + By("Creating a data federation instance in the cluster", func() { + testDataFederation = mdbv1.NewDataFederationInstance(testProject.Name, testDataFederationName, testNamespace.Name) + Expect(k8sClient.Create(context.Background(), testDataFederation)).ShouldNot(HaveOccurred()) + + Eventually(func() bool { + return testutil.CheckCondition(k8sClient, testDataFederation, status.TrueCondition(status.ReadyType)) + // TODO: Modify timeouts + }).WithTimeout(15 * time.Minute).WithPolling(PollingInterval).Should(BeTrue()) + }) + + // nolint:dupl + By("Deleting a data federation instance in the cluster does not delete it in Atlas", func() { + Expect(k8sClient.Delete(context.TODO(), testDataFederation, &client.DeleteOptions{})).To(Succeed()) + + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(context.TODO(), client.ObjectKeyFromObject(testDataFederation), testDataFederation, &client.GetOptions{})).ToNot(Succeed()) + dataFederation, _, err := atlasClient.DataFederation.Get(context.TODO(), testProject.ID(), testDataFederation.Spec.Name) + g.Expect(err).To(BeNil()) + g.Expect(dataFederation).ToNot(BeNil()) + }).WithTimeout(5 * time.Minute).WithPolling(PollingInterval).Should(Succeed()) + }) + }) + + It("Creates a data federation instance and annotates it for deletion", func() { + By("Creating a data federation instance in the cluster", func() { + testDataFederation = mdbv1.NewDataFederationInstance(testProject.Name, testDataFederationName, testNamespace.Name). + WithAnnotations(map[string]string{customresource.ResourcePolicyAnnotation: customresource.ResourcePolicyDelete}) + Expect(k8sClient.Create(context.Background(), testDataFederation)).ShouldNot(HaveOccurred()) + + Eventually(func() bool { + return testutil.CheckCondition(k8sClient, testDataFederation, status.TrueCondition(status.ReadyType)) + // TODO: Modify timeouts + }).WithTimeout(15 * time.Minute).WithPolling(PollingInterval).Should(BeTrue()) + }) + + By("Deleting annotated data federation instance in cluster should delete it from Atlas", func() { + Expect(k8sClient.Delete(context.TODO(), testDataFederation, &client.DeleteOptions{})).To(Succeed()) + + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(context.TODO(), client.ObjectKeyFromObject(testDataFederation), testDataFederation, &client.GetOptions{})).ToNot(Succeed()) + dataFederation, _, err := atlasClient.DataFederation.Get(context.TODO(), testProject.ID(), testDataFederation.Spec.Name) + g.Expect(err).ToNot(BeNil()) + g.Expect(dataFederation).To(BeNil()) + }).WithTimeout(5 * time.Minute).WithPolling(PollingInterval).Should(Succeed()) + manualDeletion = true + }) + }) + }) + +}) diff --git a/test/int/integration_suite_test.go b/test/int/integration_suite_test.go index 51a19254c2..908fbe5aee 100644 --- a/test/int/integration_suite_test.go +++ b/test/int/integration_suite_test.go @@ -256,13 +256,15 @@ func prepareControllers(deletionProtection bool) (*corev1.Namespace, context.Can Expect(err).ToNot(HaveOccurred()) err = (&atlasdatafederation.AtlasDataFederationReconciler{ - Client: k8sManager.GetClient(), - Log: logger.Named("controllers").Named("AtlasDataFederation").Sugar(), - AtlasDomain: atlasDomain, - EventRecorder: k8sManager.GetEventRecorderFor("AtlasDatabaseUser"), - ResourceWatcher: watch.NewResourceWatcher(), - GlobalAPISecret: kube.ObjectKey(namespace.Name, "atlas-operator-api-key"), - GlobalPredicates: globalPredicates, + Client: k8sManager.GetClient(), + Log: logger.Named("controllers").Named("AtlasDataFederation").Sugar(), + AtlasDomain: atlasDomain, + EventRecorder: k8sManager.GetEventRecorderFor("AtlasDatabaseUser"), + ResourceWatcher: watch.NewResourceWatcher(), + GlobalAPISecret: kube.ObjectKey(namespace.Name, "atlas-operator-api-key"), + GlobalPredicates: globalPredicates, + ObjectDeletionProtection: deletionProtection, + SubObjectDeletionProtection: deletionProtection, }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred())