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  })