diff --git a/pkg/kubelet/kubelet_pods_linux_test.go b/pkg/kubelet/kubelet_pods_linux_test.go index 798d8e167b2..7b76d0a1932 100644 --- a/pkg/kubelet/kubelet_pods_linux_test.go +++ b/pkg/kubelet/kubelet_pods_linux_test.go @@ -20,16 +20,21 @@ limitations under the License. package kubelet import ( + "fmt" "testing" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/utils/ptr" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" _ "k8s.io/kubernetes/pkg/apis/core/install" + "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/volume" volumetest "k8s.io/kubernetes/pkg/volume/testing" "k8s.io/kubernetes/pkg/volume/util/hostutil" "k8s.io/kubernetes/pkg/volume/util/subpath" @@ -40,16 +45,21 @@ func TestMakeMounts(t *testing.T) { propagationHostToContainer := v1.MountPropagationHostToContainer propagationBidirectional := v1.MountPropagationBidirectional propagationNone := v1.MountPropagationNone + image1 := &runtimeapi.ImageSpec{Image: "image1"} + image2 := &runtimeapi.ImageSpec{Image: "image2"} testCases := map[string]struct { - container v1.Container - podVolumes kubecontainer.VolumeMap - supportsRRO bool - expectErr bool - expectedErrMsg string - expectedMounts []kubecontainer.Mount + container v1.Container + podVolumes kubecontainer.VolumeMap + imageVolumes kubecontainer.ImageVolumes + imageVolumeFeatureEnabled []bool + supportsRRO bool + expectErr bool + expectedErrMsg string + expectedMounts []kubecontainer.Mount }{ - "valid mounts in unprivileged container": { + "valid mounts in unprivileged container": { // TODO: remove it once image volume feature is GA + imageVolumeFeatureEnabled: []bool{true, false}, podVolumes: kubecontainer.VolumeMap{ "disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}}, "disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}}, @@ -118,7 +128,8 @@ func TestMakeMounts(t *testing.T) { }, expectErr: false, }, - "valid mounts in privileged container": { + "valid mounts in privileged container": { // TODO: remove it once image volume feature is GA + imageVolumeFeatureEnabled: []bool{true, false}, podVolumes: kubecontainer.VolumeMap{ "disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}}, "disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}}, @@ -177,7 +188,198 @@ func TestMakeMounts(t *testing.T) { }, expectErr: false, }, + "valid mounts in unprivileged container with image volumes": { + imageVolumeFeatureEnabled: []bool{true}, + podVolumes: kubecontainer.VolumeMap{ + "disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}}, + "disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}}, + "disk5": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}}, + "image1": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}}, + "image2": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}}, + }, + imageVolumes: kubecontainer.ImageVolumes{ + "image1": image1, + "image2": image2, + }, + container: v1.Container{ + Name: "container1", + VolumeMounts: []v1.VolumeMount{ + { + MountPath: "/etc/hosts", + Name: "disk", + ReadOnly: false, + MountPropagation: &propagationHostToContainer, + }, + { + MountPath: "/mnt/path3", + Name: "disk", + ReadOnly: true, + MountPropagation: &propagationNone, + }, + { + MountPath: "/mnt/path4", + Name: "disk4", + ReadOnly: false, + }, + { + MountPath: "/mnt/path5", + Name: "disk5", + ReadOnly: false, + }, + { + MountPath: "/mnt/image1", + Name: "image1", + ReadOnly: false, + }, + { + MountPath: "/mnt/image2", + Name: "image2", + ReadOnly: true, + }, + }, + }, + expectedMounts: []kubecontainer.Mount{ + { + Name: "disk", + ContainerPath: "/etc/hosts", + HostPath: "/mnt/disk", + ReadOnly: false, + SELinuxRelabel: false, + Propagation: runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER, + }, + { + Name: "disk", + ContainerPath: "/mnt/path3", + HostPath: "/mnt/disk", + ReadOnly: true, + SELinuxRelabel: false, + Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE, + }, + { + Name: "disk4", + ContainerPath: "/mnt/path4", + HostPath: "/mnt/host", + ReadOnly: false, + SELinuxRelabel: false, + Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE, + }, + { + Name: "disk5", + ContainerPath: "/mnt/path5", + HostPath: "/var/lib/kubelet/podID/volumes/empty/disk5", + ReadOnly: false, + SELinuxRelabel: false, + Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE, + }, + { + Name: "image1", + ContainerPath: "/mnt/image1", + Image: image1, + ReadOnly: true, + SELinuxRelabel: false, + }, + { + Name: "image2", + ContainerPath: "/mnt/image2", + Image: image2, + ReadOnly: true, + SELinuxRelabel: false, + }, + }, + expectErr: false, + }, + "valid mounts in privileged container with image volumes": { + imageVolumeFeatureEnabled: []bool{true}, + podVolumes: kubecontainer.VolumeMap{ + "disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}}, + "disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}}, + "disk5": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}}, + "image1": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}}, + "image2": kubecontainer.VolumeInfo{Mounter: &stubVolume{attributes: volume.Attributes{ReadOnly: true}}}, + }, + imageVolumes: kubecontainer.ImageVolumes{ + "image1": image1, + "image2": image2, + }, + container: v1.Container{ + Name: "container1", + VolumeMounts: []v1.VolumeMount{ + { + MountPath: "/etc/hosts", + Name: "disk", + ReadOnly: false, + MountPropagation: &propagationBidirectional, + }, + { + MountPath: "/mnt/path3", + Name: "disk", + ReadOnly: true, + MountPropagation: &propagationHostToContainer, + }, + { + MountPath: "/mnt/path4", + Name: "disk4", + ReadOnly: false, + }, + { + MountPath: "/mnt/image1", + Name: "image1", + ReadOnly: false, + }, + { + MountPath: "/mnt/image2", + Name: "image2", + ReadOnly: true, + }, + }, + SecurityContext: &v1.SecurityContext{ + Privileged: &bTrue, + }, + }, + expectedMounts: []kubecontainer.Mount{ + { + Name: "disk", + ContainerPath: "/etc/hosts", + HostPath: "/mnt/disk", + ReadOnly: false, + SELinuxRelabel: false, + Propagation: runtimeapi.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + { + Name: "disk", + ContainerPath: "/mnt/path3", + HostPath: "/mnt/disk", + ReadOnly: true, + SELinuxRelabel: false, + Propagation: runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER, + }, + { + Name: "disk4", + ContainerPath: "/mnt/path4", + HostPath: "/mnt/host", + ReadOnly: false, + SELinuxRelabel: false, + Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE, + }, + { + Name: "image1", + ContainerPath: "/mnt/image1", + Image: image1, + ReadOnly: true, + SELinuxRelabel: false, + }, + { + Name: "image2", + ContainerPath: "/mnt/image2", + Image: image2, + ReadOnly: true, + SELinuxRelabel: false, + }, + }, + expectErr: false, + }, "invalid absolute SubPath": { + imageVolumeFeatureEnabled: []bool{true, false}, podVolumes: kubecontainer.VolumeMap{ "disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}}, }, @@ -195,6 +397,7 @@ func TestMakeMounts(t *testing.T) { expectedErrMsg: "error SubPath `/must/not/be/absolute` must not be an absolute path", }, "invalid SubPath with backsteps": { + imageVolumeFeatureEnabled: []bool{true, false}, podVolumes: kubecontainer.VolumeMap{ "disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}}, }, @@ -212,7 +415,7 @@ func TestMakeMounts(t *testing.T) { expectedErrMsg: "unable to provision SubPath `no/backsteps/../allowed`: must not contain '..'", }, "volume doesn't exist": { - podVolumes: kubecontainer.VolumeMap{}, + imageVolumeFeatureEnabled: []bool{true, false}, container: v1.Container{ VolumeMounts: []v1.VolumeMount{ { @@ -226,6 +429,7 @@ func TestMakeMounts(t *testing.T) { expectedErrMsg: "cannot find volume \"disk\" to mount into container \"\"", }, "volume mounter is nil": { + imageVolumeFeatureEnabled: []bool{true, false}, podVolumes: kubecontainer.VolumeMap{ "disk": kubecontainer.VolumeInfo{}, }, @@ -244,32 +448,36 @@ func TestMakeMounts(t *testing.T) { } for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - fhu := hostutil.NewFakeHostUtil(nil) - fsp := &subpath.FakeSubpath{} - pod := v1.Pod{ - Spec: v1.PodSpec{ - HostNetwork: true, - }, - } - - mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", []string{""}, tc.podVolumes, fhu, fsp, nil, tc.supportsRRO, nil) - - // validate only the error if we expect an error - if tc.expectErr { - if err == nil || err.Error() != tc.expectedErrMsg { - t.Fatalf("expected error message `%s` but got `%v`", tc.expectedErrMsg, err) + for _, featureEnabled := range tc.imageVolumeFeatureEnabled { + name := fmt.Sprintf("features.ImageVolume is %v, %s", featureEnabled, name) + t.Run(name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, featureEnabled) + fhu := hostutil.NewFakeHostUtil(nil) + fsp := &subpath.FakeSubpath{} + pod := v1.Pod{ + Spec: v1.PodSpec{ + HostNetwork: true, + }, } - return - } - // otherwise validate the mounts - if err != nil { - t.Fatal(err) - } + mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", []string{""}, tc.podVolumes, fhu, fsp, nil, tc.supportsRRO, tc.imageVolumes) - assert.Equal(t, tc.expectedMounts, mounts, "mounts of container %+v", tc.container) - }) + // validate only the error if we expect an error + if tc.expectErr { + if err == nil || err.Error() != tc.expectedErrMsg { + t.Fatalf("expected error message `%s` but got `%v`", tc.expectedErrMsg, err) + } + return + } + + // otherwise validate the mounts + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, tc.expectedMounts, mounts, "mounts of container %+v", tc.container) + }) + } } } diff --git a/pkg/kubelet/kubelet_volumes_test.go b/pkg/kubelet/kubelet_volumes_test.go index 30509115028..bc322f6302b 100644 --- a/pkg/kubelet/kubelet_volumes_test.go +++ b/pkg/kubelet/kubelet_volumes_test.go @@ -689,7 +689,8 @@ func TestVolumeUnmountAndDetachControllerEnabled(t *testing.T) { } type stubVolume struct { - path string + path string + attributes volume.Attributes volume.MetricsNil } @@ -698,7 +699,7 @@ func (f *stubVolume) GetPath() string { } func (f *stubVolume) GetAttributes() volume.Attributes { - return volume.Attributes{} + return f.attributes } func (f *stubVolume) SetUp(mounterArgs volume.MounterArgs) error {