github.com/oam-dev/kubevela@v1.9.11/test/e2e-test/app_resourcetracker_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 controllers_test
    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  	"github.com/pkg/errors"
    28  
    29  	appsv1 "k8s.io/api/apps/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    37  	"sigs.k8s.io/yaml"
    38  
    39  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    40  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    41  	"github.com/oam-dev/kubevela/pkg/oam"
    42  	"github.com/oam-dev/kubevela/pkg/oam/util"
    43  )
    44  
    45  var _ = Describe("Test application cross namespace resource", func() {
    46  	ctx := context.Background()
    47  	var namespace, crossNamespace string
    48  
    49  	BeforeEach(func() {
    50  		namespace = randomNamespaceName("app-resource-tracker-e2e")
    51  		ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
    52  		Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    53  
    54  		crossNamespace = randomNamespaceName("cross-namespace")
    55  		crossNs := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: crossNamespace}}
    56  		Expect(k8sClient.Create(ctx, &crossNs)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    57  
    58  		Eventually(func() error {
    59  			ns := new(corev1.Namespace)
    60  			return k8sClient.Get(ctx, types.NamespacedName{Name: namespace}, ns)
    61  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
    62  		Eventually(func() error {
    63  			ns := new(corev1.Namespace)
    64  			return k8sClient.Get(ctx, types.NamespacedName{Name: crossNamespace}, ns)
    65  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
    66  	})
    67  
    68  	AfterEach(func() {
    69  		By("Clean up resources after a test")
    70  		k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace))
    71  		k8sClient.DeleteAllOf(ctx, &v1beta1.WorkloadDefinition{}, client.InNamespace(namespace))
    72  		k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace))
    73  		k8sClient.DeleteAllOf(ctx, &v1beta1.Application{}, client.InNamespace(namespace))
    74  
    75  		Expect(k8sClient.Delete(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
    76  		Expect(k8sClient.Delete(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: crossNamespace}}, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
    77  	})
    78  
    79  	It("Test application containing cluster-scoped trait", func() {
    80  		By("Install TraitDefinition")
    81  		traitDef := &v1beta1.TraitDefinition{}
    82  		Expect(yaml.Unmarshal([]byte(fmt.Sprintf(clusterScopeTraitDefYAML, namespace)), traitDef)).Should(Succeed())
    83  		Expect(k8sClient.Create(ctx, traitDef)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    84  
    85  		By("Create Application")
    86  		var (
    87  			appName       = "cluster-scope-trait-app"
    88  			app           = new(v1beta1.Application)
    89  			componentName = "cluster-scope-trait-comp"
    90  		)
    91  		app = &v1beta1.Application{
    92  			ObjectMeta: metav1.ObjectMeta{
    93  				Name:      appName,
    94  				Namespace: namespace,
    95  			},
    96  			Spec: v1beta1.ApplicationSpec{
    97  				Components: []common.ApplicationComponent{
    98  					{
    99  						Name:       componentName,
   100  						Type:       "worker",
   101  						Properties: &runtime.RawExtension{Raw: []byte(`{"image": "nginx:latest"}`)},
   102  						Traits: []common.ApplicationTrait{{
   103  							Type:       "cluster-scope-trait",
   104  							Properties: &runtime.RawExtension{Raw: []byte("{}")},
   105  						}},
   106  					},
   107  				},
   108  			},
   109  		}
   110  		Eventually(func() error {
   111  			return k8sClient.Create(ctx, app)
   112  		}, 20*time.Second, 2*time.Second).Should(Succeed())
   113  
   114  		By("Verify the trait is created")
   115  		// sample cluster-scoped trait is PersistentVolume
   116  		pv := &corev1.PersistentVolume{}
   117  		Eventually(func() error {
   118  			return k8sClient.Get(ctx, client.ObjectKey{Name: "pv-" + componentName, Namespace: namespace}, pv)
   119  		}, 20*time.Second, 500*time.Millisecond).Should(Succeed())
   120  
   121  		By("Delete Application")
   122  		Expect(k8sClient.Delete(ctx, app)).Should(Succeed())
   123  		By("Verify cluster-scoped trait is deleted cascadingly")
   124  		Eventually(func() error {
   125  			if err := k8sClient.Get(ctx, client.ObjectKey{Name: "pv-" + componentName, Namespace: namespace}, pv); err != nil {
   126  				if apierrors.IsNotFound(err) {
   127  					return nil
   128  				}
   129  				return errors.Wrap(err, "PersistentVolume has not deleted")
   130  			}
   131  			if ctrlutil.ContainsFinalizer(pv, "kubernetes.io/pv-protection") {
   132  				return nil
   133  			}
   134  			return errors.New("PersistentVolume has not deleted")
   135  		}, 20*time.Second, 500*time.Millisecond).Should(BeNil())
   136  	})
   137  
   138  	It("Test application have cross-namespace workload", func() {
   139  		// install  component definition
   140  		crossCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace)))
   141  		ccd := new(v1beta1.ComponentDefinition)
   142  		Expect(json.Unmarshal(crossCdJson, ccd)).Should(BeNil())
   143  		Expect(k8sClient.Create(ctx, ccd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
   144  		var (
   145  			appName       = "test-app-1"
   146  			app           = new(v1beta1.Application)
   147  			componentName = "test-app-1-comp"
   148  		)
   149  		app = &v1beta1.Application{
   150  			ObjectMeta: metav1.ObjectMeta{
   151  				Name:      appName,
   152  				Namespace: namespace,
   153  			},
   154  			Spec: v1beta1.ApplicationSpec{
   155  				Components: []common.ApplicationComponent{
   156  					{
   157  						Name:       componentName,
   158  						Type:       "cross-worker",
   159  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   160  					},
   161  				},
   162  			},
   163  		}
   164  		Eventually(func() error {
   165  			return k8sClient.Create(ctx, app)
   166  		}, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
   167  		By("check resource tracker has been created and app status ")
   168  		resourceTracker := new(v1beta1.ResourceTracker)
   169  		Eventually(func() error {
   170  			app := new(v1beta1.Application)
   171  			if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
   172  				return fmt.Errorf("app not found %v", err)
   173  			}
   174  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil {
   175  				return err
   176  			}
   177  			if app.Status.Phase != common.ApplicationRunning {
   178  				return fmt.Errorf("application status is not running")
   179  			}
   180  			return nil
   181  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   182  		By("check resource is generated correctly")
   183  		Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   184  		var workload appsv1.Deployment
   185  		Eventually(func() error {
   186  			checkRt := new(v1beta1.ResourceTracker)
   187  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), checkRt); err != nil {
   188  				return err
   189  			}
   190  			depolys := new(appsv1.DeploymentList)
   191  			opts := []client.ListOption{
   192  				client.InNamespace(crossNamespace),
   193  				client.MatchingLabels{
   194  					oam.LabelAppName: appName,
   195  				},
   196  			}
   197  			err := k8sClient.List(ctx, depolys, opts...)
   198  			if err != nil || len(depolys.Items) != 1 {
   199  				return fmt.Errorf("error workload number %v", err)
   200  			}
   201  			workload = depolys.Items[0]
   202  			if len(checkRt.Spec.ManagedResources) != 1 {
   203  				return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
   204  			}
   205  			if checkRt.Spec.ManagedResources[0].Name != workload.Name {
   206  				return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", checkRt.Spec.ManagedResources[0].Name, workload.Name)
   207  			}
   208  			return nil
   209  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   210  
   211  		By("deleting application will remove resourceTracker and related workload will be removed")
   212  		Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   213  		Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
   214  		Eventually(func() error {
   215  			err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker)
   216  			if err == nil {
   217  				return fmt.Errorf("resourceTracker still exist")
   218  			}
   219  			if !apierrors.IsNotFound(err) {
   220  				return err
   221  			}
   222  			err = k8sClient.Get(ctx, types.NamespacedName{Namespace: crossNamespace, Name: workload.GetName()}, &workload)
   223  			if err == nil {
   224  				return fmt.Errorf("wrokload still exist")
   225  			}
   226  			if !apierrors.IsNotFound(err) {
   227  				return err
   228  			}
   229  			return nil
   230  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   231  	})
   232  
   233  	It("Test application have two different workload", func() {
   234  		var (
   235  			appName        = "test-app-4"
   236  			app            = new(v1beta1.Application)
   237  			component1Name = "test-app-4-comp-1"
   238  			component2Name = "test-app-4-comp-2"
   239  		)
   240  		By("install component definition")
   241  		normalCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(normalCompDefYaml, namespace)))
   242  		ncd := new(v1beta1.ComponentDefinition)
   243  		Expect(json.Unmarshal(normalCdJson, ncd)).Should(BeNil())
   244  		Expect(k8sClient.Create(ctx, ncd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
   245  
   246  		crossCdJson, err := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace)))
   247  		Expect(err).Should(BeNil())
   248  		ctd := new(v1beta1.ComponentDefinition)
   249  		Expect(json.Unmarshal(crossCdJson, ctd)).Should(BeNil())
   250  		Expect(k8sClient.Create(ctx, ctd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
   251  
   252  		app = &v1beta1.Application{
   253  			ObjectMeta: metav1.ObjectMeta{
   254  				Name:      appName,
   255  				Namespace: namespace,
   256  			},
   257  			Spec: v1beta1.ApplicationSpec{
   258  				Components: []common.ApplicationComponent{
   259  					{
   260  						Name:       component1Name,
   261  						Type:       "normal-worker",
   262  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   263  					},
   264  					{
   265  						Name:       component2Name,
   266  						Type:       "cross-worker",
   267  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   268  					},
   269  				},
   270  			},
   271  		}
   272  
   273  		Eventually(func() error {
   274  			return k8sClient.Create(ctx, app)
   275  		}, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
   276  		resourceTracker := new(v1beta1.ResourceTracker)
   277  
   278  		By("create application will generate two workload, and generate resourceTracker")
   279  		Eventually(func() error {
   280  			app = new(v1beta1.Application)
   281  			if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
   282  				return fmt.Errorf("error to get application %v", err)
   283  			}
   284  			if app.Status.Phase != common.ApplicationRunning {
   285  				return fmt.Errorf("application status not running")
   286  			}
   287  			err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker)
   288  			if err != nil {
   289  				return fmt.Errorf("error to generate resourceTracker %v", err)
   290  			}
   291  			sameOpts := []client.ListOption{
   292  				client.InNamespace(namespace),
   293  				client.MatchingLabels{
   294  					oam.LabelAppName: appName,
   295  				},
   296  			}
   297  			crossOpts := []client.ListOption{
   298  				client.InNamespace(crossNamespace),
   299  				client.MatchingLabels{
   300  					oam.LabelAppName: appName,
   301  				},
   302  			}
   303  			same, cross := new(appsv1.DeploymentList), new(appsv1.DeploymentList)
   304  			err = k8sClient.List(ctx, same, sameOpts...)
   305  			if err != nil || len(same.Items) != 1 {
   306  				return fmt.Errorf("failed generate same namespace workload")
   307  			}
   308  			err = k8sClient.List(ctx, cross, crossOpts...)
   309  			if err != nil || len(cross.Items) != 1 {
   310  				return fmt.Errorf("failed generate cross namespace trait")
   311  			}
   312  			if len(resourceTracker.Spec.ManagedResources) != 2 {
   313  				return fmt.Errorf("expect track %q resources, but got %q", 2, len(resourceTracker.Spec.ManagedResources))
   314  			}
   315  			return nil
   316  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   317  		By("update application by delete cross namespace workload")
   318  		Eventually(func() error {
   319  			app = new(v1beta1.Application)
   320  			Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   321  			app.Spec.Components = app.Spec.Components[:1] // delete a component
   322  			return k8sClient.Update(ctx, app)
   323  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   324  		Eventually(func() error {
   325  			app = new(v1beta1.Application)
   326  			if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
   327  				return fmt.Errorf("error to get application %v", err)
   328  			}
   329  			if app.Status.Phase != common.ApplicationRunning {
   330  				return fmt.Errorf("application status not running")
   331  			}
   332  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker); err != nil {
   333  				return err
   334  			}
   335  			sameOpts := []client.ListOption{
   336  				client.InNamespace(namespace),
   337  				client.MatchingLabels{
   338  					oam.LabelAppName: appName,
   339  				},
   340  			}
   341  			crossOpts := []client.ListOption{
   342  				client.InNamespace(crossNamespace),
   343  				client.MatchingLabels{
   344  					oam.LabelAppName: appName,
   345  				},
   346  			}
   347  			same, cross := new(appsv1.DeploymentList), new(appsv1.DeploymentList)
   348  			err = k8sClient.List(ctx, same, sameOpts...)
   349  			if err != nil || len(same.Items) != 1 {
   350  				return fmt.Errorf("failed generate same namespace workload")
   351  			}
   352  			err = k8sClient.List(ctx, cross, crossOpts...)
   353  			if err != nil || len(cross.Items) != 0 {
   354  				return fmt.Errorf("error : cross namespace workload still exist")
   355  			}
   356  			return nil
   357  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   358  	})
   359  
   360  	It("Update a cross namespace workload of application", func() {
   361  		// install  component definition
   362  		crossCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace)))
   363  		ccd := new(v1beta1.ComponentDefinition)
   364  		Expect(json.Unmarshal(crossCdJson, ccd)).Should(BeNil())
   365  		Expect(k8sClient.Create(ctx, ccd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
   366  		var (
   367  			appName       = "test-app-5"
   368  			app           = new(v1beta1.Application)
   369  			componentName = "test-app-5-comp"
   370  		)
   371  		app = &v1beta1.Application{
   372  			ObjectMeta: metav1.ObjectMeta{
   373  				Name:      appName,
   374  				Namespace: namespace,
   375  			},
   376  			Spec: v1beta1.ApplicationSpec{
   377  				Components: []common.ApplicationComponent{
   378  					{
   379  						Name:       componentName,
   380  						Type:       "cross-worker",
   381  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   382  					},
   383  				},
   384  			},
   385  		}
   386  		Eventually(func() error {
   387  			return k8sClient.Create(ctx, app)
   388  		}, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
   389  		By("check resource tracker has been created and app status ")
   390  		resourceTracker := new(v1beta1.ResourceTracker)
   391  		Eventually(func() error {
   392  			app := new(v1beta1.Application)
   393  			if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
   394  				return fmt.Errorf("app not found %v", err)
   395  			}
   396  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil {
   397  				return err
   398  			}
   399  			if app.Status.Phase != common.ApplicationRunning {
   400  				return fmt.Errorf("application status is not running")
   401  			}
   402  			return nil
   403  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   404  		By("check resource is generated correctly")
   405  		Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   406  		var workload appsv1.Deployment
   407  		Eventually(func() error {
   408  			depolys := new(appsv1.DeploymentList)
   409  			opts := []client.ListOption{
   410  				client.InNamespace(crossNamespace),
   411  				client.MatchingLabels{
   412  					oam.LabelAppName: appName,
   413  				},
   414  			}
   415  			err := k8sClient.List(ctx, depolys, opts...)
   416  			if err != nil || len(depolys.Items) != 1 {
   417  				return fmt.Errorf("error workload number %v", err)
   418  			}
   419  			workload = depolys.Items[0]
   420  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil {
   421  				return err
   422  			}
   423  			if workload.Spec.Template.Spec.Containers[0].Image != "busybox" {
   424  				return fmt.Errorf("container image not match")
   425  			}
   426  			if len(resourceTracker.Spec.ManagedResources) != 1 {
   427  				return fmt.Errorf("expect track %q resources, but got %q", 1, len(resourceTracker.Spec.ManagedResources))
   428  			}
   429  			return nil
   430  		}, time.Second*50, time.Millisecond*300).Should(BeNil())
   431  
   432  		By("update application and check resource status")
   433  		Eventually(func() error {
   434  			checkApp := new(v1beta1.Application)
   435  			err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, checkApp)
   436  			if err != nil {
   437  				return err
   438  			}
   439  			checkApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"nginx"}`)}
   440  			err = k8sClient.Update(ctx, checkApp)
   441  			if err != nil {
   442  				return err
   443  			}
   444  			return nil
   445  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   446  
   447  		Eventually(func() error {
   448  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker); err != nil {
   449  				return err
   450  			}
   451  			if len(resourceTracker.Spec.ManagedResources) != 1 {
   452  				return fmt.Errorf("expect track %q resources, but got %q", 1, len(resourceTracker.Spec.ManagedResources))
   453  			}
   454  			depolys := new(appsv1.DeploymentList)
   455  			opts := []client.ListOption{
   456  				client.InNamespace(crossNamespace),
   457  				client.MatchingLabels{
   458  					oam.LabelAppName: appName,
   459  				},
   460  			}
   461  			err := k8sClient.List(ctx, depolys, opts...)
   462  			if err != nil || len(depolys.Items) != 1 {
   463  				return fmt.Errorf("error workload number %v", err)
   464  			}
   465  			workload = depolys.Items[0]
   466  			if workload.Spec.Template.Spec.Containers[0].Image != "nginx" {
   467  				return fmt.Errorf("container image not match")
   468  			}
   469  			return nil
   470  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   471  
   472  		By("deleting application will remove resourceTracker and related workload will be removed")
   473  		Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   474  		Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
   475  		Eventually(func() error {
   476  			err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker)
   477  			if err == nil {
   478  				return fmt.Errorf("resourceTracker still exist")
   479  			}
   480  			if !apierrors.IsNotFound(err) {
   481  				return err
   482  			}
   483  			err = k8sClient.Get(ctx, types.NamespacedName{Namespace: crossNamespace, Name: workload.GetName()}, &workload)
   484  			if err == nil {
   485  				return fmt.Errorf("wrokload still exist")
   486  			}
   487  			if !apierrors.IsNotFound(err) {
   488  				return err
   489  			}
   490  			return nil
   491  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   492  	})
   493  
   494  	It("Test cross-namespace resource gc logic, delete a cross-ns component", func() {
   495  		var (
   496  			appName        = "test-app-6"
   497  			app            = new(v1beta1.Application)
   498  			component1Name = "test-app-6-comp-1"
   499  			component2Name = "test-app-6-comp-2"
   500  		)
   501  		By("install related definition")
   502  
   503  		crossCdJson, err := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace)))
   504  		Expect(err).Should(BeNil())
   505  		ctd := new(v1beta1.ComponentDefinition)
   506  		Expect(json.Unmarshal(crossCdJson, ctd)).Should(BeNil())
   507  		Expect(k8sClient.Create(ctx, ctd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
   508  
   509  		app = &v1beta1.Application{
   510  			ObjectMeta: metav1.ObjectMeta{
   511  				Name:      appName,
   512  				Namespace: namespace,
   513  			},
   514  			Spec: v1beta1.ApplicationSpec{
   515  				Components: []common.ApplicationComponent{
   516  					{
   517  						Name:       component1Name,
   518  						Type:       "cross-worker",
   519  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   520  					},
   521  					{
   522  						Name:       component2Name,
   523  						Type:       "cross-worker",
   524  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   525  					},
   526  				},
   527  			},
   528  		}
   529  
   530  		Eventually(func() error {
   531  			return k8sClient.Create(ctx, app)
   532  		}, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
   533  		resourceTracker := new(v1beta1.ResourceTracker)
   534  
   535  		By("create application will generate two workload, and generate resourceTracker")
   536  		Eventually(func() error {
   537  			app = new(v1beta1.Application)
   538  			if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
   539  				return fmt.Errorf("error to get application %v", err)
   540  			}
   541  			if app.Status.Phase != common.ApplicationRunning {
   542  				return fmt.Errorf("application status not running")
   543  			}
   544  			err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker)
   545  			if err != nil {
   546  				return fmt.Errorf("error to generate resourceTracker %v", err)
   547  			}
   548  			crossOpts := []client.ListOption{
   549  				client.InNamespace(crossNamespace),
   550  				client.MatchingLabels{
   551  					oam.LabelAppName: appName,
   552  				},
   553  			}
   554  			//same, cross := new(appsv1.DeploymentList), new(appsv1.DeploymentList)
   555  			workloads := new(appsv1.DeploymentList)
   556  			err = k8sClient.List(ctx, workloads, crossOpts...)
   557  			if err != nil || len(workloads.Items) != 2 {
   558  				return fmt.Errorf("failed get workloads")
   559  			}
   560  			deploy1 := workloads.Items[0]
   561  			deploy2 := workloads.Items[1]
   562  			if len(resourceTracker.Spec.ManagedResources) != 2 {
   563  				return fmt.Errorf("expect track %q resources, but got %q", 2, len(resourceTracker.Spec.ManagedResources))
   564  			}
   565  			if resourceTracker.Spec.ManagedResources[0].Namespace != crossNamespace || resourceTracker.Spec.ManagedResources[1].Namespace != crossNamespace {
   566  				return fmt.Errorf("resourceTracker recorde namespace mismatch")
   567  			}
   568  			if resourceTracker.Spec.ManagedResources[0].Name != deploy1.Name && resourceTracker.Spec.ManagedResources[1].Name != deploy1.Name {
   569  				return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Spec.ManagedResources[0].Name, deploy1.Name)
   570  			}
   571  			if resourceTracker.Spec.ManagedResources[0].Name != deploy2.Name && resourceTracker.Spec.ManagedResources[1].Name != deploy2.Name {
   572  				return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Spec.ManagedResources[0].Name, deploy2.Name)
   573  			}
   574  			return nil
   575  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   576  		By("update application by delete a cross namespace workload")
   577  		Eventually(func() error {
   578  			app = new(v1beta1.Application)
   579  			Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   580  			app.Spec.Components = app.Spec.Components[:1] // delete a component
   581  			return k8sClient.Update(ctx, app)
   582  		}, time.Second*5, time.Millisecond*300).Should(BeNil())
   583  		Eventually(func() error {
   584  			app = new(v1beta1.Application)
   585  			if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
   586  				return fmt.Errorf("error to get application %v", err)
   587  			}
   588  			if app.Status.Phase != common.ApplicationRunning {
   589  				return fmt.Errorf("application status not running")
   590  			}
   591  			err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker)
   592  			if err != nil {
   593  				return fmt.Errorf("failed to get resourceTracker %v", err)
   594  			}
   595  			crossOpts := []client.ListOption{
   596  				client.InNamespace(crossNamespace),
   597  				client.MatchingLabels{
   598  					oam.LabelAppName: appName,
   599  				},
   600  			}
   601  			workloads := new(appsv1.DeploymentList)
   602  			err = k8sClient.List(ctx, workloads, crossOpts...)
   603  			if err != nil || len(workloads.Items) != 1 {
   604  				return fmt.Errorf("failed get cross namespace workload")
   605  			}
   606  			checkRt := new(v1beta1.ResourceTracker)
   607  			err = k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), checkRt)
   608  			if err != nil {
   609  				return fmt.Errorf("error get resourceTracker")
   610  			}
   611  			if len(checkRt.Spec.ManagedResources) != 1 {
   612  				return fmt.Errorf("expect track %q resources, but got %q", 1, len(checkRt.Spec.ManagedResources))
   613  			}
   614  			return nil
   615  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   616  
   617  		By("deleting application will remove resourceTracker")
   618  		app = new(v1beta1.Application)
   619  		Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   620  		Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
   621  		Eventually(func() error {
   622  			err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker)
   623  			if err == nil {
   624  				return fmt.Errorf("resourceTracker still exist")
   625  			}
   626  			if !apierrors.IsNotFound(err) {
   627  				return err
   628  			}
   629  			return nil
   630  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   631  	})
   632  
   633  	It("Test cross-namespace resource gc logic, update a cross-ns workload's namespace", func() {
   634  		// install  related definition
   635  		crossCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(crossCompDefYaml, namespace, crossNamespace)))
   636  		ccd := new(v1beta1.ComponentDefinition)
   637  		Expect(json.Unmarshal(crossCdJson, ccd)).Should(BeNil())
   638  		Expect(k8sClient.Create(ctx, ccd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
   639  
   640  		normalCdJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(normalCompDefYaml, namespace)))
   641  		ncd := new(v1beta1.ComponentDefinition)
   642  		Expect(json.Unmarshal(normalCdJson, ncd)).Should(BeNil())
   643  		Expect(k8sClient.Create(ctx, ncd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
   644  
   645  		var (
   646  			appName       = "test-app-8"
   647  			app           = new(v1beta1.Application)
   648  			componentName = "test-app-8-comp"
   649  		)
   650  		app = &v1beta1.Application{
   651  			ObjectMeta: metav1.ObjectMeta{
   652  				Name:      appName,
   653  				Namespace: namespace,
   654  			},
   655  			Spec: v1beta1.ApplicationSpec{
   656  				Components: []common.ApplicationComponent{
   657  					{
   658  						Name:       componentName,
   659  						Type:       "cross-worker",
   660  						Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   661  					},
   662  				},
   663  			},
   664  		}
   665  		Eventually(func() error {
   666  			return k8sClient.Create(ctx, app)
   667  		}, 15*time.Second, 300*time.Microsecond).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{}))
   668  		By("check resource tracker has been created and app status ")
   669  		resourceTracker := new(v1beta1.ResourceTracker)
   670  		Eventually(func() error {
   671  			app := new(v1beta1.Application)
   672  			if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
   673  				return fmt.Errorf("app not found %v", err)
   674  			}
   675  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), resourceTracker); err != nil {
   676  				return err
   677  			}
   678  			if app.Status.Phase != common.ApplicationRunning {
   679  				return fmt.Errorf("application status is not running")
   680  			}
   681  			return nil
   682  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   683  		By("check resource is generated correctly")
   684  		Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
   685  		var workload appsv1.Deployment
   686  		Eventually(func() error {
   687  			checkRt := new(v1beta1.ResourceTracker)
   688  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 1), checkRt); err != nil {
   689  				return err
   690  			}
   691  			depolys := new(appsv1.DeploymentList)
   692  			opts := []client.ListOption{
   693  				client.InNamespace(crossNamespace),
   694  				client.MatchingLabels{
   695  					oam.LabelAppName: appName,
   696  				},
   697  			}
   698  			err := k8sClient.List(ctx, depolys, opts...)
   699  			if err != nil || len(depolys.Items) != 1 {
   700  				return fmt.Errorf("error workload number %v", err)
   701  			}
   702  			workload = depolys.Items[0]
   703  			if len(checkRt.Spec.ManagedResources) != 1 {
   704  				return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
   705  			}
   706  			if checkRt.Spec.ManagedResources[0].Name != workload.Name {
   707  				return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", checkRt.Spec.ManagedResources[0].Name, workload.Name)
   708  			}
   709  			return nil
   710  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   711  
   712  		By("update application modify workload namespace")
   713  		Eventually(func() error {
   714  			app = new(v1beta1.Application)
   715  			err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)
   716  			if err != nil {
   717  				return err
   718  			}
   719  			app.Spec.Components[0].Type = "normal-worker"
   720  			err = k8sClient.Update(ctx, app)
   721  			if err != nil {
   722  				return err
   723  			}
   724  			return nil
   725  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   726  		Eventually(func() error {
   727  			if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name, 2), resourceTracker); err != nil {
   728  				return err
   729  			}
   730  			err := k8sClient.Get(ctx, types.NamespacedName{Namespace: crossNamespace, Name: workload.GetName()}, &workload)
   731  			if err == nil {
   732  				return fmt.Errorf("wrokload still exist")
   733  			}
   734  			if !apierrors.IsNotFound(err) {
   735  				return err
   736  			}
   737  			newWorkload := new(appsv1.Deployment)
   738  			err = k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workload.GetName()}, newWorkload)
   739  			if err != nil {
   740  				return fmt.Errorf("generate same namespace workload error")
   741  			}
   742  			return nil
   743  		}, time.Second*5, time.Millisecond*500).Should(BeNil())
   744  	})
   745  })
   746  
   747  func generateResourceTrackerKey(namespace string, appName string, revision int) types.NamespacedName {
   748  	return types.NamespacedName{Name: fmt.Sprintf("%s-v%d-%s", appName, revision, namespace)}
   749  }
   750  
   751  const (
   752  	crossCompDefYaml = `
   753  apiVersion: core.oam.dev/v1beta1
   754  kind: ComponentDefinition
   755  metadata:
   756    name: cross-worker
   757    namespace: %s
   758    annotations:
   759      definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
   760  spec:
   761    workload:
   762      definition:
   763        apiVersion: apps/v1
   764        kind: Deployment
   765    extension:
   766      healthPolicy: |
   767        isHealth: context.output.status.readyReplicas == context.output.status.replicas
   768      template: |
   769        output: {
   770            apiVersion: "apps/v1"
   771            kind:       "Deployment"
   772            metadata: {
   773                namespace: "%s"
   774            }
   775            spec: {
   776                replicas: 0
   777                template: {
   778                    metadata: labels: {
   779                        "app.oam.dev/component": context.name
   780                    }
   781  
   782                    spec: {
   783                        containers: [{
   784                            name:  context.name
   785                            image: parameter.image
   786  
   787                            if parameter["cmd"] != _|_ {
   788                                command: parameter.cmd
   789                            }
   790                        }]
   791                    }
   792                }
   793  
   794                selector:
   795                    matchLabels:
   796                        "app.oam.dev/component": context.name
   797            }
   798        }
   799  
   800        parameter: {
   801            // +usage=Which image would you like to use for your service
   802            // +short=i
   803            image: string
   804  
   805            cmd?: [...string]
   806        }
   807  `
   808  	normalCompDefYaml = `
   809  apiVersion: core.oam.dev/v1beta1
   810  kind: ComponentDefinition
   811  metadata:
   812    name: normal-worker
   813    namespace: %s
   814    annotations:
   815      definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
   816  spec:
   817    workload:
   818      definition:
   819        apiVersion: apps/v1
   820        kind: Deployment
   821    extension:
   822      healthPolicy: |
   823        isHealth: context.output.status.readyReplicas == context.output.status.replicas
   824      template: |
   825        output: {
   826            apiVersion: "apps/v1"
   827            kind:       "Deployment"
   828            spec: {
   829                replicas: 0
   830                template: {
   831                    metadata: labels: {
   832                        "app.oam.dev/component": context.name
   833                    }
   834  
   835                    spec: {
   836                        containers: [{
   837                            name:  context.name
   838                            image: parameter.image
   839  
   840                            if parameter["cmd"] != _|_ {
   841                                command: parameter.cmd
   842                            }
   843                        }]
   844                    }
   845                }
   846  
   847                selector:
   848                    matchLabels:
   849                        "app.oam.dev/component": context.name
   850            }
   851        }
   852  
   853        parameter: {
   854            // +usage=Which image would you like to use for your service
   855            // +short=i
   856            image: string
   857  
   858            cmd?: [...string]
   859        }
   860  `
   861  
   862  	clusterScopeTraitDefYAML = `
   863  apiVersion: core.oam.dev/v1beta1
   864  kind: TraitDefinition
   865  metadata:
   866    name: cluster-scope-trait
   867    namespace: %s
   868  spec:
   869    appliesToWorkloads:
   870      - deployments.apps
   871    extension:
   872      template: |-
   873        outputs: pv: { 
   874          apiVersion: "v1"
   875          kind:       "PersistentVolume"
   876          metadata: name: "pv-\(context.name)"
   877          spec: {
   878             accessModes: ["ReadWriteOnce"]
   879             capacity: storage: "5Gi"
   880             persistentVolumeReclaimPolicy: "Retain"
   881             storageClassName:              "test-sc"
   882             csi: {
   883             	driver:       "gcs.csi.ofek.dev"
   884             	volumeHandle: "csi-gcs"
   885             	nodePublishSecretRef: {
   886             	    name:      "bucket-sa-\(context.name)"
   887             	    namespace: context.namespace
   888             	}
   889             }
   890          }
   891        }
   892  `
   893  )