From 77deed4ab80c122604347e5ece0e1865eeb46ad6 Mon Sep 17 00:00:00 2001 From: Amit Barve Date: Tue, 11 Apr 2023 16:42:49 -0700 Subject: [PATCH] Add test for support of NFS mount LCOW kernel needs to be built with certain config options(`CONFIG_NFS_FS=y`, `CONFIG_NFS_V4=y` & `CONFIG_NFS_V4_1=y`)_in order to be able to successfully run a NFS client and mount a NFS inside a container. This test attempts to mount a (fake) NFS server to ensure that the kernel has the capabilities of running a NFS client. We don't mount a real NFS server because creating a real NFS server that will work in all kinds of test environments is not simple. Instead, we look at the error returned by the NFS mount operation and decide if the failure is because the server wasn't available (i.e a `Connection refused` error) or because the kernel doesn't support NFS clients (`No Device` error). Limitations on different approaches of starting a real NFS server: 1. Starting another LCOW container that runs a NFS server: By default on Linux the NFS server runs in the kernel and to enable that the kernel must be built with `NFSD_*` config options (note that the config options for running NFS server are different than the config options required for NFS client), which we don't currently do and it doesn't make sense to just enable these options for a test. 2. Running a userspace NFS server: There are a few userspace NFS server projects but getting them to run inside the UtilityVM wasn't very easy. We didn't want to spend a lot of time on this test. 3. Running NFS server on the windows host: Not all builds of windows support this so the test won't run in all environments. Signed-off-by: Amit Barve --- test/cri-containerd/container_test.go | 71 +++++++++++++++++++++++++++ test/cri-containerd/main_test.go | 1 + 2 files changed, 72 insertions(+) diff --git a/test/cri-containerd/container_test.go b/test/cri-containerd/container_test.go index 8fbf9ee295..e70f25bfe8 100644 --- a/test/cri-containerd/container_test.go +++ b/test/cri-containerd/container_test.go @@ -874,3 +874,74 @@ func Test_RunContainer_ExecUser_Root_LCOW(t *testing.T) { t.Fatalf("expected user for exec to be 'root', got %q", string(r.Stdout)) } } + +// creates a linux container and attempts to mount a (non-existent) nfs share. Tests if the kernel has the +// required modules for supporting NFS mount. +func Test_Container_NFSMount_LCOW(t *testing.T) { + requireFeatures(t, featureLCOW) + + pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowUbuntu}) + + client := newTestRuntimeClient(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // start a privileged pod & container. container must be privileged in order to be able to mount a NFS + // share. + sandboxRequest := getRunPodSandboxRequest(t, lcowRuntimeHandler) + sandboxRequest.Config.Linux = &runtime.LinuxPodSandboxConfig{ + SecurityContext: &runtime.LinuxSandboxSecurityContext{ + Privileged: true, + }, + } + podID := runPodSandbox(t, client, ctx, sandboxRequest) + defer removePodSandbox(t, client, ctx, podID) + defer stopPodSandbox(t, client, ctx, podID) + + requestTemplate := getCreateContainerRequest( + podID, + t.Name()+"-container", + imageLcowUbuntu, + []string{"bash", "-c", "while true; do echo 'hello'; sleep 1; done"}, + sandboxRequest.Config, + ) + requestTemplate.Config.Linux = &runtime.LinuxContainerConfig{ + SecurityContext: &runtime.LinuxContainerSecurityContext{ + Privileged: true, + }, + } + containerID := createContainer(t, client, ctx, requestTemplate) + defer removeContainer(t, client, ctx, containerID) + startContainer(t, client, ctx, containerID) + defer stopContainer(t, client, ctx, containerID) + + execHelper := func(ctrID string, cmd []string) { + stdout, stderr, errcode := execContainer(t, client, ctx, ctrID, cmd) + if errcode != 0 { + t.Helper() + t.Logf("stdout: %s \n\n stderr: %s\n\n", stdout, stderr) + t.Fatalf("failed to run '%v'\n: errcode: %d", cmd, errcode) + } + } + + // setup nfs client + nfsdir := "/mnt/nfstest" + execHelper(containerID, []string{"apt", "update"}) + execHelper(containerID, []string{"apt", "install", "-y", "nfs-common"}) + execHelper(containerID, []string{"mkdir", "-p", nfsdir}) + + // There is no NFS daemon running in the container, so it is expected that the mount call fails with + // the connection refused error. However getting upto the connection refused error verifies that the + // container has all the required NFS client modules to successfully mount a NFS. (This also means + // that the kernel was correctly built with the NFS client options). `retry=0` ensures it fails + // immediately instead of retrying. If the kernel isn't correctly configured the call would fail with + // "No Device" error. + stdout, stderr, errcode := execContainer(t, client, ctx, containerID, []string{"mount", "-v", "-t", "nfs", "localhost:/fake/nfs/mount", nfsdir, "-o", "vers=4,minorversion=1,sec=sys,retry=0"}) + if errcode != 32 { // 32 is mount failure + t.Logf("stdout: %s \n\n stderr: %s\n", stdout, stderr) + t.Fatalf("mount call is expected to fail with mount failure error code: 32, errcode was %d instead", errcode) + } else if !strings.Contains(stderr, "Connection refused") { + t.Logf("stdout: %s \n\n stderr: %s\n", stdout, stderr) + t.Fatalf("mount call is expected to fail with Connection refused error") + } +} diff --git a/test/cri-containerd/main_test.go b/test/cri-containerd/main_test.go index 0687cb5be1..65223567da 100644 --- a/test/cri-containerd/main_test.go +++ b/test/cri-containerd/main_test.go @@ -56,6 +56,7 @@ const ( imageLcowAlpineCoreDump = "cplatpublic.azurecr.io/stackoverflow-alpine:latest" imageLcowCosmos = "cosmosarno/spark-master:2.4.1_2019-04-18_8e864ce" imageLcowCustomUser = "cplatpublic.azurecr.io/linux_custom_user:latest" + imageLcowUbuntu = "ubuntu:latest" alpineAspNet = "mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine3.11" alpineAspnetUpgrade = "mcr.microsoft.com/dotnet/core/aspnet:3.1.2-alpine3.11"