From 87bf03fb2e4ff17e073fd751a811fe464f6ad076 Mon Sep 17 00:00:00 2001 From: Milla Samuel Date: Mon, 25 Aug 2025 17:02:06 +0800 Subject: [PATCH] Add Versionstamp To Restore CRD --- api/v1beta2/foundationdbrestore_types.go | 4 ++ api/v1beta2/zz_generated.deepcopy.go | 5 ++ ...foundationdb.org_foundationdbrestores.yaml | 4 ++ controllers/admin_client_test.go | 8 ++-- controllers/start_restore.go | 3 +- docs/restore_spec.md | 1 + e2e/fixtures/fdb_backup.go | 4 +- e2e/fixtures/fdb_restore.go | 7 ++- .../operator_backup_test.go | 16 +++++-- fdbclient/admin_client.go | 15 +++--- fdbclient/admin_client_test.go | 48 ++++++++++++++++++- pkg/fdbadminclient/admin_client.go | 3 +- pkg/fdbadminclient/mock/admin_client_mock.go | 3 +- 13 files changed, 99 insertions(+), 22 deletions(-) diff --git a/api/v1beta2/foundationdbrestore_types.go b/api/v1beta2/foundationdbrestore_types.go index 0a887e19d..6203a84bc 100644 --- a/api/v1beta2/foundationdbrestore_types.go +++ b/api/v1beta2/foundationdbrestore_types.go @@ -65,6 +65,10 @@ type FoundationDBRestoreSpec struct { // The path to the encryption key used to encrypt the backup. // +kubebuilder:validation:MaxLength=4096 EncryptionKeyPath string `json:"encryptionKeyPath,omitempty"` + + // Instead of the latest version the backup can be restored to, restore to the specified version. + // +nullable + BackupVersion *uint64 `json:"backupVersion,omitempty"` } // FoundationDBRestoreStatus describes the current status of the restore for a cluster. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 43934d586..5d1602393 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -986,6 +986,11 @@ func (in *FoundationDBRestoreSpec) DeepCopyInto(out *FoundationDBRestoreSpec) { *out = make(FoundationDBCustomParameters, len(*in)) copy(*out, *in) } + if in.BackupVersion != nil { + in, out := &in.BackupVersion, &out.BackupVersion + *out = new(uint64) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FoundationDBRestoreSpec. diff --git a/config/crd/bases/apps.foundationdb.org_foundationdbrestores.yaml b/config/crd/bases/apps.foundationdb.org_foundationdbrestores.yaml index efe6fb4d0..f026af019 100644 --- a/config/crd/bases/apps.foundationdb.org_foundationdbrestores.yaml +++ b/config/crd/bases/apps.foundationdb.org_foundationdbrestores.yaml @@ -36,6 +36,10 @@ spec: type: object spec: properties: + backupVersion: + format: int64 + nullable: true + type: integer blobStoreConfiguration: properties: accountName: diff --git a/controllers/admin_client_test.go b/controllers/admin_client_test.go index 806178116..d93ac93ae 100644 --- a/controllers/admin_client_test.go +++ b/controllers/admin_client_test.go @@ -364,10 +364,10 @@ var _ = Describe("admin_client_test", func() { Expect( mockAdminClient.StartRestore( "blobstore://test@test-service/test-backup", - nil, - "", - ), - ).To(Succeed()) + fdbv1beta2.FoundationDBRestore{ + Spec: fdbv1beta2.FoundationDBRestoreSpec{ + DestinationClusterName: cluster.Name, + }})).To(Succeed()) restoreStatus, err = mockAdminClient.GetRestoreStatus() Expect(err).NotTo(HaveOccurred()) diff --git a/controllers/start_restore.go b/controllers/start_restore.go index fdc68801c..1d0f1e5c7 100644 --- a/controllers/start_restore.go +++ b/controllers/start_restore.go @@ -54,8 +54,7 @@ func (s startRestore) reconcile( if len(strings.TrimSpace(status)) == 0 { err = adminClient.StartRestore( restore.BackupURL(), - restore.Spec.KeyRanges, - restore.Spec.EncryptionKeyPath, + *restore, ) if err != nil { return &requeue{curError: err} diff --git a/docs/restore_spec.md b/docs/restore_spec.md index 298e36e5c..83d50e501 100644 --- a/docs/restore_spec.md +++ b/docs/restore_spec.md @@ -56,6 +56,7 @@ FoundationDBRestoreSpec describes the desired state of the backup for a cluster. | blobStoreConfiguration | This is the configuration of the target blobstore for this backup. | *BlobStoreConfiguration | false | | customParameters | CustomParameters defines additional parameters to pass to the backup agents. | FoundationDBCustomParameters | false | | encryptionKeyPath | The path to the encryption key used to encrypt the backup. | string | false | +| backupVersion | Instead of the latest version the backup can be restored to, restore to the specified version. | *uint64 | false | [Back to TOC](#table-of-contents) diff --git a/e2e/fixtures/fdb_backup.go b/e2e/fixtures/fdb_backup.go index e75291994..57f72b5b4 100644 --- a/e2e/fixtures/fdb_backup.go +++ b/e2e/fixtures/fdb_backup.go @@ -221,7 +221,8 @@ func (fdbBackup *FdbBackup) WaitForReconciliation() { } // WaitForRestorableVersion will wait until the back is restorable. -func (fdbBackup *FdbBackup) WaitForRestorableVersion(version uint64) { +func (fdbBackup *FdbBackup) WaitForRestorableVersion(version uint64) uint64 { + var restorableVersion uint64 gomega.Eventually(func(g gomega.Gomega) uint64 { backupPod := fdbBackup.GetBackupPod() out, _, err := fdbBackup.fdbCluster.ExecuteCmdOnPod( @@ -241,6 +242,7 @@ func (fdbBackup *FdbBackup) WaitForRestorableVersion(version uint64) { return ptr.Deref(status.LatestRestorablePoint.Version, 0) }).WithTimeout(10*time.Minute).WithPolling(2*time.Second).Should(gomega.BeNumerically(">", version), "error waiting for restorable version") + return restorableVersion } // GetBackupPod returns a random backup Pod for the provided backup. diff --git a/e2e/fixtures/fdb_restore.go b/e2e/fixtures/fdb_restore.go index fb0c7177d..6dabaee83 100644 --- a/e2e/fixtures/fdb_restore.go +++ b/e2e/fixtures/fdb_restore.go @@ -36,7 +36,7 @@ import ( // CreateRestoreForCluster will create a FoundationDBRestore resource based on the provided backup resource. // For more information how the backup system with the operator is working please look at // the operator documentation: https://github.com/FoundationDB/fdb-kubernetes-operator/v2/blob/master/docs/manual/backup.md -func (factory *Factory) CreateRestoreForCluster(backup *FdbBackup) { +func (factory *Factory) CreateRestoreForCluster(backup *FdbBackup, backupVersion *uint64) { gomega.Expect(backup).NotTo(gomega.BeNil()) restore := &fdbv1beta2.FoundationDBRestore{ ObjectMeta: metav1.ObjectMeta{ @@ -49,6 +49,11 @@ func (factory *Factory) CreateRestoreForCluster(backup *FdbBackup) { CustomParameters: backup.backup.Spec.CustomParameters, }, } + + if backupVersion != nil { + restore.Spec.BackupVersion = backupVersion + } + gomega.Expect(factory.CreateIfAbsent(restore)).NotTo(gomega.HaveOccurred()) factory.AddShutdownHook(func() error { diff --git a/e2e/test_operator_backups/operator_backup_test.go b/e2e/test_operator_backups/operator_backup_test.go index 3bfca65a3..64ff7540a 100644 --- a/e2e/test_operator_backups/operator_backup_test.go +++ b/e2e/test_operator_backups/operator_backup_test.go @@ -82,7 +82,6 @@ var _ = Describe("Operator Backup", Label("e2e", "pr"), func() { var keyValues []fixtures.KeyValue var prefix byte = 'a' var backup *fixtures.FdbBackup - // Delete the backup resource after each test. Note that this will not delete the data // in the backup store. AfterEach(func() { @@ -104,7 +103,7 @@ var _ = Describe("Operator Backup", Label("e2e", "pr"), func() { It("should restore the cluster successfully", func() { fdbCluster.ClearRange([]byte{prefix}, 60) - factory.CreateRestoreForCluster(backup) + factory.CreateRestoreForCluster(backup, nil) Expect(fdbCluster.GetRange([]byte{prefix}, 25, 60)).Should(Equal(keyValues)) }) }) @@ -174,7 +173,18 @@ var _ = Describe("Operator Backup", Label("e2e", "pr"), func() { It("should restore the cluster successfully", func() { fdbCluster.ClearRange([]byte{prefix}, 60) - factory.CreateRestoreForCluster(backup) + factory.CreateRestoreForCluster(backup, nil) + Expect(fdbCluster.GetRange([]byte{prefix}, 25, 60)).Should(Equal(keyValues)) + }) + + It("should restore the cluster successfully with a restorable version", func() { + var prefix byte = 'b' + var keyValues = fdbCluster.GenerateRandomValues(10, prefix) + fdbCluster.WriteKeyValues(keyValues) + var restorableVersion = backup.WaitForRestorableVersion(fdbCluster.GetClusterVersion()) + backup.Stop() + fdbCluster.ClearRange([]byte{prefix}, 60) + factory.CreateRestoreForCluster(backup, &restorableVersion) Expect(fdbCluster.GetRange([]byte{prefix}, 25, 60)).Should(Equal(keyValues)) }) diff --git a/fdbclient/admin_client.go b/fdbclient/admin_client.go index 8382dfcc1..cd278f642 100644 --- a/fdbclient/admin_client.go +++ b/fdbclient/admin_client.go @@ -919,8 +919,7 @@ func (client *cliAdminClient) GetBackupStatus() (*fdbv1beta2.FoundationDBLiveBac // StartRestore starts a new restore. func (client *cliAdminClient) StartRestore( url string, - keyRanges []fdbv1beta2.FoundationDBKeyRange, - encryptionKeyPath string, + restore fdbv1beta2.FoundationDBRestore, ) error { args := []string{ "start", @@ -933,13 +932,17 @@ func (client *cliAdminClient) StartRestore( return verErr } - if encryptionKeyPath != "" && fdbVersion.SupportsBackupEncryption() { - args = append(args, "--encryption-key-file", encryptionKeyPath) + if restore.Spec.EncryptionKeyPath != "" && fdbVersion.SupportsBackupEncryption() { + args = append(args, "--encryption-key-file", restore.Spec.EncryptionKeyPath) + } + + if restore.Spec.BackupVersion != nil { + args = append(args, "-v", strconv.FormatUint(*restore.Spec.BackupVersion, 10)) } - if keyRanges != nil { + if restore.Spec.KeyRanges != nil { keyRangeString := "" - for _, keyRange := range keyRanges { + for _, keyRange := range restore.Spec.KeyRanges { if keyRangeString != "" { keyRangeString += ";" } diff --git a/fdbclient/admin_client_test.go b/fdbclient/admin_client_test.go index 96aa7a379..c2f7ecfd2 100644 --- a/fdbclient/admin_client_test.go +++ b/fdbclient/admin_client_test.go @@ -1129,7 +1129,11 @@ protocol fdb00b071010000`, url := "blobstore://test@test-service/test-backup" - err := client.StartRestore(url, keyRanges, encryptionKeyPath) + err := client.StartRestore(url, fdbv1beta2.FoundationDBRestore{ + Spec: fdbv1beta2.FoundationDBRestoreSpec{ + KeyRanges: keyRanges, + EncryptionKeyPath: encryptionKeyPath, + }}) Expect(err).NotTo(HaveOccurred()) Expect(mockRunner.receivedArgs[0]).To(ContainElements( @@ -1185,4 +1189,46 @@ protocol fdb00b071010000`, false, ), ) + + DescribeTable("it should properly handle backup versions", + func(actualBackupVersion *uint64, expectedBackupVersion string) { + mockRunner := &mockCommandRunner{ + mockedError: nil, + mockedOutput: []string{""}, + } + + client := &cliAdminClient{ + Cluster: &fdbv1beta2.FoundationDBCluster{ + Spec: fdbv1beta2.FoundationDBClusterSpec{ + Version: "7.3.1", + }, + Status: fdbv1beta2.FoundationDBClusterStatus{ + RunningVersion: "7.3.1", + }, + }, + log: logr.Discard(), + cmdRunner: mockRunner, + } + + url := "blobstore://test@test-service/test-backup" + + err := client.StartRestore(url, fdbv1beta2.FoundationDBRestore{ + Spec: fdbv1beta2.FoundationDBRestoreSpec{ + BackupVersion: actualBackupVersion, + }, + }) + Expect(err).NotTo(HaveOccurred()) + + if expectedBackupVersion != "" { + Expect( + mockRunner.receivedArgs[0], + ).To(ContainElements("-v", expectedBackupVersion)) + } else { + Expect(mockRunner.receivedArgs[0]).ToNot(ContainElement("-v")) + } + + }, + Entry("when it is passed in", ptr.To(uint64(1234567890123)), "1234567890123"), + Entry("when it is not passed in", nil, ""), + ) }) diff --git a/pkg/fdbadminclient/admin_client.go b/pkg/fdbadminclient/admin_client.go index f71c18c27..551452b2d 100644 --- a/pkg/fdbadminclient/admin_client.go +++ b/pkg/fdbadminclient/admin_client.go @@ -90,8 +90,7 @@ type AdminClient interface { // StartRestore starts a new restore. StartRestore( url string, - keyRanges []fdbv1beta2.FoundationDBKeyRange, - encyptionKeyPath string, + fdbRestore fdbv1beta2.FoundationDBRestore, ) error // GetRestoreStatus gets the status of the current restore. diff --git a/pkg/fdbadminclient/mock/admin_client_mock.go b/pkg/fdbadminclient/mock/admin_client_mock.go index 7a2096627..de8c0c5ec 100644 --- a/pkg/fdbadminclient/mock/admin_client_mock.go +++ b/pkg/fdbadminclient/mock/admin_client_mock.go @@ -969,8 +969,7 @@ func (client *AdminClient) GetBackupStatus() (*fdbv1beta2.FoundationDBLiveBackup // StartRestore starts a new restore. func (client *AdminClient) StartRestore( url string, - _ []fdbv1beta2.FoundationDBKeyRange, - _ string, + _ fdbv1beta2.FoundationDBRestore, ) error { adminClientMutex.Lock() defer adminClientMutex.Unlock()