github.com/oam-dev/kubevela@v1.9.11/test/e2e-test/app_revision_clean_up_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  	v1 "k8s.io/api/core/v1"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  	"sigs.k8s.io/yaml"
    34  
    35  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    36  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    37  	"github.com/oam-dev/kubevela/pkg/oam"
    38  	"github.com/oam-dev/kubevela/pkg/oam/util"
    39  )
    40  
    41  var (
    42  	appRevisionLimit = 5
    43  )
    44  
    45  var _ = Describe("Test application controller clean up appRevision", func() {
    46  	ctx := context.TODO()
    47  	var namespace string
    48  
    49  	cd := &v1beta1.ComponentDefinition{}
    50  
    51  	BeforeEach(func() {
    52  		namespace = randomNamespaceName("clean-up-revision-test")
    53  		ns := v1.Namespace{
    54  			ObjectMeta: metav1.ObjectMeta{
    55  				Name: namespace,
    56  			},
    57  		}
    58  		Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    59  
    60  		cdDefJson, _ := yaml.YAMLToJSON([]byte(fmt.Sprintf(compDefYaml, namespace)))
    61  		Expect(json.Unmarshal(cdDefJson, cd)).Should(BeNil())
    62  		Expect(k8sClient.Create(ctx, cd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
    63  	})
    64  
    65  	AfterEach(func() {
    66  		By("[TEST] Clean up resources after an integration test")
    67  		k8sClient.DeleteAllOf(ctx, &v1beta1.Application{}, client.InNamespace(namespace))
    68  		k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace))
    69  		k8sClient.DeleteAllOf(ctx, &v1beta1.WorkloadDefinition{}, client.InNamespace(namespace))
    70  		k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace))
    71  		Expect(k8sClient.Delete(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
    72  	})
    73  
    74  	It("Test clean up appRevision", func() {
    75  		appName := "app-1"
    76  		appKey := types.NamespacedName{Namespace: namespace, Name: appName}
    77  		app := getApp(appName, namespace, "normal-worker")
    78  		Eventually(func() error {
    79  			err := k8sClient.Create(ctx, app)
    80  			return err
    81  		}, 15*time.Second, 300*time.Millisecond).Should(BeNil())
    82  		checkApp := new(v1beta1.Application)
    83  		for i := 0; i < appRevisionLimit; i++ {
    84  			Eventually(func() error {
    85  				checkApp = new(v1beta1.Application)
    86  				Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
    87  				if checkApp.Status.LatestRevision == nil || checkApp.Status.LatestRevision.Revision != int64(i+1) {
    88  					return fmt.Errorf("application point to wrong revision")
    89  				}
    90  				return nil
    91  			}, time.Second*10, time.Millisecond*500).Should(BeNil())
    92  			Eventually(func() error {
    93  				checkApp = new(v1beta1.Application)
    94  				Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
    95  				property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
    96  				checkApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(property)}
    97  				if err := k8sClient.Update(ctx, checkApp); err != nil {
    98  					return err
    99  				}
   100  				return nil
   101  			}, time.Second*10, time.Millisecond*500).Should(BeNil())
   102  			Eventually(func() error {
   103  				checkApp = new(v1beta1.Application)
   104  				Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
   105  				if checkApp.Status.ObservedGeneration == checkApp.Generation && checkApp.Status.Phase == common.ApplicationRunning {
   106  					return nil
   107  				}
   108  				return fmt.Errorf("application is not observed or status %s is not running", checkApp.Status.Phase)
   109  			}, time.Second*10, time.Millisecond*500).Should(BeNil())
   110  
   111  		}
   112  		listOpts := []client.ListOption{
   113  			client.InNamespace(namespace),
   114  			client.MatchingLabels{
   115  				oam.LabelAppName: appName,
   116  			},
   117  		}
   118  		appRevisionList := new(v1beta1.ApplicationRevisionList)
   119  		Eventually(func() error {
   120  			err := k8sClient.List(ctx, appRevisionList, listOpts...)
   121  			if err != nil {
   122  				return err
   123  			}
   124  			if len(appRevisionList.Items) != appRevisionLimit+1 {
   125  				return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
   126  			}
   127  			return nil
   128  		}, time.Second*10, time.Millisecond*500).Should(BeNil())
   129  		By("create new appRevision will remove appRevision v1")
   130  		Eventually(func() error {
   131  			err := k8sClient.Get(ctx, appKey, checkApp)
   132  			if err != nil {
   133  				return err
   134  			}
   135  			property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 5)
   136  			checkApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(property)}
   137  			return k8sClient.Update(ctx, checkApp)
   138  		}, time.Second*10, time.Millisecond*500).Should(BeNil())
   139  
   140  		deletedRevison := new(v1beta1.ApplicationRevision)
   141  		revKey := types.NamespacedName{Namespace: namespace, Name: appName + "-v1"}
   142  		Eventually(func() error {
   143  			err := k8sClient.List(ctx, appRevisionList, listOpts...)
   144  			if err != nil {
   145  				return err
   146  			}
   147  			if len(appRevisionList.Items) != appRevisionLimit+1 {
   148  				return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
   149  			}
   150  			err = k8sClient.Get(ctx, revKey, deletedRevison)
   151  			if err == nil || !apierrors.IsNotFound(err) {
   152  				return fmt.Errorf("haven't clean up the oldest revision")
   153  			}
   154  			if res, err := util.CheckAppRevision(appRevisionList.Items, []int{2, 3, 4, 5, 6, 7}); err != nil || !res {
   155  				return fmt.Errorf("appRevision collection mismatch")
   156  			}
   157  			return nil
   158  		}, time.Second*10, time.Millisecond*500).Should(BeNil())
   159  
   160  		By("update app again will gc appRevision2")
   161  		Eventually(func() error {
   162  			if err := k8sClient.Get(ctx, appKey, checkApp); err != nil {
   163  				return err
   164  			}
   165  			property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 6)
   166  			checkApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(property)}
   167  			if err := k8sClient.Update(ctx, checkApp); err != nil {
   168  				return err
   169  			}
   170  			return nil
   171  		}, time.Second*10, time.Millisecond*500).Should(BeNil())
   172  		Eventually(func() error {
   173  			err := k8sClient.List(ctx, appRevisionList, listOpts...)
   174  			if err != nil {
   175  				return err
   176  			}
   177  			if len(appRevisionList.Items) != appRevisionLimit+1 {
   178  				return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
   179  			}
   180  			revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v2"}
   181  			err = k8sClient.Get(ctx, revKey, deletedRevison)
   182  			if err == nil || !apierrors.IsNotFound(err) {
   183  				return fmt.Errorf("haven't clean up the  revision-2")
   184  			}
   185  			if res, err := util.CheckAppRevision(appRevisionList.Items, []int{3, 4, 5, 6, 7, 8}); err != nil || !res {
   186  				return fmt.Errorf("appRevision collection mismatch")
   187  			}
   188  			return nil
   189  		}, time.Second*10, time.Millisecond*500).Should(BeNil())
   190  	})
   191  })
   192  
   193  var (
   194  	compDefYaml = `
   195  apiVersion: core.oam.dev/v1beta1
   196  kind: ComponentDefinition
   197  metadata:
   198    name: normal-worker
   199    namespace: %s
   200    annotations:
   201      definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
   202  spec:
   203    workload:
   204      definition:
   205        apiVersion: apps/v1
   206        kind: Deployment
   207    extension:
   208      healthPolicy: |
   209        isHealth: context.output.status.readyReplicas == context.output.status.replicas
   210      template: |
   211        output: {
   212            apiVersion: "apps/v1"
   213            kind:       "Deployment"
   214            spec: {
   215                replicas: 0
   216                template: {
   217                    metadata: labels: {
   218                        "app.oam.dev/component": context.name
   219                    }
   220  
   221                    spec: {
   222                        containers: [{
   223                            name:  context.name
   224                            image: parameter.image
   225  
   226                            if parameter["cmd"] != _|_ {
   227                                command: parameter.cmd
   228                            }
   229                        }]
   230                    }
   231                }
   232  
   233                selector:
   234                    matchLabels:
   235                        "app.oam.dev/component": context.name
   236            }
   237        }
   238  
   239        parameter: {
   240            // +usage=Which image would you like to use for your service
   241            // +short=i
   242            image: string
   243  
   244            cmd?: [...string]
   245        }
   246  `
   247  )
   248  
   249  func getApp(appName, namespace, comptype string) *v1beta1.Application {
   250  	return &v1beta1.Application{
   251  		TypeMeta: metav1.TypeMeta{
   252  			Kind:       "Application",
   253  			APIVersion: "core.oam.dev/v1beta1",
   254  		},
   255  		ObjectMeta: metav1.ObjectMeta{
   256  			Name:      appName,
   257  			Namespace: namespace,
   258  		},
   259  		Spec: v1beta1.ApplicationSpec{
   260  			Components: []common.ApplicationComponent{
   261  				{
   262  					Name:       "comp1",
   263  					Type:       comptype,
   264  					Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   265  				},
   266  			},
   267  		},
   268  	}
   269  }