From 20688c28c7e00f03481d298ceef00e15767d4ea1 Mon Sep 17 00:00:00 2001 From: Ryotaro Banno Date: Tue, 22 Oct 2024 05:05:40 +0000 Subject: [PATCH] implement ReconcileAsSecondary Signed-off-by: Ryotaro Banno --- .../controller/mantlebackup_controller.go | 164 +++++++++++++++++- test/e2e/multik8s/suite_test.go | 7 +- 2 files changed, 161 insertions(+), 10 deletions(-) diff --git a/internal/controller/mantlebackup_controller.go b/internal/controller/mantlebackup_controller.go index 5e952af0..c529270e 100644 --- a/internal/controller/mantlebackup_controller.go +++ b/internal/controller/mantlebackup_controller.go @@ -379,8 +379,46 @@ func (r *MantleBackupReconciler) ReconcileAsPrimary(ctx context.Context, backup return r.replicate(ctx, backup) } -func (r *MantleBackupReconciler) ReconcileAsSecondary(_ context.Context, _ *mantlev1.MantleBackup) (ctrl.Result, error) { - return ctrl.Result{}, nil +func (r *MantleBackupReconciler) ReconcileAsSecondary(ctx context.Context, backup *mantlev1.MantleBackup) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + if !isCreatedWhenMantleControllerWasSecondary(backup) { + logger.Info( + "skipping to reconcile the MantleBackup created by a different mantle-controller to prevent accidental data loss", + ) + return ctrl.Result{}, nil + } + + target, result, getSnapshotTargetErr := r.getSnapshotTarget(ctx, backup) + switch { + case getSnapshotTargetErr == errSkipProcessing: + return ctrl.Result{}, nil + case isErrTargetPVCNotFound(getSnapshotTargetErr): + // deletion logic may run. + case getSnapshotTargetErr == nil: + default: + return ctrl.Result{}, getSnapshotTargetErr + } + if result.Requeue { + return result, nil + } + + if !backup.ObjectMeta.DeletionTimestamp.IsZero() { + return r.finalize(ctx, backup, target, isErrTargetPVCNotFound(getSnapshotTargetErr)) + } + + if getSnapshotTargetErr != nil { + return ctrl.Result{}, getSnapshotTargetErr + } + + if !meta.IsStatusConditionTrue(backup.Status.Conditions, mantlev1.BackupConditionReadyToUse) { + result, err := r.startImport(ctx, backup, target) + if err != nil || !result.IsZero() { + return result, err + } + } + + return r.secondaryCleanup(ctx, backup) } func scheduleExpire(_ context.Context, evt event.GenericEvent, q workqueue.RateLimitingInterface) { @@ -600,9 +638,20 @@ func (r *MantleBackupReconciler) finalize( return ctrl.Result{Requeue: true}, nil } - result, err := r.primaryCleanup(ctx, backup) - if err != nil || result != (ctrl.Result{}) { - return result, err + switch r.role { + case RoleStandalone: + fallthrough + case RolePrimary: + result, err := r.primaryCleanup(ctx, backup) + if err != nil || result != (ctrl.Result{}) { + return result, err + } + + case RoleSecondary: + result, err := r.secondaryCleanup(ctx, backup) + if err != nil || result != (ctrl.Result{}) { + return result, err + } } if !controllerutil.ContainsFinalizer(backup, MantleBackupFinalizerName) { @@ -780,6 +829,104 @@ func (r *MantleBackupReconciler) export( return ctrl.Result{}, nil } +func (r *MantleBackupReconciler) startImport( + ctx context.Context, + backup *mantlev1.MantleBackup, + target *snapshotTarget, +) (ctrl.Result, error) { //nolint:unparam + if result, err := r.checkExportDataAlreadyUploaded(ctx, backup); err != nil || !result.IsZero() { + return result, err + } + + // Requeue if the PV is smaller than the PVC. (This may be the case if pvc-autoresizer is used.) + if isPVSmallerThanPVC(target.pv, target.pvc) { + return ctrl.Result{Requeue: true}, nil + } + + if err := r.updateStatusManifests(ctx, backup, target.pv, target.pvc); err != nil { + return ctrl.Result{}, err + } + + if result, err := r.reconcileDiscardJob(ctx, backup, target); err != nil || !result.IsZero() { + return result, err + } + + if result, err := r.reconcileImportJob(ctx, backup, target); err != nil || !result.IsZero() { + return result, err + } + + return ctrl.Result{}, nil +} + +func (r *MantleBackupReconciler) checkExportDataAlreadyUploaded( + _ context.Context, + _ *mantlev1.MantleBackup, +) (ctrl.Result, error) { //nolint:unparam + return ctrl.Result{}, nil +} + +func isPVSmallerThanPVC( + pv *corev1.PersistentVolume, + pvc *corev1.PersistentVolumeClaim, +) bool { + return pv.Spec.Capacity.Storage().Cmp(*pvc.Spec.Resources.Requests.Storage()) == -1 +} + +func (r *MantleBackupReconciler) updateStatusManifests( + ctx context.Context, + backup *mantlev1.MantleBackup, + pv *corev1.PersistentVolume, + pvc *corev1.PersistentVolumeClaim, +) error { + return updateStatus(ctx, r.Client, backup, func() error { + pvJSON, err := json.Marshal(*pv) + if err != nil { + return err + } + backup.Status.PVManifest = string(pvJSON) + + pvcJSON, err := json.Marshal(*pvc) + if err != nil { + return err + } + backup.Status.PVCManifest = string(pvcJSON) + + return nil + }) +} + +func (r *MantleBackupReconciler) reconcileDiscardJob( + _ context.Context, + backup *mantlev1.MantleBackup, + _ *snapshotTarget, +) (ctrl.Result, error) { //nolint:unparam + if backup.GetAnnotations()[annotSyncMode] != syncModeFull { + return ctrl.Result{}, nil + } + + // FIXME: implement here later + + return ctrl.Result{}, nil +} + +func (r *MantleBackupReconciler) reconcileImportJob( + ctx context.Context, + backup *mantlev1.MantleBackup, + _ *snapshotTarget, +) (ctrl.Result, error) { //nolint:unparam + // FIXME: implement here later + + if err := r.updateStatusCondition(ctx, backup, metav1.Condition{ + Type: mantlev1.BackupConditionReadyToUse, + Status: metav1.ConditionTrue, + Reason: mantlev1.BackupReasonNone, + }); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + func (r *MantleBackupReconciler) primaryCleanup( ctx context.Context, backup *mantlev1.MantleBackup, @@ -799,3 +946,10 @@ func (r *MantleBackupReconciler) primaryCleanup( return ctrl.Result{}, nil } + +func (r *MantleBackupReconciler) secondaryCleanup( + _ context.Context, + _ *mantlev1.MantleBackup, +) (ctrl.Result, error) { // nolint:unparam + return ctrl.Result{}, nil +} diff --git a/test/e2e/multik8s/suite_test.go b/test/e2e/multik8s/suite_test.go index c0b366b1..b7c7637c 100644 --- a/test/e2e/multik8s/suite_test.go +++ b/test/e2e/multik8s/suite_test.go @@ -151,11 +151,8 @@ func replicationTestSuite() { if secondaryMB.Status.SnapID != nil { return errors.New(".Status.SapID is incorrectly populated") } - if secondaryMB.Status.PVManifest != "" { - return errors.New(".Status.PVManifest is incorrectly populated") - } - if secondaryMB.Status.PVCManifest != "" { - return errors.New(".Status.PVCManifest is incorrectly populated") + if !meta.IsStatusConditionTrue(secondaryMB.Status.Conditions, "ReadyToUse") { + return errors.New("ReadyToUse of .Status.Conditions is not True") } return nil