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  }