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 }