From 0c9b0e2aa7dd57efe780754dafd9bdadc086c51f Mon Sep 17 00:00:00 2001 From: Cosmin Tupangiu Date: Tue, 17 May 2022 10:47:45 +0200 Subject: expose block and char devices with play kube [NO NEW TESTS NEEDED] Signed-off-by: Cosmin Tupangiu --- pkg/specgen/generate/kube/kube.go | 16 ++++++++++++++++ pkg/specgen/generate/kube/volume.go | 31 ++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) (limited to 'pkg/specgen/generate') diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index e4c149abf..795ff876e 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -381,6 +381,22 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener Options: options, } s.Volumes = append(s.Volumes, &cmVolume) + case KubeVolumeTypeCharDevice: + // We are setting the path as hostPath:mountPath to comply with DeviceFromPath (https://github.com/containers/podman/blob/eb26fa45f1326191dea27f2afabf82cb8b934140/pkg/specgen/generate/config_linux.go#L72) + // The type is here just to improve readability as it is not taken into account when the actual device is created. + device := spec.LinuxDevice{ + Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath), + Type: "c", + } + s.Devices = append(s.Devices, device) + case KubeVolumeTypeBlockDevice: + // We are setting the path as hostPath:mountPath to comply with DeviceFromPath (https://github.com/containers/podman/blob/eb26fa45f1326191dea27f2afabf82cb8b934140/pkg/specgen/generate/config_linux.go#L72) + // The type is here just to improve readability as it is not taken into account when the actual device is created. + device := spec.LinuxDevice{ + Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath), + Type: "b", + } + s.Devices = append(s.Devices, device) default: return nil, errors.Errorf("Unsupported volume source type") } diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index 27881e77a..2fc63fc7c 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -22,8 +22,10 @@ type KubeVolumeType int const ( KubeVolumeTypeBindMount KubeVolumeType = iota - KubeVolumeTypeNamed KubeVolumeType = iota - KubeVolumeTypeConfigMap KubeVolumeType = iota + KubeVolumeTypeNamed + KubeVolumeTypeConfigMap + KubeVolumeTypeBlockDevice + KubeVolumeTypeCharDevice ) //nolint:revive @@ -78,7 +80,30 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) if st.Mode()&os.ModeSocket != os.ModeSocket { return nil, errors.Errorf("checking HostPathSocket: path %s is not a socket", hostPath.Path) } - + case v1.HostPathBlockDev: + dev, err := os.Stat(hostPath.Path) + if err != nil { + return nil, errors.Wrap(err, "error checking HostPathBlockDevice") + } + if dev.Mode()&os.ModeDevice != os.ModeDevice { + return nil, errors.Errorf("checking HosPathDevice: path %s is not a block device", hostPath.Path) + } + return &KubeVolume{ + Type: KubeVolumeTypeBlockDevice, + Source: hostPath.Path, + }, nil + case v1.HostPathCharDev: + dev, err := os.Stat(hostPath.Path) + if err != nil { + return nil, errors.Wrap(err, "error checking HostPathCharDevice") + } + if dev.Mode()&os.ModeCharDevice != os.ModeCharDevice { + return nil, errors.Errorf("checking HosPathCharDevice: path %s is not a character device", hostPath.Path) + } + return &KubeVolume{ + Type: KubeVolumeTypeCharDevice, + Source: hostPath.Path, + }, nil case v1.HostPathDirectory: case v1.HostPathFile: case v1.HostPathUnset: -- cgit v1.2.3-54-g00ecf From f5c8c0911306661f062a8297eac0aee7deeac6f8 Mon Sep 17 00:00:00 2001 From: Cosmin Tupangiu Date: Wed, 18 May 2022 10:46:45 +0200 Subject: add tests and fix bug when char device pass the test as block device - add test - fix bug when a character device set in a volume as a block device is seen as block device in _pkg/specgen/generate/kube/volume.go_. At this stage the type does not matter much because the devices are recreated at lower layer but the bug allowed a CharDevice volume to be passed to lower layer as a BlockDevice. Signed-off-by: Cosmin Tupangiu --- pkg/specgen/generate/kube/kube.go | 4 +- pkg/specgen/generate/kube/volume.go | 6 +- test/e2e/play_kube_test.go | 127 ++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 5 deletions(-) (limited to 'pkg/specgen/generate') diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 795ff876e..f37d79798 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -382,7 +382,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener } s.Volumes = append(s.Volumes, &cmVolume) case KubeVolumeTypeCharDevice: - // We are setting the path as hostPath:mountPath to comply with DeviceFromPath (https://github.com/containers/podman/blob/eb26fa45f1326191dea27f2afabf82cb8b934140/pkg/specgen/generate/config_linux.go#L72) + // We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath. // The type is here just to improve readability as it is not taken into account when the actual device is created. device := spec.LinuxDevice{ Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath), @@ -390,7 +390,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener } s.Devices = append(s.Devices, device) case KubeVolumeTypeBlockDevice: - // We are setting the path as hostPath:mountPath to comply with DeviceFromPath (https://github.com/containers/podman/blob/eb26fa45f1326191dea27f2afabf82cb8b934140/pkg/specgen/generate/config_linux.go#L72) + // We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath. // The type is here just to improve readability as it is not taken into account when the actual device is created. device := spec.LinuxDevice{ Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath), diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index 2fc63fc7c..1d6d49b9d 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -85,8 +85,8 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) if err != nil { return nil, errors.Wrap(err, "error checking HostPathBlockDevice") } - if dev.Mode()&os.ModeDevice != os.ModeDevice { - return nil, errors.Errorf("checking HosPathDevice: path %s is not a block device", hostPath.Path) + if dev.Mode()&os.ModeCharDevice == os.ModeCharDevice { + return nil, errors.Errorf("checking HostPathDevice: path %s is not a block device", hostPath.Path) } return &KubeVolume{ Type: KubeVolumeTypeBlockDevice, @@ -98,7 +98,7 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) return nil, errors.Wrap(err, "error checking HostPathCharDevice") } if dev.Mode()&os.ModeCharDevice != os.ModeCharDevice { - return nil, errors.Errorf("checking HosPathCharDevice: path %s is not a character device", hostPath.Path) + return nil, errors.Errorf("checking HostPathCharDevice: path %s is not a character device", hostPath.Path) } return &KubeVolume{ Type: KubeVolumeTypeCharDevice, diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 216c3357c..161813194 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -3685,4 +3685,131 @@ ENV OPENJ9_JAVA_OPTIONS=%q Expect(usernsInCtr).Should(Exit(0)) Expect(string(usernsInCtr.Out.Contents())).To(Not(Equal(string(initialUsernsConfig)))) }) + + // Check the block devices are exposed inside container + It("podman play kube expose block device inside container", func() { + SkipIfRootless("It needs root access to create devices") + Expect(os.MkdirAll("/dev/foodevdir", os.ModePerm)).To(BeNil()) + defer os.RemoveAll("/dev/foodevdir") + + devicePath := "/dev/foodevdir/blockdevice" + mknod := SystemExec("mknod", []string{devicePath, "b", "7", "0"}) + mknod.WaitWithDefaultTimeout() + Expect(mknod).Should(Exit(0)) + + blockVolume := getHostPathVolume("BlockDevice", devicePath) + + pod := getPod(withVolume(blockVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false)))) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + // Container should be in running state + inspect := podmanTest.Podman([]string{"inspect", "--format", "{{.State.Status}}", "testPod-" + defaultCtrName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring("running")) + + // Container should have a block device /dev/loop1 + inspect = podmanTest.Podman([]string{"inspect", "--format", "{{.HostConfig.Devices}}", "testPod-" + defaultCtrName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring(devicePath)) + }) + + // Check the char devices are exposed inside container + It("podman play kube expose character device inside container", func() { + SkipIfRootless("It needs root access to create devices") + Expect(os.MkdirAll("/dev/foodevdir", os.ModePerm)).To(BeNil()) + defer os.RemoveAll("/dev/foodevdir") + + devicePath := "/dev/foodevdir/chardevice" + mknod := SystemExec("mknod", []string{devicePath, "c", "3", "1"}) + mknod.WaitWithDefaultTimeout() + Expect(mknod).Should(Exit(0)) + + charVolume := getHostPathVolume("CharDevice", devicePath) + + pod := getPod(withVolume(charVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false)))) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + // Container should be in running state + inspect := podmanTest.Podman([]string{"inspect", "--format", "{{.State.Status}}", "testPod-" + defaultCtrName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring("running")) + + // Container should have a block device /dev/loop1 + inspect = podmanTest.Podman([]string{"inspect", "--format", "{{.HostConfig.Devices}}", "testPod-" + defaultCtrName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring(devicePath)) + }) + + It("podman play kube reports error when the device does not exists", func() { + SkipIfRootless("It needs root access to create devices") + + devicePath := "/dev/foodevdir/baddevice" + + blockVolume := getHostPathVolume("BlockDevice", devicePath) + + pod := getPod(withVolume(blockVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false)))) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(125)) + }) + + It("podman play kube reports error when we try to expose char device as block device", func() { + SkipIfRootless("It needs root access to create devices") + Expect(os.MkdirAll("/dev/foodevdir", os.ModePerm)).To(BeNil()) + defer os.RemoveAll("/dev/foodevdir") + + devicePath := "/dev/foodevdir/chardevice" + mknod := SystemExec("mknod", []string{devicePath, "c", "3", "1"}) + mknod.WaitWithDefaultTimeout() + Expect(mknod).Should(Exit(0)) + + charVolume := getHostPathVolume("BlockDevice", devicePath) + + pod := getPod(withVolume(charVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false)))) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(125)) + }) + + It("podman play kube reports error when we try to expose block device as char device", func() { + SkipIfRootless("It needs root access to create devices") + Expect(os.MkdirAll("/dev/foodevdir", os.ModePerm)).To(BeNil()) + defer os.RemoveAll("/dev/foodevdir") + + devicePath := "/dev/foodevdir/blockdevice" + mknod := SystemExec("mknod", []string{devicePath, "b", "7", "0"}) + mknod.WaitWithDefaultTimeout() + Expect(mknod).Should(Exit(0)) + + charVolume := getHostPathVolume("CharDevice", devicePath) + + pod := getPod(withVolume(charVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false)))) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(125)) + }) + }) -- cgit v1.2.3-54-g00ecf