k8s.io/kubernetes@v1.29.3/pkg/kubelet/kuberuntime/kuberuntime_sandbox_test.go (about)

     1  /*
     2  Copyright 2016 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 kuberuntime
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    31  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    32  	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
    33  	"k8s.io/kubernetes/pkg/features"
    34  	containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
    35  	"k8s.io/kubernetes/pkg/kubelet/runtimeclass"
    36  	rctest "k8s.io/kubernetes/pkg/kubelet/runtimeclass/testing"
    37  	"k8s.io/utils/pointer"
    38  )
    39  
    40  func TestGeneratePodSandboxConfig(t *testing.T) {
    41  	_, _, m, err := createTestRuntimeManager()
    42  	require.NoError(t, err)
    43  	pod := newTestPod()
    44  
    45  	expectedLogDirectory := filepath.Join(podLogsRootDirectory, pod.Namespace+"_"+pod.Name+"_12345678")
    46  	expectedLabels := map[string]string{
    47  		"io.kubernetes.pod.name":      pod.Name,
    48  		"io.kubernetes.pod.namespace": pod.Namespace,
    49  		"io.kubernetes.pod.uid":       string(pod.UID),
    50  	}
    51  	expectedMetadata := &runtimeapi.PodSandboxMetadata{
    52  		Name:      pod.Name,
    53  		Namespace: pod.Namespace,
    54  		Uid:       string(pod.UID),
    55  		Attempt:   uint32(1),
    56  	}
    57  	expectedPortMappings := []*runtimeapi.PortMapping{
    58  		{
    59  			HostPort: 8080,
    60  		},
    61  	}
    62  
    63  	podSandboxConfig, err := m.generatePodSandboxConfig(pod, 1)
    64  	assert.NoError(t, err)
    65  	assert.Equal(t, expectedLabels, podSandboxConfig.Labels)
    66  	assert.Equal(t, expectedLogDirectory, podSandboxConfig.LogDirectory)
    67  	assert.Equal(t, expectedMetadata, podSandboxConfig.Metadata)
    68  	assert.Equal(t, expectedPortMappings, podSandboxConfig.PortMappings)
    69  }
    70  
    71  // TestCreatePodSandbox tests creating sandbox and its corresponding pod log directory.
    72  func TestCreatePodSandbox(t *testing.T) {
    73  	ctx := context.Background()
    74  	fakeRuntime, _, m, err := createTestRuntimeManager()
    75  	require.NoError(t, err)
    76  	pod := newTestPod()
    77  
    78  	fakeOS := m.osInterface.(*containertest.FakeOS)
    79  	fakeOS.MkdirAllFn = func(path string, perm os.FileMode) error {
    80  		// Check pod logs root directory is created.
    81  		assert.Equal(t, filepath.Join(podLogsRootDirectory, pod.Namespace+"_"+pod.Name+"_12345678"), path)
    82  		assert.Equal(t, os.FileMode(0755), perm)
    83  		return nil
    84  	}
    85  	id, _, err := m.createPodSandbox(ctx, pod, 1)
    86  	assert.NoError(t, err)
    87  	assert.Contains(t, fakeRuntime.Called, "RunPodSandbox")
    88  	sandboxes, err := fakeRuntime.ListPodSandbox(ctx, &runtimeapi.PodSandboxFilter{Id: id})
    89  	assert.NoError(t, err)
    90  	assert.Equal(t, len(sandboxes), 1)
    91  	assert.Equal(t, sandboxes[0].Id, fmt.Sprintf("%s_%s_%s_1", pod.Name, pod.Namespace, pod.UID))
    92  	assert.Equal(t, sandboxes[0].State, runtimeapi.PodSandboxState_SANDBOX_READY)
    93  }
    94  
    95  func TestGeneratePodSandboxLinuxConfigSeccomp(t *testing.T) {
    96  	_, _, m, err := createTestRuntimeManager()
    97  	require.NoError(t, err)
    98  
    99  	tests := []struct {
   100  		description     string
   101  		pod             *v1.Pod
   102  		expectedProfile v1.SeccompProfileType
   103  	}{
   104  		{
   105  			description:     "no seccomp defined at pod level should return runtime/default",
   106  			pod:             newSeccompPod(nil, nil, "", "runtime/default"),
   107  			expectedProfile: v1.SeccompProfileTypeRuntimeDefault,
   108  		},
   109  		{
   110  			description:     "seccomp field defined at pod level should not be honoured",
   111  			pod:             newSeccompPod(&v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}, nil, "", ""),
   112  			expectedProfile: v1.SeccompProfileTypeRuntimeDefault,
   113  		},
   114  		{
   115  			description:     "seccomp field defined at container level should not be honoured",
   116  			pod:             newSeccompPod(nil, &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}, "", ""),
   117  			expectedProfile: v1.SeccompProfileTypeRuntimeDefault,
   118  		},
   119  		{
   120  			description:     "seccomp annotation defined at pod level should not be honoured",
   121  			pod:             newSeccompPod(nil, nil, "unconfined", ""),
   122  			expectedProfile: v1.SeccompProfileTypeRuntimeDefault,
   123  		},
   124  		{
   125  			description:     "seccomp annotation defined at container level should not be honoured",
   126  			pod:             newSeccompPod(nil, nil, "", "unconfined"),
   127  			expectedProfile: v1.SeccompProfileTypeRuntimeDefault,
   128  		},
   129  	}
   130  
   131  	for i, test := range tests {
   132  		config, _ := m.generatePodSandboxLinuxConfig(test.pod)
   133  		actualProfile := config.SecurityContext.Seccomp.ProfileType.String()
   134  		assert.EqualValues(t, test.expectedProfile, actualProfile, "TestCase[%d]: %s", i, test.description)
   135  	}
   136  }
   137  
   138  // TestCreatePodSandbox_RuntimeClass tests creating sandbox with RuntimeClasses enabled.
   139  func TestCreatePodSandbox_RuntimeClass(t *testing.T) {
   140  	ctx := context.Background()
   141  	rcm := runtimeclass.NewManager(rctest.NewPopulatedClient())
   142  	defer rctest.StartManagerSync(rcm)()
   143  
   144  	fakeRuntime, _, m, err := createTestRuntimeManager()
   145  	require.NoError(t, err)
   146  	m.runtimeClassManager = rcm
   147  
   148  	tests := map[string]struct {
   149  		rcn             *string
   150  		expectedHandler string
   151  		expectError     bool
   152  	}{
   153  		"unspecified RuntimeClass": {rcn: nil, expectedHandler: ""},
   154  		"valid RuntimeClass":       {rcn: pointer.String(rctest.SandboxRuntimeClass), expectedHandler: rctest.SandboxRuntimeHandler},
   155  		"missing RuntimeClass":     {rcn: pointer.String("phantom"), expectError: true},
   156  	}
   157  	for name, test := range tests {
   158  		t.Run(name, func(t *testing.T) {
   159  			fakeRuntime.Called = []string{}
   160  			pod := newTestPod()
   161  			pod.Spec.RuntimeClassName = test.rcn
   162  
   163  			id, _, err := m.createPodSandbox(ctx, pod, 1)
   164  			if test.expectError {
   165  				assert.Error(t, err)
   166  			} else {
   167  				assert.NoError(t, err)
   168  				assert.Contains(t, fakeRuntime.Called, "RunPodSandbox")
   169  				assert.Equal(t, test.expectedHandler, fakeRuntime.Sandboxes[id].RuntimeHandler)
   170  			}
   171  		})
   172  	}
   173  }
   174  
   175  func newTestPod() *v1.Pod {
   176  	return &v1.Pod{
   177  		ObjectMeta: metav1.ObjectMeta{
   178  			UID:       "12345678",
   179  			Name:      "bar",
   180  			Namespace: "new",
   181  		},
   182  		Spec: v1.PodSpec{
   183  			Containers: []v1.Container{
   184  				{
   185  					Name:            "foo",
   186  					Image:           "busybox",
   187  					ImagePullPolicy: v1.PullIfNotPresent,
   188  					Ports: []v1.ContainerPort{
   189  						{
   190  							HostPort: 8080,
   191  						},
   192  					},
   193  				},
   194  			},
   195  		},
   196  	}
   197  }
   198  
   199  func newSeccompPod(podFieldProfile, containerFieldProfile *v1.SeccompProfile, podAnnotationProfile, containerAnnotationProfile string) *v1.Pod {
   200  	pod := newTestPod()
   201  	if podFieldProfile != nil {
   202  		pod.Spec.SecurityContext = &v1.PodSecurityContext{
   203  			SeccompProfile: podFieldProfile,
   204  		}
   205  	}
   206  	if containerFieldProfile != nil {
   207  		pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{
   208  			SeccompProfile: containerFieldProfile,
   209  		}
   210  	}
   211  	return pod
   212  }
   213  
   214  func TestGeneratePodSandboxWindowsConfig_HostProcess(t *testing.T) {
   215  	_, _, m, err := createTestRuntimeManager()
   216  	require.NoError(t, err)
   217  
   218  	const containerName = "container"
   219  	gmsaCreds := "gmsa-creds"
   220  	userName := "SYSTEM"
   221  	trueVar := true
   222  	falseVar := false
   223  
   224  	testCases := []struct {
   225  		name                  string
   226  		podSpec               *v1.PodSpec
   227  		expectedWindowsConfig *runtimeapi.WindowsPodSandboxConfig
   228  		expectedError         error
   229  	}{
   230  		{
   231  			name: "Empty PodSecurityContext",
   232  			podSpec: &v1.PodSpec{
   233  				Containers: []v1.Container{{
   234  					Name: containerName,
   235  				}},
   236  			},
   237  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   238  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{},
   239  			},
   240  			expectedError: nil,
   241  		},
   242  		{
   243  			name: "GMSACredentialSpec in PodSecurityContext",
   244  			podSpec: &v1.PodSpec{
   245  				SecurityContext: &v1.PodSecurityContext{
   246  					WindowsOptions: &v1.WindowsSecurityContextOptions{
   247  						GMSACredentialSpec: &gmsaCreds,
   248  					},
   249  				},
   250  				Containers: []v1.Container{{
   251  					Name: containerName,
   252  				}},
   253  			},
   254  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   255  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
   256  					CredentialSpec: "gmsa-creds",
   257  				},
   258  			},
   259  			expectedError: nil,
   260  		},
   261  		{
   262  			name: "RunAsUserName in PodSecurityContext",
   263  			podSpec: &v1.PodSpec{
   264  				SecurityContext: &v1.PodSecurityContext{
   265  					WindowsOptions: &v1.WindowsSecurityContextOptions{
   266  						RunAsUserName: &userName,
   267  					},
   268  				},
   269  				Containers: []v1.Container{{
   270  					Name: containerName,
   271  				}},
   272  			},
   273  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   274  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
   275  					RunAsUsername: "SYSTEM",
   276  				},
   277  			},
   278  			expectedError: nil,
   279  		},
   280  		{
   281  			name: "Pod with HostProcess containers and non-HostProcess containers",
   282  			podSpec: &v1.PodSpec{
   283  				SecurityContext: &v1.PodSecurityContext{
   284  					WindowsOptions: &v1.WindowsSecurityContextOptions{
   285  						HostProcess: &trueVar,
   286  					},
   287  				},
   288  				Containers: []v1.Container{{
   289  					Name: containerName,
   290  				}, {
   291  					Name: containerName,
   292  					SecurityContext: &v1.SecurityContext{
   293  						WindowsOptions: &v1.WindowsSecurityContextOptions{
   294  							HostProcess: &falseVar,
   295  						},
   296  					},
   297  				}},
   298  			},
   299  			expectedWindowsConfig: nil,
   300  			expectedError:         fmt.Errorf("pod must not contain both HostProcess and non-HostProcess containers"),
   301  		},
   302  		{
   303  			name: "Pod with HostProcess containers and HostNetwork not set",
   304  			podSpec: &v1.PodSpec{
   305  				SecurityContext: &v1.PodSecurityContext{
   306  					WindowsOptions: &v1.WindowsSecurityContextOptions{
   307  						HostProcess: &trueVar,
   308  					},
   309  				},
   310  				Containers: []v1.Container{{
   311  					Name: containerName,
   312  				}},
   313  			},
   314  			expectedWindowsConfig: nil,
   315  			expectedError:         fmt.Errorf("hostNetwork is required if Pod contains HostProcess containers"),
   316  		},
   317  		{
   318  			name: "Pod with HostProcess containers and HostNetwork set",
   319  			podSpec: &v1.PodSpec{
   320  				HostNetwork: true,
   321  				SecurityContext: &v1.PodSecurityContext{
   322  					WindowsOptions: &v1.WindowsSecurityContextOptions{
   323  						HostProcess: &trueVar,
   324  					},
   325  				},
   326  				Containers: []v1.Container{{
   327  					Name: containerName,
   328  				}},
   329  			},
   330  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   331  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
   332  					HostProcess: true,
   333  				},
   334  			},
   335  			expectedError: nil,
   336  		},
   337  		{
   338  			name: "Pod's WindowsOptions.HostProcess set to false and pod has HostProcess containers",
   339  			podSpec: &v1.PodSpec{
   340  				HostNetwork: true,
   341  				SecurityContext: &v1.PodSecurityContext{
   342  					WindowsOptions: &v1.WindowsSecurityContextOptions{
   343  						HostProcess: &falseVar,
   344  					},
   345  				},
   346  				Containers: []v1.Container{{
   347  					Name: containerName,
   348  					SecurityContext: &v1.SecurityContext{
   349  						WindowsOptions: &v1.WindowsSecurityContextOptions{
   350  							HostProcess: &trueVar,
   351  						},
   352  					},
   353  				}},
   354  			},
   355  			expectedWindowsConfig: nil,
   356  			expectedError:         fmt.Errorf("pod must not contain any HostProcess containers if Pod's WindowsOptions.HostProcess is set to false"),
   357  		},
   358  		{
   359  			name: "Pod's security context doesn't specify HostProcess containers but Container's security context does",
   360  			podSpec: &v1.PodSpec{
   361  				HostNetwork: true,
   362  				Containers: []v1.Container{{
   363  					Name: containerName,
   364  					SecurityContext: &v1.SecurityContext{
   365  						WindowsOptions: &v1.WindowsSecurityContextOptions{
   366  							HostProcess: &trueVar,
   367  						},
   368  					},
   369  				}},
   370  			},
   371  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   372  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
   373  					HostProcess: true,
   374  				},
   375  			},
   376  			expectedError: nil,
   377  		},
   378  	}
   379  
   380  	for _, testCase := range testCases {
   381  		t.Run(testCase.name, func(t *testing.T) {
   382  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WindowsHostNetwork, false)()
   383  			pod := &v1.Pod{}
   384  			pod.Spec = *testCase.podSpec
   385  
   386  			wc, err := m.generatePodSandboxWindowsConfig(pod)
   387  
   388  			assert.Equal(t, testCase.expectedWindowsConfig, wc)
   389  			assert.Equal(t, testCase.expectedError, err)
   390  		})
   391  	}
   392  }
   393  
   394  func TestGeneratePodSandboxWindowsConfig_HostNetwork(t *testing.T) {
   395  	_, _, m, err := createTestRuntimeManager()
   396  	require.NoError(t, err)
   397  
   398  	const containerName = "container"
   399  
   400  	testCases := []struct {
   401  		name                      string
   402  		hostNetworkFeatureEnabled bool
   403  		podSpec                   *v1.PodSpec
   404  		expectedWindowsConfig     *runtimeapi.WindowsPodSandboxConfig
   405  	}{
   406  		{
   407  			name:                      "feature disabled, hostNetwork=false",
   408  			hostNetworkFeatureEnabled: false,
   409  			podSpec: &v1.PodSpec{
   410  				HostNetwork: false,
   411  				Containers:  []v1.Container{{Name: containerName}},
   412  			},
   413  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   414  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{},
   415  			},
   416  		},
   417  		{
   418  			name:                      "feature disabled, hostNetwork=true",
   419  			hostNetworkFeatureEnabled: false,
   420  			podSpec: &v1.PodSpec{
   421  				HostNetwork: true,
   422  				Containers:  []v1.Container{{Name: containerName}},
   423  			},
   424  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   425  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{},
   426  			}},
   427  		{
   428  			name:                      "feature enabled, hostNetwork=false",
   429  			hostNetworkFeatureEnabled: true,
   430  			podSpec: &v1.PodSpec{
   431  				HostNetwork: false,
   432  				Containers:  []v1.Container{{Name: containerName}},
   433  			},
   434  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   435  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
   436  					NamespaceOptions: &runtimeapi.WindowsNamespaceOption{
   437  						Network: runtimeapi.NamespaceMode_POD,
   438  					},
   439  				},
   440  			},
   441  		},
   442  		{
   443  			name:                      "feature enabled, hostNetwork=true",
   444  			hostNetworkFeatureEnabled: true,
   445  			podSpec: &v1.PodSpec{
   446  				HostNetwork: true,
   447  				Containers:  []v1.Container{{Name: containerName}},
   448  			},
   449  			expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
   450  				SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
   451  					NamespaceOptions: &runtimeapi.WindowsNamespaceOption{
   452  						Network: runtimeapi.NamespaceMode_NODE,
   453  					},
   454  				},
   455  			},
   456  		},
   457  	}
   458  
   459  	for _, testCase := range testCases {
   460  		t.Run(testCase.name, func(t *testing.T) {
   461  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WindowsHostNetwork, testCase.hostNetworkFeatureEnabled)()
   462  			pod := &v1.Pod{}
   463  			pod.Spec = *testCase.podSpec
   464  
   465  			wc, err := m.generatePodSandboxWindowsConfig(pod)
   466  
   467  			assert.Equal(t, testCase.expectedWindowsConfig, wc)
   468  			assert.Equal(t, nil, err)
   469  		})
   470  	}
   471  }