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  )