Skip to content

Commit cb0d4a8

Browse files
Merge pull request #59 from shubham-pampattiwar/issue-42-max-incremental-backups
feat: add --max-incremental-backups flag to cap chain length
2 parents 8415900 + e9ea421 commit cb0d4a8

4 files changed

Lines changed: 616 additions & 2 deletions

File tree

cmd/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func main() {
8787
var datamoverImage string
8888
var datamoverImagePullPolicy string
8989
var oadpNamespace string
90+
var maxIncrementalBackups int
9091
var tlsOpts []func(*tls.Config)
9192
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
9293
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
@@ -113,12 +114,19 @@ func main() {
113114
"Image pull policy for datamover pods (Always, IfNotPresent, Never)")
114115
flag.StringVar(&oadpNamespace, "oadp-namespace", "openshift-adp",
115116
"Namespace where OADP/Velero resources are located")
117+
flag.IntVar(&maxIncrementalBackups, "max-incremental-backups", 0,
118+
"Maximum number of incremental backups per VM before forcing a full backup (0 = unlimited)")
116119
opts := zap.Options{
117120
Development: true,
118121
}
119122
opts.BindFlags(flag.CommandLine)
120123
flag.Parse()
121124

125+
if maxIncrementalBackups < 0 {
126+
fmt.Fprintln(os.Stderr, "--max-incremental-backups must be >= 0")
127+
os.Exit(1)
128+
}
129+
122130
// Allow DATAMOVER_IMAGE env var to override the default
123131
// This enables kustomize to set the image via environment variable
124132
if envImage := os.Getenv("DATAMOVER_IMAGE"); envImage != "" && datamoverImage == common.DefaultDatamoverImage {
@@ -226,6 +234,7 @@ func main() {
226234
MaxConcurrentReconciles: maxConcurrentReconciles,
227235
DatamoverImage: datamoverImage,
228236
DatamoverImagePullPolicy: corev1.PullPolicy(datamoverImagePullPolicy),
237+
MaxIncrementalBackups: maxIncrementalBackups,
229238
OADPNamespace: oadpNamespace,
230239
}).SetupWithManager(mgr); err != nil {
231240
setupLog.Error(err, "unable to create controller", "controller", "KubeVirtDataUpload")

internal/controller/kubevirt_dataupload_controller.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/json"
2323
"fmt"
2424
"io"
25+
"strconv"
2526
"strings"
2627
"time"
2728

@@ -96,6 +97,10 @@ type KubeVirtDataUploadReconciler struct {
9697
// DatamoverImagePullPolicy is the pull policy for the datamover image
9798
DatamoverImagePullPolicy corev1.PullPolicy
9899

100+
// MaxIncrementalBackups is the maximum number of incremental backups per VM
101+
// before forcing a full backup. 0 means unlimited.
102+
MaxIncrementalBackups int
103+
99104
// ObjectStoreFactory creates an ObjectStore from an UploaderConfig.
100105
// Defaults to uploader.InitObjectStore if nil. Override in tests to inject mocks.
101106
ObjectStoreFactory func(cfg *uploader.UploaderConfig) (velero.ObjectStore, error)
@@ -499,9 +504,10 @@ func (r *KubeVirtDataUploadReconciler) evaluateVMBackupStatus(
499504
// resolveBackupMode determines whether to force a full backup or allow incremental.
500505
// Returns (forceFullBackup, checkpointLookup) where checkpointLookup is the BSL
501506
// chain validation result (nil if BSL was unreachable or validation was skipped).
502-
// This covers two scenarios:
507+
// This covers three scenarios:
503508
// 1. User explicitly requested force-full-backup via annotation on DataUpload.
504509
// 2. BSL checkpoint validation found a broken chain, requiring a forced full backup.
510+
// 3. Max incremental backups limit reached (global or per-VM override).
505511
func (r *KubeVirtDataUploadReconciler) resolveBackupMode(ctx context.Context, logger logr.Logger, du *velerov2alpha1.DataUpload, vmRef *common.VMReference) (bool, *uploader.CheckpointLookupResult) {
506512
// Check if force full backup is requested via annotation.
507513
if du.Annotations[common.AnnotationForceFullBackup] == bslValidatedValue {
@@ -518,7 +524,47 @@ func (r *KubeVirtDataUploadReconciler) resolveBackupMode(ctx context.Context, lo
518524
return false, nil
519525
}
520526

521-
return r.validateBSLCheckpoint(ctx, logger, du, vmRef)
527+
forceFullBackup, checkpointLookup := r.validateBSLCheckpoint(ctx, logger, du, vmRef)
528+
529+
// Check if max incremental backups limit is reached.
530+
// ChainLength includes the root full backup, so incrementals = ChainLength - 1.
531+
if !forceFullBackup &&
532+
checkpointLookup != nil && checkpointLookup.Found && checkpointLookup.IsChainValid {
533+
maxInc := r.getEffectiveMaxIncrementalBackups(ctx, logger, vmRef)
534+
if maxInc > 0 {
535+
incrementalCount := checkpointLookup.ChainLength - 1
536+
if incrementalCount >= maxInc {
537+
logger.Info("Max incremental backups reached, forcing full backup",
538+
"incrementalCount", incrementalCount,
539+
"maxIncrementalBackups", maxInc,
540+
"chainLength", checkpointLookup.ChainLength)
541+
forceFullBackup = true
542+
}
543+
}
544+
}
545+
546+
return forceFullBackup, checkpointLookup
547+
}
548+
549+
// getEffectiveMaxIncrementalBackups returns the max incremental backups limit
550+
// for the given VM. A per-VM annotation takes precedence over the global setting.
551+
func (r *KubeVirtDataUploadReconciler) getEffectiveMaxIncrementalBackups(ctx context.Context, logger logr.Logger, vmRef *common.VMReference) int {
552+
vm := &kubevirtcorev1.VirtualMachine{}
553+
if err := r.Get(ctx, types.NamespacedName{Name: vmRef.Name, Namespace: vmRef.Namespace}, vm); err != nil {
554+
logger.V(1).Info("Could not fetch VM for max-incremental-backups annotation, using global setting",
555+
"reason", err.Error())
556+
return r.MaxIncrementalBackups
557+
}
558+
if val, ok := vm.Annotations[common.AnnotationMaxIncrementalBackups]; ok {
559+
if parsed, err := strconv.Atoi(val); err == nil && parsed >= 0 {
560+
logger.Info("Using per-VM max incremental backups override",
561+
"vm", vmRef.Name, "maxIncrementalBackups", parsed)
562+
return parsed
563+
}
564+
logger.Info("Invalid max-incremental-backups annotation value, using global setting",
565+
"vm", vmRef.Name, "annotationValue", val)
566+
}
567+
return r.MaxIncrementalBackups
522568
}
523569

524570
// validateBSLCheckpoint queries the BSL for a valid checkpoint chain and determines

0 commit comments

Comments
 (0)