k8s.io/kubernetes@v1.29.3/test/e2e/framework/pod/create.go (about) 1 /* 2 Copyright 2019 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 pod 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/util/uuid" 27 clientset "k8s.io/client-go/kubernetes" 28 imageutils "k8s.io/kubernetes/test/utils/image" 29 admissionapi "k8s.io/pod-security-admission/api" 30 ) 31 32 const ( 33 VolumeMountPathTemplate = "/mnt/volume%d" 34 VolumeMountPath1 = "/mnt/volume1" 35 ) 36 37 // Config is a struct containing all arguments for creating a pod. 38 // SELinux testing requires to pass HostIPC and HostPID as boolean arguments. 39 type Config struct { 40 NS string 41 PVCs []*v1.PersistentVolumeClaim 42 PVCsReadOnly bool 43 InlineVolumeSources []*v1.VolumeSource 44 SecurityLevel admissionapi.Level 45 Command string 46 HostIPC bool 47 HostPID bool 48 SeLinuxLabel *v1.SELinuxOptions 49 FsGroup *int64 50 NodeSelection NodeSelection 51 ImageID imageutils.ImageID 52 PodFSGroupChangePolicy *v1.PodFSGroupChangePolicy 53 } 54 55 // CreateUnschedulablePod with given claims based on node selector 56 func CreateUnschedulablePod(ctx context.Context, client clientset.Interface, namespace string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim, securityLevel admissionapi.Level, command string) (*v1.Pod, error) { 57 pod := MakePod(namespace, nodeSelector, pvclaims, securityLevel, command) 58 pod, err := client.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{}) 59 if err != nil { 60 return nil, fmt.Errorf("pod Create API error: %w", err) 61 } 62 // Waiting for pod to become Unschedulable 63 err = WaitForPodNameUnschedulableInNamespace(ctx, client, pod.Name, namespace) 64 if err != nil { 65 return pod, fmt.Errorf("pod %q is not Unschedulable: %w", pod.Name, err) 66 } 67 // get fresh pod info 68 pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 69 if err != nil { 70 return pod, fmt.Errorf("pod Get API error: %w", err) 71 } 72 return pod, nil 73 } 74 75 // CreateClientPod defines and creates a pod with a mounted PV. Pod runs infinite loop until killed. 76 func CreateClientPod(ctx context.Context, c clientset.Interface, ns string, pvc *v1.PersistentVolumeClaim) (*v1.Pod, error) { 77 return CreatePod(ctx, c, ns, nil, []*v1.PersistentVolumeClaim{pvc}, admissionapi.LevelPrivileged, "") 78 } 79 80 // CreatePod with given claims based on node selector 81 func CreatePod(ctx context.Context, client clientset.Interface, namespace string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim, securityLevel admissionapi.Level, command string) (*v1.Pod, error) { 82 pod := MakePod(namespace, nodeSelector, pvclaims, securityLevel, command) 83 pod, err := client.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{}) 84 if err != nil { 85 return nil, fmt.Errorf("pod Create API error: %w", err) 86 } 87 // Waiting for pod to be running 88 err = WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace) 89 if err != nil { 90 return pod, fmt.Errorf("pod %q is not Running: %w", pod.Name, err) 91 } 92 // get fresh pod info 93 pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 94 if err != nil { 95 return pod, fmt.Errorf("pod Get API error: %w", err) 96 } 97 return pod, nil 98 } 99 100 // CreateSecPod creates security pod with given claims 101 func CreateSecPod(ctx context.Context, client clientset.Interface, podConfig *Config, timeout time.Duration) (*v1.Pod, error) { 102 return CreateSecPodWithNodeSelection(ctx, client, podConfig, timeout) 103 } 104 105 // CreateSecPodWithNodeSelection creates security pod with given claims 106 func CreateSecPodWithNodeSelection(ctx context.Context, client clientset.Interface, podConfig *Config, timeout time.Duration) (*v1.Pod, error) { 107 pod, err := MakeSecPod(podConfig) 108 if err != nil { 109 return nil, fmt.Errorf("Unable to create pod: %w", err) 110 } 111 112 pod, err = client.CoreV1().Pods(podConfig.NS).Create(ctx, pod, metav1.CreateOptions{}) 113 if err != nil { 114 return nil, fmt.Errorf("pod Create API error: %w", err) 115 } 116 117 // Waiting for pod to be running 118 err = WaitTimeoutForPodRunningInNamespace(ctx, client, pod.Name, podConfig.NS, timeout) 119 if err != nil { 120 return pod, fmt.Errorf("pod %q is not Running: %w", pod.Name, err) 121 } 122 // get fresh pod info 123 pod, err = client.CoreV1().Pods(podConfig.NS).Get(ctx, pod.Name, metav1.GetOptions{}) 124 if err != nil { 125 return pod, fmt.Errorf("pod Get API error: %w", err) 126 } 127 return pod, nil 128 } 129 130 // MakePod returns a pod definition based on the namespace. The pod references the PVC's 131 // name. A slice of BASH commands can be supplied as args to be run by the pod 132 func MakePod(ns string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim, securityLevel admissionapi.Level, command string) *v1.Pod { 133 if len(command) == 0 { 134 command = "trap exit TERM; while true; do sleep 1; done" 135 } 136 podSpec := &v1.Pod{ 137 TypeMeta: metav1.TypeMeta{ 138 Kind: "Pod", 139 APIVersion: "v1", 140 }, 141 ObjectMeta: metav1.ObjectMeta{ 142 GenerateName: "pvc-tester-", 143 Namespace: ns, 144 }, 145 Spec: v1.PodSpec{ 146 Containers: []v1.Container{ 147 { 148 Name: "write-pod", 149 Image: GetDefaultTestImage(), 150 Command: GenerateScriptCmd(command), 151 SecurityContext: GenerateContainerSecurityContext(securityLevel), 152 }, 153 }, 154 RestartPolicy: v1.RestartPolicyOnFailure, 155 }, 156 } 157 setVolumes(&podSpec.Spec, pvclaims, nil /*inline volume sources*/, false /*PVCs readonly*/) 158 if nodeSelector != nil { 159 podSpec.Spec.NodeSelector = nodeSelector 160 } 161 if securityLevel == admissionapi.LevelRestricted { 162 podSpec = MustMixinRestrictedPodSecurity(podSpec) 163 } 164 165 return podSpec 166 } 167 168 // MakeSecPod returns a pod definition based on the namespace. The pod references the PVC's 169 // name. A slice of BASH commands can be supplied as args to be run by the pod. 170 func MakeSecPod(podConfig *Config) (*v1.Pod, error) { 171 if podConfig.NS == "" { 172 return nil, fmt.Errorf("Cannot create pod with empty namespace") 173 } 174 if len(podConfig.Command) == 0 { 175 podConfig.Command = "trap exit TERM; while true; do sleep 1; done" 176 } 177 178 podName := "pod-" + string(uuid.NewUUID()) 179 if podConfig.FsGroup == nil && !NodeOSDistroIs("windows") { 180 podConfig.FsGroup = func(i int64) *int64 { 181 return &i 182 }(1000) 183 } 184 podSpec := &v1.Pod{ 185 TypeMeta: metav1.TypeMeta{ 186 Kind: "Pod", 187 APIVersion: "v1", 188 }, 189 ObjectMeta: metav1.ObjectMeta{ 190 Name: podName, 191 Namespace: podConfig.NS, 192 }, 193 Spec: *MakePodSpec(podConfig), 194 } 195 return podSpec, nil 196 } 197 198 // MakePodSpec returns a PodSpec definition 199 func MakePodSpec(podConfig *Config) *v1.PodSpec { 200 image := imageutils.BusyBox 201 if podConfig.ImageID != imageutils.None { 202 image = podConfig.ImageID 203 } 204 securityLevel := podConfig.SecurityLevel 205 if securityLevel == "" { 206 securityLevel = admissionapi.LevelBaseline 207 } 208 podSpec := &v1.PodSpec{ 209 HostIPC: podConfig.HostIPC, 210 HostPID: podConfig.HostPID, 211 SecurityContext: GeneratePodSecurityContext(podConfig.FsGroup, podConfig.SeLinuxLabel), 212 Containers: []v1.Container{ 213 { 214 Name: "write-pod", 215 Image: GetTestImage(image), 216 Command: GenerateScriptCmd(podConfig.Command), 217 SecurityContext: GenerateContainerSecurityContext(securityLevel), 218 }, 219 }, 220 RestartPolicy: v1.RestartPolicyOnFailure, 221 } 222 223 if podConfig.PodFSGroupChangePolicy != nil { 224 podSpec.SecurityContext.FSGroupChangePolicy = podConfig.PodFSGroupChangePolicy 225 } 226 227 setVolumes(podSpec, podConfig.PVCs, podConfig.InlineVolumeSources, podConfig.PVCsReadOnly) 228 SetNodeSelection(podSpec, podConfig.NodeSelection) 229 return podSpec 230 } 231 232 func setVolumes(podSpec *v1.PodSpec, pvcs []*v1.PersistentVolumeClaim, inlineVolumeSources []*v1.VolumeSource, pvcsReadOnly bool) { 233 var volumeMounts = make([]v1.VolumeMount, 0) 234 var volumeDevices = make([]v1.VolumeDevice, 0) 235 var volumes = make([]v1.Volume, len(pvcs)+len(inlineVolumeSources)) 236 volumeIndex := 0 237 for _, pvclaim := range pvcs { 238 volumename := fmt.Sprintf("volume%v", volumeIndex+1) 239 volumeMountPath := fmt.Sprintf(VolumeMountPathTemplate, volumeIndex+1) 240 if pvclaim.Spec.VolumeMode != nil && *pvclaim.Spec.VolumeMode == v1.PersistentVolumeBlock { 241 volumeDevices = append(volumeDevices, v1.VolumeDevice{Name: volumename, DevicePath: volumeMountPath}) 242 } else { 243 volumeMounts = append(volumeMounts, v1.VolumeMount{Name: volumename, MountPath: volumeMountPath}) 244 } 245 volumes[volumeIndex] = v1.Volume{ 246 Name: volumename, 247 VolumeSource: v1.VolumeSource{ 248 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 249 ClaimName: pvclaim.Name, 250 ReadOnly: pvcsReadOnly, 251 }, 252 }, 253 } 254 volumeIndex++ 255 } 256 for _, src := range inlineVolumeSources { 257 volumename := fmt.Sprintf("volume%v", volumeIndex+1) 258 volumeMountPath := fmt.Sprintf(VolumeMountPathTemplate, volumeIndex+1) 259 // In-line volumes can be only filesystem, not block. 260 volumeMounts = append(volumeMounts, v1.VolumeMount{Name: volumename, MountPath: volumeMountPath}) 261 volumes[volumeIndex] = v1.Volume{Name: volumename, VolumeSource: *src} 262 volumeIndex++ 263 } 264 podSpec.Containers[0].VolumeMounts = volumeMounts 265 podSpec.Containers[0].VolumeDevices = volumeDevices 266 podSpec.Volumes = volumes 267 }