diff --git a/api/v1alpha1/types_etcd.go b/api/v1alpha1/types_etcd.go index d899b1f53..b599e5770 100644 --- a/api/v1alpha1/types_etcd.go +++ b/api/v1alpha1/types_etcd.go @@ -178,6 +178,13 @@ type BackupSpec struct { // DeltaSnapshotMemoryLimit defines the memory limit after which delta snapshots will be taken // +optional DeltaSnapshotMemoryLimit *resource.Quantity `json:"deltaSnapshotMemoryLimit,omitempty"` + // DeltaSnapshotRetentionPeriod defines the duration for which delta snapshots will be retained, excluding the latest snapshot set. + // The value should be a string formatted as a duration (e.g., '1s', '2m', '3h', '4d') + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9][0-9]*([.][0-9]+)?(s|m|h|d))+$" + // +optional + DeltaSnapshotRetentionPeriod *metav1.Duration `json:"deltaSnapshotRetentionPeriod,omitempty"` + // SnapshotCompression defines the specification for compression of Snapshots. // +optional SnapshotCompression *CompressionSpec `json:"compression,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 563aae732..b8f81b5e1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -83,6 +83,11 @@ func (in *BackupSpec) DeepCopyInto(out *BackupSpec) { x := (*in).DeepCopy() *out = &x } + if in.DeltaSnapshotRetentionPeriod != nil { + in, out := &in.DeltaSnapshotRetentionPeriod, &out.DeltaSnapshotRetentionPeriod + *out = new(metav1.Duration) + **out = **in + } if in.SnapshotCompression != nil { in, out := &in.SnapshotCompression, &out.SnapshotCompression *out = new(CompressionSpec) diff --git a/charts/druid/charts/crds/templates/10-crd-druid.gardener.cloud_etcds.yaml b/charts/druid/charts/crds/templates/10-crd-druid.gardener.cloud_etcds.yaml index 5a4f271f1..56e122612 100644 --- a/charts/druid/charts/crds/templates/10-crd-druid.gardener.cloud_etcds.yaml +++ b/charts/druid/charts/crds/templates/10-crd-druid.gardener.cloud_etcds.yaml @@ -146,6 +146,13 @@ spec: description: DeltaSnapshotPeriod defines the period after which delta snapshots will be taken type: string + deltaSnapshotRetentionPeriod: + description: DeltaSnapshotRetentionPeriod defines the duration + for which delta snapshots will be retained, excluding the latest + snapshot set. The value should be a string formatted as a duration + (e.g., '1s', '2m', '3h', '4d') + pattern: ^([0-9][0-9]*([.][0-9]+)?(s|m|h|d))+$ + type: string enableProfiling: description: EnableProfiling defines if profiling should be enabled for the etcd-backup-restore-sidecar diff --git a/config/crd/bases/10-crd-druid.gardener.cloud_etcds.yaml b/config/crd/bases/10-crd-druid.gardener.cloud_etcds.yaml index 5a4f271f1..56e122612 100644 --- a/config/crd/bases/10-crd-druid.gardener.cloud_etcds.yaml +++ b/config/crd/bases/10-crd-druid.gardener.cloud_etcds.yaml @@ -146,6 +146,13 @@ spec: description: DeltaSnapshotPeriod defines the period after which delta snapshots will be taken type: string + deltaSnapshotRetentionPeriod: + description: DeltaSnapshotRetentionPeriod defines the duration + for which delta snapshots will be retained, excluding the latest + snapshot set. The value should be a string formatted as a duration + (e.g., '1s', '2m', '3h', '4d') + pattern: ^([0-9][0-9]*([.][0-9]+)?(s|m|h|d))+$ + type: string enableProfiling: description: EnableProfiling defines if profiling should be enabled for the etcd-backup-restore-sidecar diff --git a/pkg/component/etcd/statefulset/statefulset_test.go b/pkg/component/etcd/statefulset/statefulset_test.go index c1b6a3584..d21ed2255 100644 --- a/pkg/component/etcd/statefulset/statefulset_test.go +++ b/pkg/component/etcd/statefulset/statefulset_test.go @@ -200,6 +200,26 @@ var _ = Describe("Statefulset", func() { Expect(metav1.HasAnnotation(sts.ObjectMeta, "gardener.cloud/scaled-to-multi-node")).To(BeFalse()) }) }) + + Context("DeltaSnapshotRetentionPeriod field is set in Etcd CRD", func() { + It("should include --delta-snapshot-retention-period flag in etcd-backup-restore container command", func() { + etcd.Spec.Backup.DeltaSnapshotRetentionPeriod = &metav1.Duration{Duration: time.Hour * 24} + values = GenerateValues( + etcd, + pointer.Int32(clientPort), + pointer.Int32(serverPort), + pointer.Int32(backupPort), + imageEtcd, + imageBR, + checkSumAnnotations, false, true) + stsDeployer = New(cl, logr.Discard(), values) + Expect(stsDeployer.Deploy(ctx)).To(Succeed()) + + sts := &appsv1.StatefulSet{} + Expect(cl.Get(ctx, kutil.Key(namespace, values.Name), sts)).To(Succeed()) + checkStatefulset(sts, values) + }) + }) }) Context("when statefulset exists", func() { @@ -252,6 +272,26 @@ var _ = Describe("Statefulset", func() { Expect(updatedSts.Spec.PodManagementPolicy).To(Equal(appsv1.ParallelPodManagement)) }) }) + + Context("DeltaSnapshotRetentionPeriod field is updated in Etcd CRD", func() { + It("should update --delta-snapshot-retention-period flag in etcd-backup-restore container command", func() { + etcd.Spec.Backup.DeltaSnapshotRetentionPeriod = &metav1.Duration{Duration: time.Hour * 48} + values = GenerateValues( + etcd, + pointer.Int32(clientPort), + pointer.Int32(serverPort), + pointer.Int32(backupPort), + imageEtcd, + imageBR, + checkSumAnnotations, false, true) + stsDeployer = New(cl, logr.Discard(), values) + Expect(stsDeployer.Deploy(ctx)).To(Succeed()) + + sts := &appsv1.StatefulSet{} + Expect(cl.Get(ctx, kutil.Key(namespace, values.Name), sts)).To(Succeed()) + checkStatefulset(sts, values) + }) + }) }) Context("with backup", func() { @@ -421,8 +461,6 @@ func checkBackup(etcd *druidv1alpha1.Etcd, sts *appsv1.StatefulSet) { func checkStatefulset(sts *appsv1.StatefulSet, values Values) { checkStsOwnerRefs(sts.ObjectMeta.OwnerReferences, values) - store, err := druidutils.StorageProviderFromInfraProvider(values.BackupStore.Provider) - Expect(err).NotTo(HaveOccurred()) Expect(*sts).To(MatchFields(IgnoreExtras, Fields{ "ObjectMeta": MatchFields(IgnoreExtras, Fields{ "Name": Equal(values.Name), @@ -538,42 +576,7 @@ func checkStatefulset(sts *appsv1.StatefulSet, values Values) { }), backupRestore: MatchFields(IgnoreExtras, Fields{ - "Args": MatchAllElements(cmdIterator, Elements{ - "server": Equal("server"), - "--cert=/var/etcd/ssl/client/client/tls.crt": Equal("--cert=/var/etcd/ssl/client/client/tls.crt"), - "--key=/var/etcd/ssl/client/client/tls.key": Equal("--key=/var/etcd/ssl/client/client/tls.key"), - "--cacert=/var/etcd/ssl/client/ca/ca.crt": Equal("--cacert=/var/etcd/ssl/client/ca/ca.crt"), - "--server-cert=/var/etcd/ssl/client/server/tls.crt": Equal("--server-cert=/var/etcd/ssl/client/server/tls.crt"), - "--server-key=/var/etcd/ssl/client/server/tls.key": Equal("--server-key=/var/etcd/ssl/client/server/tls.key"), - "--data-dir=/var/etcd/data/new.etcd": Equal("--data-dir=/var/etcd/data/new.etcd"), - "--restoration-temp-snapshots-dir=/var/etcd/data/restoration.temp": Equal("--restoration-temp-snapshots-dir=/var/etcd/data/restoration.temp"), - "--insecure-transport=false": Equal("--insecure-transport=false"), - "--insecure-skip-tls-verify=false": Equal("--insecure-skip-tls-verify=false"), - "--snapstore-temp-directory=/var/etcd/data/temp": Equal("--snapstore-temp-directory=/var/etcd/data/temp"), - fmt.Sprintf("%s=%s", "--etcd-connection-timeout-leader-election", etcdLeaderElectionConnectionTimeout.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--etcd-connection-timeout-leader-election", values.LeaderElection.EtcdConnectionTimeout.Duration.String())), - "--etcd-connection-timeout=5m": Equal("--etcd-connection-timeout=5m"), - "--enable-snapshot-lease-renewal=true": Equal("--enable-snapshot-lease-renewal=true"), - "--enable-member-lease-renewal=true": Equal("--enable-member-lease-renewal=true"), - "--k8s-heartbeat-duration=10s": Equal("--k8s-heartbeat-duration=10s"), - fmt.Sprintf("--defragmentation-schedule=%s", *values.DefragmentationSchedule): Equal(fmt.Sprintf("--defragmentation-schedule=%s", *values.DefragmentationSchedule)), - fmt.Sprintf("--schedule=%s", *values.FullSnapshotSchedule): Equal(fmt.Sprintf("--schedule=%s", *values.FullSnapshotSchedule)), - fmt.Sprintf("%s=%s", "--garbage-collection-policy", *values.GarbageCollectionPolicy): Equal(fmt.Sprintf("%s=%s", "--garbage-collection-policy", *values.GarbageCollectionPolicy)), - fmt.Sprintf("%s=%s", "--storage-provider", store): Equal(fmt.Sprintf("%s=%s", "--storage-provider", store)), - fmt.Sprintf("%s=%s", "--store-prefix", values.BackupStore.Prefix): Equal(fmt.Sprintf("%s=%s", "--store-prefix", values.BackupStore.Prefix)), - fmt.Sprintf("--delta-snapshot-memory-limit=%d", values.DeltaSnapshotMemoryLimit.Value()): Equal(fmt.Sprintf("--delta-snapshot-memory-limit=%d", values.DeltaSnapshotMemoryLimit.Value())), - fmt.Sprintf("--garbage-collection-policy=%s", *values.GarbageCollectionPolicy): Equal(fmt.Sprintf("--garbage-collection-policy=%s", *values.GarbageCollectionPolicy)), - fmt.Sprintf("--endpoints=https://%s-local:%d", values.Name, clientPort): Equal(fmt.Sprintf("--endpoints=https://%s-local:%d", values.Name, clientPort)), - fmt.Sprintf("--service-endpoints=https://%s:%d", values.ClientServiceName, clientPort): Equal(fmt.Sprintf("--service-endpoints=https://%s:%d", values.ClientServiceName, clientPort)), - fmt.Sprintf("--embedded-etcd-quota-bytes=%d", int64(values.Quota.Value())): Equal(fmt.Sprintf("--embedded-etcd-quota-bytes=%d", int64(values.Quota.Value()))), - fmt.Sprintf("%s=%s", "--delta-snapshot-period", values.DeltaSnapshotPeriod.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--delta-snapshot-period", values.DeltaSnapshotPeriod.Duration.String())), - fmt.Sprintf("%s=%s", "--garbage-collection-period", values.GarbageCollectionPeriod.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--garbage-collection-period", values.GarbageCollectionPeriod.Duration.String())), - fmt.Sprintf("%s=%s", "--auto-compaction-mode", *values.AutoCompactionMode): Equal(fmt.Sprintf("%s=%s", "--auto-compaction-mode", *values.AutoCompactionMode)), - fmt.Sprintf("%s=%s", "--auto-compaction-retention", *values.AutoCompactionRetention): Equal(fmt.Sprintf("%s=%s", "--auto-compaction-retention", *values.AutoCompactionRetention)), - fmt.Sprintf("%s=%s", "--etcd-snapshot-timeout", values.EtcdSnapshotTimeout.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--etcd-snapshot-timeout", values.EtcdSnapshotTimeout.Duration.String())), - fmt.Sprintf("%s=%s", "--etcd-defrag-timeout", values.EtcdDefragTimeout.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--etcd-defrag-timeout", values.EtcdDefragTimeout.Duration.String())), - fmt.Sprintf("%s=%s", "--delta-snapshot-lease-name", values.DeltaSnapLeaseName): Equal(fmt.Sprintf("%s=%s", "--delta-snapshot-lease-name", values.DeltaSnapLeaseName)), - fmt.Sprintf("%s=%s", "--full-snapshot-lease-name", values.FullSnapLeaseName): Equal(fmt.Sprintf("%s=%s", "--full-snapshot-lease-name", values.FullSnapLeaseName)), - }), + "Args": MatchAllElements(cmdIterator, expectedBackupArgs(&values)), "Ports": ConsistOf([]corev1.ContainerPort{ { Name: "server", @@ -930,3 +933,49 @@ func checkLocalProviderVaues(etcd *druidv1alpha1.Etcd, sts *appsv1.StatefulSet, MountPath: "/home/nonroot/" + container, })) } + +func expectedBackupArgs(values *Values) Elements { + store, err := druidutils.StorageProviderFromInfraProvider(values.BackupStore.Provider) + Expect(err).NotTo(HaveOccurred()) + elements := Elements{ + "server": Equal("server"), + "--cert=/var/etcd/ssl/client/client/tls.crt": Equal("--cert=/var/etcd/ssl/client/client/tls.crt"), + "--key=/var/etcd/ssl/client/client/tls.key": Equal("--key=/var/etcd/ssl/client/client/tls.key"), + "--cacert=/var/etcd/ssl/client/ca/ca.crt": Equal("--cacert=/var/etcd/ssl/client/ca/ca.crt"), + "--server-cert=/var/etcd/ssl/client/server/tls.crt": Equal("--server-cert=/var/etcd/ssl/client/server/tls.crt"), + "--server-key=/var/etcd/ssl/client/server/tls.key": Equal("--server-key=/var/etcd/ssl/client/server/tls.key"), + "--data-dir=/var/etcd/data/new.etcd": Equal("--data-dir=/var/etcd/data/new.etcd"), + "--restoration-temp-snapshots-dir=/var/etcd/data/restoration.temp": Equal("--restoration-temp-snapshots-dir=/var/etcd/data/restoration.temp"), + "--insecure-transport=false": Equal("--insecure-transport=false"), + "--insecure-skip-tls-verify=false": Equal("--insecure-skip-tls-verify=false"), + "--snapstore-temp-directory=/var/etcd/data/temp": Equal("--snapstore-temp-directory=/var/etcd/data/temp"), + fmt.Sprintf("%s=%s", "--etcd-connection-timeout-leader-election", etcdLeaderElectionConnectionTimeout.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--etcd-connection-timeout-leader-election", values.LeaderElection.EtcdConnectionTimeout.Duration.String())), + "--etcd-connection-timeout=5m": Equal("--etcd-connection-timeout=5m"), + "--enable-snapshot-lease-renewal=true": Equal("--enable-snapshot-lease-renewal=true"), + "--enable-member-lease-renewal=true": Equal("--enable-member-lease-renewal=true"), + "--k8s-heartbeat-duration=10s": Equal("--k8s-heartbeat-duration=10s"), + fmt.Sprintf("--defragmentation-schedule=%s", *values.DefragmentationSchedule): Equal(fmt.Sprintf("--defragmentation-schedule=%s", *values.DefragmentationSchedule)), + fmt.Sprintf("--schedule=%s", *values.FullSnapshotSchedule): Equal(fmt.Sprintf("--schedule=%s", *values.FullSnapshotSchedule)), + fmt.Sprintf("%s=%s", "--garbage-collection-policy", *values.GarbageCollectionPolicy): Equal(fmt.Sprintf("%s=%s", "--garbage-collection-policy", *values.GarbageCollectionPolicy)), + fmt.Sprintf("%s=%s", "--storage-provider", store): Equal(fmt.Sprintf("%s=%s", "--storage-provider", store)), + fmt.Sprintf("%s=%s", "--store-prefix", values.BackupStore.Prefix): Equal(fmt.Sprintf("%s=%s", "--store-prefix", values.BackupStore.Prefix)), + fmt.Sprintf("--delta-snapshot-memory-limit=%d", values.DeltaSnapshotMemoryLimit.Value()): Equal(fmt.Sprintf("--delta-snapshot-memory-limit=%d", values.DeltaSnapshotMemoryLimit.Value())), + fmt.Sprintf("--garbage-collection-policy=%s", *values.GarbageCollectionPolicy): Equal(fmt.Sprintf("--garbage-collection-policy=%s", *values.GarbageCollectionPolicy)), + fmt.Sprintf("--endpoints=https://%s-local:%d", values.Name, clientPort): Equal(fmt.Sprintf("--endpoints=https://%s-local:%d", values.Name, clientPort)), + fmt.Sprintf("--service-endpoints=https://%s:%d", values.ClientServiceName, clientPort): Equal(fmt.Sprintf("--service-endpoints=https://%s:%d", values.ClientServiceName, clientPort)), + fmt.Sprintf("--embedded-etcd-quota-bytes=%d", int64(values.Quota.Value())): Equal(fmt.Sprintf("--embedded-etcd-quota-bytes=%d", int64(values.Quota.Value()))), + fmt.Sprintf("%s=%s", "--delta-snapshot-period", values.DeltaSnapshotPeriod.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--delta-snapshot-period", values.DeltaSnapshotPeriod.Duration.String())), + fmt.Sprintf("%s=%s", "--garbage-collection-period", values.GarbageCollectionPeriod.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--garbage-collection-period", values.GarbageCollectionPeriod.Duration.String())), + fmt.Sprintf("%s=%s", "--auto-compaction-mode", *values.AutoCompactionMode): Equal(fmt.Sprintf("%s=%s", "--auto-compaction-mode", *values.AutoCompactionMode)), + fmt.Sprintf("%s=%s", "--auto-compaction-retention", *values.AutoCompactionRetention): Equal(fmt.Sprintf("%s=%s", "--auto-compaction-retention", *values.AutoCompactionRetention)), + fmt.Sprintf("%s=%s", "--etcd-snapshot-timeout", values.EtcdSnapshotTimeout.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--etcd-snapshot-timeout", values.EtcdSnapshotTimeout.Duration.String())), + fmt.Sprintf("%s=%s", "--etcd-defrag-timeout", values.EtcdDefragTimeout.Duration.String()): Equal(fmt.Sprintf("%s=%s", "--etcd-defrag-timeout", values.EtcdDefragTimeout.Duration.String())), + fmt.Sprintf("%s=%s", "--delta-snapshot-lease-name", values.DeltaSnapLeaseName): Equal(fmt.Sprintf("%s=%s", "--delta-snapshot-lease-name", values.DeltaSnapLeaseName)), + fmt.Sprintf("%s=%s", "--full-snapshot-lease-name", values.FullSnapLeaseName): Equal(fmt.Sprintf("%s=%s", "--full-snapshot-lease-name", values.FullSnapLeaseName)), + } + + if values.DeltaSnapshotRetentionPeriod != nil { + elements[fmt.Sprintf("--delta-snapshot-retention-period=%s", values.DeltaSnapshotRetentionPeriod.Duration.String())] = Equal(fmt.Sprintf("--delta-snapshot-retention-period=%s", values.DeltaSnapshotRetentionPeriod.Duration.String())) + } + return elements +} diff --git a/pkg/component/etcd/statefulset/values.go b/pkg/component/etcd/statefulset/values.go index aee65cc3d..e71e29e3b 100644 --- a/pkg/component/etcd/statefulset/values.go +++ b/pkg/component/etcd/statefulset/values.go @@ -89,7 +89,8 @@ type Values struct { EnableProfiling *bool - DeltaSnapshotPeriod *metav1.Duration + DeltaSnapshotPeriod *metav1.Duration + DeltaSnapshotRetentionPeriod *metav1.Duration SnapshotCompression *druidv1alpha1.CompressionSpec HeartbeatDuration *metav1.Duration diff --git a/pkg/component/etcd/statefulset/values_helper.go b/pkg/component/etcd/statefulset/values_helper.go index affbb4142..a3e05d1e1 100644 --- a/pkg/component/etcd/statefulset/values_helper.go +++ b/pkg/component/etcd/statefulset/values_helper.go @@ -91,8 +91,9 @@ func GenerateValues( BackupStore: etcd.Spec.Backup.Store, EnableProfiling: etcd.Spec.Backup.EnableProfiling, - DeltaSnapshotPeriod: etcd.Spec.Backup.DeltaSnapshotPeriod, - DeltaSnapshotMemoryLimit: etcd.Spec.Backup.DeltaSnapshotMemoryLimit, + DeltaSnapshotPeriod: etcd.Spec.Backup.DeltaSnapshotPeriod, + DeltaSnapshotRetentionPeriod: etcd.Spec.Backup.DeltaSnapshotRetentionPeriod, + DeltaSnapshotMemoryLimit: etcd.Spec.Backup.DeltaSnapshotMemoryLimit, DefragmentationSchedule: etcd.Spec.Etcd.DefragmentationSchedule, FullSnapshotSchedule: etcd.Spec.Backup.FullSnapshotSchedule, @@ -277,6 +278,10 @@ func getBackupRestoreCommand(val Values) []string { command = append(command, "--delta-snapshot-period="+val.DeltaSnapshotPeriod.Duration.String()) } + if val.DeltaSnapshotRetentionPeriod != nil { + command = append(command, "--delta-snapshot-retention-period="+val.DeltaSnapshotRetentionPeriod.Duration.String()) + } + var deltaSnapshotMemoryLimit = defaultSnapshotMemoryLimit if val.DeltaSnapshotMemoryLimit != nil { deltaSnapshotMemoryLimit = val.DeltaSnapshotMemoryLimit.Value()