diff --git a/.action_templates/jobs/tests.yaml b/.action_templates/jobs/tests.yaml index f360ee3d6..79f6c27c7 100644 --- a/.action_templates/jobs/tests.yaml +++ b/.action_templates/jobs/tests.yaml @@ -66,3 +66,5 @@ tests: distro: ubi - test-name: replica_set_remove_user distro: ubi + - test-name: replica_set_custom_persistentvolumeclaimretentionpolicy_test + distro: ubi \ No newline at end of file diff --git a/.github/workflows/e2e-fork.yml b/.github/workflows/e2e-fork.yml index a5c3ae53e..47d4a3ef5 100644 --- a/.github/workflows/e2e-fork.yml +++ b/.github/workflows/e2e-fork.yml @@ -151,6 +151,8 @@ jobs: distro: ubi - test-name: replica_set_remove_user distro: ubi + - test-name: replica_set_custom_persistentvolumeclaimretentionpolicy_test + distro: ubi steps: # template: .action_templates/steps/cancel-previous.yaml - name: Cancel Previous Runs diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8501431b6..195d631b3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -157,6 +157,8 @@ jobs: distro: ubi - test-name: replica_set_remove_user distro: ubi + - test-name: replica_set_custom_persistentvolumeclaimretentionpolicy_test + distro: ubi steps: # template: .action_templates/steps/cancel-previous.yaml - name: Cancel Previous Runs diff --git a/config/samples/mongodb.com_v1_mongodbcommunity_persistent_volume_claim_retention_policy.yaml b/config/samples/mongodb.com_v1_mongodbcommunity_persistent_volume_claim_retention_policy.yaml new file mode 100644 index 000000000..fe4d83edf --- /dev/null +++ b/config/samples/mongodb.com_v1_mongodbcommunity_persistent_volume_claim_retention_policy.yaml @@ -0,0 +1,46 @@ +--- +apiVersion: mongodbcommunity.mongodb.com/v1 +kind: MongoDBCommunity +metadata: + name: mongodb-specify-volumeclaimretention-values +spec: + members: 3 + type: ReplicaSet + version: "6.0.5" + security: + authentication: + modes: ["SCRAM"] + users: + - name: my-user + db: admin + passwordSecretRef: # a reference to the secret that will be used to generate the user's password + name: my-user-password + roles: + - name: clusterAdmin + db: admin + - name: userAdminAnyDatabase + db: admin + scramCredentialsSecretName: my-scram + statefulSet: + spec: + persistentVolumeClaimRetentionPolicy: + WhenDeleted: "Delete" + WhenScaled: "Delete" + template: + spec: + containers: + - name: mongodb-agent + readinessProbe: + failureThreshold: 50 + initialDelaySeconds: 10 + +# the user credentials will be generated from this secret +# once the credentials are generated, this secret is no longer required +--- +apiVersion: v1 +kind: Secret +metadata: + name: my-user-password +type: Opaque +stringData: + password: diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 6109fac02..a2987999f 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -11,8 +11,11 @@ ## Improvements - Refactored environment variable propagation ([#1676](https://github.com/mongodb/mongodb-kubernetes-operator/pull/1676)). - Introduced a linter to limit inappropriate usage of environment variables within the codebase ([#1690](https://github.com/mongodb/mongodb-kubernetes-operator/pull/1690)). + - Add persistentVolumeClaimRetentionPolicy on MongoDB statefulset ([#1633](https://github.com/mongodb/mongodb-kubernetes-operator/pull/1633)). ## Security & Dependency Updates - **CVE Updates**: Updated packages `crypto`, `net` and `oauth2` to remediate multiple CVEs - Upgraded to Go 1.24 and Kubernetes dependencies to 1.30.x . + + diff --git a/pkg/kube/statefulset/merge_statefulset_test.go b/pkg/kube/statefulset/merge_statefulset_test.go index 7ae2acd53..94e018c65 100644 --- a/pkg/kube/statefulset/merge_statefulset_test.go +++ b/pkg/kube/statefulset/merge_statefulset_test.go @@ -60,6 +60,7 @@ func TestGetLabelSelectorRequirementByKey(t *testing.T) { } func TestMergeSpec(t *testing.T) { + overridePersistentVolumeClaimRetentionPolicy := appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{WhenDeleted: "Delete", WhenScaled: "Delete"} original := New( WithName("original"), @@ -67,6 +68,7 @@ func TestMergeSpec(t *testing.T) { WithReplicas(3), WithRevisionHistoryLimit(10), WithPodManagementPolicyType(appsv1.OrderedReadyPodManagement), + WithPersistentVolumeClaimRetentionPolicy(appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{}), WithSelector(&metav1.LabelSelector{ MatchLabels: map[string]string{ "a": "1", @@ -95,6 +97,7 @@ func TestMergeSpec(t *testing.T) { WithReplicas(5), WithRevisionHistoryLimit(15), WithPodManagementPolicyType(appsv1.ParallelPodManagement), + WithPersistentVolumeClaimRetentionPolicy(overridePersistentVolumeClaimRetentionPolicy), WithSelector(&metav1.LabelSelector{ MatchLabels: map[string]string{ "a": "10", @@ -124,6 +127,7 @@ func TestMergeSpec(t *testing.T) { assert.Equal(t, int32(5), *mergedSpec.Replicas) assert.Equal(t, int32(15), *mergedSpec.RevisionHistoryLimit) assert.Equal(t, appsv1.ParallelPodManagement, mergedSpec.PodManagementPolicy) + assert.Equal(t, overridePersistentVolumeClaimRetentionPolicy, *mergedSpec.PersistentVolumeClaimRetentionPolicy) }) matchLabels := mergedSpec.Selector.MatchLabels diff --git a/pkg/kube/statefulset/statefulset.go b/pkg/kube/statefulset/statefulset.go index d6e7660cb..1d24bd15f 100644 --- a/pkg/kube/statefulset/statefulset.go +++ b/pkg/kube/statefulset/statefulset.go @@ -315,6 +315,12 @@ func WithObjectMetadata(labels map[string]string, annotations map[string]string) } } +func WithPersistentVolumeClaimRetentionPolicy(persistentVolumeClaimRetentionPolicy appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy) Modification { + return func(set *appsv1.StatefulSet) { + set.Spec.PersistentVolumeClaimRetentionPolicy = &persistentVolumeClaimRetentionPolicy + } +} + func findVolumeClaimIndexByName(name string, pvcs []corev1.PersistentVolumeClaim) int { for idx, pvc := range pvcs { if pvc.Name == name { diff --git a/pkg/util/merge/merge_statefulset.go b/pkg/util/merge/merge_statefulset.go index a6a9f4879..4d76f2bd5 100644 --- a/pkg/util/merge/merge_statefulset.go +++ b/pkg/util/merge/merge_statefulset.go @@ -53,6 +53,12 @@ func StatefulSetSpecs(defaultSpec, overrideSpec appsv1.StatefulSetSpec) appsv1.S mergedSpec.ServiceName = overrideSpec.ServiceName } + if overrideSpec.PersistentVolumeClaimRetentionPolicy != nil { + overridePersistentVolumeClaimRetentionPolicy := appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{WhenDeleted: overrideSpec.PersistentVolumeClaimRetentionPolicy.WhenDeleted, + WhenScaled: overrideSpec.PersistentVolumeClaimRetentionPolicy.WhenScaled} + mergedSpec.PersistentVolumeClaimRetentionPolicy = &overridePersistentVolumeClaimRetentionPolicy + } + mergedSpec.Template = PodTemplateSpecs(defaultSpec.Template, overrideSpec.Template) mergedSpec.VolumeClaimTemplates = VolumeClaimTemplates(defaultSpec.VolumeClaimTemplates, overrideSpec.VolumeClaimTemplates) return mergedSpec diff --git a/test/e2e/mongodbtests/mongodbtests.go b/test/e2e/mongodbtests/mongodbtests.go index a7bbf30df..e6f6fe45b 100644 --- a/test/e2e/mongodbtests/mongodbtests.go +++ b/test/e2e/mongodbtests/mongodbtests.go @@ -221,6 +221,18 @@ func StatefulSetHasUpdateStrategy(ctx context.Context, mdb *mdbv1.MongoDBCommuni } } +// StatefulSetHasPersistentVolumeClaimRetentionPolicy verifies that the StatefulSet holding this MongoDB +// resource has the correct PersistentVolumeClaim Retention Policy +func StatefulSetHasPersistentVolumeClaimRetentionPolicy(ctx context.Context, mdb *mdbv1.MongoDBCommunity, persistentVolumeClaimRetentionPolicy appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy) func(t *testing.T) { + return func(t *testing.T) { + err := wait.ForStatefulSetToHavePersistentVolumeClaimRetentionPolicy(ctx, t, mdb, persistentVolumeClaimRetentionPolicy, wait.RetryInterval(time.Second*15), wait.Timeout(time.Minute*8)) + if err != nil { + t.Fatal(err) + } + t.Logf("StatefulSet %s/%s is ready!", mdb.Namespace, mdb.Name) + } +} + // GetPersistentVolumes returns all persistent volumes on the cluster func getPersistentVolumesList(ctx context.Context) (*corev1.PersistentVolumeList, error) { return e2eutil.TestClient.CoreV1Client.PersistentVolumes().List(ctx, metav1.ListOptions{}) diff --git a/test/e2e/replica_set_custom_persistentvolumeclaimretentionpolicy_test/replica_set_custom_persistentvolumeclaimretentionpolicy_test.go b/test/e2e/replica_set_custom_persistentvolumeclaimretentionpolicy_test/replica_set_custom_persistentvolumeclaimretentionpolicy_test.go new file mode 100644 index 000000000..945f837da --- /dev/null +++ b/test/e2e/replica_set_custom_persistentvolumeclaimretentionpolicy_test/replica_set_custom_persistentvolumeclaimretentionpolicy_test.go @@ -0,0 +1,56 @@ +package replica_set_custom_persistentvolumclaimretentionpolicy_test + +import ( + "context" + "fmt" + "os" + "testing" + + e2eutil "github.com/mongodb/mongodb-kubernetes-operator/test/e2e" + "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/mongodbtests" + "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/setup" + . "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/util/mongotester" + appsv1 "k8s.io/api/apps/v1" +) + +func TestMain(m *testing.M) { + code, err := e2eutil.RunTest(m) + if err != nil { + fmt.Println(err) + } + os.Exit(code) +} + +func TestReplicaSetCustomPersistentVolumeClaimRetentionPolicy(t *testing.T) { + ctx := context.Background() + testCtx := setup.Setup(ctx, t) + defer testCtx.Teardown() + + mdb, user := e2eutil.NewTestMongoDB(testCtx, "mdb0", "") + overridePersistentVolumeClaim := appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{WhenDeleted: "Delete", WhenScaled: "Delete"} + mdb.Spec.StatefulSetConfiguration.SpecWrapper.Spec.PersistentVolumeClaimRetentionPolicy = &overridePersistentVolumeClaim + scramUser := mdb.GetAuthUsers()[0] + + _, err := setup.GeneratePasswordForUser(testCtx, user, "") + if err != nil { + t.Fatal(err) + } + + tester, err := FromResource(ctx, t, mdb) + if err != nil { + t.Fatal(err) + } + + t.Run("Create MongoDB Resource", mongodbtests.CreateMongoDBResource(&mdb, testCtx)) + t.Run("Basic tests", mongodbtests.BasicFunctionality(ctx, &mdb)) + t.Run("Keyfile authentication is configured", tester.HasKeyfileAuth(3)) + t.Run("Test Basic Connectivity", tester.ConnectivitySucceeds()) + t.Run("Test SRV Connectivity", tester.ConnectivitySucceeds(WithURI(mdb.MongoSRVURI("")), WithoutTls(), WithReplicaSet((mdb.Name)))) + t.Run("Test Basic Connectivity with generated connection string secret", + tester.ConnectivitySucceeds(WithURI(mongodbtests.GetConnectionStringForUser(ctx, mdb, scramUser)))) + t.Run("Test SRV Connectivity with generated connection string secret", + tester.ConnectivitySucceeds(WithURI(mongodbtests.GetSrvConnectionStringForUser(ctx, mdb, scramUser)))) + t.Run("Ensure Authentication", tester.EnsureAuthenticationIsConfigured(3)) + t.Run("AutomationConfig has the correct version", mongodbtests.AutomationConfigVersionHasTheExpectedVersion(ctx, &mdb, 1)) + t.Run("Statefulset has the expected PersistentVolumeClaimRetentionPolicy", mongodbtests.StatefulSetHasPersistentVolumeClaimRetentionPolicy(ctx, &mdb, overridePersistentVolumeClaim)) +} diff --git a/test/e2e/util/wait/wait.go b/test/e2e/util/wait/wait.go index 54798860e..eda0b135f 100644 --- a/test/e2e/util/wait/wait.go +++ b/test/e2e/util/wait/wait.go @@ -97,6 +97,18 @@ func ForStatefulSetToHaveUpdateStrategy(ctx context.Context, t *testing.T, mdb * }) } +// ForStatefulSetToHavePersistentVolumeClaimRetentionPolicy waits until all replicas of the StatefulSet with the given name +// have reached the ready status +func ForStatefulSetToHavePersistentVolumeClaimRetentionPolicy(ctx context.Context, t *testing.T, mdb *mdbv1.MongoDBCommunity, persistentVolumeClaimRetentionPolicy appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy, opts ...Configuration) error { + options := newOptions(opts...) + return waitForStatefulSetCondition(ctx, t, mdb, options, func(sts appsv1.StatefulSet) bool { + if sts.Spec.PersistentVolumeClaimRetentionPolicy.WhenScaled == persistentVolumeClaimRetentionPolicy.WhenScaled && sts.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted == persistentVolumeClaimRetentionPolicy.WhenDeleted { + return true + } + return false + }) +} + // ForStatefulSetToBeReady waits until all replicas of the StatefulSet with the given name // have reached the ready status func ForStatefulSetToBeReady(ctx context.Context, t *testing.T, mdb *mdbv1.MongoDBCommunity, opts ...Configuration) error {