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 }