k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/storageversion/gc_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes 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 storageversion
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	apiserverinternalv1alpha1 "k8s.io/api/apiserverinternal/v1alpha1"
    27  	coordinationv1 "k8s.io/api/coordination/v1"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/util/wait"
    31  	"k8s.io/apiserver/pkg/features"
    32  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    33  	"k8s.io/client-go/informers"
    34  	"k8s.io/client-go/kubernetes"
    35  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    36  	"k8s.io/klog/v2/ktesting"
    37  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    38  	"k8s.io/kubernetes/pkg/controller/storageversiongc"
    39  	"k8s.io/kubernetes/pkg/controlplane"
    40  	controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver"
    41  	"k8s.io/kubernetes/test/integration/framework"
    42  	"k8s.io/utils/pointer"
    43  )
    44  
    45  const (
    46  	svName     = "storageversion.integration.test.foos"
    47  	idA        = "id-1"
    48  	idB        = "id-2"
    49  	idNonExist = "id-non-exist"
    50  )
    51  
    52  func TestStorageVersionGarbageCollection(t *testing.T) {
    53  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true)
    54  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StorageVersionAPI, true)
    55  	result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
    56  	defer result.TearDownFn()
    57  
    58  	kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)
    59  	if err != nil {
    60  		t.Fatalf("Unexpected error: %v", err)
    61  	}
    62  
    63  	informers := informers.NewSharedInformerFactory(kubeclient, time.Second)
    64  	leaseInformer := informers.Coordination().V1().Leases()
    65  	storageVersionInformer := informers.Internal().V1alpha1().StorageVersions()
    66  
    67  	_, ctx := ktesting.NewTestContext(t)
    68  	controller := storageversiongc.NewStorageVersionGC(ctx, kubeclient, leaseInformer, storageVersionInformer)
    69  
    70  	ctx, cancel := context.WithCancel(context.Background())
    71  	defer cancel()
    72  	go leaseInformer.Informer().Run(ctx.Done())
    73  	go storageVersionInformer.Informer().Run(ctx.Done())
    74  	go controller.Run(ctx)
    75  
    76  	createTestAPIServerIdentityLease(t, kubeclient, idA)
    77  	createTestAPIServerIdentityLease(t, kubeclient, idB)
    78  
    79  	t.Run("storage version with non-existing id should be GC'ed", func(t *testing.T) {
    80  		createTestStorageVersion(t, kubeclient, idNonExist)
    81  		assertStorageVersionDeleted(t, kubeclient)
    82  	})
    83  
    84  	t.Run("storage version with valid id should not be GC'ed", func(t *testing.T) {
    85  		createTestStorageVersion(t, kubeclient, idA)
    86  		time.Sleep(10 * time.Second)
    87  		sv, err := kubeclient.InternalV1alpha1().StorageVersions().Get(
    88  			context.TODO(), svName, metav1.GetOptions{})
    89  		if err != nil {
    90  			t.Fatalf("failed to retrieve valid storage version: %v", err)
    91  		}
    92  		if len(sv.Status.StorageVersions) != 1 {
    93  			t.Errorf("unexpected number of storage version entries, expected 1, got: %v",
    94  				sv.Status.StorageVersions)
    95  		}
    96  		expectedID := idA
    97  		if sv.Status.StorageVersions[0].APIServerID != expectedID {
    98  			t.Errorf("unexpected storage version entry id, expected %v, got: %v",
    99  				expectedID, sv.Status.StorageVersions[0].APIServerID)
   100  		}
   101  		assertCommonEncodingVersion(t, kubeclient, pointer.String(idToVersion(t, idA)))
   102  		if err := kubeclient.InternalV1alpha1().StorageVersions().Delete(
   103  			context.TODO(), svName, metav1.DeleteOptions{}); err != nil {
   104  			t.Fatalf("failed to cleanup valid storage version: %v", err)
   105  		}
   106  	})
   107  
   108  	t.Run("deleting an id should delete a storage version entry that it owns", func(t *testing.T) {
   109  		createTestStorageVersion(t, kubeclient, idA, idB)
   110  		assertStorageVersionEntries(t, kubeclient, 2, idA)
   111  		assertCommonEncodingVersion(t, kubeclient, nil)
   112  		deleteTestAPIServerIdentityLease(t, kubeclient, idA)
   113  		assertStorageVersionEntries(t, kubeclient, 1, idB)
   114  		assertCommonEncodingVersion(t, kubeclient, pointer.String(idToVersion(t, idB)))
   115  	})
   116  
   117  	t.Run("deleting an id should delete a storage version object that it owns entirely", func(t *testing.T) {
   118  		deleteTestAPIServerIdentityLease(t, kubeclient, idB)
   119  		assertStorageVersionDeleted(t, kubeclient)
   120  	})
   121  }
   122  
   123  func createTestStorageVersion(t *testing.T, client kubernetes.Interface, ids ...string) {
   124  	sv := &apiserverinternalv1alpha1.StorageVersion{
   125  		ObjectMeta: metav1.ObjectMeta{
   126  			Name: svName,
   127  		},
   128  	}
   129  	for _, id := range ids {
   130  		version := idToVersion(t, id)
   131  		v := apiserverinternalv1alpha1.ServerStorageVersion{
   132  			APIServerID:       id,
   133  			EncodingVersion:   version,
   134  			DecodableVersions: []string{version},
   135  		}
   136  		sv.Status.StorageVersions = append(sv.Status.StorageVersions, v)
   137  	}
   138  	// every id is unique and creates a different version. We know we have a common encoding
   139  	// version when there is only one id. Pick it
   140  	if len(ids) == 1 {
   141  		sv.Status.CommonEncodingVersion = pointer.String(sv.Status.StorageVersions[0].EncodingVersion)
   142  	}
   143  
   144  	createdSV, err := client.InternalV1alpha1().StorageVersions().Create(context.TODO(), sv, metav1.CreateOptions{})
   145  	if err != nil {
   146  		t.Fatalf("failed to create storage version %s: %v", svName, err)
   147  	}
   148  	// update the created sv with intended status
   149  	createdSV.Status = sv.Status
   150  	if _, err := client.InternalV1alpha1().StorageVersions().UpdateStatus(
   151  		context.TODO(), createdSV, metav1.UpdateOptions{}); err != nil {
   152  		t.Fatalf("failed to update store version status: %v", err)
   153  	}
   154  }
   155  
   156  func assertStorageVersionDeleted(t *testing.T, client kubernetes.Interface) {
   157  	if err := wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
   158  		_, err := client.InternalV1alpha1().StorageVersions().Get(
   159  			context.TODO(), svName, metav1.GetOptions{})
   160  		if apierrors.IsNotFound(err) {
   161  			return true, nil
   162  		}
   163  		if err != nil {
   164  			return false, err
   165  		}
   166  		return false, nil
   167  	}); err != nil {
   168  		t.Fatalf("failed to wait for storageversion garbage collection: %v", err)
   169  	}
   170  }
   171  
   172  func createTestAPIServerIdentityLease(t *testing.T, client kubernetes.Interface, name string) {
   173  	lease := &coordinationv1.Lease{
   174  		ObjectMeta: metav1.ObjectMeta{
   175  			Name:      name,
   176  			Namespace: metav1.NamespaceSystem,
   177  			Labels: map[string]string{
   178  				controlplaneapiserver.IdentityLeaseComponentLabelKey: controlplane.KubeAPIServer,
   179  			},
   180  		},
   181  		Spec: coordinationv1.LeaseSpec{
   182  			HolderIdentity:       pointer.String(name),
   183  			LeaseDurationSeconds: pointer.Int32(3600),
   184  			// create fresh leases
   185  			AcquireTime: &metav1.MicroTime{Time: time.Now()},
   186  			RenewTime:   &metav1.MicroTime{Time: time.Now()},
   187  		},
   188  	}
   189  	if _, err := client.CoordinationV1().Leases(metav1.NamespaceSystem).Create(
   190  		context.TODO(), lease, metav1.CreateOptions{}); err != nil {
   191  		t.Fatalf("failed to create apiserver identity lease %s: %v", name, err)
   192  	}
   193  }
   194  
   195  func deleteTestAPIServerIdentityLease(t *testing.T, client kubernetes.Interface, name string) {
   196  	if err := client.CoordinationV1().Leases(metav1.NamespaceSystem).Delete(
   197  		context.TODO(), name, metav1.DeleteOptions{}); err != nil {
   198  		t.Fatalf("failed to delete apiserver identity lease %s: %v", name, err)
   199  	}
   200  }
   201  
   202  func assertStorageVersionEntries(t *testing.T, client kubernetes.Interface,
   203  	numEntries int, firstID string) {
   204  	var lastErr error
   205  	if err := wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
   206  		sv, err := client.InternalV1alpha1().StorageVersions().Get(
   207  			context.TODO(), svName, metav1.GetOptions{})
   208  		if err != nil {
   209  			return false, err
   210  		}
   211  		if len(sv.Status.StorageVersions) != numEntries {
   212  			lastErr = fmt.Errorf("unexpected number of storage version entries, expected %v, got: %v",
   213  				numEntries, len(sv.Status.StorageVersions))
   214  			return false, nil
   215  		}
   216  		if sv.Status.StorageVersions[0].APIServerID != firstID {
   217  			lastErr = fmt.Errorf("unexpected first storage version entry id, expected %v, got: %v",
   218  				firstID, sv.Status.StorageVersions[0].APIServerID)
   219  			return false, nil
   220  		}
   221  		return true, nil
   222  	}); err != nil {
   223  		t.Fatalf("failed to get expected storage verion entries: %v, last error: %v", err, lastErr)
   224  	}
   225  }
   226  
   227  func assertCommonEncodingVersion(t *testing.T, client kubernetes.Interface, e *string) {
   228  	sv, err := client.InternalV1alpha1().StorageVersions().Get(
   229  		context.TODO(), svName, metav1.GetOptions{})
   230  	if err != nil {
   231  		t.Fatalf("failed to retrieve storage version: %v", err)
   232  	}
   233  	if e == nil {
   234  		if sv.Status.CommonEncodingVersion != nil {
   235  			t.Errorf("unexpected non-nil common encoding version: %v", sv.Status.CommonEncodingVersion)
   236  		}
   237  		return
   238  	}
   239  	if sv.Status.CommonEncodingVersion == nil || *sv.Status.CommonEncodingVersion != *e {
   240  		t.Errorf("unexpected common encoding version, expected: %v, got %v", e, sv.Status.CommonEncodingVersion)
   241  	}
   242  }
   243  
   244  func idToVersion(t *testing.T, id string) string {
   245  	// TODO(roycaihw): rewrite the test, use a id-version table
   246  	if !strings.HasPrefix(id, "id-") {
   247  		t.Fatalf("should not happen: test using id without id- prefix: %s", id)
   248  	}
   249  	return fmt.Sprintf("v%s", strings.TrimPrefix(id, "id-"))
   250  }