k8s.io/kubernetes@v1.29.3/test/integration/statefulset/util.go (about)

     1  /*
     2  Copyright 2018 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 statefulset
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	v1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/labels"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	"k8s.io/apiserver/pkg/admission"
    33  	"k8s.io/client-go/informers"
    34  	clientset "k8s.io/client-go/kubernetes"
    35  	typedappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
    36  	typedv1 "k8s.io/client-go/kubernetes/typed/core/v1"
    37  	restclient "k8s.io/client-go/rest"
    38  	"k8s.io/client-go/util/retry"
    39  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    40  	api "k8s.io/kubernetes/pkg/apis/core"
    41  
    42  	//svc "k8s.io/kubernetes/pkg/api/v1/service"
    43  	"k8s.io/kubernetes/pkg/controller/statefulset"
    44  	"k8s.io/kubernetes/test/integration/framework"
    45  )
    46  
    47  const (
    48  	pollInterval = 100 * time.Millisecond
    49  	pollTimeout  = 60 * time.Second
    50  )
    51  
    52  func labelMap() map[string]string {
    53  	return map[string]string{"foo": "bar"}
    54  }
    55  
    56  // newService returns a service with a fake name for StatefulSet to be created soon
    57  func newHeadlessService(namespace string) *v1.Service {
    58  	return &v1.Service{
    59  		TypeMeta: metav1.TypeMeta{
    60  			Kind:       "Service",
    61  			APIVersion: "v1",
    62  		},
    63  		ObjectMeta: metav1.ObjectMeta{
    64  			Namespace: namespace,
    65  			Name:      "fake-service-name",
    66  		},
    67  		Spec: v1.ServiceSpec{
    68  			ClusterIP: "None",
    69  			Ports: []v1.ServicePort{
    70  				{Port: 80, Name: "http", Protocol: "TCP"},
    71  			},
    72  			Selector: labelMap(),
    73  		},
    74  	}
    75  }
    76  
    77  // newSTS returns a StatefulSet with a fake container image
    78  func newSTS(name, namespace string, replicas int) *appsv1.StatefulSet {
    79  	replicasCopy := int32(replicas)
    80  	return &appsv1.StatefulSet{
    81  		TypeMeta: metav1.TypeMeta{
    82  			Kind:       "StatefulSet",
    83  			APIVersion: "apps/v1",
    84  		},
    85  		ObjectMeta: metav1.ObjectMeta{
    86  			Namespace: namespace,
    87  			Name:      name,
    88  		},
    89  		Spec: appsv1.StatefulSetSpec{
    90  			PodManagementPolicy: appsv1.ParallelPodManagement,
    91  			Replicas:            &replicasCopy,
    92  			Selector: &metav1.LabelSelector{
    93  				MatchLabels: labelMap(),
    94  			},
    95  			Template: v1.PodTemplateSpec{
    96  				ObjectMeta: metav1.ObjectMeta{
    97  					Labels: labelMap(),
    98  				},
    99  				Spec: v1.PodSpec{
   100  					Containers: []v1.Container{
   101  						{
   102  							Name:  "fake-name",
   103  							Image: "fakeimage",
   104  							VolumeMounts: []v1.VolumeMount{
   105  								{Name: "datadir", MountPath: "/data/"},
   106  								{Name: "home", MountPath: "/home"},
   107  							},
   108  						},
   109  					},
   110  					Volumes: []v1.Volume{
   111  						{
   112  							Name: "datadir",
   113  							VolumeSource: v1.VolumeSource{
   114  								PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
   115  									ClaimName: "fake-pvc-name",
   116  								},
   117  							},
   118  						},
   119  						{
   120  							Name: "home",
   121  							VolumeSource: v1.VolumeSource{
   122  								HostPath: &v1.HostPathVolumeSource{
   123  									Path: fmt.Sprintf("/tmp/%v", "home"),
   124  								},
   125  							},
   126  						},
   127  					},
   128  				},
   129  			},
   130  			ServiceName: "fake-service-name",
   131  			UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
   132  				Type: appsv1.RollingUpdateStatefulSetStrategyType,
   133  			},
   134  			VolumeClaimTemplates: []v1.PersistentVolumeClaim{
   135  				// for volume mount "datadir"
   136  				newStatefulSetPVC("fake-pvc-name"),
   137  			},
   138  		},
   139  	}
   140  }
   141  
   142  func newStatefulSetPVC(name string) v1.PersistentVolumeClaim {
   143  	return v1.PersistentVolumeClaim{
   144  		ObjectMeta: metav1.ObjectMeta{
   145  			Name: name,
   146  			Annotations: map[string]string{
   147  				"volume.alpha.kubernetes.io/storage-class": "anything",
   148  			},
   149  		},
   150  		Spec: v1.PersistentVolumeClaimSpec{
   151  			AccessModes: []v1.PersistentVolumeAccessMode{
   152  				v1.ReadWriteOnce,
   153  			},
   154  			Resources: v1.VolumeResourceRequirements{
   155  				Requests: v1.ResourceList{
   156  					v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI),
   157  				},
   158  			},
   159  		},
   160  	}
   161  }
   162  
   163  // scSetup sets up necessities for Statefulset integration test, including control plane, apiserver, informers, and clientset
   164  func scSetup(ctx context.Context, t *testing.T) (kubeapiservertesting.TearDownFunc, *statefulset.StatefulSetController, informers.SharedInformerFactory, clientset.Interface) {
   165  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   166  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   167  
   168  	config := restclient.CopyConfig(server.ClientConfig)
   169  	clientSet, err := clientset.NewForConfig(config)
   170  	if err != nil {
   171  		t.Fatalf("error in create clientset: %v", err)
   172  	}
   173  	resyncPeriod := 12 * time.Hour
   174  	informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "statefulset-informers")), resyncPeriod)
   175  
   176  	sc := statefulset.NewStatefulSetController(
   177  		ctx,
   178  		informers.Core().V1().Pods(),
   179  		informers.Apps().V1().StatefulSets(),
   180  		informers.Core().V1().PersistentVolumeClaims(),
   181  		informers.Apps().V1().ControllerRevisions(),
   182  		clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "statefulset-controller")),
   183  	)
   184  
   185  	return server.TearDownFn, sc, informers, clientSet
   186  }
   187  
   188  // Run STS controller and informers
   189  func runControllerAndInformers(sc *statefulset.StatefulSetController, informers informers.SharedInformerFactory) context.CancelFunc {
   190  	ctx, cancel := context.WithCancel(context.Background())
   191  	informers.Start(ctx.Done())
   192  	go sc.Run(ctx, 5)
   193  	return cancel
   194  }
   195  
   196  func createHeadlessService(t *testing.T, clientSet clientset.Interface, headlessService *v1.Service) {
   197  	_, err := clientSet.CoreV1().Services(headlessService.Namespace).Create(context.TODO(), headlessService, metav1.CreateOptions{})
   198  	if err != nil {
   199  		t.Fatalf("failed creating headless service: %v", err)
   200  	}
   201  }
   202  
   203  func createSTSs(t *testing.T, clientSet clientset.Interface, stss []*appsv1.StatefulSet) []*appsv1.StatefulSet {
   204  	var createdSTSs []*appsv1.StatefulSet
   205  	for _, sts := range stss {
   206  		createdSTS, err := clientSet.AppsV1().StatefulSets(sts.Namespace).Create(context.TODO(), sts, metav1.CreateOptions{})
   207  		if err != nil {
   208  			t.Fatalf("failed to create sts %s: %v", sts.Name, err)
   209  		}
   210  		createdSTSs = append(createdSTSs, createdSTS)
   211  	}
   212  	return createdSTSs
   213  }
   214  
   215  func createPods(t *testing.T, clientSet clientset.Interface, pods []*v1.Pod) []*v1.Pod {
   216  	var createdPods []*v1.Pod
   217  	for _, pod := range pods {
   218  		createdPod, err := clientSet.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
   219  		if err != nil {
   220  			t.Fatalf("failed to create pod %s: %v", pod.Name, err)
   221  		}
   222  		createdPods = append(createdPods, createdPod)
   223  	}
   224  
   225  	return createdPods
   226  }
   227  
   228  func createSTSsPods(t *testing.T, clientSet clientset.Interface, stss []*appsv1.StatefulSet, pods []*v1.Pod) ([]*appsv1.StatefulSet, []*v1.Pod) {
   229  	return createSTSs(t, clientSet, stss), createPods(t, clientSet, pods)
   230  }
   231  
   232  // Verify .Status.Replicas is equal to .Spec.Replicas
   233  func waitSTSStable(t *testing.T, clientSet clientset.Interface, sts *appsv1.StatefulSet) {
   234  	stsClient := clientSet.AppsV1().StatefulSets(sts.Namespace)
   235  	desiredGeneration := sts.Generation
   236  	if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
   237  		newSTS, err := stsClient.Get(context.TODO(), sts.Name, metav1.GetOptions{})
   238  		if err != nil {
   239  			return false, err
   240  		}
   241  		return newSTS.Status.Replicas == *newSTS.Spec.Replicas && newSTS.Status.ObservedGeneration >= desiredGeneration, nil
   242  	}); err != nil {
   243  		t.Fatalf("failed to verify .Status.Replicas is equal to .Spec.Replicas for sts %s: %v", sts.Name, err)
   244  	}
   245  }
   246  
   247  func updatePod(t *testing.T, podClient typedv1.PodInterface, podName string, updateFunc func(*v1.Pod)) *v1.Pod {
   248  	var pod *v1.Pod
   249  	if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   250  		newPod, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{})
   251  		if err != nil {
   252  			return err
   253  		}
   254  		updateFunc(newPod)
   255  		pod, err = podClient.Update(context.TODO(), newPod, metav1.UpdateOptions{})
   256  		return err
   257  	}); err != nil {
   258  		t.Fatalf("failed to update pod %s: %v", podName, err)
   259  	}
   260  	return pod
   261  }
   262  
   263  func updatePodStatus(t *testing.T, podClient typedv1.PodInterface, podName string, updateStatusFunc func(*v1.Pod)) *v1.Pod {
   264  	var pod *v1.Pod
   265  	if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   266  		newPod, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{})
   267  		if err != nil {
   268  			return err
   269  		}
   270  		updateStatusFunc(newPod)
   271  		pod, err = podClient.UpdateStatus(context.TODO(), newPod, metav1.UpdateOptions{})
   272  		return err
   273  	}); err != nil {
   274  		t.Fatalf("failed to update status of pod %s: %v", podName, err)
   275  	}
   276  	return pod
   277  }
   278  
   279  func getPods(t *testing.T, podClient typedv1.PodInterface, labelMap map[string]string) *v1.PodList {
   280  	podSelector := labels.Set(labelMap).AsSelector()
   281  	options := metav1.ListOptions{LabelSelector: podSelector.String()}
   282  	pods, err := podClient.List(context.TODO(), options)
   283  	if err != nil {
   284  		t.Fatalf("failed obtaining a list of pods that match the pod labels %v: %v", labelMap, err)
   285  	}
   286  	if pods == nil {
   287  		t.Fatalf("obtained a nil list of pods")
   288  	}
   289  	return pods
   290  }
   291  
   292  func getStatefulSetPVCs(t *testing.T, pvcClient typedv1.PersistentVolumeClaimInterface, sts *appsv1.StatefulSet) []*v1.PersistentVolumeClaim {
   293  	pvcs := []*v1.PersistentVolumeClaim{}
   294  	for i := int32(0); i < *sts.Spec.Replicas; i++ {
   295  		pvcName := fmt.Sprintf("%s-%s-%d", sts.Spec.VolumeClaimTemplates[0].Name, sts.Name, i)
   296  		pvc, err := pvcClient.Get(context.TODO(), pvcName, metav1.GetOptions{})
   297  		if err != nil {
   298  			t.Fatalf("failed to get PVC %s: %v", pvcName, err)
   299  		}
   300  		pvcs = append(pvcs, pvc)
   301  	}
   302  	return pvcs
   303  }
   304  
   305  func verifyOwnerRef(t *testing.T, pvc *v1.PersistentVolumeClaim, kind string, expected bool) {
   306  	found := false
   307  	for _, ref := range pvc.GetOwnerReferences() {
   308  		if ref.Kind == kind {
   309  			if expected {
   310  				found = true
   311  			} else {
   312  				t.Fatalf("Found %s ref but expected none for PVC %s", kind, pvc.Name)
   313  			}
   314  		}
   315  	}
   316  	if expected && !found {
   317  		t.Fatalf("Expected %s ref but found none for PVC %s", kind, pvc.Name)
   318  	}
   319  }
   320  
   321  func updateSTS(t *testing.T, stsClient typedappsv1.StatefulSetInterface, stsName string, updateFunc func(*appsv1.StatefulSet)) *appsv1.StatefulSet {
   322  	var sts *appsv1.StatefulSet
   323  	if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   324  		newSTS, err := stsClient.Get(context.TODO(), stsName, metav1.GetOptions{})
   325  		if err != nil {
   326  			return err
   327  		}
   328  		updateFunc(newSTS)
   329  		sts, err = stsClient.Update(context.TODO(), newSTS, metav1.UpdateOptions{})
   330  		return err
   331  	}); err != nil {
   332  		t.Fatalf("failed to update sts %s: %v", stsName, err)
   333  	}
   334  	return sts
   335  }
   336  
   337  // Update .Spec.Replicas to replicas and verify .Status.Replicas is changed accordingly
   338  func scaleSTS(t *testing.T, c clientset.Interface, sts *appsv1.StatefulSet, replicas int32) {
   339  	stsClient := c.AppsV1().StatefulSets(sts.Namespace)
   340  	if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   341  		newSTS, err := stsClient.Get(context.TODO(), sts.Name, metav1.GetOptions{})
   342  		if err != nil {
   343  			return err
   344  		}
   345  		*newSTS.Spec.Replicas = replicas
   346  		sts, err = stsClient.Update(context.TODO(), newSTS, metav1.UpdateOptions{})
   347  		return err
   348  	}); err != nil {
   349  		t.Fatalf("failed to update .Spec.Replicas to %d for sts %s: %v", replicas, sts.Name, err)
   350  	}
   351  	waitSTSStable(t, c, sts)
   352  }
   353  
   354  var _ admission.ValidationInterface = &fakePodFailAdmission{}
   355  
   356  type fakePodFailAdmission struct {
   357  	lock             sync.Mutex
   358  	limitedPodNumber int
   359  	succeedPodsCount int
   360  }
   361  
   362  func (f *fakePodFailAdmission) Handles(operation admission.Operation) bool {
   363  	return operation == admission.Create
   364  }
   365  
   366  func (f *fakePodFailAdmission) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) (err error) {
   367  	if attr.GetKind().GroupKind() != api.Kind("Pod") {
   368  		return nil
   369  	}
   370  
   371  	f.lock.Lock()
   372  	defer f.lock.Unlock()
   373  
   374  	if f.succeedPodsCount >= f.limitedPodNumber {
   375  		return fmt.Errorf("fakePodFailAdmission error")
   376  	}
   377  	f.succeedPodsCount++
   378  	return nil
   379  }