github.com/oam-dev/kubevela@v1.9.11/pkg/resourcekeeper/statekeep_suite_test.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package resourcekeeper 18 19 import ( 20 "context" 21 "encoding/json" 22 "reflect" 23 24 "github.com/crossplane/crossplane-runtime/pkg/fieldpath" 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/runtime" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 "sigs.k8s.io/yaml" 33 34 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 35 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 36 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 37 "github.com/oam-dev/kubevela/pkg/oam" 38 "github.com/oam-dev/kubevela/pkg/utils/apply" 39 ) 40 41 var _ = Describe("Test ResourceKeeper StateKeep", func() { 42 43 createConfigMapClusterObjectReference := func(name string) common.ClusterObjectReference { 44 return common.ClusterObjectReference{ 45 ObjectReference: corev1.ObjectReference{ 46 Kind: "ConfigMap", 47 APIVersion: corev1.SchemeGroupVersion.String(), 48 Name: name, 49 Namespace: "default", 50 }, 51 } 52 } 53 54 createConfigMapWithSharedBy := func(name string, ns string, appName string, sharedBy string, value string) *unstructured.Unstructured { 55 o := &unstructured.Unstructured{ 56 Object: map[string]interface{}{ 57 "metadata": map[string]interface{}{ 58 "name": name, 59 "namespace": ns, 60 "labels": map[string]interface{}{ 61 oam.LabelAppName: appName, 62 oam.LabelAppNamespace: ns, 63 }, 64 "annotations": map[string]interface{}{oam.AnnotationAppSharedBy: sharedBy}, 65 }, 66 "data": map[string]interface{}{ 67 "key": value, 68 }, 69 }, 70 } 71 o.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap")) 72 return o 73 } 74 75 createConfigMap := func(name string, value string) *unstructured.Unstructured { 76 return createConfigMapWithSharedBy(name, "default", "", "", value) 77 } 78 79 It("Test StateKeep for various scene", func() { 80 cli := testClient 81 82 setOwner := func(obj *unstructured.Unstructured) { 83 labels := obj.GetLabels() 84 if labels == nil { 85 labels = map[string]string{} 86 } 87 labels[oam.LabelAppName] = "app" 88 labels[oam.LabelAppNamespace] = "default" 89 obj.SetLabels(labels) 90 } 91 92 // state-keep add this resource 93 cm1 := createConfigMap("cm1", "value") 94 setOwner(cm1) 95 cmRaw1, err := json.Marshal(cm1) 96 Expect(err).Should(Succeed()) 97 98 // state-keep skip this resource 99 cm2 := createConfigMap("cm2", "value") 100 setOwner(cm2) 101 Expect(cli.Create(context.Background(), cm2)).Should(Succeed()) 102 103 // state-keep delete this resource 104 cm3 := createConfigMap("cm3", "value") 105 setOwner(cm3) 106 Expect(cli.Create(context.Background(), cm3)).Should(Succeed()) 107 108 // state-keep delete this resource 109 cm4 := createConfigMap("cm4", "value") 110 setOwner(cm4) 111 cmRaw4, err := json.Marshal(cm4) 112 Expect(err).Should(Succeed()) 113 Expect(cli.Create(context.Background(), cm4)).Should(Succeed()) 114 115 // state-keep update this resource 116 cm5 := createConfigMap("cm5", "value") 117 setOwner(cm5) 118 cmRaw5, err := json.Marshal(cm5) 119 Expect(err).Should(Succeed()) 120 cm5.Object["data"].(map[string]interface{})["key"] = "changed" 121 Expect(cli.Create(context.Background(), cm5)).Should(Succeed()) 122 123 app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "default"}} 124 h := &resourceKeeper{ 125 Client: cli, 126 app: app, 127 applicator: apply.NewAPIApplicator(cli), 128 cache: newResourceCache(cli, app), 129 } 130 131 h._currentRT = &v1beta1.ResourceTracker{ 132 Spec: v1beta1.ResourceTrackerSpec{ 133 ManagedResources: []v1beta1.ManagedResource{{ 134 ClusterObjectReference: createConfigMapClusterObjectReference("cm1"), 135 Data: &runtime.RawExtension{Raw: cmRaw1}, 136 }, { 137 ClusterObjectReference: createConfigMapClusterObjectReference("cm2"), 138 }, { 139 ClusterObjectReference: createConfigMapClusterObjectReference("cm3"), 140 Deleted: true, 141 }, { 142 ClusterObjectReference: createConfigMapClusterObjectReference("cm4"), 143 Data: &runtime.RawExtension{Raw: cmRaw4}, 144 Deleted: true, 145 }, { 146 ClusterObjectReference: createConfigMapClusterObjectReference("cm5"), 147 Data: &runtime.RawExtension{Raw: cmRaw5}, 148 }}, 149 }, 150 } 151 152 Expect(h.StateKeep(context.Background())).Should(Succeed()) 153 cms := &unstructured.UnstructuredList{} 154 cms.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap")) 155 Expect(cli.List(context.Background(), cms, client.InNamespace("default"))).Should(Succeed()) 156 Expect(len(cms.Items)).Should(Equal(3)) 157 Expect(cms.Items[0].GetName()).Should(Equal("cm1")) 158 Expect(cms.Items[1].GetName()).Should(Equal("cm2")) 159 Expect(cms.Items[2].GetName()).Should(Equal("cm5")) 160 Expect(cms.Items[2].Object["data"].(map[string]interface{})["key"].(string)).Should(Equal("value")) 161 162 Expect(cli.Get(context.Background(), client.ObjectKeyFromObject(cm1), cm1)).Should(Succeed()) 163 cm1.SetLabels(map[string]string{ 164 oam.LabelAppName: "app-2", 165 oam.LabelAppNamespace: "default", 166 }) 167 Expect(cli.Update(context.Background(), cm1)).Should(Succeed()) 168 err = h.StateKeep(context.Background()) 169 Expect(err).ShouldNot(Succeed()) 170 Expect(err.Error()).Should(ContainSubstring("failed to re-apply")) 171 }) 172 173 It("Test StateKeep for shared resources", func() { 174 cli := testClient 175 ctx := context.Background() 176 Expect(cli.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-shared"}})).Should(Succeed()) 177 cm1 := createConfigMapWithSharedBy("cm1", "test-shared", "app", "test-shared/app", "x") 178 cmRaw1, err := json.Marshal(cm1) 179 Expect(err).Should(Succeed()) 180 cm2 := createConfigMapWithSharedBy("cm2", "test-shared", "app", "", "y") 181 cmRaw2, err := json.Marshal(cm2) 182 Expect(err).Should(Succeed()) 183 app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test-shared"}} 184 h := &resourceKeeper{ 185 Client: cli, 186 app: app, 187 applicator: apply.NewAPIApplicator(cli), 188 cache: newResourceCache(cli, app), 189 } 190 h.sharedResourcePolicy = &v1alpha1.SharedResourcePolicySpec{Rules: []v1alpha1.SharedResourcePolicyRule{{ 191 Selector: v1alpha1.ResourcePolicyRuleSelector{ResourceTypes: []string{"ConfigMap"}}, 192 }}} 193 h._currentRT = &v1beta1.ResourceTracker{ 194 Spec: v1beta1.ResourceTrackerSpec{ 195 ManagedResources: []v1beta1.ManagedResource{{ 196 ClusterObjectReference: createConfigMapClusterObjectReference("cm1"), 197 Data: &runtime.RawExtension{Raw: cmRaw1}, 198 }, { 199 ClusterObjectReference: createConfigMapClusterObjectReference("cm2"), 200 Data: &runtime.RawExtension{Raw: cmRaw2}, 201 }}, 202 }, 203 } 204 cm1 = createConfigMapWithSharedBy("cm1", "test-shared", "app", "test-shared/app,test-shared/another", "z") 205 Expect(cli.Create(ctx, cm1)).Should(Succeed()) 206 cm2 = createConfigMapWithSharedBy("cm2", "test-shared", "another", "test-shared/another,test-shared/app", "z") 207 Expect(cli.Create(ctx, cm2)).Should(Succeed()) 208 Expect(h.StateKeep(ctx)).Should(Succeed()) 209 Expect(cli.Get(ctx, client.ObjectKeyFromObject(cm1), cm1)).Should(Succeed()) 210 Expect(cm1.Object["data"].(map[string]interface{})["key"]).Should(Equal("x")) 211 Expect(cli.Get(ctx, client.ObjectKeyFromObject(cm2), cm2)).Should(Succeed()) 212 Expect(cm2.Object["data"].(map[string]interface{})["key"]).Should(Equal("z")) 213 }) 214 215 It("Test StateKeep for apply-once policy", func() { 216 217 clusterManifest := &unstructured.Unstructured{} 218 clusterJson, err := yaml.YAMLToJSON([]byte(clusterYaml)) 219 Expect(err).Should(Succeed()) 220 err = json.Unmarshal(clusterJson, clusterManifest) 221 Expect(err).Should(Succeed()) 222 223 memoryManifest := &unstructured.Unstructured{} 224 memoryJson, err := yaml.YAMLToJSON([]byte(memoryYaml)) 225 Expect(err).Should(Succeed()) 226 err = json.Unmarshal(memoryJson, memoryManifest) 227 Expect(err).Should(Succeed()) 228 229 // state-keep skip spec.replicas 230 pathWithReplicas := []string{"spec.replicas"} 231 replicasValue, err := fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithReplicas[0]) 232 Expect(err).Should(Succeed()) 233 err = fieldpath.Pave(memoryManifest.UnstructuredContent()).SetValue(pathWithReplicas[0], replicasValue) 234 Expect(err).Should(Succeed()) 235 newReplicasValue, err := fieldpath.Pave(memoryManifest.UnstructuredContent()).GetValue(pathWithReplicas[0]) 236 Expect(err).Should(Succeed()) 237 Expect(reflect.DeepEqual(replicasValue, newReplicasValue)).Should(Equal(true)) 238 239 // state-keep skip spec.template.spec.containers[0].image 240 pathWithImage := []string{"spec.template.spec.containers[0].image"} 241 imageValue, err := fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithImage[0]) 242 Expect(err).Should(Succeed()) 243 err = fieldpath.Pave(memoryManifest.UnstructuredContent()).SetValue(pathWithImage[0], imageValue) 244 Expect(err).Should(Succeed()) 245 newImageValue, err := fieldpath.Pave(memoryManifest.UnstructuredContent()).GetValue(pathWithImage[0]) 246 Expect(err).Should(Succeed()) 247 Expect(reflect.DeepEqual(imageValue, newImageValue)).Should(Equal(true)) 248 249 // state-keep skip spec.template.spec.containers[0].resources 250 pathWithResources := []string{"spec.template.spec.containers[0].resources"} 251 resourcesValue, err := fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithResources[0]) 252 Expect(err).Should(Succeed()) 253 err = fieldpath.Pave(memoryManifest.UnstructuredContent()).SetValue(pathWithResources[0], resourcesValue) 254 Expect(err).Should(Succeed()) 255 newResourcesValue, err := fieldpath.Pave(memoryManifest.UnstructuredContent()).GetValue(pathWithResources[0]) 256 Expect(err).Should(Succeed()) 257 Expect(reflect.DeepEqual(resourcesValue, newResourcesValue)).Should(Equal(true)) 258 259 // state-keep with index error skip spec.template.spec.containers[1].resources 260 pathWithIndexError := []string{"spec.template.spec.containers[1].resources"} 261 _, err = fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithIndexError[0]) 262 Expect(err).Should(Not(BeNil())) 263 264 // state-keep with path error skip spec.template[0].spec.containers[0].resources 265 pathWithPathError := []string{"spec.template[0].spec.containers[0].resources"} 266 _, err = fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithPathError[0]) 267 Expect(err).Should(Not(BeNil())) 268 }) 269 270 It("Test StateKeep for FindStrategy", func() { 271 272 cli := testClient 273 createDeployment := func(name string, value *int32) *unstructured.Unstructured { 274 o := &unstructured.Unstructured{ 275 Object: map[string]interface{}{ 276 "metadata": map[string]interface{}{ 277 "name": name, 278 "namespace": "default", 279 "labels": map[string]interface{}{oam.LabelAppComponent: name}, 280 }, 281 "spec": map[string]interface{}{ 282 "replicas": value, 283 }, 284 }, 285 } 286 o.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Deployment")) 287 return o 288 } 289 290 // state-keep add this resource 291 replicas := int32(2) 292 deploy := createDeployment("fourierapp03-comp-01", &replicas) 293 deployRaw, err := json.Marshal(deploy) 294 Expect(err).Should(Succeed()) 295 296 createDeploymentClusterObjectReference := func(name string) common.ClusterObjectReference { 297 return common.ClusterObjectReference{ 298 ObjectReference: corev1.ObjectReference{ 299 Kind: "Deployment", 300 APIVersion: corev1.SchemeGroupVersion.String(), 301 Name: name, 302 Namespace: "default", 303 }, 304 } 305 } 306 307 app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "default"}, 308 Spec: v1beta1.ApplicationSpec{ 309 Components: []common.ApplicationComponent{ 310 { 311 Name: "fourierapp03-comp-01", 312 Type: "worker", 313 Properties: &runtime.RawExtension{Raw: []byte("{\"cmd\":[\"sleep\",\"1000\"],\"image\":\"busybox\"}")}, 314 }, 315 }, 316 Policies: []v1beta1.AppPolicy{ 317 { 318 Name: "apply-once-01", 319 Type: "apply-once", 320 Properties: &runtime.RawExtension{Raw: []byte(`{"enable": true,"rules": [{"selector": { "componentNames": ["fourierapp03-comp-01"], "resourceTypes": ["Deployment" ], "strategy": {"path": ["spec.replicas"] } }}]}`)}, 321 }, 322 }, 323 }} 324 h := &resourceKeeper{ 325 Client: cli, 326 app: app, 327 applicator: apply.NewAPIApplicator(cli), 328 cache: newResourceCache(cli, app), 329 applyOncePolicy: &v1alpha1.ApplyOncePolicySpec{ 330 Enable: true, 331 Rules: []v1alpha1.ApplyOncePolicyRule{{ 332 Selector: v1alpha1.ResourcePolicyRuleSelector{ 333 CompNames: []string{"fourierapp03-comp-01"}, 334 ResourceTypes: []string{"Deployment"}, 335 }, 336 Strategy: &v1alpha1.ApplyOnceStrategy{Path: []string{"spec.replicas"}}, 337 }, 338 }, 339 }, 340 } 341 h._currentRT = &v1beta1.ResourceTracker{ 342 Spec: v1beta1.ResourceTrackerSpec{ 343 ManagedResources: []v1beta1.ManagedResource{{ 344 ClusterObjectReference: createDeploymentClusterObjectReference("fourierapp03-comp-01"), 345 Data: &runtime.RawExtension{Raw: deployRaw}, 346 }}, 347 }, 348 } 349 applyOnceStrategy := h.applyOncePolicy.FindStrategy(deploy) 350 Expect(applyOnceStrategy.Path).Should(Equal([]string{"spec.replicas"})) 351 }) 352 }) 353 354 const ( 355 clusterYaml = ` 356 apiVersion: apps/v1 357 kind: Deployment 358 metadata: 359 annotations: 360 app.io/display-name: fourier-container-040 361 app.io/replicas: '1' 362 deployment.kubernetes.io/revision: '31' 363 io.cmb/liveness_probe_alert_level: warning 364 io.cmb/readiness_probe_alert_level: warning 365 creationTimestamp: '2022-01-12T05:59:50Z' 366 generation: 77 367 labels: 368 app.io/name: fourier-container-040.lt31-04-fourier 369 app.cmboam.io/name: fourier-appfile-040.lt31-04 370 component.cmboam.io/name: fourier-component-040.lt31-04-fourier 371 workload-type: Deployment 372 name: fourier-container-040 373 namespace: lt31-04-fourier 374 resourceVersion: '547401259' 375 uid: c74afeba-18a2-412a-84b4-bd48144356e0 376 spec: 377 progressDeadlineSeconds: 600 378 replicas: 10 379 revisionHistoryLimit: 10 380 selector: 381 matchLabels: 382 app.io/name: fourier-container-040.lt31-04-fourier 383 workload-type: Deployment 384 strategy: 385 rollingUpdate: 386 maxSurge: 25% 387 maxUnavailable: 25% 388 type: RollingUpdate 389 template: 390 metadata: 391 creationTimestamp: null 392 labels: 393 workload-type: Deployment 394 spec: 395 affinity: {} 396 containers: 397 - image: 'cmb.cn/console/proj_gin_test:v2_clusterYaml' 398 imagePullPolicy: IfNotPresent 399 lifecycle: 400 preStop: 401 exec: 402 command: 403 - sh 404 - '-c' 405 - sleep 30 406 livenessProbe: 407 failureThreshold: 3 408 initialDelaySeconds: 30 409 periodSeconds: 30 410 successThreshold: 1 411 tcpSocket: 412 port: 8001 413 timeoutSeconds: 5 414 name: fourier-container-040 415 ports: 416 - containerPort: 8001 417 protocol: TCP 418 readinessProbe: 419 failureThreshold: 3 420 initialDelaySeconds: 30 421 periodSeconds: 30 422 successThreshold: 1 423 tcpSocket: 424 port: 8001 425 timeoutSeconds: 5 426 resources: 427 limits: 428 cpu: '10' 429 memory: 10Gi 430 requests: 431 cpu: 10m 432 memory: 10Mi 433 terminationMessagePath: /dev/termination-log 434 terminationMessagePolicy: File 435 dnsPolicy: ClusterFirst 436 restartPolicy: Always 437 schedulerName: default-scheduler 438 securityContext: {} 439 terminationGracePeriodSeconds: 30 440 status: 441 availableReplicas: 1 442 conditions: 443 - lastTransitionTime: '2022-04-15T02:03:07Z' 444 lastUpdateTime: '2022-04-15T02:03:07Z' 445 message: Deployment has minimum availability. 446 reason: MinimumReplicasAvailable 447 status: 'True' 448 type: Available 449 - lastTransitionTime: '2022-01-12T07:00:52Z' 450 lastUpdateTime: '2022-04-26T07:29:24Z' 451 message: >- 452 ReplicaSet "fourier-container-040-79b8f79fd9" has successfully 453 progressed. 454 reason: NewReplicaSetAvailable 455 status: 'True' 456 type: Progressing 457 observedGeneration: 77 458 readyReplicas: 1 459 replicas: 1 460 updatedReplicas: 1 461 462 ` 463 464 memoryYaml = ` 465 apiVersion: apps/v1 466 kind: Deployment 467 metadata: 468 annotations: 469 app.io/display-name: fourier-container-040 470 app.io/replicas: '1' 471 deployment.kubernetes.io/revision: '31' 472 io.cmb/liveness_probe_alert_level: warning 473 io.cmb/readiness_probe_alert_level: warning 474 creationTimestamp: '2022-01-12T05:59:50Z' 475 generation: 77 476 labels: 477 app.io/name: fourier-container-040.lt31-04-fourier 478 app.cmboam.io/name: fourier-appfile-040.lt31-04 479 component.cmboam.io/name: fourier-component-040.lt31-04-fourier 480 workload-type: Deployment 481 name: fourier-container-040 482 namespace: lt31-04-fourier 483 resourceVersion: '547401259' 484 uid: c74afeba-18a2-412a-84b4-bd48144356e0 485 spec: 486 progressDeadlineSeconds: 600 487 replicas: 5 488 revisionHistoryLimit: 10 489 selector: 490 matchLabels: 491 app.io/name: fourier-container-040.lt31-04-fourier 492 workload-type: Deployment 493 strategy: 494 rollingUpdate: 495 maxSurge: 25% 496 maxUnavailable: 25% 497 type: RollingUpdate 498 template: 499 metadata: 500 creationTimestamp: null 501 labels: 502 workload-type: Deployment 503 spec: 504 affinity: {} 505 containers: 506 - image: 'cmb.cn/console/proj_gin_test:v2_memoryYaml' 507 imagePullPolicy: IfNotPresent 508 lifecycle: 509 preStop: 510 exec: 511 command: 512 - sh 513 - '-c' 514 - sleep 30 515 livenessProbe: 516 failureThreshold: 3 517 initialDelaySeconds: 30 518 periodSeconds: 30 519 successThreshold: 1 520 tcpSocket: 521 port: 8001 522 timeoutSeconds: 5 523 name: fourier-container-040 524 ports: 525 - containerPort: 8001 526 protocol: TCP 527 readinessProbe: 528 failureThreshold: 3 529 initialDelaySeconds: 30 530 periodSeconds: 30 531 successThreshold: 1 532 tcpSocket: 533 port: 8001 534 timeoutSeconds: 5 535 resources: 536 limits: 537 cpu: '5' 538 memory: 5Gi 539 requests: 540 cpu: 5m 541 memory: 5Mi 542 terminationMessagePath: /dev/termination-log 543 terminationMessagePolicy: File 544 dnsPolicy: ClusterFirst 545 restartPolicy: Always 546 schedulerName: default-scheduler 547 securityContext: {} 548 terminationGracePeriodSeconds: 30 549 status: 550 availableReplicas: 1 551 conditions: 552 - lastTransitionTime: '2022-04-15T02:03:07Z' 553 lastUpdateTime: '2022-04-15T02:03:07Z' 554 message: Deployment has minimum availability. 555 reason: MinimumReplicasAvailable 556 status: 'True' 557 type: Available 558 - lastTransitionTime: '2022-01-12T07:00:52Z' 559 lastUpdateTime: '2022-04-26T07:29:24Z' 560 message: >- 561 ReplicaSet "fourier-container-040-79b8f79fd9" has successfully 562 progressed. 563 reason: NewReplicaSetAvailable 564 status: 'True' 565 type: Progressing 566 observedGeneration: 77 567 readyReplicas: 1 568 replicas: 1 569 updatedReplicas: 1 570 571 ` 572 )