github.com/oam-dev/kubevela@v1.9.11/pkg/resourcekeeper/gc_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  	"fmt"
    22  	"testing"
    23  
    24  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    25  	"github.com/stretchr/testify/require"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    33  
    34  	apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    35  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    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/resourcetracker"
    39  	"github.com/oam-dev/kubevela/pkg/utils/common"
    40  )
    41  
    42  func TestResourceKeeperGarbageCollect(t *testing.T) {
    43  	MarkWithProbability = 1.0
    44  	r := require.New(t)
    45  	cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
    46  	ctx := context.Background()
    47  
    48  	rtMaps := map[int64]*v1beta1.ResourceTracker{}
    49  	cmMaps := map[int]*unstructured.Unstructured{}
    50  	crMaps := map[int]*appsv1.ControllerRevision{}
    51  
    52  	crRT := &v1beta1.ResourceTracker{
    53  		ObjectMeta: metav1.ObjectMeta{Name: "app-comp-rev", Labels: map[string]string{
    54  			oam.LabelAppName:      "app",
    55  			oam.LabelAppNamespace: "default",
    56  			oam.LabelAppUID:       "uid",
    57  		}, Finalizers: []string{resourcetracker.Finalizer}},
    58  		Spec: v1beta1.ResourceTrackerSpec{
    59  			Type: v1beta1.ResourceTrackerTypeComponentRevision,
    60  		},
    61  	}
    62  	r.NoError(cli.Create(ctx, crRT))
    63  
    64  	createRT := func(gen int64) {
    65  		_rt := &v1beta1.ResourceTracker{
    66  			ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("app-v%d", gen), Labels: map[string]string{
    67  				oam.LabelAppName:      "app",
    68  				oam.LabelAppNamespace: "default",
    69  				oam.LabelAppUID:       "uid",
    70  			}, Finalizers: []string{resourcetracker.Finalizer}},
    71  			Spec: v1beta1.ResourceTrackerSpec{
    72  				Type:                  v1beta1.ResourceTrackerTypeVersioned,
    73  				ApplicationGeneration: gen,
    74  			},
    75  		}
    76  		r.NoError(cli.Create(ctx, _rt))
    77  		rtMaps[gen] = _rt
    78  	}
    79  
    80  	addConfigMapToRT := func(i int, gen int64, compID int) {
    81  		_rt := rtMaps[gen]
    82  		if _, exists := cmMaps[i]; !exists {
    83  			cm := &unstructured.Unstructured{}
    84  			cm.SetName(fmt.Sprintf("cm-%d", i))
    85  			cm.SetNamespace("default")
    86  			cm.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap"))
    87  			cm.SetLabels(map[string]string{
    88  				oam.LabelAppComponent: fmt.Sprintf("comp-%d", compID),
    89  				oam.LabelAppNamespace: "default",
    90  				oam.LabelAppName:      "app",
    91  			})
    92  			r.NoError(cli.Create(ctx, cm))
    93  			cmMaps[i] = cm
    94  		}
    95  		if _, exists := crMaps[compID]; !exists {
    96  			cr := &appsv1.ControllerRevision{Data: runtime.RawExtension{Raw: []byte(`{}`)}}
    97  			cr.SetName(fmt.Sprintf("cr-comp-%d", compID))
    98  			cr.SetNamespace("default")
    99  			cr.SetLabels(map[string]string{
   100  				oam.LabelAppComponent: fmt.Sprintf("comp-%d", compID),
   101  			})
   102  			r.NoError(cli.Create(ctx, cr))
   103  			crMaps[compID] = cr
   104  			obj := &unstructured.Unstructured{}
   105  			obj.SetName(cr.GetName())
   106  			obj.SetNamespace(cr.GetNamespace())
   107  			obj.SetLabels(cr.GetLabels())
   108  			r.NoError(resourcetracker.RecordManifestsInResourceTracker(ctx, cli, crRT, []*unstructured.Unstructured{obj}, true, false, ""))
   109  		}
   110  		r.NoError(resourcetracker.RecordManifestsInResourceTracker(ctx, cli, _rt, []*unstructured.Unstructured{cmMaps[i]}, true, false, ""))
   111  	}
   112  
   113  	checkCount := func(cmCount, rtCount int, crCount int) {
   114  		n := 0
   115  		for _, v := range cmMaps {
   116  			o := &unstructured.Unstructured{}
   117  			o.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap"))
   118  			err := cli.Get(ctx, client.ObjectKeyFromObject(v), o)
   119  			if err == nil {
   120  				n += 1
   121  			}
   122  		}
   123  		r.Equal(cmCount, n)
   124  		_rts := &v1beta1.ResourceTrackerList{}
   125  		r.NoError(cli.List(ctx, _rts))
   126  		r.Equal(rtCount, len(_rts.Items))
   127  		_crs := &appsv1.ControllerRevisionList{}
   128  		r.NoError(cli.List(ctx, _crs))
   129  		r.Equal(crCount, len(_crs.Items))
   130  	}
   131  
   132  	createRK := func(gen int64, keepLegacy bool, order v1alpha1.GarbageCollectOrder, components ...apicommon.ApplicationComponent) *resourceKeeper {
   133  		_rk, err := NewResourceKeeper(ctx, cli, &v1beta1.Application{
   134  			ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "default", UID: "uid", Generation: gen},
   135  			Spec:       v1beta1.ApplicationSpec{Components: components},
   136  		})
   137  		r.NoError(err)
   138  		rk := _rk.(*resourceKeeper)
   139  		rk.garbageCollectPolicy = &v1alpha1.GarbageCollectPolicySpec{
   140  			Order:              order,
   141  			KeepLegacyResource: keepLegacy,
   142  		}
   143  		return rk
   144  	}
   145  
   146  	createRT(1)
   147  	addConfigMapToRT(1, 1, 1)
   148  	addConfigMapToRT(2, 1, 2)
   149  	createRT(2)
   150  	addConfigMapToRT(1, 2, 1)
   151  	addConfigMapToRT(3, 2, 3)
   152  	createRT(3)
   153  	addConfigMapToRT(4, 3, 3)
   154  	createRT(4)
   155  	addConfigMapToRT(5, 4, 4)
   156  	addConfigMapToRT(6, 4, 5)
   157  	addConfigMapToRT(7, 4, 6)
   158  	checkCount(7, 5, 6)
   159  
   160  	opts := []GCOption{DisableLegacyGCOption{}}
   161  	// no need to gc
   162  	rk := createRK(4, true, "")
   163  	finished, _, err := rk.GarbageCollect(ctx, opts...)
   164  	r.NoError(err)
   165  	r.True(finished)
   166  	checkCount(7, 5, 6)
   167  
   168  	// delete rt2, trigger gc for cm3
   169  	dt := metav1.Now()
   170  	rtMaps[2].SetDeletionTimestamp(&dt)
   171  	r.NoError(cli.Update(ctx, rtMaps[2]))
   172  	rk = createRK(4, true, "")
   173  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   174  	r.NoError(err)
   175  	r.False(finished)
   176  	rk = createRK(4, true, "")
   177  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   178  	r.NoError(err)
   179  	r.True(finished)
   180  	checkCount(6, 4, 6)
   181  
   182  	// delete cm4, trigger gc for rt3, comp-3 no use
   183  	r.NoError(cli.Delete(ctx, cmMaps[4]))
   184  	rk = createRK(5, true, "")
   185  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   186  	r.NoError(err)
   187  	r.True(finished)
   188  	checkCount(5, 3, 5)
   189  
   190  	// upgrade and gc legacy rt1
   191  	rk = createRK(4, false, "")
   192  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   193  	r.NoError(err)
   194  	r.False(finished)
   195  	rk = createRK(4, false, "")
   196  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   197  	r.NoError(err)
   198  	r.True(finished)
   199  	checkCount(3, 2, 3)
   200  
   201  	// delete with sequential
   202  	comps := []apicommon.ApplicationComponent{
   203  		{
   204  			Name: "comp-5",
   205  			DependsOn: []string{
   206  				"comp-6",
   207  			},
   208  		},
   209  		{
   210  			Name: "comp-6",
   211  			DependsOn: []string{
   212  				"comp-7",
   213  			},
   214  		},
   215  		{
   216  			Name: "comp-7",
   217  		},
   218  	}
   219  	rk = createRK(5, false, v1alpha1.OrderDependency, comps...)
   220  	rtMaps[3].SetDeletionTimestamp(&dt)
   221  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   222  	r.NoError(err)
   223  	r.False(finished)
   224  	rk = createRK(5, false, v1alpha1.OrderDependency, comps...)
   225  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   226  	r.NoError(err)
   227  	r.False(finished)
   228  	rk = createRK(5, false, v1alpha1.OrderDependency, comps...)
   229  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   230  	r.NoError(err)
   231  	r.True(finished)
   232  
   233  	r.NoError(cli.Get(ctx, client.ObjectKeyFromObject(crRT), crRT))
   234  	// recreate rt, delete app, gc all
   235  	createRT(5)
   236  	addConfigMapToRT(8, 5, 8)
   237  	addConfigMapToRT(9, 5, 8)
   238  	createRT(6)
   239  	addConfigMapToRT(9, 6, 8)
   240  	addConfigMapToRT(10, 6, 8)
   241  	checkCount(3, 3, 1)
   242  
   243  	rk = createRK(6, false, "")
   244  	rk.app.SetDeletionTimestamp(&dt)
   245  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   246  	r.NoError(err)
   247  	r.False(finished)
   248  	rk = createRK(6, false, "")
   249  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   250  	r.NoError(err)
   251  	r.True(finished)
   252  	checkCount(0, 0, 0)
   253  
   254  	rk = createRK(7, false, "")
   255  	finished, _, err = rk.GarbageCollect(ctx, opts...)
   256  	r.NoError(err)
   257  	r.True(finished)
   258  }
   259  
   260  func TestCheckDependentComponent(t *testing.T) {
   261  	rk := &resourceKeeper{
   262  		app: &v1beta1.Application{
   263  			Spec: v1beta1.ApplicationSpec{
   264  				Components: []apicommon.ApplicationComponent{
   265  					{
   266  						Name: "comp-1",
   267  						Outputs: workflowv1alpha1.StepOutputs{
   268  							{
   269  								Name: "output-1",
   270  							},
   271  						},
   272  					},
   273  					{
   274  						Name: "comp-2",
   275  						Outputs: workflowv1alpha1.StepOutputs{
   276  							{
   277  								Name: "output-2",
   278  							},
   279  						},
   280  					},
   281  					{
   282  						Name: "comp-3",
   283  						Inputs: workflowv1alpha1.StepInputs{
   284  							{
   285  								From: "output-1",
   286  							},
   287  							{
   288  								From: "output-2",
   289  							},
   290  						},
   291  					},
   292  					{
   293  						Name:      "comp-4",
   294  						DependsOn: []string{"comp-3"},
   295  					},
   296  					{
   297  						Name:      "comp-5",
   298  						DependsOn: []string{"comp-4", "comp-3"},
   299  					},
   300  				},
   301  			},
   302  		},
   303  	}
   304  	testCases := []struct {
   305  		comp   string
   306  		result []string
   307  	}{
   308  		{
   309  			comp:   "comp-1",
   310  			result: []string{"comp-3"},
   311  		},
   312  		{
   313  			comp:   "comp-2",
   314  			result: []string{"comp-3"},
   315  		},
   316  		{
   317  			comp:   "comp-3",
   318  			result: []string{"comp-4", "comp-5"},
   319  		},
   320  		{
   321  			comp:   "comp-4",
   322  			result: []string{"comp-5"},
   323  		},
   324  		{
   325  			comp:   "comp-5",
   326  			result: []string{},
   327  		},
   328  	}
   329  	gcHandler := &gcHandler{
   330  		resourceKeeper: rk,
   331  	}
   332  	r := require.New(t)
   333  	for _, tc := range testCases {
   334  		mr := v1beta1.ManagedResource{
   335  			OAMObjectReference: apicommon.OAMObjectReference{
   336  				Component: tc.comp,
   337  			},
   338  		}
   339  		r.Equal(gcHandler.checkDependentComponent(mr), tc.result)
   340  	}
   341  }
   342  
   343  func TestEnableMarkStageGCOnWorkflowFailure(t *testing.T) {
   344  	h := &resourceKeeper{garbageCollectPolicy: &v1alpha1.GarbageCollectPolicySpec{ContinueOnFailure: true}}
   345  	options := []GCOption{DisableMarkStageGCOption{}}
   346  	cfg := h.buildGCConfig(context.Background(), options...)
   347  	require.True(t, cfg.disableMark)
   348  	cfg = h.buildGCConfig(WithPhase(context.Background(), apicommon.ApplicationWorkflowFailed), options...)
   349  	require.False(t, cfg.disableMark)
   350  }