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 )