k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/controlplane/apiserver_identity_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 controlplane 18 19 import ( 20 "context" 21 "crypto/sha256" 22 "encoding/base32" 23 "fmt" 24 "os" 25 "strings" 26 "testing" 27 "time" 28 29 "golang.org/x/crypto/cryptobyte" 30 31 coordinationv1 "k8s.io/api/coordination/v1" 32 corev1 "k8s.io/api/core/v1" 33 apierrors "k8s.io/apimachinery/pkg/api/errors" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/util/wait" 36 "k8s.io/apiserver/pkg/features" 37 utilfeature "k8s.io/apiserver/pkg/util/feature" 38 "k8s.io/client-go/kubernetes" 39 featuregatetesting "k8s.io/component-base/featuregate/testing" 40 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 41 "k8s.io/kubernetes/pkg/controlplane" 42 controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver" 43 "k8s.io/kubernetes/test/integration/framework" 44 "k8s.io/utils/pointer" 45 ) 46 47 const ( 48 testLeaseName = "apiserver-lease-test" 49 ) 50 51 func expectedAPIServerIdentity(t *testing.T, hostname string) string { 52 b := cryptobyte.NewBuilder(nil) 53 b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 54 b.AddBytes([]byte(hostname)) 55 }) 56 b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 57 b.AddBytes([]byte("kube-apiserver")) 58 }) 59 hashData, err := b.Bytes() 60 if err != nil { 61 t.Fatalf("error building hash data for apiserver identity: %v", err) 62 } 63 64 hash := sha256.Sum256(hashData) 65 return "apiserver-" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:16])) 66 } 67 68 func TestCreateLeaseOnStart(t *testing.T) { 69 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true) 70 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 71 defer result.TearDownFn() 72 73 kubeclient, err := kubernetes.NewForConfig(result.ClientConfig) 74 if err != nil { 75 t.Fatalf("Unexpected error: %v", err) 76 } 77 78 hostname, err := os.Hostname() 79 if err != nil { 80 t.Fatalf("Unexpected error getting apiserver hostname: %v", err) 81 } 82 83 t.Logf(`Waiting the kube-apiserver Lease to be created`) 84 if err := wait.PollImmediate(500*time.Millisecond, 10*time.Second, func() (bool, error) { 85 leases, err := kubeclient. 86 CoordinationV1(). 87 Leases(metav1.NamespaceSystem). 88 List(context.TODO(), metav1.ListOptions{LabelSelector: controlplaneapiserver.IdentityLeaseComponentLabelKey + "=" + controlplane.KubeAPIServer}) 89 if err != nil { 90 return false, err 91 } 92 93 if leases == nil { 94 return false, nil 95 } 96 97 if len(leases.Items) != 1 { 98 return false, nil 99 } 100 101 lease := leases.Items[0] 102 if lease.Name != expectedAPIServerIdentity(t, hostname) { 103 return false, fmt.Errorf("unexpected apiserver identity, got: %v, expected: %v", lease.Name, expectedAPIServerIdentity(t, hostname)) 104 } 105 106 if lease.Labels[corev1.LabelHostname] != hostname { 107 return false, fmt.Errorf("unexpected hostname label, got: %v, expected: %v", lease.Labels[corev1.LabelHostname], hostname) 108 } 109 110 return true, nil 111 }); err != nil { 112 t.Fatalf("Failed to see the kube-apiserver lease: %v", err) 113 } 114 } 115 116 func TestLeaseGarbageCollection(t *testing.T) { 117 oldIdentityLeaseDurationSeconds := controlplaneapiserver.IdentityLeaseDurationSeconds 118 oldIdentityLeaseGCPeriod := controlplaneapiserver.IdentityLeaseGCPeriod 119 oldIdentityLeaseRenewIntervalPeriod := controlplaneapiserver.IdentityLeaseRenewIntervalPeriod 120 defer func() { 121 // reset the default values for leases after this test 122 controlplaneapiserver.IdentityLeaseDurationSeconds = oldIdentityLeaseDurationSeconds 123 controlplaneapiserver.IdentityLeaseGCPeriod = oldIdentityLeaseGCPeriod 124 controlplaneapiserver.IdentityLeaseRenewIntervalPeriod = oldIdentityLeaseRenewIntervalPeriod 125 }() 126 127 // Shorten lease parameters so GC behavior can be exercised in integration tests 128 controlplaneapiserver.IdentityLeaseDurationSeconds = 1 129 controlplaneapiserver.IdentityLeaseGCPeriod = time.Second 130 controlplaneapiserver.IdentityLeaseRenewIntervalPeriod = time.Second 131 132 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true) 133 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 134 defer result.TearDownFn() 135 136 kubeclient, err := kubernetes.NewForConfig(result.ClientConfig) 137 if err != nil { 138 t.Fatalf("Unexpected error: %v", err) 139 } 140 expiredLease := newTestLease(time.Now().Add(-2*time.Hour), metav1.NamespaceSystem) 141 t.Run("expired apiserver lease should be garbage collected", 142 testLeaseGarbageCollected(t, kubeclient, expiredLease)) 143 144 freshLease := newTestLease(time.Now().Add(-2*time.Minute), metav1.NamespaceSystem) 145 t.Run("fresh apiserver lease should not be garbage collected", 146 testLeaseNotGarbageCollected(t, kubeclient, freshLease)) 147 148 expiredLease.Labels = nil 149 t.Run("expired non-identity lease should not be garbage collected", 150 testLeaseNotGarbageCollected(t, kubeclient, expiredLease)) 151 152 // identity leases (with apiserver.kubernetes.io/identity label) created in user namespaces should not be GC'ed 153 expiredNonKubeSystemLease := newTestLease(time.Now().Add(-2*time.Hour), metav1.NamespaceDefault) 154 t.Run("expired non-system identity lease should not be garbage collected", 155 testLeaseNotGarbageCollected(t, kubeclient, expiredNonKubeSystemLease)) 156 } 157 158 func testLeaseGarbageCollected(t *testing.T, client kubernetes.Interface, lease *coordinationv1.Lease) func(t *testing.T) { 159 return func(t *testing.T) { 160 ns := lease.Namespace 161 if _, err := client.CoordinationV1().Leases(ns).Create(context.TODO(), lease, metav1.CreateOptions{}); err != nil { 162 t.Fatalf("Unexpected error creating lease: %v", err) 163 } 164 if err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) { 165 _, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{}) 166 if err == nil { 167 return false, nil 168 } 169 if apierrors.IsNotFound(err) { 170 return true, nil 171 } 172 return false, err 173 }); err != nil { 174 t.Fatalf("Failed to see the expired lease garbage collected: %v", err) 175 } 176 177 } 178 } 179 180 func testLeaseNotGarbageCollected(t *testing.T, client kubernetes.Interface, lease *coordinationv1.Lease) func(t *testing.T) { 181 return func(t *testing.T) { 182 ns := lease.Namespace 183 if _, err := client.CoordinationV1().Leases(ns).Create(context.TODO(), lease, metav1.CreateOptions{}); err != nil { 184 t.Fatalf("Unexpected error creating lease: %v", err) 185 } 186 if err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) { 187 _, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{}) 188 if err != nil && apierrors.IsNotFound(err) { 189 return true, nil 190 } 191 return false, nil 192 }); err == nil { 193 t.Fatalf("Unexpected valid lease getting garbage collected") 194 } 195 if _, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{}); err != nil { 196 t.Fatalf("Failed to retrieve valid lease: %v", err) 197 } 198 if err := client.CoordinationV1().Leases(ns).Delete(context.TODO(), lease.Name, metav1.DeleteOptions{}); err != nil { 199 t.Fatalf("Failed to clean up valid lease: %v", err) 200 } 201 } 202 } 203 204 func newTestLease(acquireTime time.Time, namespace string) *coordinationv1.Lease { 205 return &coordinationv1.Lease{ 206 ObjectMeta: metav1.ObjectMeta{ 207 Name: testLeaseName, 208 Namespace: namespace, 209 Labels: map[string]string{ 210 controlplaneapiserver.IdentityLeaseComponentLabelKey: controlplane.KubeAPIServer, 211 }, 212 }, 213 Spec: coordinationv1.LeaseSpec{ 214 HolderIdentity: pointer.String(testLeaseName), 215 LeaseDurationSeconds: pointer.Int32(3600), 216 AcquireTime: &metav1.MicroTime{Time: acquireTime}, 217 RenewTime: &metav1.MicroTime{Time: acquireTime}, 218 }, 219 } 220 }