diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 05e01d7b35..29a5111e70 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -137,6 +137,7 @@ jobs: "deployment-ns", "deployment-wide", "encryption-at-rest", + "free-tier", "global-deployment", "helm-ns", "helm-update", diff --git a/pkg/controller/atlasdeployment/advanced_deployment.go b/pkg/controller/atlasdeployment/advanced_deployment.go index 6ed5a171fe..43cded1234 100644 --- a/pkg/controller/atlasdeployment/advanced_deployment.go +++ b/pkg/controller/atlasdeployment/advanced_deployment.go @@ -15,6 +15,8 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/compat" ) +const FreeTier = "M0" + func (r *AtlasDeploymentReconciler) ensureAdvancedDeploymentState(ctx *workflow.Context, project *mdbv1.AtlasProject, deployment *mdbv1.AtlasDeployment) (*mongodbatlas.AdvancedCluster, workflow.Result) { advancedDeploymentSpec := deployment.Spec.AdvancedDeploymentSpec @@ -210,12 +212,16 @@ func handleAutoscaling(ctx *workflow.Context, desiredDeployment *mdbv1.AdvancedD // MergedAdvancedDeployment will return the result of merging AtlasDeploymentSpec with Atlas Advanced Deployment func MergedAdvancedDeployment(atlasDeploymentAsAtlas mongodbatlas.AdvancedCluster, specDeployment mdbv1.AdvancedDeploymentSpec) (mergedDeployment mdbv1.AdvancedDeploymentSpec, atlasDeployment mdbv1.AdvancedDeploymentSpec, err error) { + if IsFreeTierAdvancedDeployment(&atlasDeploymentAsAtlas) { + atlasDeploymentAsAtlas.DiskSizeGB = nil + } atlasDeployment, err = AdvancedDeploymentFromAtlas(atlasDeploymentAsAtlas) if err != nil { return } mergedDeployment = mdbv1.AdvancedDeploymentSpec{} + if err = compat.JSONCopy(&mergedDeployment, atlasDeployment); err != nil { return } @@ -236,6 +242,23 @@ func MergedAdvancedDeployment(atlasDeploymentAsAtlas mongodbatlas.AdvancedCluste return } +func IsFreeTierAdvancedDeployment(deployment *mongodbatlas.AdvancedCluster) bool { + if deployment != nil && deployment.ReplicationSpecs != nil { + for _, replicationSpec := range deployment.ReplicationSpecs { + if replicationSpec.RegionConfigs != nil { + for _, regionConfig := range replicationSpec.RegionConfigs { + if regionConfig != nil && + regionConfig.ElectableSpecs != nil && + regionConfig.ElectableSpecs.InstanceSize == FreeTier { + return true + } + } + } + } + } + return false +} + func AdvancedDeploymentFromAtlas(advancedDeployment mongodbatlas.AdvancedCluster) (mdbv1.AdvancedDeploymentSpec, error) { result := mdbv1.AdvancedDeploymentSpec{} if err := compat.JSONCopy(&result, advancedDeployment); err != nil { diff --git a/pkg/controller/atlasdeployment/deployment.go b/pkg/controller/atlasdeployment/deployment.go index 5dc329919d..2001f74ae0 100644 --- a/pkg/controller/atlasdeployment/deployment.go +++ b/pkg/controller/atlasdeployment/deployment.go @@ -126,10 +126,20 @@ func cleanupDeployment(deployment mongodbatlas.Cluster) mongodbatlas.Cluster { deployment.ReplicationSpec = nil deployment.ConnectionStrings = nil deployment = removeOutdatedFields(&deployment, nil) + if IsFreeTierCluster(&deployment) { + deployment.DiskSizeGB = nil + } return deployment } +func IsFreeTierCluster(deployment *mongodbatlas.Cluster) bool { + if deployment != nil && deployment.ProviderSettings != nil && deployment.ProviderSettings.InstanceSizeName == "M0" { + return true + } + return false +} + // removeOutdatedFields unsets fields which are should be empty based on flags func removeOutdatedFields(removeFrom *mongodbatlas.Cluster, lookAt *mongodbatlas.Cluster) mongodbatlas.Cluster { if lookAt == nil { diff --git a/test/e2e/data/deployments.go b/test/e2e/data/deployments.go index 012fc24c18..527f62a687 100644 --- a/test/e2e/data/deployments.go +++ b/test/e2e/data/deployments.go @@ -18,6 +18,7 @@ const ( InstanceSizeM20 = "M20" InstanceSizeM30 = "M30" InstanceSizeM0 = "M0" + AWSRegion = "US_EAST_1" ServerlessProviderName = "SERVERLESS" ) @@ -274,7 +275,7 @@ func CreateBasicFreeDeployment(name string) *v1.AtlasDeployment { Name: ProjectName, }, DeploymentSpec: &v1.DeploymentSpec{ - Name: "cluster-basics-free", + Name: name, ProviderSettings: &v1.ProviderSettingsSpec{ InstanceSizeName: InstanceSizeM0, ProviderName: "TENANT", @@ -285,3 +286,52 @@ func CreateBasicFreeDeployment(name string) *v1.AtlasDeployment { }, } } + +func CreateFreeAdvancedDeployment(name string) *v1.AtlasDeployment { + return &v1.AtlasDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1.AtlasDeploymentSpec{ + Project: common.ResourceRefNamespaced{ + Name: ProjectName, + }, + AdvancedDeploymentSpec: &v1.AdvancedDeploymentSpec{ + Name: name, + BackupEnabled: toptr.MakePtr(false), + BiConnector: &v1.BiConnectorSpec{ + Enabled: toptr.MakePtr(false), + ReadPreference: "secondary", + }, + ClusterType: string(v1.TypeReplicaSet), + EncryptionAtRestProvider: "NONE", + PitEnabled: toptr.MakePtr(false), + Paused: toptr.MakePtr(false), + RootCertType: "ISRGROOTX1", + VersionReleaseSystem: "LTS", + ReplicationSpecs: []*v1.AdvancedReplicationSpec{ + { + NumShards: 1, + ZoneName: "Zone 1", + RegionConfigs: []*v1.AdvancedRegionConfig{ + { + ElectableSpecs: &v1.Specs{ + InstanceSize: InstanceSizeM0, + }, + Priority: toptr.MakePtr(7), + ProviderName: string(provider.ProviderTenant), + BackingProviderName: string(provider.ProviderAWS), + RegionName: AWSRegion, + }, + }, + }, + }, + }, + ProcessArgs: &v1.ProcessArgs{ + JavascriptEnabled: toptr.MakePtr(true), + MinimumEnabledTLSProtocol: "TLS1_2", + NoTableScan: toptr.MakePtr(false), + }, + }, + } +} diff --git a/test/e2e/free_tier_test.go b/test/e2e/free_tier_test.go new file mode 100644 index 0000000000..32f8ac9ef2 --- /dev/null +++ b/test/e2e/free_tier_test.go @@ -0,0 +1,81 @@ +package e2e_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.mongodb.org/atlas/mongodbatlas" + + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/provider" + "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/actions" + "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/actions/deploy" + "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/api/atlas" + "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/data" + "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/model" +) + +var _ = Describe("Free tier", Label("free-tier"), func() { + var testData *model.TestDataProvider + + _ = AfterEach(func() { + GinkgoWriter.Write([]byte("\n")) + GinkgoWriter.Write([]byte("===============================================\n")) + GinkgoWriter.Write([]byte("Free tier test\n")) + GinkgoWriter.Write([]byte("Operator namespace: " + testData.Resources.Namespace + "\n")) + GinkgoWriter.Write([]byte("===============================================\n")) + if CurrentSpecReport().Failed() { + Expect(actions.SaveProjectsToFile(testData.Context, testData.K8SClient, testData.Resources.Namespace)).Should(Succeed()) + Expect(actions.SaveDeploymentsToFile(testData.Context, testData.K8SClient, testData.Resources.Namespace)).Should(Succeed()) + } + By("Delete Resources", func() { + actions.DeleteTestDataDeployments(testData) + actions.DeleteTestDataProject(testData) + actions.AfterEachFinalCleanup([]model.TestDataProvider{*testData}) + }) + }) + + DescribeTable("Operator should support exported CR for free tier deployments", + func(test *model.TestDataProvider) { + testData = test + actions.ProjectCreationFlow(test) + freeTierDeploymentFlow(test) + }, + Entry("Test free tier deployment", + model.DataProvider( + "free-tier", + model.NewEmptyAtlasKeyType().UseDefaultFullAccess(), + 40000, + []func(*model.TestDataProvider){}, + ).WithProject(data.DefaultProject()).WithInitialDeployments(data.CreateBasicFreeDeployment("free-tier")), + ), + Entry("Test free tier advanced deployment", + model.DataProvider( + "free-tier-advanced", + model.NewEmptyAtlasKeyType().UseDefaultFullAccess(), + 40000, + []func(*model.TestDataProvider){}, + ).WithProject(data.DefaultProject()).WithInitialDeployments(data.CreateFreeAdvancedDeployment("free-tier")), + ), + ) +}) + +func freeTierDeploymentFlow(userData *model.TestDataProvider) { + By("Create free cluster in Atlas", func() { + aClient := atlas.GetClientOrFail() + Expect(userData.InitialDeployments).Should(HaveLen(1)) + name := userData.InitialDeployments[0].GetDeploymentName() + _, _, err := aClient.Client.Clusters.Create(userData.Context, userData.Project.ID(), &mongodbatlas.Cluster{ + Name: name, + ProviderSettings: &mongodbatlas.ProviderSettings{ + ProviderName: string(provider.ProviderTenant), + RegionName: "US_EAST_1", + InstanceSizeName: data.InstanceSizeM0, + BackingProviderName: string(provider.ProviderAWS), + }, + }) + Expect(err).ShouldNot(HaveOccurred()) + }) + + By("Apply deployment CR", func() { + deploy.CreateInitialDeployments(userData) + }) +}