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  }