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 }