Skip to content

Commit dcc8d64

Browse files
authored
chore: resolve continuous backup base for annotation restore (#10318)
1 parent 8824572 commit dcc8d64

2 files changed

Lines changed: 137 additions & 1 deletion

File tree

pkg/controller/plan/restore.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3131
k8sruntime "k8s.io/apimachinery/pkg/runtime"
3232
"k8s.io/apimachinery/pkg/util/json"
33+
"k8s.io/client-go/tools/record"
3334
"sigs.k8s.io/controller-runtime/pkg/client"
3435
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3536

@@ -40,6 +41,7 @@ import (
4041
"github.com/apecloud/kubeblocks/pkg/controller/factory"
4142
"github.com/apecloud/kubeblocks/pkg/controller/instanceset"
4243
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
44+
dprestore "github.com/apecloud/kubeblocks/pkg/dataprotection/restore"
4345
dputils "github.com/apecloud/kubeblocks/pkg/dataprotection/utils"
4446
)
4547

@@ -99,7 +101,11 @@ func (r *RestoreManager) DoRestore(comp *component.SynthesizedComponent, compObj
99101
if backupObj.Status.BackupMethod == nil {
100102
return intctrlutil.NewErrorf(intctrlutil.ErrorTypeRestoreFailed, `status.backupMethod of backup "%s" can not be empty`, backupObj.Name)
101103
}
102-
if err = r.DoPrepareData(comp, compObj, backupObj); err != nil {
104+
prepareDataBackupObj, err := r.resolvePrepareDataBackup(backupObj)
105+
if err != nil {
106+
return err
107+
}
108+
if err = r.DoPrepareData(comp, compObj, prepareDataBackupObj); err != nil {
103109
return err
104110
}
105111
if compObj.Status.Phase != appsv1.RunningComponentPhase {
@@ -124,6 +130,44 @@ func (r *RestoreManager) DoRestore(comp *component.SynthesizedComponent, compObj
124130
return r.cleanupRestoreAnnotations(comp.Name)
125131
}
126132

133+
func (r *RestoreManager) resolvePrepareDataBackup(backupObj *dpv1alpha1.Backup) (*dpv1alpha1.Backup, error) {
134+
if backupObj.Status.BackupMethod.TargetVolumes != nil {
135+
return backupObj, nil
136+
}
137+
restore := &dpv1alpha1.Restore{
138+
ObjectMeta: metav1.ObjectMeta{
139+
Name: r.Cluster.Name + "-annotation-restore-preview",
140+
Namespace: r.Cluster.Namespace,
141+
},
142+
Spec: dpv1alpha1.RestoreSpec{
143+
Backup: dpv1alpha1.BackupRef{
144+
Name: backupObj.Name,
145+
Namespace: r.namespace,
146+
},
147+
RestoreTime: r.RestoreTime,
148+
},
149+
}
150+
reqCtx := intctrlutil.RequestCtx{
151+
Ctx: r.Ctx,
152+
Recorder: record.NewFakeRecorder(16),
153+
}
154+
restoreMGR := dprestore.NewRestoreManager(restore, reqCtx.Recorder, r.Scheme, r.Client)
155+
backupSet, err := restoreMGR.GetBackupActionSetByNamespaced(reqCtx, r.Client, backupObj.Name, r.namespace)
156+
if err != nil {
157+
return nil, err
158+
}
159+
if backupSet.ActionSet == nil || backupSet.ActionSet.Spec.BackupType != dpv1alpha1.BackupTypeContinuous {
160+
return backupObj, nil
161+
}
162+
if err := restoreMGR.BuildContinuousRestoreManager(reqCtx, r.Client, *backupSet); err != nil {
163+
return nil, err
164+
}
165+
if len(restoreMGR.PrepareDataBackupSets) == 0 {
166+
return backupObj, nil
167+
}
168+
return restoreMGR.PrepareDataBackupSets[0].Backup, nil
169+
}
170+
127171
func (r *RestoreManager) DoPrepareData(comp *component.SynthesizedComponent,
128172
compObj *appsv1.Component,
129173
backupObj *dpv1alpha1.Backup) error {

pkg/controller/plan/restore_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/apecloud/kubeblocks/pkg/constant"
3939
"github.com/apecloud/kubeblocks/pkg/controller/component"
4040
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
41+
dptypes "github.com/apecloud/kubeblocks/pkg/dataprotection/types"
4142
"github.com/apecloud/kubeblocks/pkg/generics"
4243
testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps"
4344
testdp "github.com/apecloud/kubeblocks/pkg/testutil/dataprotection"
@@ -257,6 +258,97 @@ var _ = Describe("Restore", func() {
257258
g.Expect(tmpCluster.Annotations[constant.RestoreFromBackupAnnotationKey]).Should(BeEmpty())
258259
})).Should(Succeed())
259260
})
261+
262+
It("creates prepareData from the base backup when annotation restore references a continuous backup", func() {
263+
continuousActionSetName := "continuous-actionset-" + randomStr
264+
continuousActionSet := testapps.CreateCustomizedObj(&testCtx, "backup/actionset.yaml",
265+
&dpv1alpha1.ActionSet{}, testapps.WithName(continuousActionSetName), func(actionSet *dpv1alpha1.ActionSet) {
266+
actionSet.Spec.BackupType = dpv1alpha1.BackupTypeContinuous
267+
})
268+
Expect(continuousActionSet.Name).Should(Equal(continuousActionSetName))
269+
270+
baseStatus := backup.Status
271+
baseStatus.BackupMethod.ActionSetName = fullBackupActionSetName
272+
baseStatus.TimeRange = &dpv1alpha1.BackupTimeRange{
273+
Start: baseStatus.StartTimestamp,
274+
End: baseStatus.CompletionTimestamp,
275+
}
276+
patchBackupStatus(baseStatus, client.ObjectKeyFromObject(backup))
277+
Expect(testapps.ChangeObj(&testCtx, backup, func(baseBackup *dpv1alpha1.Backup) {
278+
baseBackup.Labels[dptypes.BackupTypeLabelKey] = string(dpv1alpha1.BackupTypeFull)
279+
baseBackup.Labels[dptypes.BackupPolicyLabelKey] = testdp.BackupPolicyName
280+
})).Should(Succeed())
281+
282+
restoreTime := now.Add(time.Minute * 30)
283+
continuousBackupName := "continuous-backup-" + randomStr
284+
continuousBackup := testdp.NewBackupFactory(testCtx.DefaultNamespace, continuousBackupName).
285+
SetLabels(map[string]string{
286+
constant.AppInstanceLabelKey: sourceCluster,
287+
constant.KBAppComponentLabelKey: defaultCompName,
288+
dptypes.BackupTypeLabelKey: string(dpv1alpha1.BackupTypeContinuous),
289+
dptypes.BackupPolicyLabelKey: testdp.BackupPolicyName,
290+
}).
291+
SetBackupPolicyName(testdp.BackupPolicyName).
292+
SetBackupMethod(testdp.ContinuousMethodName).
293+
Create(&testCtx).GetObject()
294+
continuousStart := &metav1.Time{Time: now.Time}
295+
continuousStop := &metav1.Time{Time: now.Add(time.Hour)}
296+
continuousStatus := dpv1alpha1.BackupStatus{
297+
Phase: dpv1alpha1.BackupPhaseCompleted,
298+
StartTimestamp: continuousStart,
299+
CompletionTimestamp: continuousStop,
300+
TimeRange: &dpv1alpha1.BackupTimeRange{
301+
Start: continuousStart,
302+
End: continuousStop,
303+
},
304+
BackupMethod: &dpv1alpha1.BackupMethod{
305+
Name: testdp.ContinuousMethodName,
306+
ActionSetName: continuousActionSetName,
307+
},
308+
}
309+
patchBackupStatus(continuousStatus, client.ObjectKeyFromObject(continuousBackup))
310+
311+
By("restore from a continuous backup annotation")
312+
restoreFromBackup := fmt.Sprintf(`{"%s": {"name":"%s", "restoreTime":"%s"}}`,
313+
defaultCompName, continuousBackup.Name, restoreTime.UTC().Format(time.RFC3339))
314+
Expect(testapps.ChangeObj(&testCtx, cluster, func(tmpCluster *appsv1.Cluster) {
315+
tmpCluster.Annotations = map[string]string{
316+
constant.RestoreFromBackupAnnotationKey: restoreFromBackup,
317+
}
318+
})).Should(Succeed())
319+
320+
restoreMGR := NewRestoreManager(ctx, k8sClient, cluster, scheme.Scheme, nil, 3, 0)
321+
err := restoreMGR.DoRestore(synthesizedComponent, compObj, true)
322+
Expect(intctrlutil.IsTargetError(err, intctrlutil.ErrorTypeNeedWaiting)).Should(BeTrue())
323+
324+
By("checking prepareData is created from the resolved base backup")
325+
restoreMeta := restoreMGR.GetRestoreObjectMeta(synthesizedComponent, dpv1alpha1.PrepareData, "")
326+
Eventually(testapps.CheckObj(&testCtx, types.NamespacedName{Name: restoreMeta.Name, Namespace: restoreMeta.Namespace}, func(g Gomega, restore *dpv1alpha1.Restore) {
327+
g.Expect(restore.Spec.Backup.Name).Should(Equal(backup.Name))
328+
})).Should(Succeed())
329+
330+
By("checking postReady still replays from the continuous backup")
331+
Expect(testapps.GetAndChangeObjStatus(&testCtx, types.NamespacedName{Name: restoreMeta.Name, Namespace: restoreMeta.Namespace}, func(restore *dpv1alpha1.Restore) {
332+
restore.Status.Phase = dpv1alpha1.RestorePhaseCompleted
333+
})()).Should(Succeed())
334+
Expect(testapps.ChangeObjStatus(&testCtx, cluster, func() {
335+
cluster.Status.Phase = appsv1.RunningClusterPhase
336+
cluster.Status.Components = map[string]appsv1.ClusterComponentStatus{
337+
defaultCompName: {
338+
Phase: appsv1.RunningComponentPhase,
339+
},
340+
}
341+
})).Should(Succeed())
342+
Expect(testapps.ChangeObjStatus(&testCtx, compObj, func() {
343+
compObj.Status.Phase = appsv1.RunningComponentPhase
344+
})).Should(Succeed())
345+
346+
_ = restoreMGR.DoRestore(synthesizedComponent, compObj, true)
347+
restoreMeta = restoreMGR.GetRestoreObjectMeta(synthesizedComponent, dpv1alpha1.PostReady, "")
348+
Eventually(testapps.CheckObj(&testCtx, types.NamespacedName{Name: restoreMeta.Name, Namespace: restoreMeta.Namespace}, func(g Gomega, restore *dpv1alpha1.Restore) {
349+
g.Expect(restore.Spec.Backup.Name).Should(Equal(continuousBackup.Name))
350+
})).Should(Succeed())
351+
})
260352
})
261353
Context("Cluster InstanceTemplate ranges Restore", func() {
262354
const (

0 commit comments

Comments
 (0)