github.com/kubevela/workflow@v0.6.0/pkg/providers/kube/handle_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 kube 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "testing" 24 "time" 25 26 . "github.com/onsi/ginkgo" 27 . "github.com/onsi/gomega" 28 29 corev1 "k8s.io/api/core/v1" 30 crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 31 "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/types" 36 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 37 "k8s.io/client-go/rest" 38 "k8s.io/utils/pointer" 39 "sigs.k8s.io/controller-runtime/pkg/client" 40 "sigs.k8s.io/controller-runtime/pkg/envtest" 41 "sigs.k8s.io/yaml" 42 43 monitorContext "github.com/kubevela/pkg/monitor/context" 44 45 wfContext "github.com/kubevela/workflow/pkg/context" 46 "github.com/kubevela/workflow/pkg/cue/model/value" 47 "github.com/kubevela/workflow/pkg/cue/packages" 48 ) 49 50 // These tests use Ginkgo (BDD-style Go testing framework). Refer to 51 // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 52 53 var cfg *rest.Config 54 var k8sClient client.Client 55 var testEnv *envtest.Environment 56 var scheme = runtime.NewScheme() 57 var pd *packages.PackageDiscover 58 var p *provider 59 60 func TestProvider(t *testing.T) { 61 RegisterFailHandler(Fail) 62 63 RunSpecs(t, "Test Definition Suite") 64 } 65 66 var _ = BeforeSuite(func(done Done) { 67 By("Bootstrapping test environment") 68 testEnv = &envtest.Environment{ 69 ControlPlaneStartTimeout: time.Minute, 70 ControlPlaneStopTimeout: time.Minute, 71 UseExistingCluster: pointer.BoolPtr(false), 72 } 73 var err error 74 cfg, err = testEnv.Start() 75 Expect(err).ToNot(HaveOccurred()) 76 Expect(cfg).ToNot(BeNil()) 77 Expect(clientgoscheme.AddToScheme(scheme)).Should(BeNil()) 78 Expect(crdv1.AddToScheme(scheme)).Should(BeNil()) 79 // +kubebuilder:scaffold:scheme 80 By("Create the k8s client") 81 k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) 82 Expect(err).ToNot(HaveOccurred()) 83 Expect(k8sClient).ToNot(BeNil()) 84 pd, err = packages.NewPackageDiscover(cfg) 85 Expect(err).ToNot(HaveOccurred()) 86 87 d := &dispatcher{ 88 cli: k8sClient, 89 } 90 p = &provider{ 91 cli: k8sClient, 92 handlers: Handlers{ 93 Apply: d.apply, 94 Delete: d.delete, 95 }, 96 labels: map[string]string{ 97 "hello": "world", 98 }, 99 } 100 close(done) 101 }, 120) 102 103 var _ = AfterSuite(func() { 104 By("Tearing down the test environment") 105 err := testEnv.Stop() 106 Expect(err).ToNot(HaveOccurred()) 107 }) 108 109 var _ = Describe("Test Workflow Provider Kube", func() { 110 It("apply and read", func() { 111 ctx, err := newWorkflowContextForTest() 112 Expect(err).ToNot(HaveOccurred()) 113 114 v, err := value.NewValue(fmt.Sprintf(` 115 value:{ 116 %s 117 metadata: name: "app" 118 metadata: labels: { 119 "test": "test" 120 } 121 } 122 cluster: "" 123 `, componentStr), nil, "") 124 Expect(err).ToNot(HaveOccurred()) 125 mCtx := monitorContext.NewTraceContext(context.Background(), "") 126 err = p.Apply(mCtx, ctx, v, nil) 127 Expect(err).ToNot(HaveOccurred()) 128 // test patch 129 v, err = value.NewValue(fmt.Sprintf(` 130 value:{ 131 %s 132 metadata: name: "app" 133 } 134 cluster: "" 135 `, componentStr), nil, "") 136 Expect(err).ToNot(HaveOccurred()) 137 err = p.Apply(mCtx, ctx, v, nil) 138 Expect(err).ToNot(HaveOccurred()) 139 workload := &corev1.Pod{} 140 Eventually(func() error { 141 return k8sClient.Get(context.Background(), client.ObjectKey{ 142 Namespace: "default", 143 Name: "app", 144 }, workload) 145 }, time.Second*2, time.Millisecond*300).Should(BeNil()) 146 Expect(len(workload.GetLabels())).To(Equal(2)) 147 148 v, err = value.NewValue(fmt.Sprintf(` 149 value: { 150 %s 151 metadata: name: "app" 152 } 153 cluster: "" 154 `, componentStr), nil, "") 155 Expect(err).ToNot(HaveOccurred()) 156 err = p.Read(mCtx, ctx, v, nil) 157 Expect(err).ToNot(HaveOccurred()) 158 result, err := v.LookupValue("value") 159 Expect(err).ToNot(HaveOccurred()) 160 161 expected := new(unstructured.Unstructured) 162 ev, err := result.MakeValue(expectedCue) 163 Expect(err).ToNot(HaveOccurred()) 164 err = ev.UnmarshalTo(expected) 165 Expect(err).ToNot(HaveOccurred()) 166 167 err = result.FillObject(expected.Object) 168 Expect(err).ToNot(HaveOccurred()) 169 }) 170 171 It("patch & apply", func() { 172 ctx, err := newWorkflowContextForTest() 173 Expect(err).ToNot(HaveOccurred()) 174 175 v, err := value.NewValue(fmt.Sprintf(` 176 value:{ 177 %s 178 metadata: name: "test-app-1" 179 metadata: labels: { 180 "test": "test" 181 } 182 } 183 cluster: "" 184 `, componentStr), nil, "") 185 Expect(err).ToNot(HaveOccurred()) 186 mCtx := monitorContext.NewTraceContext(context.Background(), "") 187 err = p.Apply(mCtx, ctx, v, nil) 188 Expect(err).ToNot(HaveOccurred()) 189 190 v, err = value.NewValue(` 191 value: { 192 apiVersion: "v1" 193 kind: "Pod" 194 metadata: name: "test-app-1" 195 } 196 cluster: "" 197 patch: { 198 metadata: name: "test-app-1" 199 spec: { 200 containers: [{ 201 // +patchStrategy=retainKeys 202 image: "nginx:notfound" 203 }] 204 } 205 }`, nil, "") 206 Expect(err).ToNot(HaveOccurred()) 207 err = p.Patch(mCtx, ctx, v, nil) 208 Expect(err).ToNot(HaveOccurred()) 209 210 pod := &corev1.Pod{} 211 Expect(err).ToNot(HaveOccurred()) 212 Eventually(func() error { 213 return k8sClient.Get(context.Background(), client.ObjectKey{ 214 Namespace: "default", 215 Name: "test-app-1", 216 }, pod) 217 }, time.Second*2, time.Millisecond*300).Should(BeNil()) 218 Expect(pod.Name).To(Equal("test-app-1")) 219 Expect(pod.Spec.Containers[0].Image).To(Equal("nginx:notfound")) 220 }) 221 222 It("list", func() { 223 ctx := context.Background() 224 for i := 2; i >= 0; i-- { 225 err := k8sClient.Create(ctx, &corev1.Pod{ 226 ObjectMeta: metav1.ObjectMeta{ 227 Name: fmt.Sprintf("test-%v", i), 228 Namespace: "default", 229 Labels: map[string]string{ 230 "test": "test", 231 "index": fmt.Sprintf("test-%v", i), 232 }, 233 }, 234 Spec: corev1.PodSpec{ 235 Containers: []corev1.Container{ 236 { 237 Name: fmt.Sprintf("test-%v", i), 238 Image: "busybox", 239 }, 240 }, 241 }, 242 }) 243 Expect(err).ToNot(HaveOccurred()) 244 } 245 246 By("List pods with labels test=test") 247 v, err := value.NewValue(` 248 resource: { 249 apiVersion: "v1" 250 kind: "Pod" 251 } 252 filter: { 253 namespace: "default" 254 matchingLabels: { 255 test: "test" 256 } 257 } 258 cluster: "" 259 `, nil, "") 260 Expect(err).ToNot(HaveOccurred()) 261 wfCtx, err := newWorkflowContextForTest() 262 Expect(err).ToNot(HaveOccurred()) 263 mCtx := monitorContext.NewTraceContext(context.Background(), "") 264 err = p.List(mCtx, wfCtx, v, nil) 265 Expect(err).ToNot(HaveOccurred()) 266 result, err := v.LookupValue("list") 267 Expect(err).ToNot(HaveOccurred()) 268 expected := &metav1.PartialObjectMetadataList{} 269 err = result.UnmarshalTo(expected) 270 Expect(err).ToNot(HaveOccurred()) 271 Expect(len(expected.Items)).Should(Equal(4)) 272 273 By("List pods with labels index=test-1") 274 v, err = value.NewValue(` 275 resource: { 276 apiVersion: "v1" 277 kind: "Pod" 278 } 279 filter: { 280 matchingLabels: { 281 index: "test-1" 282 } 283 } 284 cluster: "" 285 `, nil, "") 286 Expect(err).ToNot(HaveOccurred()) 287 err = p.List(mCtx, wfCtx, v, nil) 288 Expect(err).ToNot(HaveOccurred()) 289 result, err = v.LookupValue("list") 290 Expect(err).ToNot(HaveOccurred()) 291 expected = &metav1.PartialObjectMetadataList{} 292 err = result.UnmarshalTo(expected) 293 Expect(err).ToNot(HaveOccurred()) 294 Expect(len(expected.Items)).Should(Equal(1)) 295 }) 296 297 It("delete", func() { 298 ctx := context.Background() 299 err := k8sClient.Create(ctx, &corev1.Pod{ 300 ObjectMeta: metav1.ObjectMeta{ 301 Name: "test", 302 Namespace: "default", 303 }, 304 Spec: corev1.PodSpec{ 305 Containers: []corev1.Container{ 306 { 307 Name: "test", 308 Image: "busybox", 309 }, 310 }, 311 }, 312 }) 313 Expect(err).ToNot(HaveOccurred()) 314 err = k8sClient.Get(ctx, types.NamespacedName{ 315 Name: "test", 316 Namespace: "default", 317 }, &corev1.Pod{}) 318 Expect(err).ToNot(HaveOccurred()) 319 320 v, err := value.NewValue(` 321 value: { 322 apiVersion: "v1" 323 kind: "Pod" 324 metadata: { 325 name: "test" 326 namespace: "default" 327 } 328 } 329 cluster: "" 330 `, nil, "") 331 Expect(err).ToNot(HaveOccurred()) 332 wfCtx, err := newWorkflowContextForTest() 333 Expect(err).ToNot(HaveOccurred()) 334 mCtx := monitorContext.NewTraceContext(context.Background(), "") 335 err = p.Delete(mCtx, wfCtx, v, nil) 336 Expect(err).ToNot(HaveOccurred()) 337 err = k8sClient.Get(ctx, types.NamespacedName{ 338 Name: "test", 339 Namespace: "default", 340 }, &corev1.Pod{}) 341 Expect(err).To(HaveOccurred()) 342 Expect(errors.IsNotFound(err)).Should(Equal(true)) 343 }) 344 345 It("delete with labels", func() { 346 ctx := context.Background() 347 err := k8sClient.Create(ctx, &corev1.Pod{ 348 ObjectMeta: metav1.ObjectMeta{ 349 Name: "test", 350 Namespace: "default", 351 Labels: map[string]string{ 352 "test.oam.dev": "true", 353 }, 354 }, 355 Spec: corev1.PodSpec{ 356 Containers: []corev1.Container{ 357 { 358 Name: "test", 359 Image: "busybox", 360 }, 361 }, 362 }, 363 }) 364 Expect(err).ToNot(HaveOccurred()) 365 err = k8sClient.Get(ctx, types.NamespacedName{ 366 Name: "test", 367 Namespace: "default", 368 }, &corev1.Pod{}) 369 Expect(err).ToNot(HaveOccurred()) 370 371 v, err := value.NewValue(` 372 value: { 373 apiVersion: "v1" 374 kind: "Pod" 375 metadata: { 376 namespace: "default" 377 } 378 } 379 filter: { 380 namespace: "default" 381 matchingLabels: { 382 "test.oam.dev": "true" 383 } 384 } 385 cluster: "" 386 `, nil, "") 387 Expect(err).ToNot(HaveOccurred()) 388 wfCtx, err := newWorkflowContextForTest() 389 Expect(err).ToNot(HaveOccurred()) 390 mCtx := monitorContext.NewTraceContext(context.Background(), "") 391 err = p.Delete(mCtx, wfCtx, v, nil) 392 Expect(err).ToNot(HaveOccurred()) 393 err = k8sClient.Get(ctx, types.NamespacedName{ 394 Name: "test", 395 Namespace: "default", 396 }, &corev1.Pod{}) 397 Expect(err).To(HaveOccurred()) 398 Expect(errors.IsNotFound(err)).Should(Equal(true)) 399 }) 400 401 It("apply parallel", func() { 402 ctx, err := newWorkflowContextForTest() 403 Expect(err).ToNot(HaveOccurred()) 404 405 v, err := value.NewValue(fmt.Sprintf(` 406 value:[ 407 { 408 %s 409 metadata: name: "app1" 410 }, 411 { 412 %s 413 metadata: name: "app1" 414 } 415 ] 416 cluster: "" 417 `, componentStr, componentStr), nil, "") 418 Expect(err).ToNot(HaveOccurred()) 419 mCtx := monitorContext.NewTraceContext(context.Background(), "") 420 err = p.ApplyInParallel(mCtx, ctx, v, nil) 421 Expect(err).ToNot(HaveOccurred()) 422 }) 423 424 It("test error case", func() { 425 ctx, err := newWorkflowContextForTest() 426 Expect(err).ToNot(HaveOccurred()) 427 428 v, err := value.NewValue(` 429 value: { 430 kind: "Pod" 431 apiVersion: "v1" 432 spec: close({kind: 12}) 433 }`, nil, "") 434 Expect(err).ToNot(HaveOccurred()) 435 mCtx := monitorContext.NewTraceContext(context.Background(), "") 436 err = p.Apply(mCtx, ctx, v, nil) 437 Expect(err).To(HaveOccurred()) 438 439 v, _ = value.NewValue(` 440 value: { 441 kind: "Pod" 442 apiVersion: "v1" 443 } 444 patch: _|_ 445 `, nil, "") 446 err = p.Apply(mCtx, ctx, v, nil) 447 Expect(err).To(HaveOccurred()) 448 449 v, err = value.NewValue(` 450 value: { 451 metadata: { 452 name: "app-xx" 453 namespace: "default" 454 } 455 kind: "Pod" 456 apiVersion: "v1" 457 } 458 cluster: "test" 459 `, nil, "") 460 Expect(err).ToNot(HaveOccurred()) 461 err = p.Read(mCtx, ctx, v, nil) 462 Expect(err).ToNot(HaveOccurred()) 463 errV, err := v.Field("err") 464 Expect(err).ToNot(HaveOccurred()) 465 Expect(errV.Exists()).Should(BeTrue()) 466 467 v, err = value.NewValue(` 468 val: { 469 metadata: { 470 name: "app-xx" 471 namespace: "default" 472 } 473 kind: "Pod" 474 apiVersion: "v1" 475 } 476 `, nil, "") 477 Expect(err).ToNot(HaveOccurred()) 478 err = p.Read(mCtx, ctx, v, nil) 479 Expect(err).To(HaveOccurred()) 480 err = p.Apply(mCtx, ctx, v, nil) 481 Expect(err).To(HaveOccurred()) 482 }) 483 }) 484 485 func newWorkflowContextForTest() (wfContext.Context, error) { 486 cm := corev1.ConfigMap{} 487 488 testCaseJson, err := yaml.YAMLToJSON([]byte(testCaseYaml)) 489 if err != nil { 490 return nil, err 491 } 492 err = json.Unmarshal(testCaseJson, &cm) 493 if err != nil { 494 return nil, err 495 } 496 497 wfCtx := new(wfContext.WorkflowContext) 498 err = wfCtx.LoadFromConfigMap(cm) 499 return wfCtx, err 500 } 501 502 var ( 503 componentStr = `apiVersion: "v1" 504 kind: "Pod" 505 metadata: { 506 labels: { 507 app: "nginx" 508 } 509 } 510 spec: { 511 containers: [{ 512 env: [{ 513 name: "APP" 514 value: "nginx" 515 }] 516 image: "nginx:1.14.2" 517 imagePullPolicy: "IfNotPresent" 518 name: "main" 519 ports: [{ 520 containerPort: 8080 521 protocol: "TCP" 522 }] 523 }] 524 }` 525 testCaseYaml = `apiVersion: v1 526 data: 527 test: "" 528 kind: ConfigMap 529 metadata: 530 name: app-v1 531 ` 532 expectedCue = `status: { 533 phase: "Pending" 534 qosClass: "BestEffort" 535 } 536 apiVersion: "v1" 537 kind: "Pod" 538 metadata: { 539 name: "app" 540 labels: { 541 app: "nginx" 542 } 543 namespace: "default" 544 } 545 spec: { 546 containers: [{ 547 name: "main" 548 env: [{ 549 name: "APP" 550 value: "nginx" 551 }] 552 image: "nginx:1.14.2" 553 imagePullPolicy: "IfNotPresent" 554 ports: [{ 555 containerPort: 8080 556 protocol: "TCP" 557 }] 558 resources: {} 559 terminationMessagePath: "/dev/termination-log" 560 terminationMessagePolicy: "File" 561 }] 562 dnsPolicy: "ClusterFirst" 563 enableServiceLinks: true 564 preemptionPolicy: "PreemptLowerPriority" 565 priority: 0 566 restartPolicy: "Always" 567 schedulerName: "default-scheduler" 568 securityContext: {} 569 terminationGracePeriodSeconds: 30 570 tolerations: [{ 571 effect: "NoExecute" 572 key: "node.kubernetes.io/not-ready" 573 operator: "Exists" 574 tolerationSeconds: 300 575 }, { 576 effect: "NoExecute" 577 key: "node.kubernetes.io/unreachable" 578 operator: "Exists" 579 tolerationSeconds: 300 580 }] 581 }` 582 )