diff --git a/docs/controller-protocol.md b/docs/controller-protocol.md
index bf25950b..43103b29 100644
--- a/docs/controller-protocol.md
+++ b/docs/controller-protocol.md
@@ -8,6 +8,8 @@
- [CreateOrUpdateMantleBackupResponse](#proto-CreateOrUpdateMantleBackupResponse)
- [CreateOrUpdatePVCRequest](#proto-CreateOrUpdatePVCRequest)
- [CreateOrUpdatePVCResponse](#proto-CreateOrUpdatePVCResponse)
+ - [ListMantleBackupRequest](#proto-ListMantleBackupRequest)
+ - [ListMantleBackupResponse](#proto-ListMantleBackupResponse)
- [MantleService](#proto-MantleService)
@@ -78,6 +80,37 @@ CreateOrUpdatePVCResponse is a response message for CreateOrUpdatePVC RPC.
+
+
+
+### ListMantleBackupRequest
+ListMantleBackupRequest is a request message for ListMantleBackup RPC.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| pvcUID | [string](#string) | | |
+| namespace | [string](#string) | | |
+
+
+
+
+
+
+
+
+### ListMantleBackupResponse
+ListMantleBackupResponse is a response message for ListMantleBackup RPC.
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| mantleBackupList | [bytes](#bytes) | | |
+
+
+
+
+
@@ -94,6 +127,7 @@ CreateOrUpdatePVCResponse is a response message for CreateOrUpdatePVC RPC.
| ----------- | ------------ | ------------- | ------------|
| CreateOrUpdatePVC | [CreateOrUpdatePVCRequest](#proto-CreateOrUpdatePVCRequest) | [CreateOrUpdatePVCResponse](#proto-CreateOrUpdatePVCResponse) | |
| CreateOrUpdateMantleBackup | [CreateOrUpdateMantleBackupRequest](#proto-CreateOrUpdateMantleBackupRequest) | [CreateOrUpdateMantleBackupResponse](#proto-CreateOrUpdateMantleBackupResponse) | |
+| ListMantleBackup | [ListMantleBackupRequest](#proto-ListMantleBackupRequest) | [ListMantleBackupResponse](#proto-ListMantleBackupResponse) | |
diff --git a/internal/controller/internal/testutil/resources.go b/internal/controller/internal/testutil/resources.go
index da62a9c1..f9517aa7 100644
--- a/internal/controller/internal/testutil/resources.go
+++ b/internal/controller/internal/testutil/resources.go
@@ -199,6 +199,15 @@ func (r *ResourceManager) WaitForBackupReady(ctx context.Context, backup *mantle
}).WithContext(ctx).Should(Succeed())
}
+func (r *ResourceManager) WaitForBackupSyncedToRemote(ctx context.Context, backup *mantlev1.MantleBackup) {
+ EventuallyWithOffset(1, func(g Gomega, ctx context.Context) {
+ err := r.client.Get(ctx, types.NamespacedName{Name: backup.Name, Namespace: backup.Namespace}, backup)
+ g.Expect(err).NotTo(HaveOccurred())
+
+ g.Expect(meta.IsStatusConditionTrue(backup.Status.Conditions, mantlev1.BackupConditionSyncedToRemote)).Should(BeTrue())
+ }).WithContext(ctx).Should(Succeed())
+}
+
// cf. https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#pointer-method-example
type ObjectConstraint[T any] interface {
client.Object
diff --git a/internal/controller/mantlebackup_controller.go b/internal/controller/mantlebackup_controller.go
index 6e8147cb..89a4971e 100644
--- a/internal/controller/mantlebackup_controller.go
+++ b/internal/controller/mantlebackup_controller.go
@@ -34,8 +34,13 @@ const (
labelLocalBackupTargetPVCUID = "mantle.cybozu.io/local-backup-target-pvc-uid"
labelRemoteBackupTargetPVCUID = "mantle.cybozu.io/remote-backup-target-pvc-uid"
annotRemoteUID = "mantle.cybozu.io/remote-uid"
+ annotDiffFrom = "mantle.cybozu.io/diff-from"
annotDiffTo = "mantle.cybozu.io/diff-to"
annotRetainIfExpired = "mantle.cybozu.io/retain-if-expired"
+ annotSyncMode = "mantle.cybozu.io/sync-mode"
+
+ syncModeFull = "full"
+ syncModeIncremental = "incremental"
)
// MantleBackupReconciler reconciles a MantleBackup object
@@ -397,10 +402,14 @@ func (r *MantleBackupReconciler) replicate(
if err != nil || result != (ctrl.Result{}) {
return result, err
}
- prepareResult, result, err := r.prepareForDataSynchronization(ctx, backup, r.primarySettings.Client)
- if err != nil || result != (ctrl.Result{}) {
- return result, err
+ prepareResult, err := r.prepareForDataSynchronization(ctx, backup, r.primarySettings.Client)
+ if err != nil {
+ return ctrl.Result{}, err
}
+
+ // FIXME: Delete this code after implementing export().
+ prepareResult.isSecondaryMantleBackupReadyToUse = true
+
if prepareResult.isSecondaryMantleBackupReadyToUse {
return r.primaryCleanup(ctx, backup)
}
@@ -602,21 +611,146 @@ func (r *MantleBackupReconciler) finalize(
}
type dataSyncPrepareResult struct {
- isIncremental bool
+ isIncremental bool // NOTE: The value is forcibly set to false if isSecondaryMantleBackupReadyToUse is true.
isSecondaryMantleBackupReadyToUse bool
diffFrom *mantlev1.MantleBackup // non-nil value iff isIncremental is true.
}
func (r *MantleBackupReconciler) prepareForDataSynchronization(
- _ context.Context,
- _ *mantlev1.MantleBackup,
- _ proto.MantleServiceClient,
-) (*dataSyncPrepareResult, ctrl.Result, error) { //nolint:unparam
+ ctx context.Context,
+ backup *mantlev1.MantleBackup,
+ msc proto.MantleServiceClient,
+) (*dataSyncPrepareResult, error) {
+ exportTargetPVCUID, ok := backup.GetLabels()[labelLocalBackupTargetPVCUID]
+ if !ok {
+ return nil, fmt.Errorf(`"%s" label is missing`, labelLocalBackupTargetPVCUID)
+ }
+ resp, err := msc.ListMantleBackup(
+ ctx,
+ &proto.ListMantleBackupRequest{
+ PvcUID: exportTargetPVCUID,
+ Namespace: backup.GetNamespace(),
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ secondaryBackups := make([]mantlev1.MantleBackup, 0)
+ err = json.Unmarshal(resp.MantleBackupList, &secondaryBackups)
+ if err != nil {
+ return nil, err
+ }
+ secondaryBackupMap := convertToMap(secondaryBackups)
+
+ isSecondaryMantleBackupReadyToUse := false
+ secondaryBackup, ok := secondaryBackupMap[backup.GetName()]
+ if !ok {
+ return nil, fmt.Errorf("secondary MantleBackup not found: %s, %s",
+ backup.GetName(), backup.GetNamespace())
+ }
+ isSecondaryMantleBackupReadyToUse = meta.IsStatusConditionTrue(
+ secondaryBackup.Status.Conditions,
+ mantlev1.BackupConditionReadyToUse,
+ )
+
+ if isSecondaryMantleBackupReadyToUse {
+ return &dataSyncPrepareResult{
+ isIncremental: false,
+ isSecondaryMantleBackupReadyToUse: true,
+ diffFrom: nil,
+ }, nil
+ }
+
+ if syncMode, ok := backup.GetAnnotations()[annotSyncMode]; ok {
+ switch syncMode {
+ case syncModeFull:
+ return &dataSyncPrepareResult{
+ isIncremental: false,
+ isSecondaryMantleBackupReadyToUse: isSecondaryMantleBackupReadyToUse,
+ diffFrom: nil,
+ }, nil
+ case syncModeIncremental:
+ diffFromName, ok := backup.GetAnnotations()[annotDiffFrom]
+ if !ok {
+ return nil, fmt.Errorf(`"%s" annotation is missing`, annotDiffFrom)
+ }
+
+ var diffFrom mantlev1.MantleBackup
+ err = r.Client.Get(ctx, types.NamespacedName{
+ Name: diffFromName,
+ Namespace: backup.GetNamespace(),
+ }, &diffFrom)
+ if err != nil {
+ return nil, err
+ }
+
+ return &dataSyncPrepareResult{
+ isIncremental: true,
+ isSecondaryMantleBackupReadyToUse: isSecondaryMantleBackupReadyToUse,
+ diffFrom: &diffFrom,
+ }, nil
+ default:
+ return nil, fmt.Errorf("unknown sync mode: %s", syncMode)
+ }
+ }
+
+ var primaryBackupList mantlev1.MantleBackupList
+ // TODO: Perhaps, we may have to use the client without cache.
+ err = r.Client.List(ctx, &primaryBackupList, &client.ListOptions{
+ LabelSelector: labels.SelectorFromSet(map[string]string{labelLocalBackupTargetPVCUID: exportTargetPVCUID}),
+ Namespace: backup.GetNamespace(),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ diffFrom := searchForDiffOriginMantleBackup(backup, primaryBackupList.Items, secondaryBackupMap)
+ isIncremental := (diffFrom != nil)
+
return &dataSyncPrepareResult{
- isIncremental: false,
- isSecondaryMantleBackupReadyToUse: true,
- diffFrom: nil,
- }, ctrl.Result{}, nil
+ isIncremental: isIncremental,
+ isSecondaryMantleBackupReadyToUse: isSecondaryMantleBackupReadyToUse,
+ diffFrom: diffFrom,
+ }, nil
+}
+
+func convertToMap(mantleBackups []mantlev1.MantleBackup) map[string]*mantlev1.MantleBackup {
+ m := make(map[string]*mantlev1.MantleBackup)
+ for _, mantleBackup := range mantleBackups {
+ mantleBackup := mantleBackup
+ m[mantleBackup.GetName()] = &mantleBackup
+ }
+ return m
+}
+
+func searchForDiffOriginMantleBackup(
+ backup *mantlev1.MantleBackup,
+ primaryBackups []mantlev1.MantleBackup,
+ secondaryBackupMap map[string]*mantlev1.MantleBackup,
+) *mantlev1.MantleBackup {
+ var diffOrigin *mantlev1.MantleBackup
+ for _, primaryBackup := range primaryBackups {
+ primaryBackup := primaryBackup
+ secondaryBackup, ok := secondaryBackupMap[primaryBackup.Name]
+ if !ok {
+ continue
+ }
+ if !meta.IsStatusConditionTrue(primaryBackup.Status.Conditions, mantlev1.BackupConditionReadyToUse) ||
+ !meta.IsStatusConditionTrue(secondaryBackup.Status.Conditions, mantlev1.BackupConditionReadyToUse) {
+ continue
+ }
+ if !primaryBackup.DeletionTimestamp.IsZero() || !secondaryBackup.DeletionTimestamp.IsZero() {
+ continue
+ }
+ if *backup.Status.SnapID <= *primaryBackup.Status.SnapID {
+ continue
+ }
+ if diffOrigin == nil || *diffOrigin.Status.SnapID < *primaryBackup.Status.SnapID {
+ diffOrigin = &primaryBackup
+ }
+ }
+
+ return diffOrigin
}
func (r *MantleBackupReconciler) export(
diff --git a/internal/controller/mantlebackup_controller_test.go b/internal/controller/mantlebackup_controller_test.go
index b87f27ee..fd31a793 100644
--- a/internal/controller/mantlebackup_controller_test.go
+++ b/internal/controller/mantlebackup_controller_test.go
@@ -3,13 +3,16 @@ package controller
import (
"context"
"encoding/json"
+ "slices"
"sync"
"time"
mantlev1 "github.com/cybozu-go/mantle/api/v1"
"github.com/cybozu-go/mantle/internal/controller/internal/testutil"
+ "github.com/cybozu-go/mantle/pkg/controller/proto"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ "google.golang.org/grpc"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -89,137 +92,171 @@ var _ = Describe("MantleBackup controller", func() {
}()
}
- BeforeEach(func() {
- mgrUtil = testutil.NewManagerUtil(context.Background(), cfg, scheme.Scheme)
-
- reconciler = NewMantleBackupReconciler(
- mgrUtil.GetManager().GetClient(),
- mgrUtil.GetManager().GetScheme(),
- resMgr.ClusterID,
- RoleStandalone,
- nil,
- )
- reconciler.ceph = testutil.NewFakeRBD()
- err := reconciler.SetupWithManager(mgrUtil.GetManager())
+ AfterEach(func() {
+ err := mgrUtil.Stop()
Expect(err).NotTo(HaveOccurred())
+ })
- setupExpireQueueSniffer()
+ Context("when the role is `standalone`", func() {
+ BeforeEach(func() {
+ mgrUtil = testutil.NewManagerUtil(context.Background(), cfg, scheme.Scheme)
+
+ reconciler = NewMantleBackupReconciler(
+ mgrUtil.GetManager().GetClient(),
+ mgrUtil.GetManager().GetScheme(),
+ resMgr.ClusterID,
+ RoleStandalone,
+ nil,
+ )
+ reconciler.ceph = testutil.NewFakeRBD()
+ err := reconciler.SetupWithManager(mgrUtil.GetManager())
+ Expect(err).NotTo(HaveOccurred())
- mgrUtil.Start()
- time.Sleep(100 * time.Millisecond)
+ setupExpireQueueSniffer()
- ns = resMgr.CreateNamespace()
- })
+ mgrUtil.Start()
+ time.Sleep(100 * time.Millisecond)
- AfterEach(func() {
- err := mgrUtil.Stop()
- Expect(err).NotTo(HaveOccurred())
- })
+ ns = resMgr.CreateNamespace()
+ })
- It("should be ready to use", func(ctx SpecContext) {
- pv, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
- Expect(err).NotTo(HaveOccurred())
+ It("should be ready to use", func(ctx SpecContext) {
+ pv, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
- backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
- waitForHavingFinalizer(ctx, backup)
- resMgr.WaitForBackupReady(ctx, backup)
+ waitForHavingFinalizer(ctx, backup)
+ resMgr.WaitForBackupReady(ctx, backup)
- pvcJS := backup.Status.PVCManifest
- Expect(pvcJS).NotTo(BeEmpty())
- pvcStored := corev1.PersistentVolumeClaim{}
- err = json.Unmarshal([]byte(pvcJS), &pvcStored)
- Expect(err).NotTo(HaveOccurred())
- Expect(pvcStored.Name).To(Equal(pvc.Name))
- Expect(pvcStored.Namespace).To(Equal(pvc.Namespace))
+ pvcJS := backup.Status.PVCManifest
+ Expect(pvcJS).NotTo(BeEmpty())
+ pvcStored := corev1.PersistentVolumeClaim{}
+ err = json.Unmarshal([]byte(pvcJS), &pvcStored)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(pvcStored.Name).To(Equal(pvc.Name))
+ Expect(pvcStored.Namespace).To(Equal(pvc.Namespace))
- pvJS := backup.Status.PVManifest
- Expect(pvJS).NotTo(BeEmpty())
- pvStored := corev1.PersistentVolume{}
- err = json.Unmarshal([]byte(pvJS), &pvStored)
- Expect(err).NotTo(HaveOccurred())
- Expect(pvStored.Name).To(Equal(pv.Name))
+ pvJS := backup.Status.PVManifest
+ Expect(pvJS).NotTo(BeEmpty())
+ pvStored := corev1.PersistentVolume{}
+ err = json.Unmarshal([]byte(pvJS), &pvStored)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(pvStored.Name).To(Equal(pv.Name))
- snaps, err := reconciler.ceph.RBDSnapLs(resMgr.PoolName, pv.Spec.CSI.VolumeAttributes["imageName"])
- Expect(err).NotTo(HaveOccurred())
- Expect(snaps).To(HaveLen(1))
- snapID := backup.Status.SnapID
- Expect(snapID).To(Equal(&snaps[0].Id))
+ snaps, err := reconciler.ceph.RBDSnapLs(resMgr.PoolName, pv.Spec.CSI.VolumeAttributes["imageName"])
+ Expect(err).NotTo(HaveOccurred())
+ Expect(snaps).To(HaveLen(1))
+ snapID := backup.Status.SnapID
+ Expect(snapID).To(Equal(&snaps[0].Id))
- err = k8sClient.Delete(ctx, backup)
- Expect(err).NotTo(HaveOccurred())
+ err = k8sClient.Delete(ctx, backup)
+ Expect(err).NotTo(HaveOccurred())
- testutil.CheckDeletedEventually[mantlev1.MantleBackup](ctx, k8sClient, backup.Name, backup.Namespace)
- })
+ testutil.CheckDeletedEventually[mantlev1.MantleBackup](ctx, k8sClient, backup.Name, backup.Namespace)
+ })
- It("should still be ready to use even if the PVC lost", func(ctx SpecContext) {
- _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
- Expect(err).NotTo(HaveOccurred())
+ It("should still be ready to use even if the PVC lost", func(ctx SpecContext) {
+ _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
- backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
- waitForHavingFinalizer(ctx, backup)
- resMgr.WaitForBackupReady(ctx, backup)
+ waitForHavingFinalizer(ctx, backup)
+ resMgr.WaitForBackupReady(ctx, backup)
- pvc.Status.Phase = corev1.ClaimLost // simulate lost PVC
- err = k8sClient.Status().Update(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ pvc.Status.Phase = corev1.ClaimLost // simulate lost PVC
+ err = k8sClient.Status().Update(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
- resMgr.WaitForBackupReady(ctx, backup)
- })
+ resMgr.WaitForBackupReady(ctx, backup)
+ })
+
+ DescribeTable("MantleBackup with correct expiration",
+ func(ctx SpecContext, expire string) {
+ _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
+
+ _, err = resMgr.CreateUniqueBackupFor(ctx, pvc, func(backup *mantlev1.MantleBackup) {
+ backup.Spec.Expire = expire
+ })
+ Expect(err).NotTo(HaveOccurred())
+ },
+ Entry("min expire", "1d"),
+ Entry("max expire", "15d"),
+ Entry("complex expire", "1w2d3h4m5s"),
+ )
- DescribeTable("MantleBackup with correct expiration",
- func(ctx SpecContext, expire string) {
+ DescribeTable("MantleBackup with incorrect expiration",
+ func(ctx SpecContext, expire string) {
+ _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
+
+ _, err = resMgr.CreateUniqueBackupFor(ctx, pvc, func(backup *mantlev1.MantleBackup) {
+ backup.Spec.Expire = expire
+ })
+ Expect(err).To(Or(
+ MatchError(ContainSubstring("expire must be")),
+ MatchError(ContainSubstring("body must be of type duration")),
+ ))
+ },
+ Entry("invalid short expire", "23h"),
+ Entry("invalid long expire", "15d1s"),
+ Entry("invalid duration", "foo"),
+ )
+
+ It("Should reject updating the expire field", func(ctx SpecContext) {
_, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
Expect(err).NotTo(HaveOccurred())
- _, err = resMgr.CreateUniqueBackupFor(ctx, pvc, func(backup *mantlev1.MantleBackup) {
- backup.Spec.Expire = expire
- })
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
Expect(err).NotTo(HaveOccurred())
- },
- Entry("min expire", "1d"),
- Entry("max expire", "15d"),
- Entry("complex expire", "1w2d3h4m5s"),
- )
- DescribeTable("MantleBackup with incorrect expiration",
- func(ctx SpecContext, expire string) {
- _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ expire, err := strfmt.ParseDuration(backup.Spec.Expire)
Expect(err).NotTo(HaveOccurred())
+ expire += time.Hour
+ backup.Spec.Expire = expire.String()
+ err = k8sClient.Update(ctx, backup)
+ Expect(err).To(MatchError(ContainSubstring("spec.expire is immutable")))
+ })
- _, err = resMgr.CreateUniqueBackupFor(ctx, pvc, func(backup *mantlev1.MantleBackup) {
- backup.Spec.Expire = expire
- })
- Expect(err).To(Or(
- MatchError(ContainSubstring("expire must be")),
- MatchError(ContainSubstring("body must be of type duration")),
- ))
- },
- Entry("invalid short expire", "23h"),
- Entry("invalid long expire", "15d1s"),
- Entry("invalid duration", "foo"),
- )
+ DescribeTable("MantleBackup expiration",
+ func(ctx SpecContext, offset time.Duration) {
+ _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
- It("Should reject updating the expire field", func(ctx SpecContext) {
- _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
- Expect(err).NotTo(HaveOccurred())
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
- backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ By("waiting for the backup to be ready")
+ resMgr.WaitForBackupReady(ctx, backup)
- expire, err := strfmt.ParseDuration(backup.Spec.Expire)
- Expect(err).NotTo(HaveOccurred())
- expire += time.Hour
- backup.Spec.Expire = expire.String()
- err = k8sClient.Update(ctx, backup)
- Expect(err).To(MatchError(ContainSubstring("spec.expire is immutable")))
- })
+ expectCreatedAt := backup.Status.CreatedAt.Time
+
+ By("simulate backup expiration")
+ newCreatedAt := simulateExpire(ctx, backup, offset)
+
+ By("wait for the backup to be deleted")
+ testutil.CheckDeletedEventually[mantlev1.MantleBackup](ctx, k8sClient, backup.Name, backup.Namespace)
- DescribeTable("MantleBackup expiration",
- func(ctx SpecContext, offset time.Duration) {
+ By("check the queued backup has the correct createdAt")
+ // If expiration is deferred, the backup with the new createdAt is queued.
+ // Otherwise, the backup is not queued after updating the createdAt, so the backup has the original createdAt.
+ if offset > 0 {
+ expectCreatedAt = newCreatedAt
+ }
+ v, ok := lastExpireQueuedBackups.Load(types.NamespacedName{Namespace: backup.Namespace, Name: backup.Name})
+ Expect(ok).To(BeTrue())
+ createdAt := v.(*mantlev1.MantleBackup).Status.CreatedAt.Time
+ Expect(createdAt).To(BeTemporally("==", expectCreatedAt))
+ },
+ Entry("an already expired backup should be deleted immediately", -time.Hour),
+ Entry("a near expiring backup should be deleted after expiration", 10*time.Second),
+ )
+
+ It("should retain the backup if it has the retain-if-expired annotation", func(ctx SpecContext) {
_, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
Expect(err).NotTo(HaveOccurred())
@@ -229,84 +266,284 @@ var _ = Describe("MantleBackup controller", func() {
By("waiting for the backup to be ready")
resMgr.WaitForBackupReady(ctx, backup)
- expectCreatedAt := backup.Status.CreatedAt.Time
-
By("simulate backup expiration")
- newCreatedAt := simulateExpire(ctx, backup, offset)
+ simulateExpire(ctx, backup, -time.Hour)
- By("wait for the backup to be deleted")
- testutil.CheckDeletedEventually[mantlev1.MantleBackup](ctx, k8sClient, backup.Name, backup.Namespace)
+ By("checking the backup is not deleted")
+ err = k8sClient.Get(ctx, types.NamespacedName{Name: backup.Name, Namespace: backup.Namespace}, backup)
+ Expect(err).NotTo(HaveOccurred())
+ })
- By("check the queued backup has the correct createdAt")
- // If expiration is deferred, the backup with the new createdAt is queued.
- // Otherwise, the backup is not queued after updating the createdAt, so the backup has the original createdAt.
- if offset > 0 {
- expectCreatedAt = newCreatedAt
- }
- v, ok := lastExpireQueuedBackups.Load(types.NamespacedName{Namespace: backup.Namespace, Name: backup.Name})
- Expect(ok).To(BeTrue())
- createdAt := v.(*mantlev1.MantleBackup).Status.CreatedAt.Time
- Expect(createdAt).To(BeTemporally("==", expectCreatedAt))
- },
- Entry("an already expired backup should be deleted immediately", -time.Hour),
- Entry("a near expiring backup should be deleted after expiration", 10*time.Second),
- )
+ It("should not be ready to use if the PVC is the lost state from the beginning", func(ctx SpecContext) {
+ pv, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
+ pv.Status.Phase = corev1.VolumeAvailable
+ err = k8sClient.Status().Update(ctx, pv)
+ Expect(err).NotTo(HaveOccurred())
+ pvc.Status.Phase = corev1.ClaimLost
+ err = k8sClient.Status().Update(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
- It("should retain the backup if it has the retain-if-expired annotation", func(ctx SpecContext) {
- _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
- Expect(err).NotTo(HaveOccurred())
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
- backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ waitForBackupNotReady(ctx, backup)
+ })
- By("waiting for the backup to be ready")
- resMgr.WaitForBackupReady(ctx, backup)
+ It("should not be ready to use if specified non-existent PVC name", func(ctx SpecContext) {
+ var err error
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, &corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "non-existent-pvc",
+ Namespace: ns,
+ },
+ })
+ Expect(err).NotTo(HaveOccurred())
- By("simulate backup expiration")
- simulateExpire(ctx, backup, -time.Hour)
+ waitForBackupNotReady(ctx, backup)
+ })
- By("checking the backup is not deleted")
- err = k8sClient.Get(ctx, types.NamespacedName{Name: backup.Name, Namespace: backup.Namespace}, backup)
- Expect(err).NotTo(HaveOccurred())
+ It("should fail the resource creation the second time if the same MantleBackup is created twice", func(ctx SpecContext) {
+ _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
+
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = k8sClient.Create(ctx, backup)
+ Expect(err).To(HaveOccurred())
+ })
})
- It("should not be ready to use if the PVC is the lost state from the beginning", func(ctx SpecContext) {
- pv, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
- Expect(err).NotTo(HaveOccurred())
- pv.Status.Phase = corev1.VolumeAvailable
- err = k8sClient.Status().Update(ctx, pv)
- Expect(err).NotTo(HaveOccurred())
- pvc.Status.Phase = corev1.ClaimLost
- err = k8sClient.Status().Update(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ Context("when the role is `primary`", func() {
+ BeforeEach(func() {
+ mgrUtil = testutil.NewManagerUtil(context.Background(), cfg, scheme.Scheme)
+
+ reconciler = NewMantleBackupReconciler(
+ mgrUtil.GetManager().GetClient(),
+ mgrUtil.GetManager().GetScheme(),
+ resMgr.ClusterID,
+ RolePrimary,
+ &PrimarySettings{
+ Client: &mockGRPCClient{},
+ },
+ )
+ reconciler.ceph = testutil.NewFakeRBD()
+ err := reconciler.SetupWithManager(mgrUtil.GetManager())
+ Expect(err).NotTo(HaveOccurred())
- backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ setupExpireQueueSniffer()
- waitForBackupNotReady(ctx, backup)
- })
+ mgrUtil.Start()
+ time.Sleep(100 * time.Millisecond)
- It("should not be ready to use if specified non-existent PVC name", func(ctx SpecContext) {
- var err error
- backup, err := resMgr.CreateUniqueBackupFor(ctx, &corev1.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: "non-existent-pvc",
- Namespace: ns,
- },
+ ns = resMgr.CreateNamespace()
})
- Expect(err).NotTo(HaveOccurred())
- waitForBackupNotReady(ctx, backup)
- })
+ It("should be synced to remote", func(ctx SpecContext) {
+ pv, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
+ Expect(err).NotTo(HaveOccurred())
- It("should fail the resource creation the second time if the same MantleBackup is created twice", func(ctx SpecContext) {
- _, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns)
- Expect(err).NotTo(HaveOccurred())
+ backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
+ Expect(err).NotTo(HaveOccurred())
- backup, err := resMgr.CreateUniqueBackupFor(ctx, pvc)
- Expect(err).NotTo(HaveOccurred())
+ waitForHavingFinalizer(ctx, backup)
+ resMgr.WaitForBackupReady(ctx, backup)
+ resMgr.WaitForBackupSyncedToRemote(ctx, backup)
+
+ pvcJS := backup.Status.PVCManifest
+ Expect(pvcJS).NotTo(BeEmpty())
+ pvcStored := corev1.PersistentVolumeClaim{}
+ err = json.Unmarshal([]byte(pvcJS), &pvcStored)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(pvcStored.Name).To(Equal(pvc.Name))
+ Expect(pvcStored.Namespace).To(Equal(pvc.Namespace))
+
+ pvJS := backup.Status.PVManifest
+ Expect(pvJS).NotTo(BeEmpty())
+ pvStored := corev1.PersistentVolume{}
+ err = json.Unmarshal([]byte(pvJS), &pvStored)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(pvStored.Name).To(Equal(pv.Name))
- err = k8sClient.Create(ctx, backup)
- Expect(err).To(HaveOccurred())
+ snaps, err := reconciler.ceph.RBDSnapLs(resMgr.PoolName, pv.Spec.CSI.VolumeAttributes["imageName"])
+ Expect(err).NotTo(HaveOccurred())
+ Expect(snaps).To(HaveLen(1))
+ snapID := backup.Status.SnapID
+ Expect(snapID).To(Equal(&snaps[0].Id))
+
+ // TODO: Currently, there is no way to check if the annotations are set correctly.
+ // After implementing export() function, the annotations check should be added
+ // for various conditions.
+
+ err = k8sClient.Delete(ctx, backup)
+ Expect(err).NotTo(HaveOccurred())
+
+ testutil.CheckDeletedEventually[mantlev1.MantleBackup](ctx, k8sClient, backup.Name, backup.Namespace)
+ })
})
})
+
+type mockGRPCClient struct {
+ backup mantlev1.MantleBackup
+}
+
+var _ proto.MantleServiceClient = (*mockGRPCClient)(nil)
+
+func (m *mockGRPCClient) CreateOrUpdatePVC(
+ ctx context.Context,
+ req *proto.CreateOrUpdatePVCRequest,
+ opts ...grpc.CallOption,
+) (*proto.CreateOrUpdatePVCResponse, error) {
+ return &proto.CreateOrUpdatePVCResponse{}, nil
+}
+
+func (m *mockGRPCClient) CreateOrUpdateMantleBackup(
+ ctx context.Context,
+ req *proto.CreateOrUpdateMantleBackupRequest,
+ opts ...grpc.CallOption,
+) (*proto.CreateOrUpdateMantleBackupResponse, error) {
+ err := json.Unmarshal([]byte(req.MantleBackup), &m.backup)
+ if err != nil {
+ return nil, err
+ }
+ return &proto.CreateOrUpdateMantleBackupResponse{}, nil
+}
+
+func (m *mockGRPCClient) ListMantleBackup(
+ ctx context.Context,
+ req *proto.ListMantleBackupRequest,
+ opts ...grpc.CallOption,
+) (*proto.ListMantleBackupResponse, error) {
+ backups := []mantlev1.MantleBackup{m.backup}
+ data, err := json.Marshal(backups)
+ if err != nil {
+ return nil, err
+ }
+ return &proto.ListMantleBackupResponse{
+ MantleBackupList: data,
+ }, nil
+}
+
+func int2Ptr(i int) *int {
+ return &i
+}
+
+var _ = Describe("searchDiffOriginMantleBackup", func() {
+ testMantleBackup := mantlev1.MantleBackup{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test0",
+ },
+ Status: mantlev1.MantleBackupStatus{
+ SnapID: int2Ptr(5),
+ },
+ }
+
+ basePrimaryBackups := []mantlev1.MantleBackup{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test1",
+ },
+ Status: mantlev1.MantleBackupStatus{
+ Conditions: []metav1.Condition{
+ {
+ Type: mantlev1.BackupConditionReadyToUse,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ SnapID: int2Ptr(1),
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test2",
+ },
+ Status: mantlev1.MantleBackupStatus{
+ Conditions: []metav1.Condition{
+ {
+ Type: mantlev1.BackupConditionReadyToUse,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ SnapID: int2Ptr(6),
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test3",
+ },
+ Status: mantlev1.MantleBackupStatus{
+ Conditions: []metav1.Condition{
+ {
+ Type: mantlev1.BackupConditionReadyToUse,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ SnapID: int2Ptr(3),
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test4",
+ },
+ Status: mantlev1.MantleBackupStatus{
+ Conditions: []metav1.Condition{
+ {
+ Type: mantlev1.BackupConditionReadyToUse,
+ Status: metav1.ConditionTrue,
+ },
+ },
+ SnapID: int2Ptr(4),
+ },
+ },
+ }
+ // Note that slices.Clone() does the shallow copy.
+ // ref. https://pkg.go.dev/slices#Clone
+ primaryBackupsWithConditionFalse := slices.Clone(basePrimaryBackups)
+ primaryBackupsWithConditionFalse[2] = *basePrimaryBackups[2].DeepCopy()
+ meta.SetStatusCondition(&primaryBackupsWithConditionFalse[2].Status.Conditions,
+ metav1.Condition{
+ Type: mantlev1.BackupConditionReadyToUse,
+ Status: metav1.ConditionFalse,
+ })
+ primaryBackupsWithDeletionTimestamp := slices.Clone(basePrimaryBackups)
+ primaryBackupsWithDeletionTimestamp[2] = *basePrimaryBackups[2].DeepCopy()
+ now := metav1.Now()
+ primaryBackupsWithDeletionTimestamp[2].SetDeletionTimestamp(&now)
+
+ testSecondaryMantleBackups := map[string]*mantlev1.MantleBackup{
+ "test1": basePrimaryBackups[0].DeepCopy(),
+ // "test2" cannot exist on the secondary cluster
+ // because it has a higher snapID than "test0".
+ "test3": basePrimaryBackups[2].DeepCopy(),
+ // "test4" is intentionally omitted.
+ }
+
+ DescribeTable("Search for the MantleBackup which is used for the diff origin",
+ func(backup *mantlev1.MantleBackup,
+ primaryBackups []mantlev1.MantleBackup,
+ secondaryBackupMap map[string]*mantlev1.MantleBackup,
+ shouldFindBackup bool,
+ expectedBackupName string) {
+ foundBackup := searchForDiffOriginMantleBackup(backup, primaryBackups, secondaryBackupMap)
+ if shouldFindBackup {
+ Expect(foundBackup).NotTo(BeNil())
+ Expect(foundBackup.GetName()).To(Equal(expectedBackupName))
+ } else {
+ Expect(foundBackup).To(BeNil())
+ }
+ },
+ Entry("should return nil when no MantleBackup found on the secondary cluster",
+ &testMantleBackup, basePrimaryBackups, make(map[string]*mantlev1.MantleBackup),
+ false, ""),
+ Entry("should find the correct MantleBackup",
+ &testMantleBackup, basePrimaryBackups, testSecondaryMantleBackups,
+ true, "test3"),
+ Entry("should skip the not-ready MantleBackup",
+ &testMantleBackup, primaryBackupsWithConditionFalse, testSecondaryMantleBackups,
+ true, "test1"),
+ Entry("should skip the MantleBackup with the deletion timestamp",
+ &testMantleBackup, primaryBackupsWithDeletionTimestamp, testSecondaryMantleBackups,
+ true, "test1"),
+ )
+})
diff --git a/internal/controller/replication.go b/internal/controller/replication.go
index f3be04fe..f7f58193 100644
--- a/internal/controller/replication.go
+++ b/internal/controller/replication.go
@@ -9,6 +9,7 @@ import (
"github.com/cybozu-go/mantle/pkg/controller/proto"
"google.golang.org/grpc"
corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -184,3 +185,22 @@ func (s *SecondaryServer) CreateOrUpdateMantleBackup(
return &proto.CreateOrUpdateMantleBackupResponse{}, nil
}
+
+func (s *SecondaryServer) ListMantleBackup(
+ ctx context.Context,
+ req *proto.ListMantleBackupRequest,
+) (*proto.ListMantleBackupResponse, error) {
+ var backupList mantlev1.MantleBackupList
+ err := s.reader.List(ctx, &backupList, &client.ListOptions{
+ LabelSelector: labels.SelectorFromSet(map[string]string{labelRemoteBackupTargetPVCUID: req.PvcUID}),
+ Namespace: req.Namespace,
+ })
+ if err != nil {
+ return nil, err
+ }
+ data, err := json.Marshal(backupList.Items)
+ if err != nil {
+ return nil, err
+ }
+ return &proto.ListMantleBackupResponse{MantleBackupList: data}, nil
+}
diff --git a/pkg/controller/proto/controller.pb.go b/pkg/controller/proto/controller.pb.go
index bcbcc878..fb9f0d80 100644
--- a/pkg/controller/proto/controller.pb.go
+++ b/pkg/controller/proto/controller.pb.go
@@ -203,6 +203,110 @@ func (*CreateOrUpdateMantleBackupResponse) Descriptor() ([]byte, []int) {
return file_pkg_controller_proto_controller_proto_rawDescGZIP(), []int{3}
}
+// ListMantleBackupRequest is a request message for ListMantleBackup RPC.
+type ListMantleBackupRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ PvcUID string `protobuf:"bytes,1,opt,name=pvcUID,proto3" json:"pvcUID,omitempty"`
+ Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
+}
+
+func (x *ListMantleBackupRequest) Reset() {
+ *x = ListMantleBackupRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pkg_controller_proto_controller_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListMantleBackupRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListMantleBackupRequest) ProtoMessage() {}
+
+func (x *ListMantleBackupRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_pkg_controller_proto_controller_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListMantleBackupRequest.ProtoReflect.Descriptor instead.
+func (*ListMantleBackupRequest) Descriptor() ([]byte, []int) {
+ return file_pkg_controller_proto_controller_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ListMantleBackupRequest) GetPvcUID() string {
+ if x != nil {
+ return x.PvcUID
+ }
+ return ""
+}
+
+func (x *ListMantleBackupRequest) GetNamespace() string {
+ if x != nil {
+ return x.Namespace
+ }
+ return ""
+}
+
+// ListMantleBackupResponse is a response message for ListMantleBackup RPC.
+type ListMantleBackupResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ MantleBackupList []byte `protobuf:"bytes,1,opt,name=mantleBackupList,proto3" json:"mantleBackupList,omitempty"`
+}
+
+func (x *ListMantleBackupResponse) Reset() {
+ *x = ListMantleBackupResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pkg_controller_proto_controller_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListMantleBackupResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListMantleBackupResponse) ProtoMessage() {}
+
+func (x *ListMantleBackupResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_pkg_controller_proto_controller_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListMantleBackupResponse.ProtoReflect.Descriptor instead.
+func (*ListMantleBackupResponse) Descriptor() ([]byte, []int) {
+ return file_pkg_controller_proto_controller_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ListMantleBackupResponse) GetMantleBackupList() []byte {
+ if x != nil {
+ return x.MantleBackupList
+ }
+ return nil
+}
+
var File_pkg_controller_proto_controller_proto protoreflect.FileDescriptor
var file_pkg_controller_proto_controller_proto_rawDesc = []byte{
@@ -221,25 +325,40 @@ var file_pkg_controller_proto_controller_proto_rawDesc = []byte{
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61,
0x63, 0x6b, 0x75, 0x70, 0x22, 0x24, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b,
- 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xda, 0x01, 0x0a, 0x0d, 0x4d,
- 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x11,
- 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x56,
- 0x43, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
- 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x56, 0x43, 0x52, 0x65, 0x71, 0x75, 0x65,
- 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
- 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x56, 0x43, 0x52, 0x65, 0x73, 0x70,
- 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72,
- 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b,
- 0x75, 0x70, 0x12, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
- 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42,
- 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x70,
- 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64,
- 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75,
- 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x79, 0x62, 0x6f, 0x7a, 0x75, 0x2d, 0x67, 0x6f, 0x2f,
- 0x6d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72,
- 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
- 0x74, 0x6f, 0x33,
+ 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x17, 0x4c, 0x69,
+ 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x76, 0x63, 0x55, 0x49, 0x44, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x76, 0x63, 0x55, 0x49, 0x44, 0x12, 0x1c, 0x0a,
+ 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x46, 0x0a, 0x18, 0x4c,
+ 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x6d, 0x61, 0x6e, 0x74, 0x6c,
+ 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x0c, 0x52, 0x10, 0x6d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x4c,
+ 0x69, 0x73, 0x74, 0x32, 0xaf, 0x02, 0x0a, 0x0d, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x53, 0x65,
+ 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f,
+ 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x56, 0x43, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74,
+ 0x65, 0x50, 0x56, 0x43, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x50, 0x56, 0x43, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a,
+ 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d,
+ 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x28, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72,
+ 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x74,
+ 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+ 0x12, 0x53, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61,
+ 0x63, 0x6b, 0x75, 0x70, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73,
+ 0x74, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73,
+ 0x74, 0x4d, 0x61, 0x6e, 0x74, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x79, 0x62, 0x6f, 0x7a, 0x75, 0x2d, 0x67, 0x6f, 0x2f, 0x6d, 0x61,
+ 0x6e, 0x74, 0x6c, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x33,
}
var (
@@ -254,20 +373,24 @@ func file_pkg_controller_proto_controller_proto_rawDescGZIP() []byte {
return file_pkg_controller_proto_controller_proto_rawDescData
}
-var file_pkg_controller_proto_controller_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_pkg_controller_proto_controller_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_pkg_controller_proto_controller_proto_goTypes = []interface{}{
(*CreateOrUpdatePVCRequest)(nil), // 0: proto.CreateOrUpdatePVCRequest
(*CreateOrUpdatePVCResponse)(nil), // 1: proto.CreateOrUpdatePVCResponse
(*CreateOrUpdateMantleBackupRequest)(nil), // 2: proto.CreateOrUpdateMantleBackupRequest
(*CreateOrUpdateMantleBackupResponse)(nil), // 3: proto.CreateOrUpdateMantleBackupResponse
+ (*ListMantleBackupRequest)(nil), // 4: proto.ListMantleBackupRequest
+ (*ListMantleBackupResponse)(nil), // 5: proto.ListMantleBackupResponse
}
var file_pkg_controller_proto_controller_proto_depIdxs = []int32{
0, // 0: proto.MantleService.CreateOrUpdatePVC:input_type -> proto.CreateOrUpdatePVCRequest
2, // 1: proto.MantleService.CreateOrUpdateMantleBackup:input_type -> proto.CreateOrUpdateMantleBackupRequest
- 1, // 2: proto.MantleService.CreateOrUpdatePVC:output_type -> proto.CreateOrUpdatePVCResponse
- 3, // 3: proto.MantleService.CreateOrUpdateMantleBackup:output_type -> proto.CreateOrUpdateMantleBackupResponse
- 2, // [2:4] is the sub-list for method output_type
- 0, // [0:2] is the sub-list for method input_type
+ 4, // 2: proto.MantleService.ListMantleBackup:input_type -> proto.ListMantleBackupRequest
+ 1, // 3: proto.MantleService.CreateOrUpdatePVC:output_type -> proto.CreateOrUpdatePVCResponse
+ 3, // 4: proto.MantleService.CreateOrUpdateMantleBackup:output_type -> proto.CreateOrUpdateMantleBackupResponse
+ 5, // 5: proto.MantleService.ListMantleBackup:output_type -> proto.ListMantleBackupResponse
+ 3, // [3:6] is the sub-list for method output_type
+ 0, // [0:3] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -327,6 +450,30 @@ func file_pkg_controller_proto_controller_proto_init() {
return nil
}
}
+ file_pkg_controller_proto_controller_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListMantleBackupRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pkg_controller_proto_controller_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListMantleBackupResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -334,7 +481,7 @@ func file_pkg_controller_proto_controller_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pkg_controller_proto_controller_proto_rawDesc,
NumEnums: 0,
- NumMessages: 4,
+ NumMessages: 6,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/pkg/controller/proto/controller.proto b/pkg/controller/proto/controller.proto
index 79adbf1d..d1935094 100644
--- a/pkg/controller/proto/controller.proto
+++ b/pkg/controller/proto/controller.proto
@@ -24,7 +24,19 @@ message CreateOrUpdateMantleBackupResponse {
// nothing.
}
+// ListMantleBackupRequest is a request message for ListMantleBackup RPC.
+message ListMantleBackupRequest {
+ string pvcUID = 1;
+ string namespace = 2;
+}
+
+// ListMantleBackupResponse is a response message for ListMantleBackup RPC.
+message ListMantleBackupResponse {
+ bytes mantleBackupList = 1;
+}
+
service MantleService {
rpc CreateOrUpdatePVC(CreateOrUpdatePVCRequest) returns (CreateOrUpdatePVCResponse);
rpc CreateOrUpdateMantleBackup(CreateOrUpdateMantleBackupRequest) returns (CreateOrUpdateMantleBackupResponse);
+ rpc ListMantleBackup(ListMantleBackupRequest) returns (ListMantleBackupResponse);
}
diff --git a/pkg/controller/proto/controller_grpc.pb.go b/pkg/controller/proto/controller_grpc.pb.go
index a000e4ed..5e612e8f 100644
--- a/pkg/controller/proto/controller_grpc.pb.go
+++ b/pkg/controller/proto/controller_grpc.pb.go
@@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9
const (
MantleService_CreateOrUpdatePVC_FullMethodName = "/proto.MantleService/CreateOrUpdatePVC"
MantleService_CreateOrUpdateMantleBackup_FullMethodName = "/proto.MantleService/CreateOrUpdateMantleBackup"
+ MantleService_ListMantleBackup_FullMethodName = "/proto.MantleService/ListMantleBackup"
)
// MantleServiceClient is the client API for MantleService service.
@@ -29,6 +30,7 @@ const (
type MantleServiceClient interface {
CreateOrUpdatePVC(ctx context.Context, in *CreateOrUpdatePVCRequest, opts ...grpc.CallOption) (*CreateOrUpdatePVCResponse, error)
CreateOrUpdateMantleBackup(ctx context.Context, in *CreateOrUpdateMantleBackupRequest, opts ...grpc.CallOption) (*CreateOrUpdateMantleBackupResponse, error)
+ ListMantleBackup(ctx context.Context, in *ListMantleBackupRequest, opts ...grpc.CallOption) (*ListMantleBackupResponse, error)
}
type mantleServiceClient struct {
@@ -59,12 +61,23 @@ func (c *mantleServiceClient) CreateOrUpdateMantleBackup(ctx context.Context, in
return out, nil
}
+func (c *mantleServiceClient) ListMantleBackup(ctx context.Context, in *ListMantleBackupRequest, opts ...grpc.CallOption) (*ListMantleBackupResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(ListMantleBackupResponse)
+ err := c.cc.Invoke(ctx, MantleService_ListMantleBackup_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
// MantleServiceServer is the server API for MantleService service.
// All implementations must embed UnimplementedMantleServiceServer
// for forward compatibility.
type MantleServiceServer interface {
CreateOrUpdatePVC(context.Context, *CreateOrUpdatePVCRequest) (*CreateOrUpdatePVCResponse, error)
CreateOrUpdateMantleBackup(context.Context, *CreateOrUpdateMantleBackupRequest) (*CreateOrUpdateMantleBackupResponse, error)
+ ListMantleBackup(context.Context, *ListMantleBackupRequest) (*ListMantleBackupResponse, error)
mustEmbedUnimplementedMantleServiceServer()
}
@@ -81,6 +94,9 @@ func (UnimplementedMantleServiceServer) CreateOrUpdatePVC(context.Context, *Crea
func (UnimplementedMantleServiceServer) CreateOrUpdateMantleBackup(context.Context, *CreateOrUpdateMantleBackupRequest) (*CreateOrUpdateMantleBackupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateOrUpdateMantleBackup not implemented")
}
+func (UnimplementedMantleServiceServer) ListMantleBackup(context.Context, *ListMantleBackupRequest) (*ListMantleBackupResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method ListMantleBackup not implemented")
+}
func (UnimplementedMantleServiceServer) mustEmbedUnimplementedMantleServiceServer() {}
func (UnimplementedMantleServiceServer) testEmbeddedByValue() {}
@@ -138,6 +154,24 @@ func _MantleService_CreateOrUpdateMantleBackup_Handler(srv interface{}, ctx cont
return interceptor(ctx, in, info, handler)
}
+func _MantleService_ListMantleBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(ListMantleBackupRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(MantleServiceServer).ListMantleBackup(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: MantleService_ListMantleBackup_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(MantleServiceServer).ListMantleBackup(ctx, req.(*ListMantleBackupRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
// MantleService_ServiceDesc is the grpc.ServiceDesc for MantleService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -153,6 +187,10 @@ var MantleService_ServiceDesc = grpc.ServiceDesc{
MethodName: "CreateOrUpdateMantleBackup",
Handler: _MantleService_CreateOrUpdateMantleBackup_Handler,
},
+ {
+ MethodName: "ListMantleBackup",
+ Handler: _MantleService_ListMantleBackup_Handler,
+ },
},
Streams: []grpc.StreamDesc{},
Metadata: "pkg/controller/proto/controller.proto",