diff --git a/integration/container_volume_test.go b/integration/container_volume_test.go new file mode 100644 index 000000000..449f8002f --- /dev/null +++ b/integration/container_volume_test.go @@ -0,0 +1,152 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package integration + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" +) + +func createRegularFile(basePath, content string) (string, error) { + newFolder := filepath.Join(basePath, "regular") + err := os.Mkdir(newFolder, 0755) + if err != nil { + return "", err + } + + newFile := filepath.Join(newFolder, "foo.txt") + err = ioutil.WriteFile(newFile, []byte(content), 0644) + return filepath.Join("regular", "foo.txt"), err +} + +func fileInSymlinkedFolder(basePath, targetFile string) (string, error) { + symlinkFolder := filepath.Join(basePath, "symlink_folder") + err := os.Symlink(filepath.Dir(targetFile), symlinkFolder) + + return filepath.Join(symlinkFolder, filepath.Base(targetFile)), err +} + +func symlinkedFile(basePath, targetFile string) (string, error) { + symlinkFile := filepath.Join(basePath, "symlink_file") + err := os.Symlink(targetFile, symlinkFile) + + return symlinkFile, err +} + +func symlinkedFileInSymlinkedFolder(basePath, targetFile string) (string, error) { + symlinkFolderFile, err := fileInSymlinkedFolder(basePath, targetFile) + if err != nil { + return "", err + } + + return symlinkedFile(basePath, symlinkFolderFile) +} + +func TestContainerSymlinkVolumes(t *testing.T) { + for name, testCase := range map[string]struct { + createFileFn func(basePath, targetFile string) (string, error) + }{ + // Create difference file / symlink scenarios: + // - symlink_file -> regular_folder/regular_file + // - symlink_folder/regular_file (symlink_folder -> regular_folder) + // - symlink_file -> symlink_folder/regular_file (symlink_folder -> regular_folder) + "file in symlinked folder": { + createFileFn: fileInSymlinkedFolder, + }, + "symlinked file": { + createFileFn: symlinkedFile, + }, + "symlinkedFileInSymlinkedFolder": { + createFileFn: symlinkedFileInSymlinkedFolder, + }, + } { + testCase := testCase // capture range variable + t.Run(name, func(t *testing.T) { + testPodLogDir, err := ioutil.TempDir("", "symlink-test") + require.NoError(t, err) + defer os.RemoveAll(testPodLogDir) + + testVolDir, err := ioutil.TempDir("", "symlink-test-vol") + require.NoError(t, err) + defer os.RemoveAll(testVolDir) + + content := "hello there\n" + regularFile, err := createRegularFile(testVolDir, content) + require.NoError(t, err) + + file, err := testCase.createFileFn(testVolDir, regularFile) + require.NoError(t, err) + + t.Log("Create test sandbox with log directory") + sbConfig := PodSandboxConfig("sandbox", "test-symlink", + WithPodLogDirectory(testPodLogDir), + ) + sb, err := runtimeService.RunPodSandbox(sbConfig, *runtimeHandler) + require.NoError(t, err) + defer func() { + assert.NoError(t, runtimeService.StopPodSandbox(sb)) + assert.NoError(t, runtimeService.RemovePodSandbox(sb)) + }() + + var ( + testImage = GetImage(BusyBox) + containerName = "test-container" + ) + + EnsureImageExists(t, testImage) + + t.Log("Create a container with a symlink volume mount") + cnConfig := ContainerConfig( + containerName, + testImage, + WithCommand("cat", "/mounted_file"), + WithLogPath(containerName), + WithVolumeMount(file, "/mounted_file"), + ) + + cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig) + require.NoError(t, err) + + t.Log("Start the container") + require.NoError(t, runtimeService.StartContainer(cn)) + + t.Log("Wait for container to finish running") + require.NoError(t, Eventually(func() (bool, error) { + s, err := runtimeService.ContainerStatus(cn) + if err != nil { + return false, err + } + if s.GetState() == runtime.ContainerState_CONTAINER_EXITED { + return true, nil + } + return false, nil + }, time.Second, 30*time.Second)) + + output, err := ioutil.ReadFile(filepath.Join(testPodLogDir, containerName)) + assert.NoError(t, err) + + assert.Contains(t, string(output), content) + }) + } +} diff --git a/integration/main_test.go b/integration/main_test.go index cfd3d7fc2..5f247290b 100644 --- a/integration/main_test.go +++ b/integration/main_test.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "strconv" "strings" "testing" @@ -206,6 +207,15 @@ func WithResources(r *runtime.LinuxContainerResources) ContainerOpts { //nolint: } } +func WithVolumeMount(hostPath, containerPath string) ContainerOpts { + return func(c *runtime.ContainerConfig) { + hostPath, _ = filepath.Abs(hostPath) + containerPath, _ = filepath.Abs(containerPath) + mount := &runtime.Mount{HostPath: hostPath, ContainerPath: containerPath} + c.Mounts = append(c.Mounts, mount) + } +} + // Add container command. func WithCommand(cmd string, args ...string) ContainerOpts { return func(c *runtime.ContainerConfig) {