github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/gc_policy_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  	"reflect"
    24  	"strconv"
    25  	"time"
    26  
    27  	v1 "k8s.io/api/apps/v1"
    28  	networkingv1 "k8s.io/api/networking/v1"
    29  
    30  	. "github.com/onsi/ginkgo/v2"
    31  	. "github.com/onsi/gomega"
    32  	"github.com/pkg/errors"
    33  	corev1 "k8s.io/api/core/v1"
    34  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	"sigs.k8s.io/controller-runtime/pkg/client"
    39  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    40  	"sigs.k8s.io/yaml"
    41  
    42  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    43  
    44  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    45  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    46  	"github.com/oam-dev/kubevela/pkg/oam"
    47  	"github.com/oam-dev/kubevela/pkg/oam/testutil"
    48  	"github.com/oam-dev/kubevela/pkg/oam/util"
    49  	"github.com/oam-dev/kubevela/pkg/resourcekeeper"
    50  )
    51  
    52  var _ = Describe("Test Application with GC options", func() {
    53  	ctx := context.Background()
    54  
    55  	ns := &corev1.Namespace{
    56  		ObjectMeta: metav1.ObjectMeta{
    57  			Name: "vela-test-app-with-gc-options",
    58  		},
    59  	}
    60  
    61  	worker := &v1beta1.ComponentDefinition{}
    62  	workerCdDefJson, _ := yaml.YAMLToJSON([]byte(componentDefYaml))
    63  
    64  	ingressTrait := &v1beta1.TraitDefinition{}
    65  	ingressTdDefJson, _ := yaml.YAMLToJSON([]byte(ingressTraitDefYaml))
    66  
    67  	configMap := &v1beta1.TraitDefinition{}
    68  	configMapTdDefJson, _ := yaml.YAMLToJSON([]byte(configMapTraitDefYaml))
    69  
    70  	BeforeEach(func() {
    71  		Expect(k8sClient.Create(ctx, ns.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    72  
    73  		Expect(json.Unmarshal(workerCdDefJson, worker)).Should(BeNil())
    74  		Expect(k8sClient.Create(ctx, worker.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    75  
    76  		Expect(json.Unmarshal(ingressTdDefJson, ingressTrait)).Should(BeNil())
    77  		Expect(k8sClient.Create(ctx, ingressTrait.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    78  
    79  		Expect(json.Unmarshal(configMapTdDefJson, configMap)).Should(BeNil())
    80  		Expect(k8sClient.Create(ctx, configMap.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    81  	})
    82  
    83  	Context("Test Application enable gc option keepLegacyResource", func() {
    84  		baseApp := &v1beta1.Application{
    85  			TypeMeta: metav1.TypeMeta{
    86  				Kind:       "Application",
    87  				APIVersion: "core.oam.dev/v1beta1",
    88  			},
    89  			ObjectMeta: metav1.ObjectMeta{
    90  				Name: "baseApp",
    91  			},
    92  			Spec: v1beta1.ApplicationSpec{
    93  				Components: []common.ApplicationComponent{
    94  					{
    95  						Name:       "worker",
    96  						Type:       "worker",
    97  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
    98  						Traits: []common.ApplicationTrait{{
    99  							Type:       "ingress-without-healthcheck",
   100  							Properties: &runtime.RawExtension{Raw: []byte(`{"domain":"test.com","http":{"/": 80}}`)},
   101  						}},
   102  					},
   103  				},
   104  				Policies: []v1beta1.AppPolicy{{
   105  					Name:       "keep-legacy-resource",
   106  					Type:       "garbage-collect",
   107  					Properties: &runtime.RawExtension{Raw: []byte(`{"keepLegacyResource": true}`)},
   108  				}},
   109  			},
   110  		}
   111  
   112  		It("Each update will create a new workload and trait object", func() {
   113  			resourcekeeper.MarkWithProbability = 1.0
   114  			app := baseApp.DeepCopy()
   115  			app.SetNamespace(ns.Name)
   116  			app.SetName("app-with-worker-ingress")
   117  
   118  			Expect(k8sClient.Create(ctx, app)).Should(BeNil())
   119  			appV1 := new(v1beta1.Application)
   120  			Eventually(func() error {
   121  				_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   122  				if err != nil {
   123  					return err
   124  				}
   125  				if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), appV1); err != nil {
   126  					return err
   127  				}
   128  				if appV1.Status.Phase != common.ApplicationRunning {
   129  					return errors.New("app is not in running status")
   130  				}
   131  				return nil
   132  			}, 3*time.Second, 300*time.Second).Should(BeNil())
   133  
   134  			By("update app with new component name")
   135  			for i := 2; i <= 6; i++ {
   136  				Eventually(func() error {
   137  					oldApp := new(v1beta1.Application)
   138  					Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(app), oldApp)).Should(BeNil())
   139  					updateApp := oldApp.DeepCopy()
   140  					updateApp.Spec.Components[0].Name = fmt.Sprintf("%s-v%d", "worker", i)
   141  					return k8sClient.Update(ctx, updateApp)
   142  				}, time.Second*3, time.Microsecond*300).Should(BeNil())
   143  
   144  				testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   145  				newApp := new(v1beta1.Application)
   146  				Eventually(func() error {
   147  					_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   148  					if err != nil {
   149  						return err
   150  					}
   151  					if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), newApp); err != nil {
   152  						return err
   153  					}
   154  					if newApp.Status.Phase != common.ApplicationRunning {
   155  						return errors.New("app is not in running status")
   156  					}
   157  					return nil
   158  				}, 3*time.Second, 300*time.Second).Should(BeNil())
   159  				Expect(newApp.Status.LatestRevision.Revision).Should(Equal(int64(i)))
   160  
   161  				rtObjKey := client.ObjectKey{Name: fmt.Sprintf("%s-v%d-%s", app.Name, i, ns.Name)}
   162  				rt := new(v1beta1.ResourceTracker)
   163  				Expect(k8sClient.Get(ctx, rtObjKey, rt)).Should(BeNil())
   164  				Expect(len(rt.Spec.ManagedResources)).Should(Equal(3))
   165  
   166  				for _, obj := range rt.Spec.ManagedResources {
   167  					un := new(unstructured.Unstructured)
   168  					un.SetGroupVersionKind(obj.GroupVersionKind())
   169  					Expect(k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}, un)).Should(BeNil())
   170  				}
   171  			}
   172  
   173  			By("check the resourceTrackers number")
   174  			listOpts := []client.ListOption{
   175  				client.MatchingLabels{
   176  					oam.LabelAppName:      app.Name,
   177  					oam.LabelAppNamespace: app.Namespace,
   178  				}}
   179  
   180  			rtList := &v1beta1.ResourceTrackerList{}
   181  			Expect(k8sClient.List(ctx, rtList, listOpts...))
   182  			Expect(len(rtList.Items)).Should(Equal(6))
   183  
   184  			By("delete one resourceTracker to test the gc of legacy resources")
   185  			testRT := &v1beta1.ResourceTracker{
   186  				ObjectMeta: metav1.ObjectMeta{
   187  					Name: fmt.Sprintf("%s-v%d-%s", app.Name, 1, ns.Name),
   188  				},
   189  			}
   190  			Expect(k8sClient.Delete(ctx, testRT)).Should(BeNil())
   191  			for _, obj := range testRT.Spec.ManagedResources {
   192  				un := &unstructured.Unstructured{}
   193  				un.SetGroupVersionKind(obj.GroupVersionKind())
   194  				Eventually(func() error {
   195  					if err := k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Name}, un); kerrors.IsNotFound(err) {
   196  						return nil
   197  					}
   198  					return errors.Errorf("failed to gc resource %v:%s", un.GroupVersionKind(), un.GetName())
   199  				}, 3*time.Second, 300*time.Millisecond).Should(BeNil())
   200  			}
   201  
   202  			By("check the latest resources created by application")
   203  			latestApp := new(v1beta1.Application)
   204  			Expect(k8sClient.Get(ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, latestApp)).Should(BeNil())
   205  			appliedResource := latestApp.Status.AppliedResources
   206  			for _, obj := range appliedResource {
   207  				un := &unstructured.Unstructured{}
   208  				un.SetGroupVersionKind(obj.GroupVersionKind())
   209  				Expect(k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}, un)).Should(BeNil())
   210  			}
   211  
   212  			By("delete part legacy resource")
   213  			for i := 2; i <= 4; i++ {
   214  				deploy := new(v1.Deployment)
   215  				deploy.SetName(fmt.Sprintf("worker-v%d", i))
   216  				deploy.SetNamespace(ns.Name)
   217  				Expect(k8sClient.Delete(ctx, deploy))
   218  
   219  				ingress := new(networkingv1.Ingress)
   220  				ingress.SetName(fmt.Sprintf("worker-v%d", i))
   221  				ingress.SetNamespace(ns.Name)
   222  				Expect(k8sClient.Delete(ctx, ingress))
   223  
   224  				svc := new(corev1.Service)
   225  				svc.SetName(fmt.Sprintf("worker-v%d", i))
   226  				svc.SetNamespace(ns.Name)
   227  				Expect(k8sClient.Delete(ctx, svc))
   228  			}
   229  
   230  			By("update app with new component name")
   231  
   232  			Eventually(func() error {
   233  				oldApp := new(v1beta1.Application)
   234  				Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(app), oldApp)).Should(BeNil())
   235  				updateApp := oldApp.DeepCopy()
   236  				updateApp.Spec.Components[0].Name = fmt.Sprintf("%s-v%d", "worker", 12)
   237  				return k8sClient.Update(ctx, updateApp)
   238  			}, time.Second*3, time.Microsecond*300).Should(BeNil())
   239  
   240  			testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   241  			newApp := new(v1beta1.Application)
   242  			Eventually(func() error {
   243  				_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   244  				if err != nil {
   245  					return err
   246  				}
   247  				if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), newApp); err != nil {
   248  					return err
   249  				}
   250  				if newApp.Status.Phase != common.ApplicationRunning {
   251  					return errors.New("app is not in running status")
   252  				}
   253  				return nil
   254  			}, 3*time.Second, 300*time.Microsecond).Should(BeNil())
   255  			Expect(newApp.Status.LatestRevision.Revision).Should(Equal(int64(7)))
   256  
   257  			By("check the resourceTrackers number")
   258  			newRTList := &v1beta1.ResourceTrackerList{}
   259  			Expect(k8sClient.List(ctx, newRTList, listOpts...))
   260  			Expect(len(newRTList.Items)).Should(Equal(3))
   261  
   262  			By("delete all resources")
   263  			Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
   264  			testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   265  			Expect(k8sClient.List(ctx, rtList, listOpts...))
   266  			Expect(len(rtList.Items)).Should(Equal(0))
   267  		})
   268  
   269  		It("Each update will create a new workload and update a trait object", func() {
   270  			app := baseApp.DeepCopy()
   271  			app.SetNamespace(ns.Name)
   272  			app.SetName("app-with-work-ingress-configmap")
   273  
   274  			app.Spec.Components[0].Name = "job"
   275  			app.Spec.Components[0].Traits = append(app.Spec.Components[0].Traits, common.ApplicationTrait{
   276  				Type:       "configmap",
   277  				Properties: &runtime.RawExtension{Raw: []byte(`{"volumes": [{"name": "test-cm", "mountPath":"/tmp/test"}]}`)},
   278  			})
   279  
   280  			Expect(k8sClient.Create(ctx, app)).Should(BeNil())
   281  			appV1 := new(v1beta1.Application)
   282  			Eventually(func() error {
   283  				_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   284  				if err != nil {
   285  					return err
   286  				}
   287  				if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), appV1); err != nil {
   288  					return err
   289  				}
   290  				if appV1.Status.Phase != common.ApplicationRunning {
   291  					return errors.New("app is not in running status")
   292  				}
   293  				return nil
   294  			}, 3*time.Second, 300*time.Second).Should(BeNil())
   295  
   296  			By("update app's component name and the properties of configmap")
   297  			for i := 2; i <= 6; i++ {
   298  				Eventually(func() error {
   299  					oldApp := new(v1beta1.Application)
   300  					Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(app), oldApp)).Should(BeNil())
   301  					updateApp := oldApp.DeepCopy()
   302  					updateApp.Spec.Components[0].Name = fmt.Sprintf("%s-v%d", "job", i)
   303  					updateApp.Spec.Components[0].Traits = append(app.Spec.Components[0].Traits, common.ApplicationTrait{
   304  						Type:       "configmap",
   305  						Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"volumes": [{"name": "test-cm","mountPath": "/tmp/test","data": {"test": "%d"}}]}`, i))},
   306  					})
   307  					return k8sClient.Update(ctx, updateApp)
   308  				}, time.Second*3, time.Microsecond*300).Should(BeNil())
   309  
   310  				newApp := new(v1beta1.Application)
   311  				Eventually(func() error {
   312  					_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   313  					if err != nil {
   314  						return err
   315  					}
   316  					if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), newApp); err != nil {
   317  						return err
   318  					}
   319  					if newApp.Status.Phase != common.ApplicationRunning {
   320  						return errors.New("app is not in running status")
   321  					}
   322  					return nil
   323  				}, 5*time.Second, 300*time.Second).Should(BeNil())
   324  				Expect(newApp.Status.LatestRevision.Revision).Should(Equal(int64(i)))
   325  
   326  				rtObjKey := client.ObjectKey{Name: fmt.Sprintf("%s-v%d-%s", app.Name, i, ns.Name)}
   327  				rt := new(v1beta1.ResourceTracker)
   328  				Expect(k8sClient.Get(ctx, rtObjKey, rt)).Should(BeNil())
   329  				Expect(len(rt.Spec.ManagedResources)).Should(Equal(4))
   330  
   331  				for _, obj := range rt.Spec.ManagedResources {
   332  					if obj.Kind == reflect.TypeOf(corev1.ConfigMap{}).Name() {
   333  						Expect(obj.Name).Should(Equal("test-cm"))
   334  						cm := new(corev1.ConfigMap)
   335  						Expect(k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}, cm)).Should(BeNil())
   336  						Expect(cm.Data["test"]).Should(Equal(strconv.Itoa(i)))
   337  						continue
   338  					}
   339  					un := new(unstructured.Unstructured)
   340  					un.SetGroupVersionKind(obj.GroupVersionKind())
   341  					Expect(k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}, un)).Should(BeNil())
   342  				}
   343  			}
   344  
   345  			By("check the resourceTrackers number")
   346  			listOpts := []client.ListOption{
   347  				client.MatchingLabels{
   348  					oam.LabelAppName:      app.Name,
   349  					oam.LabelAppNamespace: app.Namespace,
   350  				}}
   351  
   352  			rtList := &v1beta1.ResourceTrackerList{}
   353  			Expect(k8sClient.List(ctx, rtList, listOpts...))
   354  			Expect(len(rtList.Items)).Should(Equal(6))
   355  
   356  			By("delete one resourceTracker to test the gc of legacy resources")
   357  			testRT := &v1beta1.ResourceTracker{
   358  				ObjectMeta: metav1.ObjectMeta{
   359  					Name: fmt.Sprintf("%s-v%d-%s", app.Name, 1, ns.Name),
   360  				},
   361  			}
   362  
   363  			Expect(k8sClient.Delete(ctx, testRT)).Should(BeNil())
   364  			for _, obj := range testRT.Spec.ManagedResources {
   365  				if obj.Kind == reflect.TypeOf(corev1.ConfigMap{}).Name() {
   366  					cm := new(corev1.ConfigMap)
   367  					Expect(k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}, cm)).Should(BeNil())
   368  					continue
   369  				}
   370  				un := &unstructured.Unstructured{}
   371  				un.SetGroupVersionKind(obj.GroupVersionKind())
   372  				Eventually(func() error {
   373  					if err := k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Name}, un); kerrors.IsNotFound(err) {
   374  						return nil
   375  					}
   376  					return errors.Errorf("failed to gc resource %v:%s", un.GroupVersionKind(), un.GetName())
   377  				}, 3*time.Second, 300*time.Millisecond).Should(BeNil())
   378  			}
   379  
   380  			By("check the latest resources created by application")
   381  			latestApp := new(v1beta1.Application)
   382  			Expect(k8sClient.Get(ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, latestApp)).Should(BeNil())
   383  			appliedResource := latestApp.Status.AppliedResources
   384  			for _, obj := range appliedResource {
   385  				un := &unstructured.Unstructured{}
   386  				un.SetGroupVersionKind(obj.GroupVersionKind())
   387  				Expect(k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}, un)).Should(BeNil())
   388  			}
   389  
   390  			By("delete all resources")
   391  			Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
   392  			testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   393  			Expect(k8sClient.List(ctx, rtList, listOpts...))
   394  			Expect(len(rtList.Items)).Should(Equal(0))
   395  		})
   396  
   397  		It("Each update will only update workload", func() {
   398  			resourcekeeper.MarkWithProbability = 1.0
   399  			app := baseApp.DeepCopy()
   400  			app.Spec.Components[0].Traits = nil
   401  			app.Spec.Components[0].Name = "only-work"
   402  			app.SetNamespace(ns.Name)
   403  			app.SetName("app-with-worker")
   404  
   405  			Expect(k8sClient.Create(ctx, app)).Should(BeNil())
   406  			appV1 := new(v1beta1.Application)
   407  			Eventually(func() error {
   408  				_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   409  				if err != nil {
   410  					return err
   411  				}
   412  				if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), appV1); err != nil {
   413  					return err
   414  				}
   415  				if appV1.Status.Phase != common.ApplicationRunning {
   416  					return errors.New("app is not in running status")
   417  				}
   418  				return nil
   419  			}, 3*time.Second, 300*time.Second).Should(BeNil())
   420  
   421  			By("update component with new properties")
   422  			for i := 2; i <= 11; i++ {
   423  				Eventually(func() error {
   424  					oldApp := new(v1beta1.Application)
   425  					Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(app), oldApp)).Should(BeNil())
   426  					updateApp := oldApp.DeepCopy()
   427  					updateApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"cmd":["sleep","%d"],"image":"busybox"}`, i))}
   428  					return k8sClient.Update(ctx, updateApp)
   429  				}, time.Second*3, time.Microsecond*300).Should(BeNil())
   430  
   431  				testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   432  				newApp := new(v1beta1.Application)
   433  				Eventually(func() error {
   434  					_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   435  					if err != nil {
   436  						return err
   437  					}
   438  					if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), newApp); err != nil {
   439  						return err
   440  					}
   441  					if newApp.Status.Phase != common.ApplicationRunning {
   442  						return errors.New("app is not in running status")
   443  					}
   444  					return nil
   445  				}, 3*time.Second, 300*time.Second).Should(BeNil())
   446  				Expect(newApp.Status.LatestRevision.Revision).Should(Equal(int64(i)))
   447  
   448  				rtObjKey := client.ObjectKey{Name: fmt.Sprintf("%s-v%d-%s", app.Name, i, ns.Name)}
   449  				rt := new(v1beta1.ResourceTracker)
   450  				Expect(k8sClient.Get(ctx, rtObjKey, rt)).Should(BeNil())
   451  				Expect(len(rt.Spec.ManagedResources)).Should(Equal(1))
   452  
   453  				for _, obj := range rt.Spec.ManagedResources {
   454  					un := new(unstructured.Unstructured)
   455  					un.SetGroupVersionKind(obj.GroupVersionKind())
   456  					Expect(k8sClient.Get(ctx, client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}, un)).Should(BeNil())
   457  				}
   458  			}
   459  
   460  			By("check the resourceTrackers number")
   461  			listOpts := []client.ListOption{
   462  				client.MatchingLabels{
   463  					oam.LabelAppName:      app.Name,
   464  					oam.LabelAppNamespace: app.Namespace,
   465  				}}
   466  
   467  			rtList := &v1beta1.ResourceTrackerList{}
   468  			Expect(k8sClient.List(ctx, rtList, listOpts...))
   469  			Expect(len(rtList.Items)).Should(Equal(1))
   470  
   471  			By("delete all resources")
   472  			Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
   473  			testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   474  			Expect(k8sClient.List(ctx, rtList, listOpts...))
   475  			Expect(len(rtList.Items)).Should(Equal(0))
   476  		})
   477  	})
   478  
   479  	Context("Test Application enable gc option sequential", func() {
   480  		baseApp := &v1beta1.Application{
   481  			TypeMeta: metav1.TypeMeta{
   482  				Kind:       "Application",
   483  				APIVersion: "core.oam.dev/v1beta1",
   484  			},
   485  			ObjectMeta: metav1.ObjectMeta{
   486  				Name:      "sequential-gc",
   487  				Namespace: "default",
   488  			},
   489  			Spec: v1beta1.ApplicationSpec{
   490  				Components: []common.ApplicationComponent{
   491  					{
   492  						Name:       "worker1",
   493  						Type:       "worker",
   494  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   495  						DependsOn: []string{
   496  							"worker2",
   497  						},
   498  					},
   499  					{
   500  						Name:       "worker2",
   501  						Type:       "worker",
   502  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   503  						Inputs: workflowv1alpha1.StepInputs{
   504  							{
   505  								From:         "worker3-output",
   506  								ParameterKey: "test",
   507  							},
   508  						},
   509  					},
   510  					{
   511  						Name:       "worker3",
   512  						Type:       "worker",
   513  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   514  						Outputs: workflowv1alpha1.StepOutputs{
   515  							{
   516  								Name:      "worker3-output",
   517  								ValueFrom: "output.metadata.name",
   518  							},
   519  						},
   520  					},
   521  				},
   522  				Policies: []v1beta1.AppPolicy{{
   523  					Name:       "reverse-dependency",
   524  					Type:       "garbage-collect",
   525  					Properties: &runtime.RawExtension{Raw: []byte(`{"order": "dependency"}`)},
   526  				}},
   527  			},
   528  		}
   529  
   530  		It("Test GC with sequential", func() {
   531  			resourcekeeper.MarkWithProbability = 1.0
   532  			app := baseApp.DeepCopy()
   533  
   534  			Expect(k8sClient.Create(ctx, app)).Should(BeNil())
   535  			appV1 := new(v1beta1.Application)
   536  			Eventually(func() error {
   537  				_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   538  				if err != nil {
   539  					return err
   540  				}
   541  				if err = k8sClient.Get(ctx, client.ObjectKeyFromObject(app), appV1); err != nil {
   542  					return err
   543  				}
   544  				if appV1.Status.Phase != common.ApplicationRunning {
   545  					return errors.New("app is not in running status")
   546  				}
   547  				return nil
   548  			}, 3*time.Second, 300*time.Second).Should(BeNil())
   549  
   550  			By("check the resourceTrackers number")
   551  			listOpts := []client.ListOption{
   552  				client.MatchingLabels{
   553  					oam.LabelAppName:      app.Name,
   554  					oam.LabelAppNamespace: app.Namespace,
   555  				}}
   556  
   557  			rtList := &v1beta1.ResourceTrackerList{}
   558  			Expect(k8sClient.List(ctx, rtList, listOpts...)).Should(BeNil())
   559  			Expect(len(rtList.Items)).Should(Equal(1))
   560  			workerList := &v1.DeploymentList{}
   561  			Expect(k8sClient.List(ctx, workerList, listOpts...)).Should(BeNil())
   562  			Expect(len(workerList.Items)).Should(Equal(3))
   563  
   564  			By("delete application")
   565  			Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
   566  			By("worker1 will be deleted")
   567  			testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   568  			Expect(k8sClient.List(ctx, workerList, listOpts...)).Should(BeNil())
   569  			for _, worker := range workerList.Items {
   570  				Expect(worker.Name).ShouldNot(Equal("worker1"))
   571  			}
   572  			Expect(len(workerList.Items)).Should(Equal(2))
   573  			By("worker2 will be deleted")
   574  			testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   575  			Expect(k8sClient.List(ctx, workerList, listOpts...)).Should(BeNil())
   576  			Expect(len(workerList.Items)).Should(Equal(1))
   577  			for _, worker := range workerList.Items {
   578  				Expect(worker.Name).ShouldNot(Equal("worker2"))
   579  			}
   580  			By("worker3 will be deleted")
   581  			testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   582  			Expect(k8sClient.List(ctx, workerList, listOpts...)).Should(BeNil())
   583  			Expect(len(workerList.Items)).Should(Equal(0))
   584  			testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
   585  			Expect(k8sClient.List(ctx, rtList, listOpts...)).Should(BeNil())
   586  			Expect(len(rtList.Items)).Should(Equal(0))
   587  		})
   588  	})
   589  })
   590  
   591  const (
   592  	ingressTraitDefYaml = `
   593  apiVersion: core.oam.dev/v1beta1
   594  kind: TraitDefinition
   595  metadata:
   596    name: ingress-without-healthcheck
   597    namespace: vela-system
   598  spec:
   599    schematic:
   600      cue:
   601        template: |
   602          parameter: {
   603          	domain: string
   604          	http: [string]: int
   605          }
   606          // trait template can have multiple outputs in one trait
   607          outputs: service: {
   608          	apiVersion: "v1"
   609          	kind:       "Service"
   610          	metadata:
   611          		name: context.name
   612          	spec: {
   613          		selector:
   614          			app: context.name
   615          		ports: [
   616          			for k, v in parameter.http {
   617          				port:       v
   618          				targetPort: v
   619          			},
   620          		]
   621          	}
   622          }
   623          outputs: ingress: {
   624          	apiVersion: "networking.k8s.io/v1"
   625          	kind:       "Ingress"
   626          	metadata:
   627          		name: context.name
   628          	spec: {
   629          		rules: [{
   630          			host: parameter.domain
   631          			http: {
   632          				paths: [
   633          					for k, v in parameter.http {
   634          						path: k
   635                                  pathType: "Prefix"
   636          						backend: {
   637          							service: {
   638                                          name: context.name
   639                                          port: {
   640                                              number: v
   641                                          }
   642                                      }
   643          						}
   644          					},
   645          				]
   646          			}
   647          		}]
   648          	}
   649          }
   650  `
   651  	configMapTraitDefYaml = `
   652  apiVersion: core.oam.dev/v1beta1
   653  kind: TraitDefinition
   654  metadata:
   655    annotations:
   656      definition.oam.dev/description: Create/Attach configmaps on K8s pod for your workload which follows the pod spec in path 'spec.template'.
   657    name: configmap
   658    namespace: vela-system
   659  spec:
   660    appliesToWorkloads:
   661      - '*'
   662    podDisruptive: true
   663    schematic:
   664      cue:
   665        template: |
   666          patch: spec: template: spec: {
   667          	containers: [{
   668          		// +patchKey=name
   669          		volumeMounts: [
   670          			for v in parameter.volumes {
   671          				{
   672          					name:      "volume-\(v.name)"
   673          					mountPath: v.mountPath
   674          					readOnly:  v.readOnly
   675          				}
   676          			},
   677          		]
   678          	}, ...]
   679          	// +patchKey=name
   680          	volumes: [
   681          		for v in parameter.volumes {
   682          			{
   683          				name: "volume-\(v.name)"
   684          				configMap: name: v.name
   685          			}
   686          		},
   687          	]
   688          }
   689          outputs: {
   690          	for v in parameter.volumes {
   691          		if v.data != _|_ {
   692          			"\(v.name)": {
   693          				apiVersion: "v1"
   694          				kind:       "ConfigMap"
   695          				metadata: name: v.name
   696          				data: v.data
   697          			}
   698          		}
   699          	}
   700          }
   701          parameter: {
   702          	// +usage=Specify mounted configmap names and their mount paths in the container
   703          	volumes: [...{
   704          		name:      string
   705          		mountPath: string
   706          		readOnly:  *false | bool
   707          		data?: [string]: string
   708          	}]
   709          }
   710  `
   711  )