github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/util/breakingchange/upgradehandlerto0.7.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 breakingchange
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"strings"
    26  
    27  	v1 "k8s.io/api/apps/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/runtime/schema"
    34  	apitypes "k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/util/json"
    36  	"k8s.io/client-go/dynamic"
    37  
    38  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    39  	"github.com/1aal/kubeblocks/pkg/cli/types"
    40  	"github.com/1aal/kubeblocks/pkg/constant"
    41  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    42  )
    43  
    44  var _ upgradeHandler = &upgradeHandlerTo7{}
    45  
    46  func init() {
    47  	registerUpgradeHandler([]string{"0.5", "0.6"}, "0.7", &upgradeHandlerTo7{})
    48  }
    49  
    50  type upgradeHandlerTo7 struct {
    51  }
    52  
    53  func (u *upgradeHandlerTo7) snapshot(dynamic dynamic.Interface) (map[string][]unstructured.Unstructured, error) {
    54  	resourcesMap := map[string][]unstructured.Unstructured{}
    55  	// get backupPolicy objs
    56  	if err := fillResourcesMap(dynamic, resourcesMap, types.BackupPolicyGVR(), metav1.ListOptions{}); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	// get backup objs
    61  	if err := fillResourcesMap(dynamic, resourcesMap, types.BackupGVR(), metav1.ListOptions{}); err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	// get stateful_set objs
    66  	if err := fillResourcesMap(dynamic, resourcesMap, types.StatefulSetGVR(), metav1.ListOptions{}); err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	// get deployment objs
    71  	if err := fillResourcesMap(dynamic, resourcesMap, types.DeployGVR(), metav1.ListOptions{}); err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	// get configmap objs for pulsar
    76  	if err := fillResourcesMap(dynamic, resourcesMap, types.ConfigmapGVR(), metav1.ListOptions{
    77  		LabelSelector: fmt.Sprintf("%s=%s", constant.CMTemplateNameLabelKey, brokerEnvTPL),
    78  	}); err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	// get cluster objs for qdrant
    83  	if err := fillResourcesMap(dynamic, resourcesMap, types.ClusterGVR(), metav1.ListOptions{
    84  		LabelSelector: fmt.Sprintf("%s=%s", constant.ClusterDefLabelKey, "qdrant"),
    85  	}); err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	return resourcesMap, nil
    90  }
    91  
    92  func (u *upgradeHandlerTo7) transform(dynamic dynamic.Interface, resourcesMap map[string][]unstructured.Unstructured) error {
    93  	for _, resources := range resourcesMap {
    94  		for _, obj := range resources {
    95  			switch obj.GetKind() {
    96  			case types.KindBackupPolicy:
    97  				if err := u.transformBackupPolicy(dynamic, obj); err != nil {
    98  					return err
    99  				}
   100  			case types.KindBackup:
   101  				if err := u.transformBackup(dynamic, obj); err != nil {
   102  					return err
   103  				}
   104  			case types.KindStatefulSet:
   105  				if err := u.transformStatefulSet(dynamic, obj); err != nil {
   106  					return err
   107  				}
   108  			case types.KindDeployment:
   109  				if err := u.transformDeployment(dynamic, obj); err != nil {
   110  					return err
   111  				}
   112  			case types.KindConfigMap:
   113  				if err := u.transformPulsarConfigMap(dynamic, obj); err != nil {
   114  					return err
   115  				}
   116  			case types.KindCluster:
   117  				if err := u.transformQdrantCluster(dynamic, obj); err != nil {
   118  					return err
   119  				}
   120  			}
   121  		}
   122  	}
   123  	return nil
   124  }
   125  
   126  func (u *upgradeHandlerTo7) transformBackupPolicy(dynamic dynamic.Interface, obj unstructured.Unstructured) error {
   127  	var (
   128  		backupMethods    []dpv1alpha1.BackupMethod
   129  		backupTarget     *dpv1alpha1.BackupTarget
   130  		backupRepoName   string
   131  		newSpecData      = map[string]interface{}{}
   132  		componentDefName = obj.GetLabels()["apps.kubeblocks.io/component-def-ref"]
   133  		specMap, _, _    = unstructured.NestedMap(obj.Object, "spec")
   134  	)
   135  
   136  	isMysqlHScalePolicy := componentDefName == componentMysql && strings.Contains(obj.GetName(), "hscale")
   137  	if !isMysqlHScalePolicy {
   138  		// ignore mysql hscale backup policy
   139  		if err := u.createBackupSchedule(dynamic, obj); err != nil {
   140  			return err
   141  		}
   142  	}
   143  
   144  	_, found, _ := unstructured.NestedSlice(specMap, "backupMethods")
   145  	if found {
   146  		// if exist backupMethods, nothing to do.
   147  		return nil
   148  	}
   149  
   150  	// build backup target info.
   151  	buildBackupTarget := func(source map[string]interface{}) {
   152  		if backupTarget != nil {
   153  			return
   154  		}
   155  		matchLabels, found, _ := unstructured.NestedStringMap(source, "target", "labelsSelector", "matchLabels")
   156  		if found {
   157  			backupTarget = &dpv1alpha1.BackupTarget{
   158  				PodSelector: &dpv1alpha1.PodSelector{
   159  					LabelSelector: &metav1.LabelSelector{
   160  						MatchLabels: matchLabels,
   161  					},
   162  				},
   163  			}
   164  			secretName, _, _ := unstructured.NestedString(source, "target", "secret", "name")
   165  			passwordKey, _, _ := unstructured.NestedString(source, "target", "secret", "passwordKey")
   166  			usernameKey, _, _ := unstructured.NestedString(source, "target", "secret", "usernameKey")
   167  			backupTarget.ConnectionCredential = &dpv1alpha1.ConnectionCredential{
   168  				SecretName:  secretName,
   169  				PasswordKey: passwordKey,
   170  				UsernameKey: usernameKey,
   171  			}
   172  		}
   173  	}
   174  
   175  	buildWithBackupType := func(backupType string, isMysqlHScalePolicy bool) {
   176  		policy, found, _ := unstructured.NestedMap(specMap, backupType)
   177  		if found {
   178  			var backupMethod *dpv1alpha1.BackupMethod
   179  			if backupMethod, backupRepoName = u.buildBackupMethod(componentDefName, backupType, policy); backupMethod != nil {
   180  				if isMysqlHScalePolicy {
   181  					backupMethod.Env = []corev1.EnvVar{
   182  						{Name: "SIGNAL_FILE", Value: ".restore"},
   183  					}
   184  				}
   185  				backupMethods = append(backupMethods, *backupMethod)
   186  			}
   187  			buildBackupTarget(policy)
   188  		}
   189  	}
   190  	// build backupMethod/backupTarget with datafile
   191  	buildWithBackupType(backupTypeDatafile, isMysqlHScalePolicy)
   192  
   193  	/// build backupMethod/backupTarget with snapshot
   194  	buildWithBackupType(backupTypeSnapshot, isMysqlHScalePolicy)
   195  	if backupRepoName != "" {
   196  		newSpecData["backupRepoName"] = backupRepoName
   197  	}
   198  	newSpecData["pathPrefix"] = obj.GetAnnotations()["dataprotection.kubeblocks.io/path-prefix"]
   199  	newSpecData["backupMethods"] = backupMethods
   200  	newSpecData["target"] = backupTarget
   201  	patchBytes, _ := json.Marshal(map[string]interface{}{"spec": newSpecData})
   202  	if _, err := dynamic.Resource(types.BackupPolicyGVR()).Namespace(obj.GetNamespace()).Patch(context.TODO(), obj.GetName(), apitypes.MergePatchType, patchBytes, metav1.PatchOptions{}); err != nil {
   203  		return fmt.Errorf("update backupPolicy %s failed: %s", obj.GetName(), err.Error())
   204  	}
   205  	return nil
   206  }
   207  
   208  // buildBackupMethod builds backupMethod for backup policy.
   209  func (u *upgradeHandlerTo7) buildBackupMethod(componentDefName, backupType string, source map[string]interface{}) (*dpv1alpha1.BackupMethod, string) {
   210  	backupMethod := dpv1alpha1.BackupMethod{}
   211  	buildBackupMethod := func(methodName, actionsSetName, mountPath string, useSnapshotVolumes bool) {
   212  		backupMethod.Name = methodName
   213  		backupMethod.ActionSetName = actionsSetName
   214  		backupMethod.SnapshotVolumes = &useSnapshotVolumes
   215  		targetVolumes := &dpv1alpha1.TargetVolumeInfo{}
   216  		if useSnapshotVolumes {
   217  			targetVolumes.Volumes = []string{dataVolumeName}
   218  		}
   219  		if mountPath != "" {
   220  			targetVolumes.VolumeMounts = []corev1.VolumeMount{{Name: dataVolumeName, MountPath: mountPath}}
   221  		}
   222  		backupMethod.TargetVolumes = targetVolumes
   223  	}
   224  	switch backupType {
   225  	case backupTypeDatafile:
   226  		switch componentDefName {
   227  		case componentPostgresql:
   228  			buildBackupMethod(pgbasebackupMethodName, pgBasebackupActionSet, pgsqlMountPath, false)
   229  		case componentMysql:
   230  			buildBackupMethod(xtrabackupMethodName, xtrabackupActionSet, mysqlMountPath, false)
   231  		case componentRedis:
   232  			buildBackupMethod(datafileMethodName, redisDatafileActionSet, redisMountPath, false)
   233  		case componentMongodb:
   234  			buildBackupMethod(datafileMethodName, mongoDatafileActionSet, mongodbMountPath, false)
   235  		case componentQdrant:
   236  			buildBackupMethod(datafileMethodName, qdrantSnapshotActionSet, qdrantMountPath, false)
   237  		}
   238  	case backupTypeSnapshot:
   239  		switch componentDefName {
   240  		case componentMysql:
   241  			buildBackupMethod(volumeSnapshotMethodName, volumeSnapshotForMysql, mysqlMountPath, true)
   242  		case componentMongodb:
   243  			buildBackupMethod(volumeSnapshotMethodName, volumeSnapshotForMongo, mongodbMountPath, true)
   244  		default:
   245  			buildBackupMethod(volumeSnapshotMethodName, "", "", true)
   246  		}
   247  	default:
   248  		return nil, ""
   249  	}
   250  	backupRepoName, _, _ := unstructured.NestedString(source, "backupRepoName")
   251  	return &backupMethod, backupRepoName
   252  }
   253  
   254  // createBackupSchedule creates the backup schedule by backup policy.
   255  func (u *upgradeHandlerTo7) createBackupSchedule(dynamic dynamic.Interface, obj unstructured.Unstructured) error {
   256  	_, found, _ := unstructured.NestedMap(obj.Object, "spec", "schedule")
   257  	if !found {
   258  		return nil
   259  	}
   260  	schedule := &dpv1alpha1.BackupSchedule{
   261  		TypeMeta: metav1.TypeMeta{
   262  			Kind:       types.KindBackupSchedule,
   263  			APIVersion: types.BackupScheduleGVR().GroupVersion().String(),
   264  		},
   265  		ObjectMeta: metav1.ObjectMeta{
   266  			Name:        strings.Replace(obj.GetName(), "backup-policy", "backup-schedule", 1),
   267  			Namespace:   obj.GetNamespace(),
   268  			Labels:      obj.GetLabels(),
   269  			Annotations: obj.GetAnnotations(),
   270  		},
   271  		Spec: dpv1alpha1.BackupScheduleSpec{
   272  			BackupPolicyName: obj.GetName(),
   273  			Schedules:        u.buildBackupMethodSchedule(obj),
   274  		},
   275  	}
   276  
   277  	startingDeadlineMinutes, found, _ := unstructured.NestedInt64(obj.Object, "spec", "schedule", "startingDeadlineMinutes")
   278  	if found {
   279  		schedule.Spec.StartingDeadlineMinutes = &startingDeadlineMinutes
   280  	}
   281  	unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&schedule)
   282  	if err != nil {
   283  		return err
   284  	}
   285  	_, err = dynamic.Resource(types.BackupScheduleGVR()).Namespace(obj.GetNamespace()).Create(context.TODO(),
   286  		&unstructured.Unstructured{Object: unstructuredMap}, metav1.CreateOptions{})
   287  	if err != nil && !apierrors.IsAlreadyExists(err) {
   288  		return fmt.Errorf("create backupSchedule %s failed: %s", schedule.Name, err.Error())
   289  	}
   290  	return nil
   291  }
   292  
   293  func (u *upgradeHandlerTo7) buildBackupMethodSchedule(obj unstructured.Unstructured) []dpv1alpha1.SchedulePolicy {
   294  	var schedulePolicies []dpv1alpha1.SchedulePolicy
   295  	sourceSchedule, _, _ := unstructured.NestedMap(obj.Object, "spec", "schedule")
   296  	retentionPeriod, _, _ := unstructured.NestedString(obj.Object, "spec", "retention", "ttl")
   297  	buildSchedulePolicy := func(backupMethod string, oldSchedule map[string]interface{}) dpv1alpha1.SchedulePolicy {
   298  		cronExpression, _, _ := unstructured.NestedString(oldSchedule, "cronExpression")
   299  		enabled, _, _ := unstructured.NestedBool(oldSchedule, "enable")
   300  		return dpv1alpha1.SchedulePolicy{
   301  			BackupMethod:    backupMethod,
   302  			CronExpression:  cronExpression,
   303  			RetentionPeriod: dpv1alpha1.RetentionPeriod(retentionPeriod),
   304  			Enabled:         &enabled,
   305  		}
   306  	}
   307  	datafile, _, _ := unstructured.NestedMap(sourceSchedule, "datafile")
   308  	snapshot, _, _ := unstructured.NestedMap(sourceSchedule, "snapshot")
   309  	componentDefName := obj.GetLabels()["apps.kubeblocks.io/component-def-ref"]
   310  	switch componentDefName {
   311  	case componentMysql:
   312  		schedulePolicies = append(schedulePolicies, buildSchedulePolicy(xtrabackupMethodName, datafile))
   313  	case componentMongodb:
   314  		schedulePolicy := buildSchedulePolicy(datafileMethodName, datafile)
   315  		// Note: will set dump by default, datafile tool may lead inconsistent backup data.
   316  		schedulePolicies = append(schedulePolicies, dpv1alpha1.SchedulePolicy{
   317  			BackupMethod:    "dump",
   318  			CronExpression:  schedulePolicy.CronExpression,
   319  			RetentionPeriod: schedulePolicy.RetentionPeriod,
   320  			Enabled:         schedulePolicy.Enabled,
   321  		})
   322  		// close the datafile schedule
   323  		var enable bool
   324  		schedulePolicy.Enabled = &enable
   325  		schedulePolicies = append(schedulePolicies, schedulePolicy)
   326  	case componentPostgresql:
   327  		schedulePolicies = append(schedulePolicies, buildSchedulePolicy(pgbasebackupMethodName, datafile))
   328  	case componentRedis, componentQdrant:
   329  		schedulePolicies = append(schedulePolicies, buildSchedulePolicy(datafileMethodName, datafile))
   330  	}
   331  	// set volume-snapshot
   332  	schedulePolicies = append(schedulePolicies, buildSchedulePolicy(volumeSnapshotMethodName, snapshot))
   333  	return schedulePolicies
   334  }
   335  
   336  func (u *upgradeHandlerTo7) transformBackup(dynamic dynamic.Interface, obj unstructured.Unstructured) error {
   337  	var (
   338  		newSpecData     = map[string]interface{}{}
   339  		newStatusData   = map[string]interface{}{}
   340  		specMap, _, _   = unstructured.NestedMap(obj.Object, "spec")
   341  		statusMap, _, _ = unstructured.NestedMap(obj.Object, "status")
   342  		compName        = obj.GetLabels()["apps.kubeblocks.io/component-name"]
   343  		backupMethodKey = "backupMethod"
   344  	)
   345  
   346  	backupMethod, _, _ := unstructured.NestedString(specMap, "backupMethod")
   347  	if backupMethod != "" {
   348  		// if exist backupMethod, nothing to do.
   349  		return nil
   350  	}
   351  
   352  	// covert spec of backup
   353  	backupToolName, _, _ := unstructured.NestedString(statusMap, "backupToolName")
   354  	backupType, _, _ := unstructured.NestedString(specMap, "backupType")
   355  	switch backupType {
   356  	case backupTypeSnapshot:
   357  		newSpecData[backupMethodKey] = volumeSnapshotMethodName
   358  	case backupTypeDatafile:
   359  		switch {
   360  		case strings.Contains(backupToolName, "basebackup"):
   361  			newSpecData[backupMethodKey] = pgbasebackupMethodName
   362  		case strings.Contains(backupToolName, "apecloud-mysql"):
   363  			newSpecData[backupMethodKey] = xtrabackupMethodName
   364  		default:
   365  			newSpecData[backupMethodKey] = datafileMethodName
   366  		}
   367  	case backupTypeLogfile:
   368  		// Note: set a non-existent method for required value.
   369  		newSpecData[backupMethodKey] = backupTypeLogfile
   370  	}
   371  	newSpecData["deletionPolicy"] = dpv1alpha1.BackupDeletionPolicyDelete
   372  	patchBytes, _ := json.Marshal(map[string]interface{}{"spec": newSpecData})
   373  	if _, err := dynamic.Resource(types.BackupGVR()).Namespace(obj.GetNamespace()).Patch(context.TODO(), obj.GetName(), apitypes.MergePatchType, patchBytes, metav1.PatchOptions{}); err != nil {
   374  		return fmt.Errorf("update backup %s failed: %s", obj.GetName(), err.Error())
   375  	}
   376  
   377  	// covert status of backup
   378  	newStatusData["persistentVolumeClaimName"] = statusMap["persistentVolumeClaimName"]
   379  	newStatusData["completionTimestamp"] = statusMap["completionTimestamp"]
   380  	newStatusData["startTimestamp"] = statusMap["startTimestamp"]
   381  	newStatusData["backupRepoName"] = statusMap["backupRepoName"]
   382  	newStatusData["expiration"] = statusMap["expiration"]
   383  	newStatusData["totalSize"] = statusMap["totalSize"]
   384  	newStatusData["duration"] = statusMap["duration"]
   385  	newStatusData["phase"] = statusMap["phase"]
   386  	if newStatusData["phase"] == "InProgress" {
   387  		newStatusData["phase"] = dpv1alpha1.BackupPhaseRunning
   388  	}
   389  	// covert timeRange
   390  	manifests, _, _ := unstructured.NestedMap(statusMap, "manifests")
   391  	if manifests != nil {
   392  		backupLog, _, _ := unstructured.NestedMap(manifests, "backupLog")
   393  		newStatusData["timeRange"] = map[string]interface{}{
   394  			"end":   backupLog["stopTime"],
   395  			"start": backupLog["startTime"],
   396  		}
   397  		backupTool, _, _ := unstructured.NestedMap(manifests, "backupTool")
   398  		newStatusData["path"] = backupTool["filePath"]
   399  	}
   400  	// covert backupMethod of status
   401  	newObj, err := dynamic.Resource(types.BackupGVR()).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
   402  	if err != nil {
   403  		return err
   404  	}
   405  	var (
   406  		useSnapshotVolumes bool
   407  		volumes            []string
   408  		volumeMounts       []corev1.VolumeMount
   409  		actionSetName      string
   410  	)
   411  	switch backupType {
   412  	case backupTypeSnapshot:
   413  		useSnapshotVolumes = true
   414  		volumes = append(volumes, dataVolumeName)
   415  		if compName == componentMysql {
   416  			volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: dataVolumeName, MountPath: mysqlMountPath})
   417  			actionSetName = volumeSnapshotForMysql
   418  		} else if compName == componentMongodb {
   419  			volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: dataVolumeName, MountPath: mongodbMountPath})
   420  			actionSetName = volumeSnapshotForMongo
   421  		}
   422  	case backupTypeDatafile:
   423  		switch {
   424  		case strings.Contains(backupToolName, "basebackup"):
   425  			volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: dataVolumeName, MountPath: pgsqlMountPath})
   426  			actionSetName = pgBasebackupActionSet
   427  		case strings.Contains(backupToolName, "apecloud-mysql"):
   428  			volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: dataVolumeName, MountPath: mysqlMountPath})
   429  			actionSetName = xtrabackupActionSet
   430  		case strings.Contains(backupToolName, "redis"):
   431  			volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: dataVolumeName, MountPath: redisMountPath})
   432  			actionSetName = redisDatafileActionSet
   433  		case strings.Contains(backupToolName, "mongodb"):
   434  			volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: dataVolumeName, MountPath: mongodbMountPath})
   435  			actionSetName = mongoDatafileActionSet
   436  		case strings.Contains(backupToolName, "qdrant"):
   437  			volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: dataVolumeName, MountPath: qdrantMountPath})
   438  			actionSetName = qdrantSnapshotActionSet
   439  		}
   440  	}
   441  	newStatusData[backupMethodKey] = map[string]interface{}{
   442  		"name":            newSpecData[backupMethodKey],
   443  		"actionSetName":   actionSetName,
   444  		"snapshotVolumes": useSnapshotVolumes,
   445  		"targetVolumes": map[string]interface{}{
   446  			"volumes":      volumes,
   447  			"volumeMounts": volumeMounts,
   448  		},
   449  	}
   450  	newObj.Object["status"] = newStatusData
   451  	if _, err := dynamic.Resource(types.BackupGVR()).Namespace(newObj.GetNamespace()).UpdateStatus(context.TODO(), newObj, metav1.UpdateOptions{}); err != nil {
   452  		return fmt.Errorf("update status of backup %s failed: %s", obj.GetName(), err.Error())
   453  	}
   454  	return nil
   455  }
   456  
   457  func (u *upgradeHandlerTo7) transformStatefulSet(dynamic dynamic.Interface, obj unstructured.Unstructured) error {
   458  	// filter objects not managed by KB
   459  	labels := obj.GetLabels()
   460  	if labels == nil || labels[constant.AppManagedByLabelKey] != constant.AppName {
   461  		return nil
   462  	}
   463  	// create a rsm
   464  	serviceName, _, _ := unstructured.NestedString(obj.Object, "spec", "serviceName")
   465  	matchLabels, _, _ := unstructured.NestedStringMap(obj.Object, "spec", "selector", "matchLabels")
   466  	replicas, _, _ := unstructured.NestedInt64(obj.Object, "spec", "replicas")
   467  	podManagementPolicy, _, _ := unstructured.NestedString(obj.Object, "spec", "podManagementPolicy")
   468  	pvcsUnstructured, _, _ := unstructured.NestedSlice(obj.Object, "spec", "volumeClaimTemplates")
   469  	var pvcs []corev1.PersistentVolumeClaim
   470  	for _, pvcUnstructured := range pvcsUnstructured {
   471  		pvc := &corev1.PersistentVolumeClaim{}
   472  		pvcU, _ := pvcUnstructured.(map[string]interface{})
   473  		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(pvcU, pvc); err != nil {
   474  			return err
   475  		}
   476  		pvcs = append(pvcs, *pvc)
   477  	}
   478  	template, _, _ := unstructured.NestedMap(obj.Object, "spec", "template")
   479  	podTemplate := &corev1.PodTemplateSpec{}
   480  	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(template, podTemplate); err != nil {
   481  		return err
   482  	}
   483  	updateStrategy, _, _ := unstructured.NestedString(obj.Object, "spec", "updateStrategy")
   484  	rsm := builder.NewReplicatedStateMachineBuilder(obj.GetNamespace(), obj.GetName()).
   485  		AddAnnotationsInMap(obj.GetAnnotations()).
   486  		AddLabelsInMap(obj.GetLabels()).
   487  		SetServiceName(serviceName).
   488  		AddMatchLabelsInMap(matchLabels).
   489  		SetReplicas(int32(replicas)).
   490  		SetPodManagementPolicy(v1.PodManagementPolicyType(podManagementPolicy)).
   491  		SetVolumeClaimTemplates(pvcs...).
   492  		SetTemplate(*podTemplate).
   493  		SetUpdateStrategyType(v1.StatefulSetUpdateStrategyType(updateStrategy)).
   494  		SetPaused(true).
   495  		GetObject()
   496  	gvk := schema.GroupVersionKind{
   497  		Group:   types.RSMGVR().Group,
   498  		Version: types.RSMGVR().Version,
   499  		Kind:    types.KindRSM,
   500  	}
   501  	rsm.SetGroupVersionKind(gvk)
   502  
   503  	unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(rsm)
   504  	if err != nil {
   505  		return err
   506  	}
   507  	_, err = dynamic.Resource(types.RSMGVR()).Namespace(obj.GetNamespace()).Create(context.TODO(),
   508  		&unstructured.Unstructured{Object: unstructuredMap}, metav1.CreateOptions{})
   509  	if err != nil && !apierrors.IsAlreadyExists(err) {
   510  		return fmt.Errorf("create rsm %s failed: %s", rsm.Name, err.Error())
   511  	}
   512  	return nil
   513  }
   514  
   515  func (u *upgradeHandlerTo7) transformDeployment(dynamic dynamic.Interface, obj unstructured.Unstructured) error {
   516  	labels := obj.GetLabels()
   517  	if labels == nil || labels[constant.AppManagedByLabelKey] != constant.AppName {
   518  		return nil
   519  	}
   520  
   521  	// delete deployment
   522  	patchData, _ := json.Marshal(map[string]map[string]interface{}{"metadata": {"finalizers": nil}})
   523  	if _, err := dynamic.Resource(types.DeployGVR()).Namespace(obj.GetNamespace()).Patch(context.TODO(), obj.GetName(), apitypes.MergePatchType, patchData, metav1.PatchOptions{}); err != nil {
   524  		return err
   525  	}
   526  	if err := dynamic.Resource(types.DeployGVR()).Namespace(obj.GetNamespace()).Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
   527  		return err
   528  	}
   529  	// delete env cm
   530  	envCMName := fmt.Sprintf("%s-%s", obj.GetName(), "env")
   531  	if _, err := dynamic.Resource(types.ConfigmapGVR()).Namespace(obj.GetNamespace()).Patch(context.TODO(), envCMName, apitypes.MergePatchType, patchData, metav1.PatchOptions{}); err != nil {
   532  		return err
   533  	}
   534  	if err := dynamic.Resource(types.ConfigmapGVR()).Namespace(obj.GetNamespace()).Delete(context.TODO(), envCMName, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
   535  		return err
   536  	}
   537  	// delete config cm
   538  	if compName, ok := labels[constant.KBAppComponentLabelKey]; ok {
   539  		configCMName := fmt.Sprintf("%s-%s-%s", obj.GetName(), compName, "config")
   540  		if _, err := dynamic.Resource(types.ConfigmapGVR()).Namespace(obj.GetNamespace()).Patch(context.TODO(), configCMName, apitypes.MergePatchType, patchData, metav1.PatchOptions{}); err != nil {
   541  			return err
   542  		}
   543  		err := dynamic.Resource(types.ConfigmapGVR()).Namespace(obj.GetNamespace()).Delete(context.TODO(), configCMName, metav1.DeleteOptions{})
   544  		if err != nil && !apierrors.IsNotFound(err) {
   545  			return err
   546  		}
   547  	}
   548  	// increase cluster status.observedGeneration by 1 to trigger component owned objects regeneration.
   549  	if clusterName, ok := labels[constant.AppInstanceLabelKey]; ok {
   550  		clusterObj, err := dynamic.Resource(types.ClusterGVR()).Namespace(obj.GetNamespace()).Get(context.TODO(), clusterName, metav1.GetOptions{})
   551  		if err != nil {
   552  			return err
   553  		}
   554  		status := clusterObj.Object["status"].(map[string]interface{})
   555  		generation := status["observedGeneration"].(int64)
   556  		status["observedGeneration"] = generation + 1
   557  		if _, err = dynamic.Resource(types.ClusterGVR()).Namespace(obj.GetNamespace()).UpdateStatus(context.TODO(), clusterObj, metav1.UpdateOptions{}); err != nil {
   558  			return err
   559  		}
   560  	}
   561  	return nil
   562  }
   563  
   564  func (u *upgradeHandlerTo7) transformPulsarConfigMap(dynamic dynamic.Interface, obj unstructured.Unstructured) error {
   565  	// transform pulsar broker env config map
   566  	brokerEnvConfigName := strings.Replace(obj.GetName(), "broker-env", "env", 1)
   567  	tmpObj, err := dynamic.Resource(types.ConfigmapGVR()).Namespace(obj.GetNamespace()).Get(context.TODO(), brokerEnvConfigName, metav1.GetOptions{})
   568  	if err != nil {
   569  		return err
   570  	}
   571  	oldConfigData, _, _ := unstructured.NestedMap(tmpObj.Object, "data")
   572  	zkSVC := oldConfigData["zookeeperSVC"]
   573  	// update config map data
   574  	configData, _, _ := unstructured.NestedMap(obj.Object, "data")
   575  	zkServers := fmt.Sprintf("%s:2181", zkSVC)
   576  	zookeeperServers := fmt.Sprintf("%s: %s", "zookeeperServers", zkServers)
   577  	configurationStoreServers := fmt.Sprintf("%s: %s", "configurationStoreServers", zkServers)
   578  	configData["conf"] = fmt.Sprintf("%s\n%s\n%s", configData["conf"], zookeeperServers, configurationStoreServers)
   579  	patchBytes, _ := json.Marshal(map[string]interface{}{"data": configData})
   580  	if _, err := dynamic.Resource(types.ConfigmapGVR()).Namespace(obj.GetNamespace()).Patch(context.TODO(), obj.GetName(), apitypes.MergePatchType, patchBytes, metav1.PatchOptions{}); err != nil {
   581  		return fmt.Errorf("update pulsar configmap %s failed: %s", obj.GetName(), err.Error())
   582  	}
   583  	return nil
   584  }
   585  
   586  func (u *upgradeHandlerTo7) transformQdrantCluster(dynamic dynamic.Interface, obj unstructured.Unstructured) error {
   587  	labels := obj.GetLabels()
   588  	if labels[constant.ClusterDefLabelKey] != "qdrant" {
   589  		return nil
   590  	}
   591  	specMap, _, _ := unstructured.NestedMap(obj.Object, "spec")
   592  	specMap["clusterVersionRef"] = "qdrant-1.5.0"
   593  	patchBytes, _ := json.Marshal(map[string]interface{}{"spec": specMap})
   594  	if _, err := dynamic.Resource(types.ClusterGVR()).Namespace(obj.GetNamespace()).Patch(context.TODO(), obj.GetName(), apitypes.MergePatchType, patchBytes, metav1.PatchOptions{}); err != nil {
   595  		return fmt.Errorf("update qdrant cluster %s failed: %s", obj.GetName(), err.Error())
   596  	}
   597  
   598  	return nil
   599  }