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 }