github.com/oam-dev/kubevela@v1.9.11/pkg/resourcekeeper/gc_suite_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 resourcekeeper 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "testing" 24 "time" 25 26 "github.com/crossplane/crossplane-runtime/pkg/meta" 27 . "github.com/onsi/ginkgo/v2" 28 . "github.com/onsi/gomega" 29 corev1 "k8s.io/api/core/v1" 30 rbacv1 "k8s.io/api/rbac/v1" 31 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 32 "k8s.io/apimachinery/pkg/api/errors" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 35 "k8s.io/apimachinery/pkg/runtime" 36 "k8s.io/apimachinery/pkg/types" 37 utilfeature "k8s.io/apiserver/pkg/util/feature" 38 featuregatetesting "k8s.io/component-base/featuregate/testing" 39 "sigs.k8s.io/controller-runtime/pkg/client" 40 41 "github.com/kubevela/pkg/util/rand" 42 43 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 44 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 45 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 46 "github.com/oam-dev/kubevela/pkg/features" 47 "github.com/oam-dev/kubevela/pkg/multicluster" 48 "github.com/oam-dev/kubevela/pkg/oam" 49 "github.com/oam-dev/kubevela/pkg/resourcetracker" 50 "github.com/oam-dev/kubevela/pkg/utils/apply" 51 "github.com/oam-dev/kubevela/version" 52 ) 53 54 var _ = Describe("Test ResourceKeeper garbage collection", func() { 55 56 var namespace string 57 58 BeforeEach(func() { 59 namespace = "test-ns-" + rand.RandomString(4) 60 Expect(testClient.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}})).Should(Succeed()) 61 }) 62 63 AfterEach(func() { 64 ns := &corev1.Namespace{} 65 Expect(testClient.Get(context.Background(), types.NamespacedName{Name: namespace}, ns)).Should(Succeed()) 66 Expect(testClient.Delete(context.Background(), ns)).Should(Succeed()) 67 }) 68 69 It("Test gcHandler garbage collect legacy RT", func() { 70 defer featuregatetesting.SetFeatureGateDuringTest(&testing.T{}, utilfeature.DefaultFeatureGate, features.LegacyResourceTrackerGC, true)() 71 version.VelaVersion = velaVersionNumberToUpgradeResourceTracker 72 ctx := context.Background() 73 cli := multicluster.NewFakeClient(testClient) 74 cli.AddCluster("worker", workerClient) 75 cli.AddCluster("worker-2", workerClient) 76 app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "gc-app", Namespace: namespace}} 77 bs, err := json.Marshal(&v1alpha1.EnvBindingSpec{ 78 Envs: []v1alpha1.EnvConfig{{ 79 Placement: v1alpha1.EnvPlacement{ClusterSelector: &common.ClusterSelector{Name: "worker"}}, 80 }}, 81 }) 82 Expect(err).Should(Succeed()) 83 meta.AddAnnotations(app, map[string]string{oam.AnnotationKubeVelaVersion: "v1.1.13"}) 84 app.Spec = v1beta1.ApplicationSpec{ 85 Components: []common.ApplicationComponent{}, 86 Policies: []v1beta1.AppPolicy{{ 87 Type: v1alpha1.EnvBindingPolicyType, 88 Properties: &runtime.RawExtension{Raw: bs}, 89 }}, 90 } 91 app.Status.AppliedResources = []common.ClusterObjectReference{{ 92 Cluster: "worker-2", 93 }} 94 Expect(cli.Create(ctx, app)).Should(Succeed()) 95 keeper := &resourceKeeper{Client: cli, app: app} 96 h := gcHandler{resourceKeeper: keeper} 97 rt := &v1beta1.ResourceTracker{} 98 rt.SetName("gc-app-rt-v1-" + namespace) 99 rt.SetLabels(map[string]string{ 100 oam.LabelAppName: h.app.Name, 101 oam.LabelAppNamespace: h.app.Namespace, 102 }) 103 rt3 := rt.DeepCopy() 104 rt4 := rt.DeepCopy() 105 rt5 := rt.DeepCopy() 106 rt4.SetName("gc-app-rt-v2-" + namespace) 107 Expect(cli.Create(ctx, rt)).Should(Succeed()) 108 rt2 := &v1beta1.ResourceTracker{} 109 rt2.Spec.Type = v1beta1.ResourceTrackerTypeVersioned 110 rt2.SetName("gc-app-rt-v2-" + namespace) 111 rt2.SetLabels(map[string]string{ 112 oam.LabelAppName: h.app.Name, 113 oam.LabelAppNamespace: h.app.Namespace, 114 }) 115 Expect(cli.Create(ctx, rt2)).Should(Succeed()) 116 Expect(h.GarbageCollectLegacyResourceTrackers(ctx)).Should(Succeed()) 117 Expect(cli.Create(multicluster.ContextWithClusterName(ctx, "worker"), rt3)).Should(Succeed()) 118 Expect(cli.Create(multicluster.ContextWithClusterName(ctx, "worker-2"), rt4)).Should(Succeed()) 119 120 checkRTExists := func(_ctx context.Context, name string, exists bool) { 121 _rt := &v1beta1.ResourceTracker{} 122 err := cli.Get(_ctx, types.NamespacedName{Name: name}, _rt) 123 if exists { 124 Expect(err).Should(Succeed()) 125 } else { 126 Expect(errors.IsNotFound(err)).Should(BeTrue()) 127 } 128 } 129 130 Expect(h.GarbageCollectLegacyResourceTrackers(ctx)).Should(Succeed()) 131 checkRTExists(ctx, rt.GetName(), true) 132 checkRTExists(ctx, rt2.GetName(), true) 133 checkRTExists(multicluster.ContextWithClusterName(ctx, "worker"), rt3.GetName(), true) 134 checkRTExists(multicluster.ContextWithClusterName(ctx, "worker-2"), rt4.GetName(), true) 135 136 h.resourceKeeper._currentRT = rt2 137 Expect(h.GarbageCollectLegacyResourceTrackers(ctx)).Should(Succeed()) 138 checkRTExists(ctx, rt.GetName(), false) 139 checkRTExists(ctx, rt2.GetName(), true) 140 checkRTExists(multicluster.ContextWithClusterName(ctx, "worker"), rt3.GetName(), false) 141 checkRTExists(multicluster.ContextWithClusterName(ctx, "worker-2"), rt4.GetName(), false) 142 Expect(app.GetAnnotations()[oam.AnnotationKubeVelaVersion]).Should(Equal("v1.2.0")) 143 144 crd := &apiextensionsv1.CustomResourceDefinition{} 145 Expect(workerClient.Get(ctx, types.NamespacedName{Name: "resourcetrackers.core.oam.dev"}, crd)).Should(Succeed()) 146 Expect(workerClient.Delete(ctx, crd)).Should(Succeed()) 147 Eventually(func(g Gomega) { 148 g.Expect(workerClient.List(ctx, &v1beta1.ResourceTrackerList{})).ShouldNot(Succeed()) 149 }, 10*time.Second).Should(Succeed()) 150 metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationKubeVelaVersion, "master") 151 version.VelaVersion = "master" 152 Expect(cli.Update(ctx, app)).Should(Succeed()) 153 Expect(h.GarbageCollectLegacyResourceTrackers(ctx)).Should(Succeed()) 154 Expect(app.GetAnnotations()[oam.AnnotationKubeVelaVersion]).Should(Equal("v1.2.0")) 155 156 Expect(cli.Create(ctx, rt5)).Should(Succeed()) 157 Expect(h.GarbageCollectLegacyResourceTrackers(ctx)).Should(Succeed()) 158 checkRTExists(ctx, rt5.GetName(), true) 159 }) 160 161 It("Test gcHandler garbage collect shared resources", func() { 162 ctx := context.Background() 163 cli := testClient 164 app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: namespace}} 165 166 keeper := &resourceKeeper{ 167 Client: cli, 168 app: app, 169 applicator: apply.NewAPIApplicator(cli), 170 cache: newResourceCache(cli, app), 171 } 172 h := gcHandler{resourceKeeper: keeper, cfg: newGCConfig()} 173 h._currentRT = &v1beta1.ResourceTracker{} 174 t := metav1.Now() 175 h._currentRT.SetDeletionTimestamp(&t) 176 h._currentRT.SetFinalizers([]string{resourcetracker.Finalizer}) 177 createResource := func(name, appName, appNs, sharedBy string) *unstructured.Unstructured { 178 return &unstructured.Unstructured{Object: map[string]interface{}{ 179 "apiVersion": "v1", 180 "kind": "ConfigMap", 181 "metadata": map[string]interface{}{ 182 "name": name, 183 "namespace": namespace, 184 "annotations": map[string]interface{}{oam.AnnotationAppSharedBy: sharedBy}, 185 "labels": map[string]interface{}{ 186 oam.LabelAppName: appName, 187 oam.LabelAppNamespace: appNs, 188 }, 189 }, 190 }} 191 } 192 By("Test delete normal resource") 193 o1 := createResource("o1", "app", namespace, "") 194 h._currentRT.AddManagedResource(o1, false, false, "test") 195 Expect(cli.Create(ctx, o1)).Should(Succeed()) 196 h.cache.registerResourceTrackers(h._currentRT) 197 Expect(h.Finalize(ctx)).Should(Succeed()) 198 Eventually(func(g Gomega) { 199 g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(o1), o1)).Should(Satisfy(errors.IsNotFound)) 200 }, 5*time.Second).Should(Succeed()) 201 202 By("Test delete resource shared by others") 203 o2 := createResource("o2", "app", namespace, fmt.Sprintf("%s/app,x/y", namespace)) 204 h._currentRT.AddManagedResource(o2, false, false, "test") 205 Expect(cli.Create(ctx, o2)).Should(Succeed()) 206 h.cache.registerResourceTrackers(h._currentRT) 207 Expect(h.Finalize(ctx)).Should(Succeed()) 208 Eventually(func(g Gomega) { 209 g.Expect(cli.Get(ctx, client.ObjectKeyFromObject(o2), o2)).Should(Succeed()) 210 g.Expect(o2.GetAnnotations()[oam.AnnotationAppSharedBy]).Should(Equal("x/y")) 211 g.Expect(o2.GetLabels()[oam.LabelAppNamespace]).Should(Equal("x")) 212 g.Expect(o2.GetLabels()[oam.LabelAppName]).Should(Equal("y")) 213 }, 5*time.Second).Should(Succeed()) 214 215 By("Test delete resource shared by self") 216 o3 := createResource("o3", "app", namespace, fmt.Sprintf("%s/app", namespace)) 217 h._currentRT.AddManagedResource(o3, false, false, "test") 218 Expect(cli.Create(ctx, o3)).Should(Succeed()) 219 h.cache.registerResourceTrackers(h._currentRT) 220 Expect(h.Finalize(ctx)).Should(Succeed()) 221 Eventually(func(g Gomega) { 222 Expect(cli.Get(ctx, client.ObjectKeyFromObject(o3), o3)).Should(Satisfy(errors.IsNotFound)) 223 }, 5*time.Second).Should(Succeed()) 224 }) 225 226 It("Test gc same cluster-scoped resource but legacy resource recorded with namespace", func() { 227 ctx := context.Background() 228 cr := &unstructured.Unstructured{Object: map[string]interface{}{ 229 "apiVersion": "rbac.authorization.k8s.io/v1", 230 "kind": "ClusterRole", 231 "metadata": map[string]interface{}{ 232 "name": "test-cluster-scoped-resource", 233 "labels": map[string]interface{}{ 234 oam.LabelAppName: "app", 235 oam.LabelAppNamespace: namespace, 236 }, 237 }, 238 }} 239 Expect(testClient.Create(ctx, cr)).Should(Succeed()) 240 app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: namespace}} 241 keeper := &resourceKeeper{ 242 Client: testClient, 243 app: app, 244 applicator: apply.NewAPIApplicator(testClient), 245 cache: newResourceCache(testClient, app), 246 } 247 h := gcHandler{resourceKeeper: keeper, cfg: newGCConfig()} 248 h._currentRT = &v1beta1.ResourceTracker{ObjectMeta: metav1.ObjectMeta{Name: "test-cluster-scoped-resource-v2"}} 249 Expect(testClient.Create(ctx, h._currentRT)).Should(Succeed()) 250 h._historyRTs = []*v1beta1.ResourceTracker{{ObjectMeta: metav1.ObjectMeta{Name: "test-cluster-scoped-resource-v1"}}} 251 t := metav1.Now() 252 h._historyRTs[0].SetDeletionTimestamp(&t) 253 h._historyRTs[0].SetFinalizers([]string{resourcetracker.Finalizer}) 254 h._currentRT.AddManagedResource(cr, true, false, "") 255 _cr := cr.DeepCopy() 256 _cr.SetNamespace(namespace) 257 h._historyRTs[0].AddManagedResource(_cr, true, false, "") 258 h.Init() 259 Expect(h.Finalize(ctx)).Should(Succeed()) 260 Expect(testClient.Get(ctx, client.ObjectKeyFromObject(cr), &rbacv1.ClusterRole{})).Should(Succeed()) 261 h._currentRT.Spec.ManagedResources[0].Name = "not-equal" 262 keeper.cache = newResourceCache(testClient, app) 263 h.Init() 264 Expect(h.Finalize(ctx)).Should(Succeed()) 265 Expect(testClient.Get(ctx, client.ObjectKeyFromObject(cr), &rbacv1.ClusterRole{})).Should(Satisfy(errors.IsNotFound)) 266 }) 267 268 })