Skip to content

Commit

Permalink
feat: set default backup method and validate the backup method which …
Browse files Browse the repository at this point in the history
…set by user
  • Loading branch information
fengluodb committed Oct 12, 2023
1 parent ff0c4f7 commit 9f3221f
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 9 deletions.
72 changes: 65 additions & 7 deletions internal/cli/cmd/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"context"
"encoding/json"
"fmt"
dptypes "github.com/apecloud/kubeblocks/internal/dataprotection/types"
"github.com/apecloud/kubeblocks/internal/dataprotection/utils/boolptr"
"io"
"math"
"net/http"
Expand Down Expand Up @@ -1403,13 +1405,19 @@ func (o *CreateOptions) buildBackupConfig(cls *appsv1alpha1.Cluster) error {

// must set backup method when set backup config in cli
if len(flags) > 0 {
var methodRequiredErr error
if o.BackupConfig == nil {
o.BackupConfig = &appsv1alpha1.ClusterBackup{}
}
// if the backup method is not exist and the user does not set the backup method, return error

// get default backup method and all backup methods
defaultBackupMethod, backupMethodsMap, err := getBackupMethodsFromBackupPolicyTemplates(o.Dynamic, o.ClusterDefRef)
if err != nil {
return err
}

// if backup method is empty in backup config, use the default backup method
if o.BackupConfig.Method == "" {
methodRequiredErr = fmt.Errorf("backup method is required, please use --backup-method to set it")
o.BackupConfig.Method = defaultBackupMethod
}

// if the flag is set by user, use the flag value
Expand All @@ -1420,8 +1428,10 @@ func (o *CreateOptions) buildBackupConfig(cls *appsv1alpha1.Cluster) error {
case "backup-retention-period":
o.BackupConfig.RetentionPeriod = dpv1alpha1.RetentionPeriod(o.BackupRetentionPeriod)
case "backup-method":
if _, ok := backupMethodsMap[o.BackupMethod]; !ok {
return fmt.Errorf("backup method %s is not supported, please view supported backup methods by \"kbcli cd describe %s\"", o.BackupMethod, o.ClusterDefRef)
}
o.BackupConfig.Method = o.BackupMethod
methodRequiredErr = nil
case "backup-cron-expression":
if _, err := cron.ParseStandard(o.BackupCronExpression); err != nil {
return fmt.Errorf("invalid cron expression: %s, please see https://en.wikipedia.org/wiki/Cron", o.BackupCronExpression)
Expand All @@ -1435,14 +1445,62 @@ func (o *CreateOptions) buildBackupConfig(cls *appsv1alpha1.Cluster) error {
o.BackupConfig.PITREnabled = &o.BackupPITREnabled
}
}
if methodRequiredErr != nil {
return methodRequiredErr
}
}

return nil
}

// get backup methods from backup policy template
// if method's snapshotVolumes is true, use the method as default method
func getBackupMethodsFromBackupPolicyTemplates(dynamic dynamic.Interface, clusterDefRef string) (string, map[string]struct{}, error) {
var backupPolicyTemplates []appsv1alpha1.BackupPolicyTemplate
var defaultBackupPolicyTemplate appsv1alpha1.BackupPolicyTemplate

obj, err := dynamic.Resource(types.BackupPolicyTemplateGVR()).List(context.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", constant.ClusterDefLabelKey, clusterDefRef),
})
if err != nil {
return "", nil, err
}
for _, item := range obj.Items {
var backupPolicyTemplate appsv1alpha1.BackupPolicyTemplate
err = runtime.DefaultUnstructuredConverter.FromUnstructured(item.Object, &backupPolicyTemplate)
if err != nil {
return "", nil, err
}
backupPolicyTemplates = append(backupPolicyTemplates, backupPolicyTemplate)
}

if len(backupPolicyTemplates) == 0 {
return "", nil, fmt.Errorf("failed to find backup policy template for cluster definition %s", clusterDefRef)
}
// if there is only one backup policy template, use it as default backup policy template
if len(backupPolicyTemplates) == 1 {
defaultBackupPolicyTemplate = backupPolicyTemplates[0]
}
for _, backupPolicyTemplate := range backupPolicyTemplates {
if backupPolicyTemplate.Annotations[dptypes.DefaultBackupPolicyTemplateAnnotationKey] == "true" {
defaultBackupPolicyTemplate = backupPolicyTemplate
break
}
}

var defaultBackupMethod string
var backupMethodsMap = make(map[string]struct{})
for _, policy := range defaultBackupPolicyTemplate.Spec.BackupPolicies {
for _, method := range policy.BackupMethods {
if boolptr.IsSetToTrue(method.SnapshotVolumes) {
defaultBackupMethod = method.Name
}
backupMethodsMap[method.Name] = struct{}{}
}
}
if defaultBackupMethod == "" {
return "", nil, fmt.Errorf("failed to find default backup method which snapshotVolumes is true, please check backup policy template for cluster definition %s", clusterDefRef)
}
return defaultBackupMethod, backupMethodsMap, nil
}

