mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-30 21:30:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			370 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // +build linux
 | |
| 
 | |
| /*
 | |
| Copyright 2018 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package kuberuntime
 | |
| 
 | |
| import (
 | |
| 	"reflect"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/google/go-cmp/cmp"
 | |
| 	cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	v1 "k8s.io/api/core/v1"
 | |
| 	"k8s.io/apimachinery/pkg/api/resource"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | |
| 	featuregatetesting "k8s.io/component-base/featuregate/testing"
 | |
| 	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
 | |
| 	"k8s.io/kubernetes/pkg/features"
 | |
| 	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | |
| )
 | |
| 
 | |
| func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerIndex int) *runtimeapi.ContainerConfig {
 | |
| 	container := &pod.Spec.Containers[containerIndex]
 | |
| 	podIP := ""
 | |
| 	restartCount := 0
 | |
| 	opts, _, _ := m.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP, []string{podIP})
 | |
| 	containerLogsPath := buildContainerLogsPath(container.Name, restartCount)
 | |
| 	restartCountUint32 := uint32(restartCount)
 | |
| 	envs := make([]*runtimeapi.KeyValue, len(opts.Envs))
 | |
| 
 | |
| 	expectedConfig := &runtimeapi.ContainerConfig{
 | |
| 		Metadata: &runtimeapi.ContainerMetadata{
 | |
| 			Name:    container.Name,
 | |
| 			Attempt: restartCountUint32,
 | |
| 		},
 | |
| 		Image:       &runtimeapi.ImageSpec{Image: container.Image},
 | |
| 		Command:     container.Command,
 | |
| 		Args:        []string(nil),
 | |
| 		WorkingDir:  container.WorkingDir,
 | |
| 		Labels:      newContainerLabels(container, pod),
 | |
| 		Annotations: newContainerAnnotations(container, pod, restartCount, opts),
 | |
| 		Devices:     makeDevices(opts),
 | |
| 		Mounts:      m.makeMounts(opts, container),
 | |
| 		LogPath:     containerLogsPath,
 | |
| 		Stdin:       container.Stdin,
 | |
| 		StdinOnce:   container.StdinOnce,
 | |
| 		Tty:         container.TTY,
 | |
| 		Linux:       m.generateLinuxContainerConfig(container, pod, new(int64), "", nil),
 | |
| 		Envs:        envs,
 | |
| 	}
 | |
| 	return expectedConfig
 | |
| }
 | |
| 
 | |
| func TestGenerateContainerConfig(t *testing.T) {
 | |
| 	_, imageService, m, err := createTestRuntimeManager()
 | |
| 	assert.NoError(t, err)
 | |
| 
 | |
| 	runAsUser := int64(1000)
 | |
| 	runAsGroup := int64(2000)
 | |
| 	pod := &v1.Pod{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			UID:       "12345678",
 | |
| 			Name:      "bar",
 | |
| 			Namespace: "new",
 | |
| 		},
 | |
| 		Spec: v1.PodSpec{
 | |
| 			Containers: []v1.Container{
 | |
| 				{
 | |
| 					Name:            "foo",
 | |
| 					Image:           "busybox",
 | |
| 					ImagePullPolicy: v1.PullIfNotPresent,
 | |
| 					Command:         []string{"testCommand"},
 | |
| 					WorkingDir:      "testWorkingDir",
 | |
| 					SecurityContext: &v1.SecurityContext{
 | |
| 						RunAsUser:  &runAsUser,
 | |
| 						RunAsGroup: &runAsGroup,
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	expectedConfig := makeExpectedConfig(m, pod, 0)
 | |
| 	containerConfig, _, err := m.generateContainerConfig(&pod.Spec.Containers[0], pod, 0, "", pod.Spec.Containers[0].Image, []string{}, nil)
 | |
| 	assert.NoError(t, err)
 | |
| 	assert.Equal(t, expectedConfig, containerConfig, "generate container config for kubelet runtime v1.")
 | |
| 	assert.Equal(t, runAsUser, containerConfig.GetLinux().GetSecurityContext().GetRunAsUser().GetValue(), "RunAsUser should be set")
 | |
| 	assert.Equal(t, runAsGroup, containerConfig.GetLinux().GetSecurityContext().GetRunAsGroup().GetValue(), "RunAsGroup should be set")
 | |
| 
 | |
| 	runAsRoot := int64(0)
 | |
| 	runAsNonRootTrue := true
 | |
| 	podWithContainerSecurityContext := &v1.Pod{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			UID:       "12345678",
 | |
| 			Name:      "bar",
 | |
| 			Namespace: "new",
 | |
| 		},
 | |
| 		Spec: v1.PodSpec{
 | |
| 			Containers: []v1.Container{
 | |
| 				{
 | |
| 					Name:            "foo",
 | |
| 					Image:           "busybox",
 | |
| 					ImagePullPolicy: v1.PullIfNotPresent,
 | |
| 					Command:         []string{"testCommand"},
 | |
| 					WorkingDir:      "testWorkingDir",
 | |
| 					SecurityContext: &v1.SecurityContext{
 | |
| 						RunAsNonRoot: &runAsNonRootTrue,
 | |
| 						RunAsUser:    &runAsRoot,
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	_, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil)
 | |
| 	assert.Error(t, err)
 | |
| 
 | |
| 	imageID, _ := imageService.PullImage(&runtimeapi.ImageSpec{Image: "busybox"}, nil, nil)
 | |
| 	image, _ := imageService.ImageStatus(&runtimeapi.ImageSpec{Image: imageID})
 | |
| 
 | |
| 	image.Uid = nil
 | |
| 	image.Username = "test"
 | |
| 
 | |
| 	podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsUser = nil
 | |
| 	podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsNonRoot = &runAsNonRootTrue
 | |
| 
 | |
| 	_, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil)
 | |
| 	assert.Error(t, err, "RunAsNonRoot should fail for non-numeric username")
 | |
| }
 | |
| 
 | |
| func TestGetHugepageLimitsFromResources(t *testing.T) {
 | |
| 	var baseHugepage []*runtimeapi.HugepageLimit
 | |
| 
 | |
| 	// For each page size, limit to 0.
 | |
| 	for _, pageSize := range cgroupfs.HugePageSizes {
 | |
| 		baseHugepage = append(baseHugepage, &runtimeapi.HugepageLimit{
 | |
| 			PageSize: pageSize,
 | |
| 			Limit:    uint64(0),
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		name      string
 | |
| 		resources v1.ResourceRequirements
 | |
| 		expected  []*runtimeapi.HugepageLimit
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "Success2MB",
 | |
| 			resources: v1.ResourceRequirements{
 | |
| 				Limits: v1.ResourceList{
 | |
| 					"hugepages-2Mi": resource.MustParse("2Mi"),
 | |
| 				},
 | |
| 			},
 | |
| 			expected: []*runtimeapi.HugepageLimit{
 | |
| 				{
 | |
| 					PageSize: "2MB",
 | |
| 					Limit:    2097152,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Success1GB",
 | |
| 			resources: v1.ResourceRequirements{
 | |
| 				Limits: v1.ResourceList{
 | |
| 					"hugepages-1Gi": resource.MustParse("2Gi"),
 | |
| 				},
 | |
| 			},
 | |
| 			expected: []*runtimeapi.HugepageLimit{
 | |
| 				{
 | |
| 					PageSize: "1GB",
 | |
| 					Limit:    2147483648,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Skip2MB",
 | |
| 			resources: v1.ResourceRequirements{
 | |
| 				Limits: v1.ResourceList{
 | |
| 					"hugepages-2MB": resource.MustParse("2Mi"),
 | |
| 				},
 | |
| 			},
 | |
| 			expected: []*runtimeapi.HugepageLimit{
 | |
| 				{
 | |
| 					PageSize: "2MB",
 | |
| 					Limit:    0,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Skip1GB",
 | |
| 			resources: v1.ResourceRequirements{
 | |
| 				Limits: v1.ResourceList{
 | |
| 					"hugepages-1GB": resource.MustParse("2Gi"),
 | |
| 				},
 | |
| 			},
 | |
| 			expected: []*runtimeapi.HugepageLimit{
 | |
| 				{
 | |
| 					PageSize: "1GB",
 | |
| 					Limit:    0,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Success2MBand1GB",
 | |
| 			resources: v1.ResourceRequirements{
 | |
| 				Limits: v1.ResourceList{
 | |
| 					v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"),
 | |
| 					"hugepages-2Mi":                 resource.MustParse("2Mi"),
 | |
| 					"hugepages-1Gi":                 resource.MustParse("2Gi"),
 | |
| 				},
 | |
| 			},
 | |
| 			expected: []*runtimeapi.HugepageLimit{
 | |
| 				{
 | |
| 					PageSize: "2MB",
 | |
| 					Limit:    2097152,
 | |
| 				},
 | |
| 				{
 | |
| 					PageSize: "1GB",
 | |
| 					Limit:    2147483648,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "Skip2MBand1GB",
 | |
| 			resources: v1.ResourceRequirements{
 | |
| 				Limits: v1.ResourceList{
 | |
| 					v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"),
 | |
| 					"hugepages-2MB":                 resource.MustParse("2Mi"),
 | |
| 					"hugepages-1GB":                 resource.MustParse("2Gi"),
 | |
| 				},
 | |
| 			},
 | |
| 			expected: []*runtimeapi.HugepageLimit{
 | |
| 				{
 | |
| 					PageSize: "2MB",
 | |
| 					Limit:    0,
 | |
| 				},
 | |
| 				{
 | |
| 					PageSize: "1GB",
 | |
| 					Limit:    0,
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		// Validate if machine supports hugepage size that used in test case.
 | |
| 		machineHugepageSupport := true
 | |
| 		for _, hugepageLimit := range test.expected {
 | |
| 			hugepageSupport := false
 | |
| 			for _, pageSize := range cgroupfs.HugePageSizes {
 | |
| 				if pageSize == hugepageLimit.PageSize {
 | |
| 					hugepageSupport = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if !hugepageSupport {
 | |
| 				machineHugepageSupport = false
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Case of machine can't support hugepage size
 | |
| 		if !machineHugepageSupport {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		expectedHugepages := baseHugepage
 | |
| 		for _, hugepage := range test.expected {
 | |
| 			for _, expectedHugepage := range expectedHugepages {
 | |
| 				if expectedHugepage.PageSize == hugepage.PageSize {
 | |
| 					expectedHugepage.Limit = hugepage.Limit
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		results := GetHugepageLimitsFromResources(test.resources)
 | |
| 		if !reflect.DeepEqual(expectedHugepages, results) {
 | |
| 			t.Errorf("%s test failed. Expected %v but got %v", test.name, expectedHugepages, results)
 | |
| 		}
 | |
| 
 | |
| 		for _, hugepage := range baseHugepage {
 | |
| 			hugepage.Limit = uint64(0)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGenerateLinuxContainerConfigNamespaces(t *testing.T) {
 | |
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
 | |
| 	_, _, m, err := createTestRuntimeManager()
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("error creating test RuntimeManager: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range []struct {
 | |
| 		name   string
 | |
| 		pod    *v1.Pod
 | |
| 		target *kubecontainer.ContainerID
 | |
| 		want   *runtimeapi.NamespaceOption
 | |
| 	}{
 | |
| 		{
 | |
| 			"Default namespaces",
 | |
| 			&v1.Pod{
 | |
| 				Spec: v1.PodSpec{
 | |
| 					Containers: []v1.Container{
 | |
| 						{Name: "test"},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			nil,
 | |
| 			&runtimeapi.NamespaceOption{
 | |
| 				Pid: runtimeapi.NamespaceMode_CONTAINER,
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			"PID Namespace POD",
 | |
| 			&v1.Pod{
 | |
| 				Spec: v1.PodSpec{
 | |
| 					Containers: []v1.Container{
 | |
| 						{Name: "test"},
 | |
| 					},
 | |
| 					ShareProcessNamespace: &[]bool{true}[0],
 | |
| 				},
 | |
| 			},
 | |
| 			nil,
 | |
| 			&runtimeapi.NamespaceOption{
 | |
| 				Pid: runtimeapi.NamespaceMode_POD,
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			"PID Namespace TARGET",
 | |
| 			&v1.Pod{
 | |
| 				Spec: v1.PodSpec{
 | |
| 					Containers: []v1.Container{
 | |
| 						{Name: "test"},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			&kubecontainer.ContainerID{Type: "docker", ID: "really-long-id-string"},
 | |
| 			&runtimeapi.NamespaceOption{
 | |
| 				Pid:      runtimeapi.NamespaceMode_TARGET,
 | |
| 				TargetId: "really-long-id-string",
 | |
| 			},
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			got := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", tc.target)
 | |
| 			if diff := cmp.Diff(tc.want, got.SecurityContext.NamespaceOptions); diff != "" {
 | |
| 				t.Errorf("%v: diff (-want +got):\n%v", t.Name(), diff)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 |