Add ConfigMap support for keepLatestMaintenanceJobs

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>

add changelog file

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>

lint fix

Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
This commit is contained in:
Shubham Pampattiwar
2025-07-31 16:33:46 -07:00
parent 1535afb45e
commit d8f222c83f
5 changed files with 345 additions and 3 deletions
+15 -3
View File
@@ -275,7 +275,15 @@ func (r *BackupRepoReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, errors.Wrap(err, "error check and run repo maintenance jobs")
}
if err := maintenance.DeleteOldJobs(r.Client, req.Name, r.keepLatestMaintenanceJobs); err != nil {
// Get the configured number of maintenance jobs to keep from ConfigMap, fallback to CLI parameter
keepJobs := r.keepLatestMaintenanceJobs
if configuredKeep, err := maintenance.GetKeepLatestMaintenanceJobs(ctx, r.Client, log, r.namespace, r.repoMaintenanceConfig, backupRepo); err != nil {
log.WithError(err).Warn("Failed to get keepLatestMaintenanceJobs from ConfigMap, using CLI parameter value")
} else if configuredKeep > 0 {
keepJobs = configuredKeep
}
if err := maintenance.DeleteOldJobs(r.Client, req.Name, keepJobs); err != nil {
log.WithError(err).Warn("Failed to delete old maintenance jobs")
}
}
@@ -397,8 +405,12 @@ func (r *BackupRepoReconciler) recallMaintenance(ctx context.Context, req *veler
log.Warn("Updating backup repository because of unrecorded histories")
return r.patchBackupRepository(ctx, req, func(rr *velerov1api.BackupRepository) {
if lastMaintenanceTime.After(rr.Status.LastMaintenanceTime.Time) {
log.Warnf("Updating backup repository last maintenance time (%v) from history (%v)", rr.Status.LastMaintenanceTime.Time, lastMaintenanceTime.Time)
if lastMaintenanceTime != nil && (rr.Status.LastMaintenanceTime == nil || lastMaintenanceTime.After(rr.Status.LastMaintenanceTime.Time)) {
if rr.Status.LastMaintenanceTime != nil {
log.Warnf("Updating backup repository last maintenance time (%v) from history (%v)", rr.Status.LastMaintenanceTime.Time, lastMaintenanceTime.Time)
} else {
log.Warnf("Setting backup repository last maintenance time from history (%v)", lastMaintenanceTime.Time)
}
rr.Status.LastMaintenanceTime = lastMaintenanceTime
}
@@ -1559,6 +1559,164 @@ func TestDeleteOldMaintenanceJob(t *testing.T) {
}
}
func TestDeleteOldMaintenanceJobWithConfigMap(t *testing.T) {
tests := []struct {
name string
repo *velerov1api.BackupRepository
serverKeepJobs int
expectedKeptJobs int
maintenanceJobs []batchv1api.Job
bsl *velerov1api.BackupStorageLocation
repoMaintenanceJob *corev1api.ConfigMap
}{
{
name: "test with global config",
repo: &velerov1api.BackupRepository{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "repo",
},
Spec: velerov1api.BackupRepositorySpec{
MaintenanceFrequency: metav1.Duration{Duration: testMaintenanceFrequency},
BackupStorageLocation: "default",
VolumeNamespace: "test-ns",
RepositoryType: "restic",
},
Status: velerov1api.BackupRepositoryStatus{
Phase: velerov1api.BackupRepositoryPhaseReady,
},
},
serverKeepJobs: 3,
expectedKeptJobs: 5,
maintenanceJobs: []batchv1api.Job{
*builder.ForJob("velero", "job-01").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-02").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-03").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-04").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-05").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-06").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
},
bsl: builder.ForBackupStorageLocation("velero", "default").Result(),
repoMaintenanceJob: &corev1api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "repo-maintenance-job-config",
},
Data: map[string]string{
"global": `{"keepLatestMaintenanceJobs": 5}`,
},
},
},
{
name: "test with specific repo config overriding global",
repo: &velerov1api.BackupRepository{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "repo",
},
Spec: velerov1api.BackupRepositorySpec{
MaintenanceFrequency: metav1.Duration{Duration: testMaintenanceFrequency},
BackupStorageLocation: "default",
VolumeNamespace: "test-ns",
RepositoryType: "restic",
},
Status: velerov1api.BackupRepositoryStatus{
Phase: velerov1api.BackupRepositoryPhaseReady,
},
},
serverKeepJobs: 3,
expectedKeptJobs: 2,
maintenanceJobs: []batchv1api.Job{
*builder.ForJob("velero", "job-01").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-02").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-03").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
},
bsl: builder.ForBackupStorageLocation("velero", "default").Result(),
repoMaintenanceJob: &corev1api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "repo-maintenance-job-config",
},
Data: map[string]string{
"global": `{"keepLatestMaintenanceJobs": 5}`,
"test-ns-default-restic": `{"keepLatestMaintenanceJobs": 2}`,
},
},
},
{
name: "test fallback to CLI parameter when no ConfigMap",
repo: &velerov1api.BackupRepository{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "repo",
},
Spec: velerov1api.BackupRepositorySpec{
MaintenanceFrequency: metav1.Duration{Duration: testMaintenanceFrequency},
BackupStorageLocation: "default",
VolumeNamespace: "test-ns",
RepositoryType: "restic",
},
Status: velerov1api.BackupRepositoryStatus{
Phase: velerov1api.BackupRepositoryPhaseReady,
},
},
serverKeepJobs: 2,
expectedKeptJobs: 2,
maintenanceJobs: []batchv1api.Job{
*builder.ForJob("velero", "job-01").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-02").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-03").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
*builder.ForJob("velero", "job-04").ObjectMeta(builder.WithLabels(repomaintenance.RepositoryNameLabel, "repo")).Succeeded(1).Result(),
},
bsl: builder.ForBackupStorageLocation("velero", "default").Result(),
repoMaintenanceJob: nil, // No ConfigMap
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
objects := []runtime.Object{test.repo, test.bsl}
if test.repoMaintenanceJob != nil {
objects = append(objects, test.repoMaintenanceJob)
}
crClient := velerotest.NewFakeControllerRuntimeClient(t, objects...)
for _, job := range test.maintenanceJobs {
require.NoError(t, crClient.Create(t.Context(), &job))
}
repoLocker := repository.NewRepoLocker()
mgr := repomanager.NewManager("", crClient, repoLocker, nil, nil, nil)
repoMaintenanceConfigName := ""
if test.repoMaintenanceJob != nil {
repoMaintenanceConfigName = test.repoMaintenanceJob.Name
}
reconciler := NewBackupRepoReconciler(
velerov1api.DefaultNamespace,
velerotest.NewLogger(),
crClient,
mgr,
time.Duration(0),
"",
test.serverKeepJobs,
repoMaintenanceConfigName,
kube.PodResources{},
logrus.InfoLevel,
nil,
)
_, err := reconciler.Reconcile(t.Context(), ctrl.Request{NamespacedName: types.NamespacedName{Namespace: test.repo.Namespace, Name: "repo"}})
require.NoError(t, err)
jobList := new(batchv1api.JobList)
require.NoError(t, reconciler.Client.List(t.Context(), jobList, &client.ListOptions{Namespace: "velero"}))
assert.Len(t, jobList.Items, test.expectedKeptJobs, "Expected %d jobs to be kept, but got %d", test.expectedKeptJobs, len(jobList.Items))
})
}
}
func TestInitializeRepoWithRepositoryTypes(t *testing.T) {
scheme := runtime.NewScheme()
corev1api.AddToScheme(scheme)