// parse the cluster component spec
// compatible with old file format that only specifies the components
func parseClusterComponentSpec(compByte []byte) ([]appsv1alpha1.ClusterComponentSpec, error) {
Expand Down
61 changes: 59 additions & 2 deletions internal/cli/cmd/cluster/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import (
"encoding/csv"
"encoding/json"
"fmt"
"github.com/apecloud/kubeblocks/internal/constant"
dptypes "github.com/apecloud/kubeblocks/internal/dataprotection/types"
"github.com/apecloud/kubeblocks/internal/dataprotection/utils/boolptr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strconv"
"strings"
"text/template"
Expand Down Expand Up @@ -258,8 +262,17 @@ func (o *updateOptions) buildPatch(flags []*pflag.Flag) error {

if o.cluster != nil {
// if update the backup config, the backup method must have value
if o.cluster.Spec.Backup != nil && o.cluster.Spec.Backup.Method == "" {
return fmt.Errorf("backup method is required, use --backup-method to specify one, run \"kbcli cd describe cd-name\" to show all backup methods")
if o.cluster.Spec.Backup != nil {
defaultBackupMethod, backupMethodMap, err := o.getBackupMethodsFromBackupPolicy()
if err != nil {
return err
}
if o.cluster.Spec.Backup.Method == "" {
o.cluster.Spec.Backup.Method = defaultBackupMethod
}
if _, ok := backupMethodMap[o.cluster.Spec.Backup.Method]; !ok {
return fmt.Errorf("backup method %s is not supported, please view the supported backup methods by `kbcli cd describe %s`", o.cluster.Spec.Backup.Method, o.cluster.Spec.ClusterDefRef)
}
}

data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&o.cluster.Spec)
Expand Down Expand Up @@ -603,3 +616,47 @@ func (o *updateOptions) updateBackupPitrEnabled(val string) error {
o.cluster.Spec.Backup.PITREnabled = &boolVal
return nil
}

// get backup methods from cluster's backup policy
// if method's snapshotVolumes is true, use the method as the default backup method
func (o *updateOptions) getBackupMethodsFromBackupPolicy() (string, map[string]struct{}, error) {
if o.cluster == nil {
return "", nil, fmt.Errorf("cluster is nil")
}

var backupPolicy []dpv1alpha1.BackupPolicy
obj, err := o.dynamic.Resource(types.BackupPolicyGVR()).Namespace(o.namespace).List(context.Background(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", constant.AppInstanceLabelKey, o.cluster.Name),
})
if err != nil {
return "", nil, err
}
for _, item := range obj.Items {
var bp dpv1alpha1.BackupPolicy
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(item.Object, &bp); err != nil {
return "", nil, err
}
backupPolicy = append(backupPolicy, bp)
}

var defaultBackupMethod string
var backupMethodsMap = make(map[string]struct{})
for _, policy := range backupPolicy {
if policy.Annotations[dptypes.DefaultBackupPolicyAnnotationKey] != "true" {
continue
}
if policy.Status.Phase != dpv1alpha1.AvailablePhase {
continue
}
for _, method := range policy.Spec.BackupMethods {
if boolptr.IsSetToTrue(method.SnapshotVolumes) {
defaultBackupMethod = method.Name
}
backupMethodsMap[method.Name] = struct{}{}
}
}
if defaultBackupMethod == "" {
return "", nil, fmt.Errorf("failed to find default backup method which snapshotVolumes is true, please check cluster's backup policy")
}
return defaultBackupMethod, backupMethodsMap, nil
}

0 comments on commit 9f3221f

Please sign in to comment.