@@ -53,6 +53,8 @@ const (
5353 labelComponentExportJob = "export-job"
5454 labelComponentUploadJob = "upload-job"
5555 labelComponentImportJob = "import-job"
56+ labelComponentVerifyJob = "verify-job"
57+ labelComponentVerifyVolume = "verify-volume"
5658 labelComponentZeroOutJob = "zeroout-job"
5759 labelComponentZeroOutVolume = "zeroout-volume"
5860 annotRemoteUID = "mantle.cybozu.io/remote-uid"
@@ -619,6 +621,115 @@ func (r *MantleBackupReconciler) replicateManifests(
619621 return ctrl.Result {}, nil
620622}
621623
624+ func (r * MantleBackupReconciler ) verify (
625+ ctx context.Context ,
626+ backup * mantlev1.MantleBackup ,
627+ ) error {
628+ var storedPVC corev1.PersistentVolumeClaim
629+ if err := json .Unmarshal ([]byte (backup .Status .PVCManifest ), & storedPVC ); err != nil {
630+ return fmt .Errorf ("failed to unmarshal PVC manifest: %w" , err )
631+ }
632+
633+ var storedPV corev1.PersistentVolume
634+ if err := json .Unmarshal ([]byte (backup .Status .PVManifest ), & storedPV ); err != nil {
635+ return fmt .Errorf ("failed to unmarshal PV manifest: %w" , err )
636+ }
637+
638+ snapshotName := backup .Name
639+ cloneName := fmt .Sprintf ("mantle-verify-%s" , backup .UID )
640+ pvName := fmt .Sprintf ("mantle-verify-%s" , backup .UID )
641+ pvcName := fmt .Sprintf ("mantle-verify-%s" , backup .UID )
642+ jobName := fmt .Sprintf ("mantle-verify-%s" , backup .UID )
643+
644+ // create a clone by the snapshot bound to the MB
645+ if err := createCloneByPV (ctx , r .ceph , & storedPV , snapshotName , cloneName ); err != nil {
646+ return fmt .Errorf ("failed to create a clone by the snapshot: %w" , err )
647+ }
648+
649+ // create a static PV with the clone
650+ if err := r .createOrUpdateStaticPV (
651+ ctx ,
652+ & storedPV ,
653+ cloneName ,
654+ corev1.ResourceList {
655+ corev1 .ResourceStorage : * resource .NewQuantity (* backup .Status .SnapSize , resource .BinarySI ),
656+ },
657+ pvName ,
658+ labelComponentVerifyVolume ,
659+ ); err != nil {
660+ return fmt .Errorf ("failed to create a static PV with the clone: %w" , err )
661+ }
662+
663+ // create a PVC with the PV
664+ if err := r .createOrUpdateStaticPVC (ctx , pvcName , pvName , labelComponentVerifyVolume , storedPVC .Spec .Resources ); err != nil {
665+ return fmt .Errorf ("failed to create a PVC with the PV: %w" , err )
666+ }
667+
668+ // create a Job to execute e2fsck on the PVC
669+ if err := r .createOrUpdateVerifyJob (ctx , jobName , pvcName ); err != nil {
670+ return fmt .Errorf ("failed to create a Job to execute e2fsck on the PVC: %w" , err )
671+ }
672+
673+ // wait for the Job to complete
674+ jobFinished , jobSucceeded , err := r .checkJobStatus (ctx , jobName )
675+ if err != nil {
676+ return fmt .Errorf ("failed to check the Job status: %w" , err )
677+ }
678+ if ! jobFinished {
679+ // still running
680+ return nil
681+ }
682+
683+ // update MB's conditions field
684+ condition := metav1 .ConditionTrue
685+ reason := ""
686+ if ! jobSucceeded {
687+ condition = metav1 .ConditionFalse
688+ }
689+ if err := r .updateMantleBackupCondition (
690+ ctx , backup ,
691+ mantlev1 .BackupConditionVerified ,
692+ condition ,
693+ reason ,
694+ ); err != nil {
695+ return fmt .Errorf ("failed to update MantleBackup condition: %w" , err )
696+ }
697+
698+ return nil
699+ }
700+
701+ // checkJobStatus checks the status of the Job with the given name.
702+ // It returns (is job finished, is job succeeded, error).
703+ func (r * MantleBackupReconciler ) checkJobStatus (ctx context.Context , jobName string ) (bool , bool , error ) {
704+ var job batchv1.Job
705+ if err := r .Get (
706+ ctx ,
707+ types.NamespacedName {
708+ Namespace : r .managedCephClusterID ,
709+ Name : jobName ,
710+ },
711+ & job ,
712+ ); err != nil {
713+ if aerrors .IsNotFound (err ) {
714+ // The cache must be stale.
715+ return false , false , nil
716+ }
717+ return false , false , fmt .Errorf ("failed to get Job for checking job status: %w" , err )
718+ }
719+
720+ for _ , c := range job .Status .Conditions {
721+ if c .Type == batchv1 .JobComplete && c .Status == corev1 .ConditionTrue {
722+ return true , true , nil
723+ }
724+ if c .Type == batchv1 .JobFailed && c .Status == corev1 .ConditionTrue {
725+ return true , false , nil
726+ }
727+ }
728+
729+ // job is still running
730+ return false , false , nil
731+ }
732+
622733func (r * MantleBackupReconciler ) provisionRBDSnapshot (
623734 ctx context.Context ,
624735 backup * mantlev1.MantleBackup ,
@@ -2125,6 +2236,80 @@ blkdiscard -z /dev/zeroout-rbd
21252236 return err
21262237}
21272238
2239+ func (r * MantleBackupReconciler ) createOrUpdateVerifyJob (ctx context.Context , jobName , pvcName string ) error {
2240+ var job batchv1.Job
2241+ job .SetName (jobName )
2242+ job .SetNamespace (r .managedCephClusterID )
2243+ _ , err := ctrl .CreateOrUpdate (ctx , r .Client , & job , func () error {
2244+ labels := job .GetLabels ()
2245+ if labels == nil {
2246+ labels = map [string ]string {}
2247+ }
2248+ labels ["app.kubernetes.io/name" ] = labelAppNameValue
2249+ labels ["app.kubernetes.io/component" ] = labelComponentVerifyJob
2250+ job .SetLabels (labels )
2251+
2252+ backoffLimit := int32 (65535 )
2253+ job .Spec .BackoffLimit = & backoffLimit
2254+
2255+ verifyContainerName := "verify"
2256+ job .Spec .PodFailurePolicy = & batchv1.PodFailurePolicy {
2257+ Rules : []batchv1.PodFailurePolicyRule {
2258+ {
2259+ Action : "FailJob" ,
2260+ OnExitCodes : & batchv1.PodFailurePolicyOnExitCodesRequirement {
2261+ ContainerName : & verifyContainerName ,
2262+ Operator : batchv1 .PodFailurePolicyOnExitCodesOpIn ,
2263+ Values : []int32 {1 , 2 , 3 , 4 , 5 , 6 , 7 }, // File system errors
2264+ },
2265+ },
2266+ },
2267+ }
2268+
2269+ tru := true
2270+ var zero int64 = 0
2271+ job .Spec .Template .Spec .Containers = []corev1.Container {
2272+ {
2273+ Name : "verify" ,
2274+ Image : r .podImage ,
2275+ Command : []string {
2276+ "/user/sbin/e2fsck" ,
2277+ "-fn" ,
2278+ "/dev/verify-rbd" ,
2279+ },
2280+ SecurityContext : & corev1.SecurityContext {
2281+ Privileged : & tru ,
2282+ RunAsGroup : & zero ,
2283+ RunAsUser : & zero ,
2284+ },
2285+ VolumeDevices : []corev1.VolumeDevice {
2286+ {
2287+ Name : "verify-rbd" ,
2288+ DevicePath : "/dev/verify-rbd" ,
2289+ },
2290+ },
2291+ ImagePullPolicy : corev1 .PullIfNotPresent ,
2292+ },
2293+ }
2294+
2295+ job .Spec .Template .Spec .RestartPolicy = corev1 .RestartPolicyNever
2296+
2297+ job .Spec .Template .Spec .Volumes = []corev1.Volume {
2298+ {
2299+ Name : "verify-rbd" ,
2300+ VolumeSource : corev1.VolumeSource {
2301+ PersistentVolumeClaim : & corev1.PersistentVolumeClaimVolumeSource {
2302+ ClaimName : pvcName ,
2303+ },
2304+ },
2305+ },
2306+ }
2307+
2308+ return nil
2309+ })
2310+ return err
2311+ }
2312+
21282313func (r * MantleBackupReconciler ) hasZeroOutJobCompleted (ctx context.Context , backup * mantlev1.MantleBackup ) (bool , error ) {
21292314 var job batchv1.Job
21302315 if err := r .Get (
@@ -2460,16 +2645,13 @@ func (r *MantleBackupReconciler) primaryCleanup(
24602645 return ctrl.Result {}, nil
24612646 }
24622647
2463- // Update the status of the MantleBackup. Use Patch here because Update() is
2464- // likely to fail due to "the object has been modified" error.
2465- newTarget := target .DeepCopy ()
2466- meta .SetStatusCondition (& newTarget .Status .Conditions , metav1.Condition {
2467- Type : mantlev1 .BackupConditionSyncedToRemote ,
2468- Status : metav1 .ConditionTrue ,
2469- Reason : mantlev1 .BackupReasonNone ,
2470- })
2471- if err := r .Client .Status ().Patch (ctx , newTarget , client .MergeFrom (target )); err != nil {
2472- return ctrl.Result {}, fmt .Errorf ("failed to update SyncedToRemote to True by Patch: %w" , err )
2648+ if err := r .updateMantleBackupCondition (
2649+ ctx , target ,
2650+ mantlev1 .BackupConditionSyncedToRemote ,
2651+ metav1 .ConditionTrue ,
2652+ mantlev1 .BackupReasonNone ,
2653+ ); err != nil {
2654+ return ctrl.Result {}, fmt .Errorf ("failed to set SyncedToRemote condition to True: %w" , err )
24732655 }
24742656 logger .Info ("succeeded to sync a backup to the remote ceph cluster" )
24752657
@@ -2677,3 +2859,24 @@ func (r *MantleBackupReconciler) deleteMiddleSnapshots(backup *mantlev1.MantleBa
26772859
26782860 return nil
26792861}
2862+
2863+ func (r * MantleBackupReconciler ) updateMantleBackupCondition (
2864+ ctx context.Context ,
2865+ target * mantlev1.MantleBackup ,
2866+ conditionType string ,
2867+ status metav1.ConditionStatus ,
2868+ reason string ,
2869+ ) error {
2870+ // Update the status of the MantleBackup. Use Patch here because Update() is
2871+ // likely to fail due to "the object has been modified" error.
2872+ willBeApplied := target .DeepCopy ()
2873+ meta .SetStatusCondition (& willBeApplied .Status .Conditions , metav1.Condition {
2874+ Type : mantlev1 .BackupConditionSyncedToRemote ,
2875+ Status : metav1 .ConditionTrue ,
2876+ Reason : mantlev1 .BackupReasonNone ,
2877+ })
2878+ if err := r .Client .Status ().Patch (ctx , willBeApplied , client .MergeFrom (target )); err != nil {
2879+ return fmt .Errorf ("failed to update MantleBackup condition by patch: %w" , err )
2880+ }
2881+ return nil
2882+ }
0 commit comments