Skip to content

Commit fe150cd

Browse files
authored
chore: validate sharding restore and incremental backups (#9944)
1 parent 201d60b commit fe150cd

8 files changed

Lines changed: 135 additions & 32 deletions

File tree

controllers/apps/cluster/transformer_cluster_restore.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2020
package cluster
2121

2222
import (
23+
"sort"
24+
2325
"k8s.io/apimachinery/pkg/util/json"
2426
"sigs.k8s.io/controller-runtime/pkg/client"
2527

@@ -72,43 +74,56 @@ func (c *clusterRestoreTransformer) Transform(ctx graph.TransformContext, dag *g
7274
if err != nil {
7375
return err
7476
}
77+
78+
targets := backup.Status.Targets
7579
// obtain components that have already been assigned targets.
7680
allocateTargetMap := map[string]string{}
7781
restoreDoneForShardComponents := true
7882
for _, v := range shardComponents {
7983
if model.IsObjectDeleting(&v) {
8084
continue
8185
}
82-
if v.Annotations[constant.RestoreDoneAnnotationKey] != "true" {
86+
87+
compName := v.Labels[constant.KBAppComponentLabelKey]
88+
compAnnotations := c.initClusterAnnotations(compName)
89+
90+
if v.Annotations[constant.BackupSourceTargetAnnotationKey] != "" && v.Annotations[constant.RestoreDoneAnnotationKey] != "true" {
8391
restoreDoneForShardComponents = false
8492
}
8593
if targetName, ok := v.Annotations[constant.BackupSourceTargetAnnotationKey]; ok {
86-
compName := v.Labels[constant.KBAppComponentLabelKey]
8794
allocateTargetMap[targetName] = compName
88-
c.initClusterAnnotations(compName)
89-
c.annotations[compName][constant.BackupSourceTargetAnnotationKey] = targetName
95+
compAnnotations[constant.BackupSourceTargetAnnotationKey] = targetName
9096
}
9197
}
92-
if len(allocateTargetMap) == len(backup.Status.Targets) {
98+
if len(allocateTargetMap) == len(targets) {
9399
// check if the restore is completed when all source target have allocated.
94100
if err = c.cleanupRestoreAnnotationForSharding(dag, spec.Name, restoreDoneForShardComponents); err != nil {
95101
return err
96102
}
97-
continue
98103
}
99-
for _, target := range backup.Status.Targets {
104+
// guarantee that when available targets are fewer than shards, the first shards are prioritized for restore.
105+
sort.Slice(c.shardingComps[spec.Name], func(i, j int) bool {
106+
return c.shardingComps[spec.Name][i].Name < c.shardingComps[spec.Name][j].Name
107+
})
108+
for _, target := range targets {
100109
if _, ok = allocateTargetMap[target.Name]; ok {
101110
continue
102111
}
103112
for _, compSpec := range c.shardingComps[spec.Name] {
104-
if _, ok = c.annotations[compSpec.Name][constant.BackupSourceTargetAnnotationKey]; ok {
113+
compAnnotations := c.initClusterAnnotations(compSpec.Name)
114+
if _, ok = compAnnotations[constant.BackupSourceTargetAnnotationKey]; ok {
105115
continue
106116
}
107-
c.initClusterAnnotations(compSpec.Name)
108-
c.annotations[compSpec.Name][constant.BackupSourceTargetAnnotationKey] = target.Name
117+
compAnnotations[constant.BackupSourceTargetAnnotationKey] = target.Name
109118
break
110119
}
111120
}
121+
for _, compSpec := range c.shardingComps[spec.Name] {
122+
compAnnotations := c.initClusterAnnotations(compSpec.Name)
123+
if compAnnotations[constant.BackupSourceTargetAnnotationKey] == "" {
124+
compAnnotations[constant.SkipRestoreAnnotationKey] = "true"
125+
}
126+
}
112127
}
113128
// if component needs to do post ready restore after cluster is running, annotate component
114129
if c.Cluster.Status.Phase == appsv1.RunningClusterPhase {
@@ -132,13 +147,14 @@ func (c *clusterRestoreTransformer) Transform(ctx graph.TransformContext, dag *g
132147
return nil
133148
}
134149

135-
func (c *clusterRestoreTransformer) initClusterAnnotations(compName string) {
150+
func (c *clusterRestoreTransformer) initClusterAnnotations(compName string) map[string]string {
136151
if c.annotations == nil {
137-
c.annotations = map[string]map[string]string{}
152+
c.annotations = make(map[string]map[string]string)
138153
}
139154
if c.annotations[compName] == nil {
140-
c.annotations[compName] = map[string]string{}
155+
c.annotations[compName] = make(map[string]string)
141156
}
157+
return c.annotations[compName]
142158
}
143159

144160
func (c *clusterRestoreTransformer) cleanupRestoreAnnotationForSharding(dag *graph.DAG,

controllers/dataprotection/backup_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,7 @@ func prepare4Incremental(request *dpbackup.Request) (*dpbackup.Request, error) {
10781078
return nil, fmt.Errorf("backupRepo for incremental backup can't be empty")
10791079
}
10801080
// get and validate parent backup
1081-
parentBackup, err := GetParentBackup(request.Ctx, request.Client, request.Backup, request.BackupMethod, request.BackupRepo.Name)
1081+
parentBackup, err := GetParentBackup(request.Ctx, request.Client, request.Backup, request.BackupMethod, request.BackupPolicy, request.BackupRepo.Name)
10821082
if err != nil {
10831083
return nil, err
10841084
}

controllers/dataprotection/backup_controller_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,13 @@ var _ = Describe("Backup Controller test", func() {
681681
Phase: dpv1alpha1.BackupPhaseCompleted,
682682
CompletionTimestamp: &metav1.Time{Time: time.Now().Add(-time.Hour * 24)},
683683
BackupRepoName: testdp.BackupRepoName,
684+
Target: &dpv1alpha1.BackupStatusTarget{
685+
BackupTarget: dpv1alpha1.BackupTarget{
686+
PodSelector: &dpv1alpha1.PodSelector{
687+
Strategy: dpv1alpha1.PodSelectionStrategyAny,
688+
},
689+
},
690+
},
684691
EncryptionConfig: &dpv1alpha1.EncryptionConfig{
685692
Algorithm: "AES-256-CFB",
686693
PassPhraseSecretKeyRef: &corev1.SecretKeySelector{
@@ -915,6 +922,13 @@ var _ = Describe("Backup Controller test", func() {
915922
BackupRepoName: repoName,
916923
ParentBackupName: parentBackup,
917924
BaseBackupName: baseBackup,
925+
Target: &dpv1alpha1.BackupStatusTarget{
926+
BackupTarget: dpv1alpha1.BackupTarget{
927+
PodSelector: &dpv1alpha1.PodSelector{
928+
Strategy: dpv1alpha1.PodSelectionStrategyAny,
929+
},
930+
},
931+
},
918932
TimeRange: &dpv1alpha1.BackupTimeRange{
919933
Start: step(),
920934
End: step(),

controllers/dataprotection/backuppolicydriver_controller.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,17 @@ func (r *backupPolicyAndScheduleBuilder) transformBackupPolicy() (*dpv1alpha1.Ba
218218
Annotations: r.buildAnnotations(),
219219
},
220220
}
221-
r.buildBackupPolicy(backupPolicy)
221+
if err := r.buildBackupPolicy(backupPolicy); err != nil {
222+
return nil, err
223+
}
222224
if err := controllerutil.SetControllerReference(r.Cluster, backupPolicy, r.schema); err != nil {
223225
return nil, err
224226
}
225227
return backupPolicy, r.Client.Create(r.Context, backupPolicy)
226228
}
227-
r.buildBackupPolicy(backupPolicy)
229+
if err := r.buildBackupPolicy(backupPolicy); err != nil {
230+
return nil, err
231+
}
228232
return backupPolicy, r.Client.Update(r.Context, backupPolicy)
229233
}
230234

@@ -325,7 +329,7 @@ func (r *backupPolicyAndScheduleBuilder) syncBackupSchedule(backupSchedule *dpv1
325329
}
326330

327331
// buildBackupPolicy builds a new backup policy by the backup policy template.
328-
func (r *backupPolicyAndScheduleBuilder) buildBackupPolicy(backupPolicy *dpv1alpha1.BackupPolicy) {
332+
func (r *backupPolicyAndScheduleBuilder) buildBackupPolicy(backupPolicy *dpv1alpha1.BackupPolicy) error {
329333
bpSpec := &backupPolicy.Spec
330334
// if cluster have backup repo, set backup repo name to backup policy.
331335
if r.Cluster.Spec.Backup != nil && r.Cluster.Spec.Backup.RepoName != "" {
@@ -338,17 +342,24 @@ func (r *backupPolicyAndScheduleBuilder) buildBackupPolicy(backupPolicy *dpv1alp
338342
intctrlutil.MergeMetadataMapInplace(r.buildAnnotations(), &backupPolicy.Annotations)
339343
intctrlutil.MergeMetadataMapInplace(r.buildLabels(), &backupPolicy.Labels)
340344

341-
r.buildBackupMethods(backupPolicy)
345+
if err := r.buildBackupMethods(backupPolicy); err != nil {
346+
return err
347+
}
342348

343349
if needSyncFromTemplate(backupPolicy) {
344350
bpSpec.BackoffLimit = r.backupPolicyTPL.Spec.BackoffLimit
345351
bpSpec.RetentionPolicy = r.backupPolicyTPL.Spec.RetentionPolicy
346352
if r.isSharding {
347-
bpSpec.Targets = r.buildBackupTargets(backupPolicy.Spec.Targets)
353+
targets, err := r.buildBackupTargets(backupPolicy.Spec.Targets)
354+
if err != nil {
355+
return err
356+
}
357+
bpSpec.Targets = targets
348358
} else {
349359
bpSpec.Target = r.buildBackupTarget(backupPolicy.Spec.Target, r.backupPolicyTPL.Spec.Target, r.componentName)
350360
}
351361
}
362+
return nil
352363
}
353364

354365
func (r *backupPolicyAndScheduleBuilder) setDefaultEncryptionConfig(backupPolicy *dpv1alpha1.BackupPolicy) {
@@ -400,7 +411,7 @@ func (r *backupPolicyAndScheduleBuilder) syncRoleLabelSelectorWhenReplicaChanges
400411
}
401412

402413
// buildBackupMethods build the backupMethod of tpl to backupPolicy.
403-
func (r *backupPolicyAndScheduleBuilder) buildBackupMethods(backupPolicy *dpv1alpha1.BackupPolicy) {
414+
func (r *backupPolicyAndScheduleBuilder) buildBackupMethods(backupPolicy *dpv1alpha1.BackupPolicy) error {
404415
var backupMethods []dpv1alpha1.BackupMethod
405416
oldBackupMethodMap := map[string]dpv1alpha1.BackupMethod{}
406417
for _, v := range backupPolicy.Spec.BackupMethods {
@@ -421,7 +432,11 @@ func (r *backupPolicyAndScheduleBuilder) buildBackupMethods(backupPolicy *dpv1al
421432
}
422433
if backupMethodTPL.Target != nil {
423434
if r.isSharding {
424-
backupMethod.Targets = r.buildBackupTargets(backupMethod.Targets)
435+
targets, err := r.buildBackupTargets(backupMethod.Targets)
436+
if err != nil {
437+
return err
438+
}
439+
backupMethod.Targets = targets
425440
} else {
426441
backupMethod.Target = r.buildBackupTarget(backupMethod.Target, *backupMethodTPL.Target, r.componentName)
427442
}
@@ -430,6 +445,7 @@ func (r *backupPolicyAndScheduleBuilder) buildBackupMethods(backupPolicy *dpv1al
430445
backupMethods = append(backupMethods, backupMethod)
431446
}
432447
backupPolicy.Spec.BackupMethods = backupMethods
448+
return nil
433449
}
434450

435451
func (r *backupPolicyAndScheduleBuilder) resolveBackupMethodEnv(compSpec *appsv1.ClusterComponentSpec, envs []dpv1alpha1.EnvVar) []corev1.EnvVar {
@@ -460,8 +476,15 @@ func (r *backupPolicyAndScheduleBuilder) matchMappingName(names []string, target
460476
return false
461477
}
462478

463-
func (r *backupPolicyAndScheduleBuilder) buildBackupTargets(targets []dpv1alpha1.BackupTarget) []dpv1alpha1.BackupTarget {
464-
shardComponents, _ := sharding.ListShardingComponents(r.Context, r.Client, r.Cluster, r.componentName)
479+
func (r *backupPolicyAndScheduleBuilder) buildBackupTargets(targets []dpv1alpha1.BackupTarget) ([]dpv1alpha1.BackupTarget, error) {
480+
shardComponents, err := sharding.ListShardingComponents(r.Context, r.Client, r.Cluster, r.componentName)
481+
if err != nil {
482+
return nil, err
483+
}
484+
if len(shardComponents) == 0 {
485+
return nil, fmt.Errorf("sharding components %s not found", r.componentName)
486+
}
487+
465488
sourceTargetMap := map[string]*dpv1alpha1.BackupTarget{}
466489
for i := range targets {
467490
sourceTargetMap[targets[i].Name] = &targets[i]
@@ -474,7 +497,7 @@ func (r *backupPolicyAndScheduleBuilder) buildBackupTargets(targets []dpv1alpha1
474497
backupTargets = append(backupTargets, *target)
475498
}
476499
}
477-
return backupTargets
500+
return backupTargets, nil
478501
}
479502

480503
func (r *backupPolicyAndScheduleBuilder) buildBackupTarget(

controllers/dataprotection/gc_controller_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ var _ = Describe("Data Protection Garbage Collection Controller", func() {
8888
Expiration: &now,
8989
StartTimestamp: &now,
9090
CompletionTimestamp: &now,
91+
Target: &dpv1alpha1.BackupStatusTarget{
92+
BackupTarget: dpv1alpha1.BackupTarget{
93+
PodSelector: &dpv1alpha1.PodSelector{
94+
Strategy: dpv1alpha1.PodSelectionStrategyAny,
95+
},
96+
},
97+
},
9198
}
9299
autoBackupLabel = map[string]string{
93100
dptypes.AutoBackupLabelKey: "true",
@@ -199,6 +206,13 @@ var _ = Describe("Data Protection Garbage Collection Controller", func() {
199206
olderBackup.Status.CompletionTimestamp = &metav1.Time{Time: expiredTime.Time.Add(-time.Hour * 3)}
200207
olderBackup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
201208
olderBackup.Status.BackupRepoName = testdp.BackupRepoName
209+
olderBackup.Status.Target = &dpv1alpha1.BackupStatusTarget{
210+
BackupTarget: dpv1alpha1.BackupTarget{
211+
PodSelector: &dpv1alpha1.PodSelector{
212+
Strategy: dpv1alpha1.PodSelectionStrategyAny,
213+
},
214+
},
215+
}
202216
testdp.PatchBackupStatus(&testCtx, olderKey, olderBackup.Status)
203217

204218
By("the older full backup should be not deleted, it is the latest backup for now")
@@ -221,6 +235,13 @@ var _ = Describe("Data Protection Garbage Collection Controller", func() {
221235
incrementalBackup.Status.StartTimestamp = &metav1.Time{Time: expiredTime.Time.Add(-time.Hour * 2)}
222236
incrementalBackup.Status.CompletionTimestamp = &metav1.Time{Time: expiredTime.Time.Add(-time.Hour * 2)}
223237
incrementalBackup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
238+
incrementalBackup.Status.Target = &dpv1alpha1.BackupStatusTarget{
239+
BackupTarget: dpv1alpha1.BackupTarget{
240+
PodSelector: &dpv1alpha1.PodSelector{
241+
Strategy: dpv1alpha1.PodSelectionStrategyAny,
242+
},
243+
},
244+
}
224245
testdp.PatchBackupStatus(&testCtx, incrementalKey, incrementalBackup.Status)
225246

226247
By("the incremental backup should be not deleted, its parent is the latest backup for now")
@@ -243,6 +264,13 @@ var _ = Describe("Data Protection Garbage Collection Controller", func() {
243264
latestBackup.Status.StartTimestamp = &metav1.Time{Time: expiredTime.Time.Add(-time.Hour)}
244265
latestBackup.Status.CompletionTimestamp = &metav1.Time{Time: expiredTime.Time.Add(-time.Hour)}
245266
latestBackup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
267+
latestBackup.Status.Target = &dpv1alpha1.BackupStatusTarget{
268+
BackupTarget: dpv1alpha1.BackupTarget{
269+
PodSelector: &dpv1alpha1.PodSelector{
270+
Strategy: dpv1alpha1.PodSelectionStrategyAny,
271+
},
272+
},
273+
}
246274
testdp.PatchBackupStatus(&testCtx, latestKey, latestBackup.Status)
247275

248276
By("verify the latest full backup is retained while older is deleted")

controllers/dataprotection/utils.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ func fromFlattenName(flatten string) (name string, namespace string) {
590590
// then validate and return the parent backup.
591591
// If parentBackupName is not specified, find the latest valid parent backup.
592592
func GetParentBackup(ctx context.Context, cli client.Client, backup *dpv1alpha1.Backup,
593-
backupMethod *dpv1alpha1.BackupMethod, backupRepoName string) (*dpv1alpha1.Backup, error) {
593+
backupMethod *dpv1alpha1.BackupMethod, backupPolicy *dpv1alpha1.BackupPolicy, backupRepoName string) (*dpv1alpha1.Backup, error) {
594594
if backup == nil || backupMethod == nil {
595595
return nil, fmt.Errorf("backup or backupMethod is nil")
596596
}
@@ -616,16 +616,23 @@ func GetParentBackup(ctx context.Context, cli client.Client, backup *dpv1alpha1.
616616
}, parentBackup); err != nil {
617617
return nil, err
618618
}
619-
if err := ValidateParentBackup(ctx, cli, backup, parentBackup, backupMethod, backupRepoName); err != nil {
619+
if err := ValidateParentBackup(ctx, cli, backup, parentBackup, backupMethod, backupPolicy, backupRepoName); err != nil {
620620
return nil, fmt.Errorf("failed to validate parent backup %s: %w", parentBackupName, err)
621621
}
622622
return parentBackup, nil
623623
}
624-
parentBackup, err := FindParentBackupIfNotSet(ctx, cli, backup, backupMethod, backupRepoName)
624+
parentBackup, err := FindParentBackupIfNotSet(ctx, cli, backup, backupMethod, backupPolicy, backupRepoName)
625625
if err != nil {
626626
return nil, fmt.Errorf("failed to find parent backup: %w", err)
627627
}
628628
if parentBackup == nil {
629+
// output sharding message
630+
expectedTargets := dputils.GetBackupTargets(backupPolicy, backupMethod)
631+
if len(expectedTargets) > 0 {
632+
return nil, fmt.Errorf("failed to find a valid parent backup for backup %s/%s: "+
633+
"current backup expects %d shards, this may be due to shard count mismatch with existing backups",
634+
backup.Namespace, backup.Name, len(expectedTargets))
635+
}
629636
return nil, fmt.Errorf("failed to find a valid parent backup for backup %s/%s", backup.Namespace, backup.Name)
630637
}
631638
return parentBackup, nil
@@ -637,7 +644,7 @@ func GetParentBackup(ctx context.Context, cli client.Client, backup *dpv1alpha1.
637644
// b. return the latest incremental backup.
638645
// c. return the latest full backup if incremental backups are not found.
639646
func FindParentBackupIfNotSet(ctx context.Context, cli client.Client, backup *dpv1alpha1.Backup,
640-
backupMethod *dpv1alpha1.BackupMethod, backupRepoName string) (*dpv1alpha1.Backup, error) {
647+
backupMethod *dpv1alpha1.BackupMethod, backupPolicy *dpv1alpha1.BackupPolicy, backupRepoName string) (*dpv1alpha1.Backup, error) {
641648
getLatestBackup := func(backupList []*dpv1alpha1.Backup) *dpv1alpha1.Backup {
642649
if len(backupList) == 0 {
643650
return nil
@@ -655,7 +662,7 @@ func FindParentBackupIfNotSet(ctx context.Context, cli client.Client, backup *dp
655662
client.MatchingLabels(labels)); err != nil && !apierrors.IsNotFound(err) {
656663
return nil, err
657664
}
658-
filteredbackupList := FilterParentBackups(ctx, cli, backupList, backup, backupMethod, incremental, backupRepoName)
665+
filteredbackupList := FilterParentBackups(ctx, cli, backupList, backup, backupMethod, backupPolicy, incremental, backupRepoName)
659666
return getLatestBackup(filteredbackupList), nil
660667
}
661668

@@ -701,13 +708,13 @@ func FindParentBackupIfNotSet(ctx context.Context, cli client.Client, backup *dp
701708

702709
// FilterParentBackups filters the parent backups by backup phase, backup method and end time.
703710
func FilterParentBackups(ctx context.Context, cli client.Client, backupList *dpv1alpha1.BackupList, targetBackup *dpv1alpha1.Backup,
704-
backupMethod *dpv1alpha1.BackupMethod, incremental bool, backupRepoName string) []*dpv1alpha1.Backup {
711+
backupMethod *dpv1alpha1.BackupMethod, backupPolicy *dpv1alpha1.BackupPolicy, incremental bool, backupRepoName string) []*dpv1alpha1.Backup {
705712
var res []*dpv1alpha1.Backup
706713
if backupList == nil || len(backupList.Items) == 0 {
707714
return res
708715
}
709716
for i, backup := range backupList.Items {
710-
if err := ValidateParentBackup(ctx, cli, targetBackup, &backup, backupMethod, backupRepoName); err != nil {
717+
if err := ValidateParentBackup(ctx, cli, targetBackup, &backup, backupMethod, backupPolicy, backupRepoName); err != nil {
711718
continue
712719
}
713720
// backups are listed by backup type label, validate if the backup method matches
@@ -728,7 +735,7 @@ func FilterParentBackups(ctx context.Context, cli client.Client, backupList *dpv
728735

729736
// ValidateParentBackup validates the parent backup.
730737
func ValidateParentBackup(ctx context.Context, cli client.Client, backup *dpv1alpha1.Backup, parentBackup *dpv1alpha1.Backup,
731-
backupMethod *dpv1alpha1.BackupMethod, backupRepoName string) error {
738+
backupMethod *dpv1alpha1.BackupMethod, backupPolicy *dpv1alpha1.BackupPolicy, backupRepoName string) error {
732739
// validate parent backup is completed
733740
if parentBackup.Status.Phase != dpv1alpha1.BackupPhaseCompleted {
734741
return fmt.Errorf("parent backup %s/%s is not completed", parentBackup.Namespace, parentBackup.Name)
@@ -759,6 +766,17 @@ func ValidateParentBackup(ctx context.Context, cli client.Client, backup *dpv1al
759766
parentBackup.Namespace, parentBackup.Name, err)
760767
}
761768
}
769+
// validate target count consistency for incremental backup
770+
expectedTargets := dputils.GetBackupTargets(backupPolicy, backupMethod)
771+
parentTargetCount := len(parentBackup.Status.Targets)
772+
if parentBackup.Status.Target != nil {
773+
parentTargetCount = 1
774+
}
775+
if parentTargetCount != len(expectedTargets) {
776+
return fmt.Errorf("parent backup %s/%s has %d targets, but current backup expects %d targets; "+
777+
"incremental backup requires consistent targets, it may happen by shards changed",
778+
parentBackup.Namespace, parentBackup.Name, parentTargetCount, len(expectedTargets))
779+
}
762780
return nil
763781
}
764782

0 commit comments

Comments
 (0)