Skip to content

Commit 036ec5e

Browse files
committed
feat: add an option to merge full and pitr restore job (#9404)
(cherry picked from commit b1d121d)
1 parent d2e8d67 commit 036ec5e

3 files changed

Lines changed: 112 additions & 13 deletions

File tree

pkg/constant/annotations.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ const (
5151
NodeSelectorOnceAnnotationKey = "workloads.kubeblocks.io/node-selector-once"
5252
)
5353

54+
const (
55+
// SkipBaseBackupRestoreInPitrAnnotationKey is an experimental api to unify full and continuous restore job.
56+
// It is set on the actionset CR.
57+
// If this annotaion is set to "true", then only one job will be created during restore.
58+
SkipBaseBackupRestoreInPitrAnnotationKey = "dataprotection.kubeblocks.io/skip-base-backup-restore-in-pitr"
59+
)
60+
5461
// annotations for multi-cluster
5562
const (
5663
KBAppMultiClusterPlacementKey = "apps.kubeblocks.io/multi-cluster-placement"

pkg/dataprotection/restore/manager.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,25 +168,41 @@ func (r *RestoreManager) BuildContinuousRestoreManager(reqCtx intctrlutil.Reques
168168
}
169169
}
170170

171-
backupSet, err := r.getBackupActionSetForContinuous(reqCtx, cli, continuousBackup, metav1.NewTime(restoreTime))
172-
if err != nil || backupSet == nil {
171+
baseBackupSet, err := r.getBaseBackupActionSetForContinuous(reqCtx, cli, continuousBackup, metav1.NewTime(restoreTime))
172+
if err != nil || baseBackupSet == nil {
173173
return err
174174
}
175+
176+
skipBaseBackupRestoreInPitr := false
177+
if continuousBackupSet.ActionSet.Annotations != nil {
178+
if continuousBackupSet.ActionSet.Annotations[constant.SkipBaseBackupRestoreInPitrAnnotationKey] == "true" {
179+
skipBaseBackupRestoreInPitr = true
180+
}
181+
}
182+
175183
// set base backup
176-
continuousBackupSet.BaseBackup = backupSet.Backup
177-
if backupSet.ActionSet != nil && backupSet.ActionSet.Spec.BackupType == dpv1alpha1.BackupTypeIncremental {
178-
if err = r.BuildIncrementalBackupActionSet(reqCtx, cli, *backupSet); err != nil {
184+
continuousBackupSet.BaseBackup = baseBackupSet.Backup
185+
if baseBackupSet.ActionSet != nil && baseBackupSet.ActionSet.Spec.BackupType == dpv1alpha1.BackupTypeIncremental {
186+
if skipBaseBackupRestoreInPitr {
187+
return intctrlutil.NewFatalError("unify incremental and continuous restore job is not supported")
188+
}
189+
if err = r.BuildIncrementalBackupActionSet(reqCtx, cli, *baseBackupSet); err != nil {
179190
return err
180191
}
181192
r.SetBackupSets(continuousBackupSet)
182193
} else {
183-
r.SetBackupSets(*backupSet, continuousBackupSet)
194+
if skipBaseBackupRestoreInPitr {
195+
r.Recorder.Event(r.Restore, corev1.EventTypeNormal, "SkipBaseBackupRestoreInPitr", "base backup restore skipped")
196+
r.SetBackupSets(continuousBackupSet)
197+
} else {
198+
r.SetBackupSets(*baseBackupSet, continuousBackupSet)
199+
}
184200
}
185201
return nil
186202
}
187203

188-
// getBackupActionSetForContinuous gets full or incremental backup and actionSet for continuous.
189-
func (r *RestoreManager) getBackupActionSetForContinuous(reqCtx intctrlutil.RequestCtx, cli client.Client, continuousBackup *dpv1alpha1.Backup, restoreTime metav1.Time) (*BackupActionSet, error) {
204+
// getBaseBackupActionSetForContinuous gets full or incremental backup and actionSet for continuous.
205+
func (r *RestoreManager) getBaseBackupActionSetForContinuous(reqCtx intctrlutil.RequestCtx, cli client.Client, continuousBackup *dpv1alpha1.Backup, restoreTime metav1.Time) (*BackupActionSet, error) {
190206
notFoundLatestBackup := func() (*BackupActionSet, error) {
191207
return nil, intctrlutil.NewFatalError(fmt.Sprintf(`can not found latest full or incremental backup based on backupPolicy "%s" and specified restoreTime "%s"`,
192208
continuousBackup.Spec.BackupPolicyName, restoreTime))

pkg/dataprotection/restore/manager_test.go

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,40 @@ var _ = Describe("RestoreManager Test", func() {
9797

9898
})
9999

100-
mockBackupForRestore := func(testCtx *testutil.TestContext, actionSetName, backupPVCName string, mockBackupCompleted, useVolumeSnapshotBackup bool) *dpv1alpha1.Backup {
101-
backup := testdp.NewFakeBackup(testCtx, nil)
100+
mockBackupForRestore := func(
101+
testCtx *testutil.TestContext, actionSetName, backupPVCName string,
102+
mockBackupCompleted, useVolumeSnapshotBackup bool,
103+
backupType dpv1alpha1.BackupType,
104+
startTime, endTime string,
105+
backupName string,
106+
) *dpv1alpha1.Backup {
107+
backup := testdp.NewFakeBackup(testCtx, func(backup *dpv1alpha1.Backup) {
108+
if backup.Labels == nil {
109+
backup.Labels = make(map[string]string)
110+
}
111+
backup.Labels[dptypes.BackupTypeLabelKey] = string(backupType)
112+
backup.Labels[dptypes.BackupPolicyLabelKey] = testdp.BackupPolicyName
113+
if backupName != "" {
114+
backup.Name = backupName
115+
}
116+
})
102117
if mockBackupCompleted {
103118
// then mock backup to completed
104119
backupMethodName := testdp.BackupMethodName
105120
if useVolumeSnapshotBackup {
106121
backupMethodName = testdp.VSBackupMethodName
107122
}
108123
Expect(testapps.ChangeObjStatus(testCtx, backup, func() {
109-
endTime, _ := time.Parse(time.RFC3339, "2023-01-01T10:00:00Z")
124+
var end *metav1.Time
125+
if endTime != "" {
126+
endTime, _ := time.Parse(time.RFC3339, endTime)
127+
end = &metav1.Time{Time: endTime}
128+
}
129+
var start *metav1.Time
130+
if startTime != "" {
131+
startTime, _ := time.Parse(time.RFC3339, startTime)
132+
start = &metav1.Time{Time: startTime}
133+
}
110134
backup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
111135
backup.Status.PersistentVolumeClaimName = backupPVCName
112136
testdp.MockBackupStatusTarget(backup, dpv1alpha1.PodSelectionStrategyAny)
@@ -115,7 +139,8 @@ var _ = Describe("RestoreManager Test", func() {
115139
}
116140
backup.Status.TimeRange = &dpv1alpha1.BackupTimeRange{
117141
TimeZone: "+08:00",
118-
End: &metav1.Time{Time: endTime},
142+
Start: start,
143+
End: end,
119144
}
120145
testdp.MockBackupStatusMethod(backup, backupMethodName, testdp.DataVolumeName, actionSetName)
121146
})).Should(Succeed())
@@ -125,7 +150,7 @@ var _ = Describe("RestoreManager Test", func() {
125150

126151
initResources := func(reqCtx intctrlutil.RequestCtx, _ int, useVolumeSnapshot bool, change func(f *testdp.MockRestoreFactory)) (*RestoreManager, *BackupActionSet) {
127152
By("create a completed backup")
128-
backup := mockBackupForRestore(&testCtx, actionSet.Name, testdp.BackupPVCName, true, useVolumeSnapshot)
153+
backup := mockBackupForRestore(&testCtx, actionSet.Name, testdp.BackupPVCName, true, useVolumeSnapshot, dpv1alpha1.BackupTypeFull, "", "2023-01-01T10:00:00Z", "")
129154

130155
schedulingSpec := dpv1alpha1.SchedulingSpec{
131156
NodeName: nodeName,
@@ -358,6 +383,57 @@ var _ = Describe("RestoreManager Test", func() {
358383
})).Should(Succeed())
359384
testPostReady(false)
360385
})
386+
387+
Context("BuildContinuousRestoreManager", func() {
388+
It("respects UnifyFullAndContinuousRestore annotation", func() {
389+
By("create a continuous backup")
390+
continuousBackup := mockBackupForRestore(
391+
&testCtx, actionSet.Name, testdp.BackupPVCName, true, false, dpv1alpha1.BackupTypeContinuous,
392+
"2023-01-01T09:00:00Z", "2023-01-01T12:00:00Z", "test-backup-continuous",
393+
)
394+
395+
By("create a completed backup")
396+
_ = mockBackupForRestore(&testCtx, actionSet.Name, testdp.BackupPVCName, true, false, dpv1alpha1.BackupTypeFull, "", "2023-01-01T10:00:00Z", "")
397+
398+
schedulingSpec := dpv1alpha1.SchedulingSpec{
399+
NodeName: nodeName,
400+
}
401+
402+
By("create restore")
403+
restore := testdp.NewRestoreFactory(testCtx.DefaultNamespace, testdp.RestoreName).
404+
SetBackup(continuousBackup.Name, testCtx.DefaultNamespace).
405+
SetSchedulingSpec(schedulingSpec).
406+
Create(&testCtx).
407+
SetRestoreTime("2023-01-01T11:30:00Z").
408+
Get()
409+
410+
By("create restore manager")
411+
reqCtx := getReqCtx()
412+
restoreMGR := NewRestoreManager(restore, recorder, k8sClient.Scheme(), k8sClient)
413+
backupSet, err := restoreMGR.GetBackupActionSetByNamespaced(reqCtx, k8sClient, continuousBackup.Name, testCtx.DefaultNamespace)
414+
Expect(err).ShouldNot(HaveOccurred())
415+
416+
Expect(restoreMGR.BuildContinuousRestoreManager(reqCtx, k8sClient, *backupSet)).Should(Succeed())
417+
Expect(restoreMGR.PostReadyBackupSets).Should(HaveLen(2))
418+
419+
By("set UnifyFullAndContinuousRestore annotation")
420+
Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(actionSet), func(actionset *dpv1alpha1.ActionSet) {
421+
if actionset.Annotations == nil {
422+
actionset.Annotations = make(map[string]string)
423+
}
424+
actionset.Annotations[constant.SkipBaseBackupRestoreInPitrAnnotationKey] = "true"
425+
})).Should(Succeed())
426+
427+
By("check length of backupsets")
428+
restoreMGR = NewRestoreManager(restore, recorder, k8sClient.Scheme(), k8sClient)
429+
backupSet, err = restoreMGR.GetBackupActionSetByNamespaced(reqCtx, k8sClient, continuousBackup.Name, testCtx.DefaultNamespace)
430+
Expect(err).ShouldNot(HaveOccurred())
431+
432+
Expect(restoreMGR.BuildContinuousRestoreManager(reqCtx, k8sClient, *backupSet)).Should(Succeed())
433+
Expect(restoreMGR.PostReadyBackupSets).Should(HaveLen(1))
434+
435+
})
436+
})
361437
})
362438

363439
})

0 commit comments

Comments
 (0)