github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/application_finalizer_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  
    24  	"github.com/oam-dev/kubevela/pkg/oam"
    25  	"github.com/oam-dev/kubevela/pkg/oam/testutil"
    26  
    27  	. "github.com/onsi/ginkgo/v2"
    28  	. "github.com/onsi/gomega"
    29  
    30  	"sigs.k8s.io/yaml"
    31  
    32  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    33  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    34  	"github.com/oam-dev/kubevela/pkg/oam/util"
    35  
    36  	appsv1 "k8s.io/api/apps/v1"
    37  	v1 "k8s.io/api/core/v1"
    38  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    39  	"k8s.io/apimachinery/pkg/runtime"
    40  	"k8s.io/apimachinery/pkg/types"
    41  	ctrl "sigs.k8s.io/controller-runtime"
    42  	"sigs.k8s.io/controller-runtime/pkg/client"
    43  )
    44  
    45  var _ = Describe("Test application controller finalizer logic", func() {
    46  	ctx := context.TODO()
    47  	namespace := "cross-namespace"
    48  
    49  	cd := &v1beta1.ComponentDefinition{}
    50  	cDDefJson, _ := yaml.YAMLToJSON([]byte(crossCompDefYaml))
    51  
    52  	ncd := &v1beta1.ComponentDefinition{}
    53  	ncdDefJson, _ := yaml.YAMLToJSON([]byte(normalCompDefYaml))
    54  
    55  	badCD := &v1beta1.ComponentDefinition{}
    56  	badCDJson, _ := yaml.YAMLToJSON([]byte(badCompDefYaml))
    57  
    58  	BeforeEach(func() {
    59  		ns := v1.Namespace{
    60  			ObjectMeta: metav1.ObjectMeta{
    61  				Name: namespace,
    62  			},
    63  		}
    64  		Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    65  
    66  		Expect(json.Unmarshal(cDDefJson, cd)).Should(BeNil())
    67  		Expect(k8sClient.Create(ctx, cd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    68  
    69  		Expect(json.Unmarshal(ncdDefJson, ncd)).Should(BeNil())
    70  		Expect(k8sClient.Create(ctx, ncd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    71  
    72  		Expect(json.Unmarshal(badCDJson, badCD)).Should(BeNil())
    73  		Expect(k8sClient.Create(ctx, badCD.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    74  	})
    75  
    76  	AfterEach(func() {
    77  		By("[TEST] Clean up resources after an integration test")
    78  		Expect(k8sClient.DeleteAllOf(ctx, &appsv1.Deployment{}, client.InNamespace(namespace)))
    79  		Expect(k8sClient.DeleteAllOf(ctx, &appsv1.ControllerRevision{}, client.InNamespace(namespace)))
    80  	})
    81  
    82  	It("Test error occurs in the middle of dispatching", func() {
    83  		appName := "bad-app"
    84  		appKey := types.NamespacedName{Namespace: namespace, Name: appName}
    85  		app := getApp(appName, namespace, "bad-worker")
    86  		Expect(k8sClient.Create(ctx, app)).Should(BeNil())
    87  
    88  		By("Create a bad workload app")
    89  		checkApp := &v1beta1.Application{}
    90  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
    91  		Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
    92  
    93  		// though error occurs in the middle of dispatching
    94  		// resource tracker for v1 is also created and recorded in app status
    95  		By("Verify latest app revision is also recorded in status")
    96  		Expect(checkApp.Status.LatestRevision).ShouldNot(BeNil())
    97  
    98  		By("Delete Application")
    99  		Expect(k8sClient.Delete(ctx, checkApp)).Should(BeNil())
   100  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   101  	})
   102  
   103  	It("Test cross namespace workload, then delete the app", func() {
   104  		appName := "app-2"
   105  		appKey := types.NamespacedName{Namespace: namespace, Name: appName}
   106  		app := getApp(appName, namespace, "cross-worker")
   107  		Expect(k8sClient.Create(ctx, app)).Should(BeNil())
   108  
   109  		By("Create a cross workload app")
   110  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   111  		checkApp := &v1beta1.Application{}
   112  		Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
   113  		Expect(checkApp.Status.Phase).Should(Equal(common.ApplicationRunning))
   114  		Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(1))
   115  		rt := &v1beta1.ResourceTracker{}
   116  		Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name, "v1"), rt)).Should(BeNil())
   117  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   118  		checkApp = new(v1beta1.Application)
   119  		Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
   120  		Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(1))
   121  		Expect(checkApp.Finalizers[0]).Should(BeEquivalentTo(oam.FinalizerResourceTracker))
   122  		By("delete this cross workload app")
   123  		Expect(k8sClient.Delete(ctx, checkApp)).Should(BeNil())
   124  		By("delete app will delete resourceTracker")
   125  		// reconcile will delete resourceTracker and unset app's finalizer
   126  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   127  		testutil.ReconcileOnce(reconciler, ctrl.Request{NamespacedName: appKey})
   128  		checkApp = new(v1beta1.Application)
   129  		Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(util.NotFoundMatcher{})
   130  		checkRt := new(v1beta1.ResourceTracker)
   131  		Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name, "v1"), checkRt)).Should(util.NotFoundMatcher{})
   132  	})
   133  
   134  	It("Test cross namespace workload, then update the app to change the namespace", func() {
   135  		appName := "app-3"
   136  		appKey := types.NamespacedName{Namespace: namespace, Name: appName}
   137  		app := getApp(appName, namespace, "cross-worker")
   138  		Expect(k8sClient.Create(ctx, app)).Should(BeNil())
   139  
   140  		By("Create a cross workload app")
   141  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   142  		checkApp := &v1beta1.Application{}
   143  		Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
   144  		Expect(checkApp.Status.Phase).Should(Equal(common.ApplicationRunning))
   145  		Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(1))
   146  		rt := &v1beta1.ResourceTracker{}
   147  		Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name, "v1"), rt)).Should(BeNil())
   148  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   149  		checkApp = new(v1beta1.Application)
   150  		Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
   151  		Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(1))
   152  		Expect(checkApp.Finalizers[0]).Should(BeEquivalentTo(oam.FinalizerResourceTracker))
   153  		Expect(len(rt.Spec.ManagedResources)).Should(BeEquivalentTo(1))
   154  		By("Update the app, set type to normal-worker")
   155  		checkApp.Spec.Components[0].Type = "normal-worker"
   156  		Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
   157  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   158  		checkApp = new(v1beta1.Application)
   159  		Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
   160  		Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name, "v2"), rt)).Should(Succeed())
   161  		Expect(k8sClient.Delete(ctx, checkApp)).Should(BeNil())
   162  		testutil.ReconcileOnceAfterFinalizer(reconciler, ctrl.Request{NamespacedName: appKey})
   163  	})
   164  })
   165  
   166  func getApp(appName, namespace, comptype string) *v1beta1.Application {
   167  	return &v1beta1.Application{
   168  		TypeMeta: metav1.TypeMeta{
   169  			Kind:       "Application",
   170  			APIVersion: "core.oam.dev/v1beta1",
   171  		},
   172  		ObjectMeta: metav1.ObjectMeta{
   173  			Name:      appName,
   174  			Namespace: namespace,
   175  		},
   176  		Spec: v1beta1.ApplicationSpec{
   177  			Components: []common.ApplicationComponent{
   178  				{
   179  					Name:       "comp1",
   180  					Type:       comptype,
   181  					Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   182  				},
   183  			},
   184  		},
   185  	}
   186  }
   187  
   188  func getTrackerKey(namespace, name, revision string) types.NamespacedName {
   189  	return types.NamespacedName{Name: fmt.Sprintf("%s-%s-%s", name, revision, namespace)}
   190  }
   191  
   192  const (
   193  	crossCompDefYaml = `
   194  apiVersion: core.oam.dev/v1beta1
   195  kind: ComponentDefinition
   196  metadata:
   197    name: cross-worker
   198    namespace: vela-system
   199    annotations:
   200      definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
   201  spec:
   202    workload:
   203      definition:
   204        apiVersion: apps/v1
   205        kind: Deployment
   206    extension:
   207      healthPolicy: |
   208        isHealth: context.output.status.readyReplicas == context.output.status.replicas
   209      template: |
   210        output: {
   211            apiVersion: "apps/v1"
   212            kind:       "Deployment"
   213            metadata: {
   214                namespace: "cross-namespace"
   215            }
   216            spec: {
   217                replicas: 0
   218                template: {
   219                    metadata: labels: {
   220                        "app.oam.dev/component": context.name
   221                    }
   222  
   223                    spec: {
   224                        containers: [{
   225                            name:  context.name
   226                            image: parameter.image
   227  
   228                            if parameter["cmd"] != _|_ {
   229                                command: parameter.cmd
   230                            }
   231                        }]
   232                    }
   233                }
   234  
   235                selector:
   236                    matchLabels:
   237                        "app.oam.dev/component": context.name
   238            }
   239        }
   240  
   241        parameter: {
   242            // +usage=Which image would you like to use for your service
   243            // +short=i
   244            image: string
   245  
   246            cmd?: [...string]
   247        }
   248  `
   249  
   250  	normalCompDefYaml = `
   251  apiVersion: core.oam.dev/v1beta1
   252  kind: ComponentDefinition
   253  metadata:
   254    name: normal-worker
   255    namespace: vela-system
   256    annotations:
   257      definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
   258  spec:
   259    workload:
   260      definition:
   261        apiVersion: apps/v1
   262        kind: Deployment
   263    extension:
   264      healthPolicy: |
   265        isHealth: context.output.status.readyReplicas == context.output.status.replicas
   266      template: |
   267        output: {
   268            apiVersion: "apps/v1"
   269            kind:       "Deployment"
   270            spec: {
   271                replicas: 0
   272                template: {
   273                    metadata: labels: {
   274                        "app.oam.dev/component": context.name
   275                    }
   276  
   277                    spec: {
   278                        containers: [{
   279                            name:  context.name
   280                            image: parameter.image
   281  
   282                            if parameter["cmd"] != _|_ {
   283                                command: parameter.cmd
   284                            }
   285                        }]
   286                    }
   287                }
   288  
   289                selector:
   290                    matchLabels:
   291                        "app.oam.dev/component": context.name
   292            }
   293        }
   294  
   295        parameter: {
   296            // +usage=Which image would you like to use for your service
   297            // +short=i
   298            image: string
   299  
   300            cmd?: [...string]
   301        }
   302  `
   303  	badCompDefYaml = `
   304  apiVersion: core.oam.dev/v1beta1
   305  kind: ComponentDefinition
   306  metadata:
   307    name: bad-worker
   308    namespace: vela-system
   309    annotations:
   310      definition.oam.dev/description: "It will make dispatching failed"
   311  spec:
   312    workload:
   313      definition:
   314        apiVersion: apps/v1
   315        kind: Deployment
   316    extension:
   317      template: |
   318        output: {
   319            apiVersion: "apps/v1"
   320            kind:       "Deployment"
   321            spec: {
   322                replicas: 0
   323                selector:
   324                    matchLabels:
   325                        "app.oam.dev/component": context.name
   326            }
   327        }
   328  
   329        parameter: {
   330            // +usage=Which image would you like to use for your service
   331            // +short=i
   332            image: string
   333  
   334            cmd?: [...string]
   335        }
   336  `
   337  )