m/node/kubernetes: fix attaching block PVs

Attaching a block PV to a container failed with the error:
"failed to create device node at target path: file exists".
This happened because there was already a directory at the path.
The directory should only be created for mounts, not for block devices.

I also extended the PV end-to-end test to add a block volume, and check
that it can be opened as a block device and has the expected size.

Change-Id: I40ca82cfcbfee1cb3196a900423f967b45790a64
Reviewed-on: https://review.monogon.dev/c/monogon/+/3623
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/kubernetes/csi.go b/metropolis/node/kubernetes/csi.go
index eb43ec0..40d54d0 100644
--- a/metropolis/node/kubernetes/csi.go
+++ b/metropolis/node/kubernetes/csi.go
@@ -101,11 +101,12 @@
 	default:
 		return nil, status.Error(codes.InvalidArgument, "unsupported access mode")
 	}
-	if err := os.MkdirAll(req.TargetPath, 0700); err != nil {
-		return nil, status.Errorf(codes.Internal, "unable to create requested target path: %v", err)
-	}
 	switch req.VolumeCapability.AccessType.(type) {
 	case *csi.VolumeCapability_Mount:
+		if err := os.MkdirAll(req.TargetPath, 0700); err != nil {
+			return nil, status.Errorf(codes.Internal, "unable to create requested target path: %v", err)
+		}
+
 		var mountFlags uintptr = unix.MS_BIND
 		if req.Readonly {
 			mountFlags |= unix.MS_RDONLY
diff --git a/metropolis/test/e2e/persistentvolume/BUILD.bazel b/metropolis/test/e2e/persistentvolume/BUILD.bazel
index fec0886..e98f630 100644
--- a/metropolis/test/e2e/persistentvolume/BUILD.bazel
+++ b/metropolis/test/e2e/persistentvolume/BUILD.bazel
@@ -5,7 +5,10 @@
     srcs = ["main.go"],
     importpath = "source.monogon.dev/metropolis/test/e2e/persistentvolume",
     visibility = ["//visibility:private"],
-    deps = ["@org_golang_x_sys//unix"],
+    deps = [
+        "//osbase/blockdev",
+        "@org_golang_x_sys//unix",
+    ],
 )
 
 go_binary(
diff --git a/metropolis/test/e2e/persistentvolume/main.go b/metropolis/test/e2e/persistentvolume/main.go
index 38cf329..577c343 100644
--- a/metropolis/test/e2e/persistentvolume/main.go
+++ b/metropolis/test/e2e/persistentvolume/main.go
@@ -16,6 +16,8 @@
 	"time"
 
 	"golang.org/x/sys/unix"
+
+	"source.monogon.dev/osbase/blockdev"
 )
 
 // This is a copy of the constant in metropolis/node/kubernetes/provisioner.go.
@@ -67,6 +69,19 @@
 	return nil
 }
 
+func checkBlockVolume(path string, expectedBytes uint64) error {
+	blk, err := blockdev.Open(path)
+	if err != nil {
+		return fmt.Errorf("failed to open block device %q: %w", path, err)
+	}
+	defer blk.Close()
+	sizeBytes := blk.BlockCount() * blk.BlockSize()
+	if sizeBytes != int64(expectedBytes) {
+		return fmt.Errorf("block device %q has size %v bytes, expected %v bytes", path, sizeBytes, expectedBytes)
+	}
+	return nil
+}
+
 func testPersistentVolume() error {
 	if err := checkFilesystemVolume("/vol/default", 0, 1*1024*1024); err != nil {
 		return err
@@ -77,6 +92,9 @@
 	if err := checkFilesystemVolume("/vol/readonly", unix.ST_RDONLY, 1*1024*1024); err != nil {
 		return err
 	}
+	if err := checkBlockVolume("/vol/block", 1*1024*1024); err != nil {
+		return err
+	}
 	return nil
 }
 
diff --git a/metropolis/test/e2e/suites/kubernetes/kubernetes_helpers.go b/metropolis/test/e2e/suites/kubernetes/kubernetes_helpers.go
index 85f8909..25a785d 100644
--- a/metropolis/test/e2e/suites/kubernetes/kubernetes_helpers.go
+++ b/metropolis/test/e2e/suites/kubernetes/kubernetes_helpers.go
@@ -195,6 +195,16 @@
 						VolumeMode: ptr.To(corev1.PersistentVolumeFilesystem),
 					},
 				},
+				{
+					ObjectMeta: metav1.ObjectMeta{Name: "vol-block"},
+					Spec: corev1.PersistentVolumeClaimSpec{
+						AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
+						Resources: corev1.VolumeResourceRequirements{
+							Requests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceStorage: resource.MustParse("1Mi")},
+						},
+						VolumeMode: ptr.To(corev1.PersistentVolumeBlock),
+					},
+				},
 			},
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
@@ -209,20 +219,26 @@
 							ImagePullPolicy: corev1.PullIfNotPresent,
 							Image:           "test.monogon.internal/metropolis/test/e2e/persistentvolume/persistentvolume_image",
 							VolumeMounts: []corev1.VolumeMount{
-								corev1.VolumeMount{
+								{
 									Name:      "vol-default",
 									MountPath: "/vol/default",
 								},
-								corev1.VolumeMount{
+								{
 									Name:      "vol-local-strict",
 									MountPath: "/vol/local-strict",
 								},
-								corev1.VolumeMount{
+								{
 									Name:      "vol-readonly",
 									ReadOnly:  true,
 									MountPath: "/vol/readonly",
 								},
 							},
+							VolumeDevices: []corev1.VolumeDevice{
+								{
+									Name:       "vol-block",
+									DevicePath: "/vol/block",
+								},
+							},
 						},
 					},
 				},