k8s.io/kubernetes@v1.29.3/test/e2e/framework/deployment/fixtures.go (about)

     1  /*
     2  Copyright 2017 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 deployment
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  
    24  	appsv1 "k8s.io/api/apps/v1"
    25  	v1 "k8s.io/api/core/v1"
    26  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/uuid"
    29  	clientset "k8s.io/client-go/kubernetes"
    30  	"k8s.io/kubernetes/test/e2e/framework"
    31  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    32  	testutils "k8s.io/kubernetes/test/utils"
    33  	admissionapi "k8s.io/pod-security-admission/api"
    34  )
    35  
    36  // UpdateDeploymentWithRetries updates the specified deployment with retries.
    37  func UpdateDeploymentWithRetries(c clientset.Interface, namespace, name string, applyUpdate testutils.UpdateDeploymentFunc) (*appsv1.Deployment, error) {
    38  	return testutils.UpdateDeploymentWithRetries(c, namespace, name, applyUpdate, framework.Logf, poll, pollShortTimeout)
    39  }
    40  
    41  // NewDeployment returns a deployment spec with the specified argument.
    42  func NewDeployment(deploymentName string, replicas int32, podLabels map[string]string, containerName, image string, strategyType appsv1.DeploymentStrategyType) *appsv1.Deployment {
    43  	zero := int64(0)
    44  	return &appsv1.Deployment{
    45  		ObjectMeta: metav1.ObjectMeta{
    46  			Name:   deploymentName,
    47  			Labels: podLabels,
    48  		},
    49  		Spec: appsv1.DeploymentSpec{
    50  			Replicas: &replicas,
    51  			Selector: &metav1.LabelSelector{MatchLabels: podLabels},
    52  			Strategy: appsv1.DeploymentStrategy{
    53  				Type: strategyType,
    54  			},
    55  			Template: v1.PodTemplateSpec{
    56  				ObjectMeta: metav1.ObjectMeta{
    57  					Labels: podLabels,
    58  				},
    59  				Spec: v1.PodSpec{
    60  					TerminationGracePeriodSeconds: &zero,
    61  					Containers: []v1.Container{
    62  						{
    63  							Name:            containerName,
    64  							Image:           image,
    65  							SecurityContext: &v1.SecurityContext{},
    66  						},
    67  					},
    68  				},
    69  			},
    70  		},
    71  	}
    72  }
    73  
    74  // CreateDeployment creates a deployment.
    75  func CreateDeployment(ctx context.Context, client clientset.Interface, replicas int32, podLabels map[string]string, nodeSelector map[string]string, namespace string, pvclaims []*v1.PersistentVolumeClaim, securityLevel admissionapi.Level, command string) (*appsv1.Deployment, error) {
    76  	deploymentSpec := testDeployment(replicas, podLabels, nodeSelector, namespace, pvclaims, securityLevel, command)
    77  	deployment, err := client.AppsV1().Deployments(namespace).Create(ctx, deploymentSpec, metav1.CreateOptions{})
    78  	if err != nil {
    79  		return nil, fmt.Errorf("deployment %q Create API error: %w", deploymentSpec.Name, err)
    80  	}
    81  	framework.Logf("Waiting deployment %q to complete", deploymentSpec.Name)
    82  	err = WaitForDeploymentComplete(client, deployment)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("deployment %q failed to complete: %w", deploymentSpec.Name, err)
    85  	}
    86  	return deployment, nil
    87  }
    88  
    89  // GetPodsForDeployment gets pods for the given deployment
    90  func GetPodsForDeployment(ctx context.Context, client clientset.Interface, deployment *appsv1.Deployment) (*v1.PodList, error) {
    91  	replicaSetSelector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	replicaSetListOptions := metav1.ListOptions{LabelSelector: replicaSetSelector.String()}
    97  	allReplicaSets, err := client.AppsV1().ReplicaSets(deployment.Namespace).List(ctx, replicaSetListOptions)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	ownedReplicaSets := make([]*appsv1.ReplicaSet, 0, len(allReplicaSets.Items))
   103  	for i := range allReplicaSets.Items {
   104  		if !metav1.IsControlledBy(&allReplicaSets.Items[i], deployment) {
   105  			continue
   106  		}
   107  
   108  		ownedReplicaSets = append(ownedReplicaSets, &allReplicaSets.Items[i])
   109  	}
   110  
   111  	// We ignore pod-template-hash because:
   112  	// 1. The hash result would be different upon podTemplateSpec API changes
   113  	//    (e.g. the addition of a new field will cause the hash code to change)
   114  	// 2. The deployment template won't have hash labels
   115  	podTemplatesEqualsIgnoringHash := func(template1, template2 *v1.PodTemplateSpec) bool {
   116  		t1Copy := template1.DeepCopy()
   117  		t2Copy := template2.DeepCopy()
   118  		// Remove hash labels from template.Labels before comparing
   119  		delete(t1Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
   120  		delete(t2Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
   121  		return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
   122  	}
   123  
   124  	var replicaSet *appsv1.ReplicaSet
   125  	// In rare cases, such as after cluster upgrades, Deployment may end up with
   126  	// having more than one new ReplicaSets that have the same template as its template,
   127  	// see https://github.com/kubernetes/kubernetes/issues/40415
   128  	// We deterministically choose the oldest new ReplicaSet.
   129  	sort.Sort(replicaSetsByCreationTimestamp(ownedReplicaSets))
   130  	for i, rs := range ownedReplicaSets {
   131  		if !podTemplatesEqualsIgnoringHash(&ownedReplicaSets[i].Spec.Template, &deployment.Spec.Template) {
   132  			continue
   133  		}
   134  
   135  		replicaSet = rs
   136  		break
   137  	}
   138  
   139  	if replicaSet == nil {
   140  		return nil, fmt.Errorf("expected a new replica set for deployment %q, found none", deployment.Name)
   141  	}
   142  
   143  	podSelector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	podListOptions := metav1.ListOptions{LabelSelector: podSelector.String()}
   148  	allPods, err := client.CoreV1().Pods(deployment.Namespace).List(ctx, podListOptions)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	replicaSetUID := replicaSet.UID
   154  	ownedPods := &v1.PodList{Items: make([]v1.Pod, 0, len(allPods.Items))}
   155  	for i, pod := range allPods.Items {
   156  		controllerRef := metav1.GetControllerOf(&allPods.Items[i])
   157  		if controllerRef != nil && controllerRef.UID == replicaSetUID {
   158  			ownedPods.Items = append(ownedPods.Items, pod)
   159  		}
   160  	}
   161  
   162  	return ownedPods, nil
   163  }
   164  
   165  // replicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker.
   166  type replicaSetsByCreationTimestamp []*appsv1.ReplicaSet
   167  
   168  func (o replicaSetsByCreationTimestamp) Len() int      { return len(o) }
   169  func (o replicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
   170  func (o replicaSetsByCreationTimestamp) Less(i, j int) bool {
   171  	if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
   172  		return o[i].Name < o[j].Name
   173  	}
   174  	return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
   175  }
   176  
   177  // testDeployment creates a deployment definition based on the namespace. The deployment references the PVC's
   178  // name.  A slice of BASH commands can be supplied as args to be run by the pod
   179  func testDeployment(replicas int32, podLabels map[string]string, nodeSelector map[string]string, namespace string, pvclaims []*v1.PersistentVolumeClaim, securityLevel admissionapi.Level, command string) *appsv1.Deployment {
   180  	if len(command) == 0 {
   181  		command = "trap exit TERM; while true; do sleep 1; done"
   182  	}
   183  	zero := int64(0)
   184  	deploymentName := "deployment-" + string(uuid.NewUUID())
   185  	deploymentSpec := &appsv1.Deployment{
   186  		ObjectMeta: metav1.ObjectMeta{
   187  			Name:      deploymentName,
   188  			Namespace: namespace,
   189  		},
   190  		Spec: appsv1.DeploymentSpec{
   191  			Replicas: &replicas,
   192  			Selector: &metav1.LabelSelector{
   193  				MatchLabels: podLabels,
   194  			},
   195  			Template: v1.PodTemplateSpec{
   196  				ObjectMeta: metav1.ObjectMeta{
   197  					Labels: podLabels,
   198  				},
   199  				Spec: v1.PodSpec{
   200  					TerminationGracePeriodSeconds: &zero,
   201  					Containers: []v1.Container{
   202  						{
   203  							Name:            "write-pod",
   204  							Image:           e2epod.GetDefaultTestImage(),
   205  							Command:         e2epod.GenerateScriptCmd(command),
   206  							SecurityContext: e2epod.GenerateContainerSecurityContext(securityLevel),
   207  						},
   208  					},
   209  					RestartPolicy: v1.RestartPolicyAlways,
   210  				},
   211  			},
   212  		},
   213  	}
   214  	var volumeMounts = make([]v1.VolumeMount, len(pvclaims))
   215  	var volumes = make([]v1.Volume, len(pvclaims))
   216  	for index, pvclaim := range pvclaims {
   217  		volumename := fmt.Sprintf("volume%v", index+1)
   218  		volumeMounts[index] = v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename}
   219  		volumes[index] = v1.Volume{Name: volumename, VolumeSource: v1.VolumeSource{PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: pvclaim.Name, ReadOnly: false}}}
   220  	}
   221  	deploymentSpec.Spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts
   222  	deploymentSpec.Spec.Template.Spec.Volumes = volumes
   223  	if nodeSelector != nil {
   224  		deploymentSpec.Spec.Template.Spec.NodeSelector = nodeSelector
   225  	}
   226  	return deploymentSpec
   227  }