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  }