github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/components/hscale_volume_populator.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package components 21 22 import ( 23 "context" 24 "fmt" 25 26 appsv1 "k8s.io/api/apps/v1" 27 corev1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/errors" 29 "k8s.io/apimachinery/pkg/types" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 33 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 34 "github.com/1aal/kubeblocks/pkg/constant" 35 "github.com/1aal/kubeblocks/pkg/controller/component" 36 "github.com/1aal/kubeblocks/pkg/controller/factory" 37 "github.com/1aal/kubeblocks/pkg/controller/plan" 38 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 39 ) 40 41 type dataClone interface { 42 // succeed check if data clone succeeded 43 succeed() (bool, error) 44 // cloneData do clone data, return objects that need to be created 45 cloneData(dataClone) ([]client.Object, error) 46 // clearTmpResources clear all the temporary resources created during data clone, return objects that need to be deleted 47 clearTmpResources() ([]client.Object, error) 48 49 checkBackupStatus() (backupStatus, error) 50 backup() ([]client.Object, error) 51 checkRestoreStatus(startingIndex int32) (backupStatus, error) 52 restore(startingIndex int32) ([]client.Object, error) 53 } 54 55 type backupStatus string 56 57 const ( 58 backupStatusNotCreated backupStatus = "NotCreated" 59 backupStatusProcessing backupStatus = "Processing" 60 backupStatusReadyToUse backupStatus = "ReadyToUse" 61 backupStatusFailed backupStatus = "Failed" 62 ) 63 64 func newDataClone(reqCtx intctrlutil.RequestCtx, 65 cli client.Client, 66 cluster *appsv1alpha1.Cluster, 67 component *component.SynthesizedComponent, 68 stsObj *appsv1.StatefulSet, 69 stsProto *appsv1.StatefulSet, 70 key types.NamespacedName) (dataClone, error) { 71 if component == nil { 72 return nil, nil 73 } 74 if component.HorizontalScalePolicy == nil { 75 return &dummyDataClone{ 76 baseDataClone{ 77 reqCtx: reqCtx, 78 cli: cli, 79 cluster: cluster, 80 component: component, 81 stsObj: stsObj, 82 stsProto: stsProto, 83 key: key, 84 }, 85 }, nil 86 } 87 if component.HorizontalScalePolicy.Type == appsv1alpha1.HScaleDataClonePolicyCloneVolume { 88 return &backupDataClone{ 89 baseDataClone{ 90 reqCtx: reqCtx, 91 cli: cli, 92 cluster: cluster, 93 component: component, 94 stsObj: stsObj, 95 stsProto: stsProto, 96 key: key, 97 }, 98 }, nil 99 } 100 // TODO: how about policy None and Snapshot? 101 return nil, nil 102 } 103 104 type baseDataClone struct { 105 reqCtx intctrlutil.RequestCtx 106 cli client.Client 107 cluster *appsv1alpha1.Cluster 108 component *component.SynthesizedComponent 109 stsObj *appsv1.StatefulSet 110 stsProto *appsv1.StatefulSet 111 key types.NamespacedName 112 } 113 114 func (d *baseDataClone) cloneData(realDataClone dataClone) ([]client.Object, error) { 115 objs := make([]client.Object, 0) 116 117 // check backup ready 118 status, err := realDataClone.checkBackupStatus() 119 if err != nil { 120 return nil, err 121 } 122 switch status { 123 case backupStatusNotCreated: 124 // create backup 125 backupObjs, err := realDataClone.backup() 126 if err != nil { 127 return nil, err 128 } 129 objs = append(objs, backupObjs...) 130 return objs, nil 131 case backupStatusProcessing: 132 // requeue to waiting for backup ready 133 return objs, nil 134 case backupStatusReadyToUse: 135 break 136 default: 137 panic(fmt.Sprintf("unexpected backup status: %s, clustre: %s, component: %s", 138 status, d.cluster.Name, d.component.Name)) 139 } 140 // backup's ready, then start to check restore 141 for i := *d.stsObj.Spec.Replicas; i < d.component.Replicas; i++ { 142 restoreStatus, err := realDataClone.checkRestoreStatus(i) 143 if err != nil { 144 return nil, err 145 } 146 switch restoreStatus { 147 case backupStatusNotCreated: 148 restoreObjs, err := realDataClone.restore(i) 149 if err != nil { 150 return nil, err 151 } 152 objs = append(objs, restoreObjs...) 153 case backupStatusProcessing: 154 case backupStatusReadyToUse: 155 continue 156 default: 157 panic(fmt.Sprintf("unexpected restore status: %s, clustre: %s, component: %s", 158 status, d.cluster.Name, d.component.Name)) 159 } 160 } 161 // create PVCs that do not need to restore 162 pvcObjs, err := d.createPVCs(d.excludeBackupVCTs()) 163 if err != nil { 164 return nil, err 165 } 166 objs = append(objs, pvcObjs...) 167 168 return objs, nil 169 } 170 171 func (d *baseDataClone) isPVCExists(pvcKey types.NamespacedName) (bool, error) { 172 pvc := corev1.PersistentVolumeClaim{} 173 if err := d.cli.Get(d.reqCtx.Ctx, pvcKey, &pvc); err != nil { 174 return false, client.IgnoreNotFound(err) 175 } 176 return true, nil 177 } 178 179 func (d *baseDataClone) checkAllPVCsExist() (bool, error) { 180 for i := *d.stsObj.Spec.Replicas; i < d.component.Replicas; i++ { 181 for _, vct := range d.component.VolumeClaimTemplates { 182 pvcKey := types.NamespacedName{ 183 Namespace: d.stsObj.Namespace, 184 Name: fmt.Sprintf("%s-%s-%d", vct.Name, d.stsObj.Name, i), 185 } 186 // check pvc existence 187 pvcExists, err := d.isPVCExists(pvcKey) 188 if err != nil { 189 return true, err 190 } 191 if !pvcExists { 192 return false, nil 193 } 194 } 195 } 196 return true, nil 197 } 198 199 func (d *baseDataClone) allVCTs() []*corev1.PersistentVolumeClaimTemplate { 200 vcts := make([]*corev1.PersistentVolumeClaimTemplate, 0) 201 for i := range d.component.VolumeClaimTemplates { 202 vcts = append(vcts, &d.component.VolumeClaimTemplates[i]) 203 } 204 return vcts 205 } 206 207 func (d *baseDataClone) backupVCT() *corev1.PersistentVolumeClaimTemplate { 208 return backupVCT(d.component) 209 } 210 211 func (d *baseDataClone) excludeBackupVCTs() []*corev1.PersistentVolumeClaimTemplate { 212 vcts := make([]*corev1.PersistentVolumeClaimTemplate, 0) 213 backupVCT := d.backupVCT() 214 for i := range d.component.VolumeClaimTemplates { 215 vct := &d.component.VolumeClaimTemplates[i] 216 if vct.Name != backupVCT.Name { 217 vcts = append(vcts, vct) 218 } 219 } 220 return vcts 221 } 222 223 func (d *baseDataClone) createPVCs(vcts []*corev1.PersistentVolumeClaimTemplate) ([]client.Object, error) { 224 objs := make([]client.Object, 0) 225 for i := *d.stsObj.Spec.Replicas; i < d.component.Replicas; i++ { 226 for _, vct := range vcts { 227 pvcKey := types.NamespacedName{ 228 Namespace: d.stsObj.Namespace, 229 Name: fmt.Sprintf("%s-%s-%d", vct.Name, d.stsObj.Name, i), 230 } 231 if exist, err := d.isPVCExists(pvcKey); err != nil { 232 return nil, err 233 } else if exist { 234 continue 235 } 236 pvc := factory.BuildPVC(d.cluster, d.component, vct, pvcKey, "") 237 objs = append(objs, pvc) 238 } 239 } 240 return objs, nil 241 } 242 243 func (d *baseDataClone) getBRLabels() map[string]string { 244 return map[string]string{ 245 constant.AppInstanceLabelKey: d.cluster.Name, 246 constant.KBAppComponentLabelKey: d.component.Name, 247 constant.KBManagedByKey: "cluster", // the resources are managed by which controller 248 } 249 } 250 251 type dummyDataClone struct { 252 baseDataClone 253 } 254 255 var _ dataClone = &dummyDataClone{} 256 257 func (d *dummyDataClone) succeed() (bool, error) { 258 return d.checkAllPVCsExist() 259 } 260 261 func (d *dummyDataClone) cloneData(dataClone) ([]client.Object, error) { 262 return d.createPVCs(d.allVCTs()) 263 } 264 265 func (d *dummyDataClone) clearTmpResources() ([]client.Object, error) { 266 return nil, nil 267 } 268 269 func (d *dummyDataClone) checkBackupStatus() (backupStatus, error) { 270 return backupStatusReadyToUse, nil 271 } 272 273 func (d *dummyDataClone) backup() ([]client.Object, error) { 274 panic("runtime error: dummyDataClone.backup called") 275 } 276 277 func (d *dummyDataClone) checkRestoreStatus(startingIndex int32) (backupStatus, error) { 278 return backupStatusReadyToUse, nil 279 } 280 281 func (d *dummyDataClone) restore(startingIndex int32) ([]client.Object, error) { 282 panic("runtime error: dummyDataClone.restore called") 283 } 284 285 type backupDataClone struct { 286 baseDataClone 287 } 288 289 var _ dataClone = &backupDataClone{} 290 291 func (d *backupDataClone) succeed() (bool, error) { 292 if len(d.component.VolumeClaimTemplates) == 0 { 293 d.reqCtx.Recorder.Eventf(d.cluster, 294 corev1.EventTypeNormal, 295 "HorizontalScale", 296 "no VolumeClaimTemplates, no need to do data clone.") 297 return true, nil 298 } 299 allPVCsExist, err := d.checkAllPVCsExist() 300 if err != nil || !allPVCsExist { 301 return allPVCsExist, err 302 } 303 for i := *d.stsObj.Spec.Replicas; i < d.component.Replicas; i++ { 304 restoreStatus, err := d.checkRestoreStatus(i) 305 if err != nil { 306 return false, err 307 } 308 if restoreStatus != backupStatusReadyToUse { 309 return false, nil 310 } 311 } 312 return true, nil 313 } 314 315 func (d *backupDataClone) clearTmpResources() ([]client.Object, error) { 316 objs := make([]client.Object, 0) 317 // delete backup 318 brLabels := d.getBRLabels() 319 backupList := dpv1alpha1.BackupList{} 320 if err := d.cli.List(d.reqCtx.Ctx, &backupList, client.InNamespace(d.cluster.Namespace), client.MatchingLabels(brLabels)); err != nil { 321 return nil, err 322 } 323 for i := range backupList.Items { 324 objs = append(objs, &backupList.Items[i]) 325 } 326 restoreList := dpv1alpha1.RestoreList{} 327 if err := d.cli.List(d.reqCtx.Ctx, &restoreList, client.InNamespace(d.cluster.Namespace), client.MatchingLabels(brLabels)); err != nil { 328 return nil, err 329 } 330 for i := range restoreList.Items { 331 objs = append(objs, &restoreList.Items[i]) 332 } 333 return objs, nil 334 } 335 336 func (d *backupDataClone) backup() ([]client.Object, error) { 337 objs := make([]client.Object, 0) 338 backupPolicyTplName := d.component.HorizontalScalePolicy.BackupPolicyTemplateName 339 backupPolicy, err := getBackupPolicyFromTemplate(d.reqCtx, d.cli, d.cluster, d.component.ComponentDef, backupPolicyTplName) 340 if err != nil { 341 return nil, err 342 } 343 if backupPolicy == nil { 344 return nil, intctrlutil.NewNotFound("not found any backup policy created by %s", backupPolicyTplName) 345 } 346 volumeSnapshotEnabled, err := isVolumeSnapshotEnabled(d.reqCtx.Ctx, d.cli, d.stsObj, backupVCT(d.component)) 347 if err != nil { 348 return nil, err 349 } 350 backupMethods := getBackupMethods(backupPolicy, volumeSnapshotEnabled) 351 if len(backupMethods) == 0 { 352 return nil, fmt.Errorf("no backup method found in backup policy %s", backupPolicy.Name) 353 } else if len(backupMethods) > 1 { 354 return nil, fmt.Errorf("more than one backup methods found in backup policy %s", backupPolicy.Name) 355 } 356 backup := factory.BuildBackup(d.cluster, d.component, backupPolicy.Name, d.key, backupMethods[0]) 357 objs = append(objs, backup) 358 return objs, nil 359 } 360 361 func (d *backupDataClone) checkBackupStatus() (backupStatus, error) { 362 backup := dpv1alpha1.Backup{} 363 if err := d.cli.Get(d.reqCtx.Ctx, d.key, &backup); err != nil { 364 if errors.IsNotFound(err) { 365 return backupStatusNotCreated, nil 366 } else { 367 return backupStatusFailed, err 368 } 369 } 370 if backup.Status.Phase == dpv1alpha1.BackupPhaseFailed { 371 return backupStatusFailed, intctrlutil.NewErrorf(intctrlutil.ErrorTypeBackupFailed, "backup for horizontalScaling failed: %s", 372 backup.Status.FailureReason) 373 } 374 if backup.Status.Phase == dpv1alpha1.BackupPhaseCompleted { 375 return backupStatusReadyToUse, nil 376 } 377 return backupStatusProcessing, nil 378 } 379 380 func (d *backupDataClone) restore(startingIndex int32) ([]client.Object, error) { 381 backup := &dpv1alpha1.Backup{} 382 if err := d.cli.Get(d.reqCtx.Ctx, d.key, backup); err != nil { 383 return nil, err 384 } 385 restoreMGR := plan.NewRestoreManager(d.reqCtx.Ctx, d.cli, d.cluster, nil, d.getBRLabels(), int32(1), startingIndex) 386 restore, err := restoreMGR.BuildPrepareDataRestore(d.component, backup) 387 if err != nil || restore == nil { 388 return nil, err 389 } 390 return []client.Object{restore}, nil 391 } 392 393 func (d *backupDataClone) checkRestoreStatus(startingIndex int32) (backupStatus, error) { 394 restoreMGR := plan.NewRestoreManager(d.reqCtx.Ctx, d.cli, d.cluster, nil, d.getBRLabels(), int32(1), startingIndex) 395 restoreMeta := restoreMGR.GetRestoreObjectMeta(d.component, dpv1alpha1.PrepareData) 396 restore := &dpv1alpha1.Restore{} 397 if err := d.cli.Get(d.reqCtx.Ctx, types.NamespacedName{Namespace: d.cluster.Namespace, Name: restoreMeta.Name}, restore); err != nil { 398 return backupStatusNotCreated, client.IgnoreNotFound(err) 399 } 400 if restore.Status.Phase == dpv1alpha1.RestorePhaseCompleted { 401 return backupStatusReadyToUse, nil 402 } 403 return backupStatusProcessing, nil 404 } 405 406 // getBackupPolicyFromTemplate gets backup policy from template policy template. 407 func getBackupPolicyFromTemplate(reqCtx intctrlutil.RequestCtx, 408 cli client.Client, 409 cluster *appsv1alpha1.Cluster, 410 componentDef, backupPolicyTemplateName string) (*dpv1alpha1.BackupPolicy, error) { 411 backupPolicyList := &dpv1alpha1.BackupPolicyList{} 412 if err := cli.List(reqCtx.Ctx, backupPolicyList, 413 client.InNamespace(cluster.Namespace), 414 client.MatchingLabels{ 415 constant.AppInstanceLabelKey: cluster.Name, 416 constant.KBAppComponentDefRefLabelKey: componentDef, 417 }); err != nil { 418 return nil, err 419 } 420 for _, backupPolicy := range backupPolicyList.Items { 421 if backupPolicy.Annotations[constant.BackupPolicyTemplateAnnotationKey] == backupPolicyTemplateName { 422 return &backupPolicy, nil 423 } 424 } 425 return nil, nil 426 } 427 428 func backupVCT(component *component.SynthesizedComponent) *corev1.PersistentVolumeClaimTemplate { 429 if len(component.VolumeClaimTemplates) == 0 { 430 return nil 431 } 432 vct := component.VolumeClaimTemplates[0] 433 for _, tmpVct := range component.VolumeClaimTemplates { 434 for _, volumeType := range component.VolumeTypes { 435 if volumeType.Type == appsv1alpha1.VolumeTypeData && volumeType.Name == tmpVct.Name { 436 vct = tmpVct 437 break 438 } 439 } 440 } 441 return &vct 442 } 443 444 func isVolumeSnapshotEnabled(ctx context.Context, cli client.Client, 445 sts *appsv1.StatefulSet, vct *corev1.PersistentVolumeClaimTemplate) (bool, error) { 446 if sts == nil || vct == nil { 447 return false, nil 448 } 449 pvcKey := types.NamespacedName{ 450 Namespace: sts.Namespace, 451 Name: fmt.Sprintf("%s-%s-%d", vct.Name, sts.Name, 0), 452 } 453 pvc := corev1.PersistentVolumeClaim{} 454 if err := cli.Get(ctx, pvcKey, &pvc); err != nil { 455 return false, client.IgnoreNotFound(err) 456 } 457 458 return intctrlutil.IsVolumeSnapshotEnabled(ctx, cli, pvc.Spec.VolumeName) 459 } 460 461 func getBackupMethods(backupPolicy *dpv1alpha1.BackupPolicy, useVolumeSnapshot bool) []string { 462 var vsMethods []string 463 var otherMethods []string 464 for _, method := range backupPolicy.Spec.BackupMethods { 465 if method.SnapshotVolumes != nil && *method.SnapshotVolumes { 466 vsMethods = append(vsMethods, method.Name) 467 } else { 468 otherMethods = append(otherMethods, method.Name) 469 } 470 } 471 if useVolumeSnapshot { 472 return vsMethods 473 } 474 return otherMethods 475 }