github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/workflow_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 application 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "time" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 appsv1 "k8s.io/api/apps/v1" 28 corev1 "k8s.io/api/core/v1" 29 crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 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 "k8s.io/apimachinery/pkg/types" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 "sigs.k8s.io/controller-runtime/pkg/reconcile" 37 "sigs.k8s.io/yaml" 38 39 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 40 wfTypes "github.com/kubevela/workflow/pkg/types" 41 42 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 43 oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 44 "github.com/oam-dev/kubevela/pkg/oam/testutil" 45 "github.com/oam-dev/kubevela/pkg/oam/util" 46 ) 47 48 var _ = Describe("Test Workflow", func() { 49 ctx := context.Background() 50 namespace := "test-ns" 51 52 appWithWorkflow := &oamcore.Application{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Name: "test-app", 55 Namespace: namespace, 56 }, 57 Spec: oamcore.ApplicationSpec{ 58 Components: []common.ApplicationComponent{{ 59 Name: "test-component", 60 Type: "worker", 61 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 62 }}, 63 Workflow: &oamcore.Workflow{ 64 Steps: []workflowv1alpha1.WorkflowStep{{ 65 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 66 Name: "test-wf1", 67 Type: "foowf", 68 Properties: &runtime.RawExtension{Raw: []byte(`{"namespace":"test-ns"}`)}, 69 }, 70 }}, 71 }, 72 }, 73 } 74 appWithWorkflowAndPolicy := appWithWorkflow.DeepCopy() 75 appWithWorkflowAndPolicy.Name = "test-wf-policy" 76 appWithWorkflowAndPolicy.Spec.Policies = []oamcore.AppPolicy{{ 77 Name: "test-policy-and-wf", 78 Type: "foopolicy", 79 Properties: &runtime.RawExtension{Raw: []byte(`{"key":"test"}`)}, 80 }} 81 82 appWithPolicy := &oamcore.Application{ 83 ObjectMeta: metav1.ObjectMeta{ 84 Name: "test-app-only-with-policy", 85 Namespace: namespace, 86 }, 87 Spec: oamcore.ApplicationSpec{ 88 Components: []common.ApplicationComponent{{ 89 Name: "test-component-with-policy", 90 Type: "worker", 91 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 92 }}, 93 Policies: []oamcore.AppPolicy{{ 94 Name: "test-policy", 95 Type: "foopolicy", 96 Properties: &runtime.RawExtension{Raw: []byte(`{"key":"test"}`)}, 97 }}, 98 }, 99 } 100 101 testDefinitions := []string{componentDefYaml, policyDefYaml, wfStepDefYaml} 102 103 BeforeEach(func() { 104 setupFooCRD(ctx) 105 setupNamespace(ctx, namespace) 106 setupTestDefinitions(ctx, testDefinitions, namespace) 107 By("[TEST] Set up definitions before integration test") 108 }) 109 110 AfterEach(func() { 111 Expect(k8sClient.DeleteAllOf(ctx, &appsv1.ControllerRevision{}, client.InNamespace(namespace))).Should(Succeed()) 112 }) 113 114 It("should create workload in application when policy is specified", func() { 115 Expect(k8sClient.Create(ctx, appWithPolicy)).Should(BeNil()) 116 117 // first try to add finalizer 118 tryReconcile(reconciler, appWithPolicy.Name, appWithPolicy.Namespace) 119 tryReconcile(reconciler, appWithPolicy.Name, appWithPolicy.Namespace) 120 121 deploy := &appsv1.Deployment{} 122 Expect(k8sClient.Get(ctx, client.ObjectKey{ 123 Name: appWithPolicy.Spec.Components[0].Name, 124 Namespace: appWithPolicy.Namespace, 125 }, deploy)).Should(BeNil()) 126 127 policyObj := &unstructured.Unstructured{} 128 policyObj.SetGroupVersionKind(schema.GroupVersionKind{ 129 Group: "example.com", 130 Kind: "Foo", 131 Version: "v1", 132 }) 133 134 Expect(k8sClient.Get(ctx, client.ObjectKey{ 135 Name: "test-policy", 136 Namespace: appWithPolicy.Namespace, 137 }, policyObj)).Should(BeNil()) 138 }) 139 140 It("should create workload in policy", func() { 141 appWithPolicyAndWorkflow := appWithWorkflow.DeepCopy() 142 appWithPolicyAndWorkflow.Spec.Policies = []oamcore.AppPolicy{{ 143 Name: "test-foo-policy", 144 Type: "foopolicy", 145 Properties: &runtime.RawExtension{Raw: []byte(`{"key":"test"}`)}, 146 }} 147 148 Expect(k8sClient.Create(ctx, appWithPolicyAndWorkflow)).Should(BeNil()) 149 150 // first try to add finalizer 151 tryReconcile(reconciler, appWithPolicyAndWorkflow.Name, appWithPolicyAndWorkflow.Namespace) 152 tryReconcile(reconciler, appWithPolicyAndWorkflow.Name, appWithPolicyAndWorkflow.Namespace) 153 154 appRev := &oamcore.ApplicationRevision{} 155 Expect(k8sClient.Get(ctx, client.ObjectKey{ 156 Name: appWithWorkflow.Name + "-v1", 157 Namespace: namespace, 158 }, appRev)).Should(BeNil()) 159 160 policyObj := &unstructured.Unstructured{} 161 policyObj.SetGroupVersionKind(schema.GroupVersionKind{ 162 Group: "example.com", 163 Kind: "Foo", 164 Version: "v1", 165 }) 166 167 Expect(k8sClient.Get(ctx, client.ObjectKey{ 168 Name: "test-foo-policy", 169 Namespace: appWithPolicyAndWorkflow.Namespace, 170 }, policyObj)).Should(BeNil()) 171 172 // check resource created 173 stepObj := &unstructured.Unstructured{} 174 stepObj.SetGroupVersionKind(schema.GroupVersionKind{ 175 Group: "example.com", 176 Kind: "Foo", 177 Version: "v1", 178 }) 179 180 Expect(k8sClient.Get(ctx, client.ObjectKey{ 181 Name: "test-foo", 182 Namespace: appWithWorkflow.Namespace, 183 }, stepObj)).Should(BeNil()) 184 185 // check workflow status is waiting 186 appObj := &oamcore.Application{} 187 Expect(k8sClient.Get(ctx, client.ObjectKey{ 188 Name: appWithWorkflow.Name, 189 Namespace: appWithWorkflow.Namespace, 190 }, appObj)).Should(BeNil()) 191 192 Expect(appObj.Status.Workflow.Steps[0].Name).Should(Equal("test-wf1")) 193 Expect(appObj.Status.Workflow.Steps[0].Type).Should(Equal("foowf")) 194 Expect(appObj.Status.Workflow.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseRunning)) 195 Expect(appObj.Status.Workflow.Steps[0].Reason).Should(Equal("Wait")) 196 197 // update spec to trigger spec 198 triggerWorkflowStepToSucceed(stepObj) 199 Expect(k8sClient.Update(ctx, stepObj)).Should(BeNil()) 200 201 tryReconcile(reconciler, appWithWorkflow.Name, appWithWorkflow.Namespace) 202 tryReconcile(reconciler, appWithWorkflow.Name, appWithWorkflow.Namespace) 203 204 // check workflow status is succeeded 205 Expect(k8sClient.Get(ctx, client.ObjectKey{ 206 Name: appWithWorkflow.Name, 207 Namespace: appWithWorkflow.Namespace, 208 }, appObj)).Should(BeNil()) 209 210 Expect(appObj.Status.Workflow.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseSucceeded)) 211 Expect(appObj.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) 212 }) 213 214 It("test workflow suspend", func() { 215 suspendApp := appWithWorkflow.DeepCopy() 216 suspendApp.Name = "test-app-suspend" 217 suspendApp.Spec.Workflow.Steps = []workflowv1alpha1.WorkflowStep{{ 218 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 219 Name: "suspend", 220 Type: "suspend", 221 Properties: &runtime.RawExtension{Raw: []byte(`{}`)}, 222 }, 223 }} 224 Expect(k8sClient.Create(ctx, suspendApp)).Should(BeNil()) 225 226 // first try to add finalizer 227 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 228 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 229 230 appObj := &oamcore.Application{} 231 Expect(k8sClient.Get(ctx, client.ObjectKey{ 232 Name: suspendApp.Name, 233 Namespace: suspendApp.Namespace, 234 }, appObj)).Should(BeNil()) 235 236 Expect(appObj.Status.Workflow.Suspend).Should(BeTrue()) 237 Expect(appObj.Status.Phase).Should(BeEquivalentTo(common.ApplicationWorkflowSuspending)) 238 Expect(appObj.Status.Workflow.Steps[0].Phase).Should(BeEquivalentTo(workflowv1alpha1.WorkflowStepPhaseSuspending)) 239 Expect(appObj.Status.Workflow.Steps[0].ID).ShouldNot(BeEquivalentTo("")) 240 // resume 241 appObj.Status.Workflow.Suspend = false 242 appObj.Status.Workflow.Steps[0].Phase = workflowv1alpha1.WorkflowStepPhaseRunning 243 Expect(k8sClient.Status().Patch(ctx, appObj, client.Merge)).Should(BeNil()) 244 Expect(k8sClient.Get(ctx, client.ObjectKey{ 245 Name: suspendApp.Name, 246 Namespace: suspendApp.Namespace, 247 }, appObj)).Should(BeNil()) 248 Expect(appObj.Status.Workflow.Suspend).Should(BeFalse()) 249 250 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 251 252 appObj = &oamcore.Application{} 253 Expect(k8sClient.Get(ctx, client.ObjectKey{ 254 Name: suspendApp.Name, 255 Namespace: suspendApp.Namespace, 256 }, appObj)).Should(BeNil()) 257 258 Expect(appObj.Status.Workflow.Suspend).Should(BeFalse()) 259 Expect(appObj.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) 260 }) 261 262 It("test workflow terminate a suspend workflow", func() { 263 suspendApp := appWithWorkflow.DeepCopy() 264 suspendApp.Name = "test-terminate-suspend-app" 265 suspendApp.Spec.Workflow.Steps = []workflowv1alpha1.WorkflowStep{ 266 { 267 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 268 Name: "suspend", 269 Type: "suspend", 270 Properties: &runtime.RawExtension{Raw: []byte(`{}`)}, 271 }, 272 }, 273 { 274 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 275 Name: "suspend-1", 276 Type: "suspend", 277 Properties: &runtime.RawExtension{Raw: []byte(`{}`)}, 278 }, 279 }} 280 Expect(k8sClient.Create(ctx, suspendApp)).Should(BeNil()) 281 282 // first try to add finalizer 283 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 284 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 285 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 286 287 appObj := &oamcore.Application{} 288 Expect(k8sClient.Get(ctx, client.ObjectKey{ 289 Name: suspendApp.Name, 290 Namespace: suspendApp.Namespace, 291 }, appObj)).Should(BeNil()) 292 293 Expect(appObj.Status.Workflow.Suspend).Should(BeTrue()) 294 Expect(appObj.Status.Phase).Should(BeEquivalentTo(common.ApplicationWorkflowSuspending)) 295 296 // terminate 297 appObj.Status.Workflow.Terminated = true 298 appObj.Status.Workflow.Suspend = false 299 appObj.Status.Workflow.Steps[0].Phase = workflowv1alpha1.WorkflowStepPhaseFailed 300 appObj.Status.Workflow.Steps[0].Reason = wfTypes.StatusReasonTerminate 301 Expect(k8sClient.Status().Patch(ctx, appObj, client.Merge)).Should(BeNil()) 302 303 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 304 tryReconcile(reconciler, suspendApp.Name, suspendApp.Namespace) 305 306 appObj = &oamcore.Application{} 307 Expect(k8sClient.Get(ctx, client.ObjectKey{ 308 Name: suspendApp.Name, 309 Namespace: suspendApp.Namespace, 310 }, appObj)).Should(BeNil()) 311 312 Expect(appObj.Status.Workflow.Suspend).Should(BeFalse()) 313 Expect(appObj.Status.Workflow.Terminated).Should(BeTrue()) 314 Expect(appObj.Status.Phase).Should(BeEquivalentTo(common.ApplicationWorkflowTerminated)) 315 }) 316 317 It("test application with input/output and workflow", func() { 318 ns := corev1.Namespace{ 319 ObjectMeta: metav1.ObjectMeta{ 320 Name: "app-with-inout-workflow", 321 }, 322 } 323 Expect(k8sClient.Create(ctx, &ns)).Should(BeNil()) 324 325 healthComponentDef := &oamcore.ComponentDefinition{} 326 hCDefJson, _ := yaml.YAMLToJSON([]byte(cdDefWithHealthStatusYaml)) 327 Expect(json.Unmarshal(hCDefJson, healthComponentDef)).Should(BeNil()) 328 healthComponentDef.Name = "worker-with-health" 329 healthComponentDef.Namespace = ns.Name 330 Expect(k8sClient.Create(ctx, healthComponentDef)).Should(BeNil()) 331 appwithInputOutput := &oamcore.Application{ 332 TypeMeta: metav1.TypeMeta{ 333 Kind: "Application", 334 APIVersion: "core.oam.dev/v1beta1", 335 }, 336 ObjectMeta: metav1.ObjectMeta{ 337 Name: "app-with-inout-workflow", 338 Namespace: "app-with-inout-workflow", 339 }, 340 Spec: oamcore.ApplicationSpec{ 341 Components: []common.ApplicationComponent{ 342 { 343 Name: "myweb1", 344 Type: "worker-with-health", 345 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 346 Inputs: workflowv1alpha1.StepInputs{ 347 { 348 From: "message", 349 ParameterKey: "properties.enemies", 350 }, 351 { 352 From: "message", 353 ParameterKey: "properties.lives", 354 }, 355 }, 356 }, 357 { 358 Name: "myweb2", 359 Type: "worker-with-health", 360 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)}, 361 Outputs: workflowv1alpha1.StepOutputs{ 362 {Name: "message", ValueFrom: "output.status.conditions[0].message+\",\"+outputs.gameconfig.data.lives"}, 363 }, 364 }, 365 }, 366 Workflow: &oamcore.Workflow{ 367 Steps: []workflowv1alpha1.WorkflowStep{{ 368 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 369 Name: "test-web2", 370 Type: "apply-component", 371 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb2"}`)}, 372 }, 373 }, { 374 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 375 Name: "test-web1", 376 Type: "apply-component", 377 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb1"}`)}, 378 }, 379 }}, 380 }, 381 }, 382 } 383 384 Expect(k8sClient.Create(context.Background(), appwithInputOutput)).Should(BeNil()) 385 appKey := types.NamespacedName{Namespace: ns.Name, Name: appwithInputOutput.Name} 386 testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey}) 387 388 expDeployment := &appsv1.Deployment{} 389 web1Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb1"} 390 web2Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb2"} 391 Expect(k8sClient.Get(ctx, web1Key, expDeployment)).Should(util.NotFoundMatcher{}) 392 393 checkApp := &oamcore.Application{} 394 Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) 395 Expect(k8sClient.Get(ctx, web2Key, expDeployment)).Should(BeNil()) 396 397 expDeployment.Status.Replicas = 1 398 expDeployment.Status.ReadyReplicas = 1 399 expDeployment.Status.Conditions = []appsv1.DeploymentCondition{{ 400 Message: "hello", 401 }} 402 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) 403 404 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 405 406 expDeployment = &appsv1.Deployment{} 407 Expect(k8sClient.Get(ctx, web1Key, expDeployment)).Should(BeNil()) 408 expDeployment.Status.Replicas = 1 409 expDeployment.Status.ReadyReplicas = 1 410 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) 411 412 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 413 checkApp = &oamcore.Application{} 414 Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) 415 Expect(checkApp.Status.Workflow.Mode).Should(BeEquivalentTo(fmt.Sprintf("%s-%s", workflowv1alpha1.WorkflowModeStep, workflowv1alpha1.WorkflowModeDAG))) 416 Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) 417 418 checkCM := &corev1.ConfigMap{} 419 cmKey := types.NamespacedName{ 420 Name: "myweb1game-config", 421 Namespace: ns.Name, 422 } 423 Expect(k8sClient.Get(ctx, cmKey, checkCM)).Should(BeNil()) 424 Expect(checkCM.Data["enemies"]).Should(BeEquivalentTo("hello,i am lives")) 425 Expect(checkCM.Data["lives"]).Should(BeEquivalentTo("hello,i am lives")) 426 }) 427 428 It("test application with depends on and workflow", func() { 429 ns := corev1.Namespace{ 430 ObjectMeta: metav1.ObjectMeta{ 431 Name: "app-with-dependson-workflow", 432 }, 433 } 434 Expect(k8sClient.Create(ctx, &ns)).Should(BeNil()) 435 436 healthComponentDef := &oamcore.ComponentDefinition{} 437 hCDefJson, _ := yaml.YAMLToJSON([]byte(cdDefWithHealthStatusYaml)) 438 Expect(json.Unmarshal(hCDefJson, healthComponentDef)).Should(BeNil()) 439 healthComponentDef.Name = "worker-with-health" 440 healthComponentDef.Namespace = ns.Name 441 Expect(k8sClient.Create(ctx, healthComponentDef)).Should(BeNil()) 442 appwithDependsOn := &oamcore.Application{ 443 TypeMeta: metav1.TypeMeta{ 444 Kind: "Application", 445 APIVersion: "core.oam.dev/v1beta1", 446 }, 447 ObjectMeta: metav1.ObjectMeta{ 448 Name: "app-with-dependson-workflow", 449 Namespace: ns.Name, 450 }, 451 Spec: oamcore.ApplicationSpec{ 452 Components: []common.ApplicationComponent{ 453 { 454 Name: "myweb1", 455 Type: "worker", 456 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 457 DependsOn: []string{"myweb2"}, 458 }, 459 { 460 Name: "myweb2", 461 Type: "worker-with-health", 462 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)}, 463 }, 464 }, 465 Workflow: &oamcore.Workflow{ 466 Steps: []workflowv1alpha1.WorkflowStep{{ 467 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 468 Name: "test-web2", 469 Type: "apply-component", 470 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb2"}`)}, 471 }, 472 }, { 473 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 474 Name: "test-web1", 475 Type: "apply-component", 476 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb1"}`)}, 477 }, 478 }}, 479 }, 480 }, 481 } 482 483 Expect(k8sClient.Create(context.Background(), appwithDependsOn)).Should(BeNil()) 484 appKey := types.NamespacedName{Namespace: ns.Name, Name: appwithDependsOn.Name} 485 testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey}) 486 487 expDeployment := &appsv1.Deployment{} 488 web1Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb1"} 489 web2Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb2"} 490 Expect(k8sClient.Get(ctx, web1Key, expDeployment)).Should(util.NotFoundMatcher{}) 491 492 checkApp := &oamcore.Application{} 493 Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) 494 Expect(k8sClient.Get(ctx, web2Key, expDeployment)).Should(BeNil()) 495 496 expDeployment.Status.Replicas = 1 497 expDeployment.Status.ReadyReplicas = 1 498 expDeployment.Status.Conditions = []appsv1.DeploymentCondition{{ 499 Message: "hello", 500 }} 501 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) 502 503 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 504 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 505 506 expDeployment = &appsv1.Deployment{} 507 Expect(k8sClient.Get(ctx, web1Key, expDeployment)).Should(BeNil()) 508 expDeployment.Status.Replicas = 1 509 expDeployment.Status.ReadyReplicas = 1 510 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) 511 512 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 513 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 514 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 515 checkApp = &oamcore.Application{} 516 Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) 517 Expect(checkApp.Status.Workflow.Mode).Should(BeEquivalentTo(fmt.Sprintf("%s-%s", workflowv1alpha1.WorkflowModeStep, workflowv1alpha1.WorkflowModeDAG))) 518 Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) 519 }) 520 521 It("test application with depends on and input output and workflow", func() { 522 ns := corev1.Namespace{ 523 ObjectMeta: metav1.ObjectMeta{ 524 Name: "app-with-inout-dependson-workflow", 525 }, 526 } 527 Expect(k8sClient.Create(ctx, &ns)).Should(BeNil()) 528 529 healthComponentDef := &oamcore.ComponentDefinition{} 530 hCDefJson, _ := yaml.YAMLToJSON([]byte(cdDefWithHealthStatusYaml)) 531 Expect(json.Unmarshal(hCDefJson, healthComponentDef)).Should(BeNil()) 532 healthComponentDef.Name = "worker-with-health" 533 healthComponentDef.Namespace = ns.Name 534 Expect(k8sClient.Create(ctx, healthComponentDef)).Should(BeNil()) 535 appwithInOutDependsOn := &oamcore.Application{ 536 TypeMeta: metav1.TypeMeta{ 537 Kind: "Application", 538 APIVersion: "core.oam.dev/v1beta1", 539 }, 540 ObjectMeta: metav1.ObjectMeta{ 541 Name: "app-with-inout-dependson-workflow", 542 Namespace: ns.Name, 543 }, 544 Spec: oamcore.ApplicationSpec{ 545 Components: []common.ApplicationComponent{ 546 { 547 Name: "myweb1", 548 Type: "worker-with-health", 549 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)}, 550 DependsOn: []string{"myweb2"}, 551 Inputs: workflowv1alpha1.StepInputs{ 552 { 553 From: "message", 554 ParameterKey: "properties.enemies", 555 }, 556 { 557 From: "message", 558 ParameterKey: "properties.lives", 559 }, 560 }, 561 }, 562 { 563 Name: "myweb2", 564 Type: "worker-with-health", 565 Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)}, 566 Outputs: workflowv1alpha1.StepOutputs{ 567 {Name: "message", ValueFrom: "output.status.conditions[0].message+\",\"+outputs.gameconfig.data.lives"}, 568 }, 569 }, 570 }, 571 Workflow: &oamcore.Workflow{ 572 Steps: []workflowv1alpha1.WorkflowStep{{ 573 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 574 Name: "test-web2", 575 Type: "apply-component", 576 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb2"}`)}, 577 }, 578 }, { 579 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 580 Name: "test-web1", 581 Type: "apply-component", 582 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb1"}`)}, 583 }, 584 }}, 585 }, 586 }, 587 } 588 589 Expect(k8sClient.Create(context.Background(), appwithInOutDependsOn)).Should(BeNil()) 590 appKey := types.NamespacedName{Namespace: ns.Name, Name: appwithInOutDependsOn.Name} 591 testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey}) 592 593 expDeployment := &appsv1.Deployment{} 594 web1Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb1"} 595 web2Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb2"} 596 Expect(k8sClient.Get(ctx, web1Key, expDeployment)).Should(util.NotFoundMatcher{}) 597 598 checkApp := &oamcore.Application{} 599 Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) 600 Expect(k8sClient.Get(ctx, web2Key, expDeployment)).Should(BeNil()) 601 602 expDeployment.Status.Replicas = 1 603 expDeployment.Status.ReadyReplicas = 1 604 expDeployment.Status.Conditions = []appsv1.DeploymentCondition{{ 605 Message: "hello", 606 }} 607 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) 608 609 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 610 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 611 612 expDeployment = &appsv1.Deployment{} 613 Expect(k8sClient.Get(ctx, web1Key, expDeployment)).Should(BeNil()) 614 expDeployment.Status.Replicas = 1 615 expDeployment.Status.ReadyReplicas = 1 616 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) 617 618 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 619 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 620 testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) 621 checkApp = &oamcore.Application{} 622 Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) 623 Expect(checkApp.Status.Workflow.Mode).Should(BeEquivalentTo(fmt.Sprintf("%s-%s", workflowv1alpha1.WorkflowModeStep, workflowv1alpha1.WorkflowModeDAG))) 624 Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) 625 }) 626 627 It("add workflow to an existing app ", func() { 628 ns := corev1.Namespace{ 629 ObjectMeta: metav1.ObjectMeta{ 630 Name: "existing-app-add-workflow", 631 }, 632 } 633 Expect(k8sClient.Create(ctx, &ns)).Should(BeNil()) 634 635 webComponentDef := &oamcore.ComponentDefinition{} 636 webDefJson, _ := yaml.YAMLToJSON([]byte(webComponentDefYaml)) 637 Expect(json.Unmarshal(webDefJson, webComponentDef)).Should(BeNil()) 638 webComponentDef.Namespace = ns.Name 639 Expect(k8sClient.Create(ctx, webComponentDef)).Should(BeNil()) 640 app := &oamcore.Application{ 641 TypeMeta: metav1.TypeMeta{ 642 Kind: "Application", 643 APIVersion: "core.oam.dev/v1beta1", 644 }, 645 ObjectMeta: metav1.ObjectMeta{ 646 Name: "existing-app-add-workflow", 647 Namespace: ns.Name, 648 }, 649 Spec: oamcore.ApplicationSpec{ 650 Components: []common.ApplicationComponent{ 651 { 652 Name: "myweb1", 653 Type: "webserver", 654 Properties: &runtime.RawExtension{Raw: []byte(`{"image":"busybox"}`)}, 655 }, 656 { 657 Name: "myweb2", 658 Type: "webserver", 659 Properties: &runtime.RawExtension{Raw: []byte(`{"image":"busybox"}`)}, 660 }, 661 }, 662 }, 663 } 664 665 Expect(k8sClient.Create(context.Background(), app)).Should(BeNil()) 666 appKey := types.NamespacedName{Namespace: ns.Name, Name: app.Name} 667 testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey}) 668 updateApp := &oamcore.Application{} 669 Expect(k8sClient.Get(ctx, appKey, updateApp)).Should(BeNil()) 670 Expect(updateApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) 671 updateApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(`{}`)} 672 updateApp.Spec.Workflow = &oamcore.Workflow{ 673 Steps: []workflowv1alpha1.WorkflowStep{{ 674 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 675 Name: "test-web2", 676 Type: "apply-component", 677 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb2"}`)}, 678 Outputs: workflowv1alpha1.StepOutputs{ 679 {Name: "image", ValueFrom: "output.spec.template.spec.containers[0].image"}, 680 }, 681 }, 682 }, { 683 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 684 Name: "test-web1", 685 Type: "apply-component", 686 Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb1"}`)}, 687 Inputs: workflowv1alpha1.StepInputs{ 688 { 689 From: "image", 690 ParameterKey: "image", 691 }, 692 }, 693 }, 694 }}, 695 } 696 Expect(k8sClient.Update(context.Background(), updateApp)).Should(BeNil()) 697 testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey}) 698 checkApp := &oamcore.Application{} 699 Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) 700 Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) 701 }) 702 }) 703 704 func triggerWorkflowStepToSucceed(obj *unstructured.Unstructured) { 705 unstructured.SetNestedField(obj.Object, "ready", "spec", "key") 706 } 707 708 func tryReconcile(r *Reconciler, name, ns string) { 709 appKey := client.ObjectKey{ 710 Name: name, 711 Namespace: ns, 712 } 713 714 Eventually(func() error { 715 _, err := r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: appKey}) 716 if err != nil { 717 By(fmt.Sprintf("reconcile err: %+v ", err)) 718 } 719 return err 720 }, 10*time.Second, time.Second).Should(BeNil()) 721 } 722 723 func setupNamespace(ctx context.Context, namespace string) { 724 ns := &corev1.Namespace{ 725 ObjectMeta: metav1.ObjectMeta{Name: namespace}, 726 } 727 Expect(k8sClient.Create(ctx, ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 728 } 729 730 func setupFooCRD(ctx context.Context) { 731 trueVar := true 732 foocrd := &crdv1.CustomResourceDefinition{ 733 ObjectMeta: metav1.ObjectMeta{ 734 Name: "foo.example.com", 735 }, 736 Spec: crdv1.CustomResourceDefinitionSpec{ 737 Group: "example.com", 738 Names: crdv1.CustomResourceDefinitionNames{ 739 Kind: "Foo", 740 ListKind: "FooList", 741 Plural: "foo", 742 Singular: "foo", 743 }, 744 Versions: []crdv1.CustomResourceDefinitionVersion{{ 745 Name: "v1", 746 Served: true, 747 Storage: true, 748 Schema: &crdv1.CustomResourceValidation{ 749 OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 750 Type: "object", 751 Properties: map[string]crdv1.JSONSchemaProps{ 752 "spec": { 753 Type: "object", 754 Properties: map[string]crdv1.JSONSchemaProps{ 755 "key": {Type: "string"}, 756 }, 757 }, 758 }, 759 XPreserveUnknownFields: &trueVar, 760 }, 761 }, 762 }, 763 }, 764 Scope: crdv1.NamespaceScoped, 765 }, 766 } 767 Expect(k8sClient.Create(ctx, foocrd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 768 } 769 770 func setupTestDefinitions(ctx context.Context, defs []string, ns string) { 771 for _, def := range defs { 772 defJson, err := yaml.YAMLToJSON([]byte(def)) 773 Expect(err).Should(BeNil()) 774 u := &unstructured.Unstructured{} 775 Expect(json.Unmarshal(defJson, u)).Should(BeNil()) 776 u.SetNamespace("vela-system") 777 Expect(k8sClient.Create(ctx, u)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 778 } 779 } 780 781 const ( 782 policyDefYaml = `apiVersion: core.oam.dev/v1beta1 783 kind: PolicyDefinition 784 metadata: 785 name: foopolicy 786 spec: 787 schematic: 788 cue: 789 template: | 790 output: { 791 apiVersion: "example.com/v1" 792 kind: "Foo" 793 spec: { 794 key: parameter.key 795 } 796 } 797 parameter: { 798 key: string 799 } 800 ` 801 wfStepDefYaml = `apiVersion: core.oam.dev/v1beta1 802 kind: WorkflowStepDefinition 803 metadata: 804 name: foowf 805 spec: 806 schematic: 807 cue: 808 template: | 809 import ("vela/op") 810 811 parameter: { 812 namespace: string 813 } 814 815 // apply workload to kubernetes cluster 816 apply: op.#Apply & { 817 value: { 818 apiVersion: "example.com/v1" 819 kind: "Foo" 820 metadata: { 821 name: "test-foo" 822 namespace: parameter.namespace 823 } 824 } 825 } 826 // wait until workload.status equal "Running" 827 wait: op.#ConditionalWait & { 828 continue: apply.value.spec.key != "" 829 } 830 ` 831 )