258 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    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 (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"runtime"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/containerd/containerd/v2/integration/images"
 | |
| 	specs "github.com/opencontainers/runtime-spec/specs-go"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	v1 "k8s.io/cri-api/pkg/apis/runtime/v1"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	containerUserName = "ContainerUser"
 | |
| 	// containerUserSID is a well known SID that is set on the
 | |
| 	// ContainerUser username inside a Windows container.
 | |
| 	containerUserSID = "S-1-5-93-2-2"
 | |
| )
 | |
| 
 | |
| type volumeFile struct {
 | |
| 	fileName string
 | |
| 	contents string
 | |
| }
 | |
| 
 | |
| type containerVolume struct {
 | |
| 	containerPath string
 | |
| 	files         []volumeFile
 | |
| }
 | |
| 
 | |
| func TestVolumeCopyUp(t *testing.T) {
 | |
| 	var (
 | |
| 		testImage   = images.Get(images.VolumeCopyUp)
 | |
| 		execTimeout = time.Minute
 | |
| 	)
 | |
| 
 | |
| 	t.Logf("Create a sandbox")
 | |
| 	sb, sbConfig := PodSandboxConfigWithCleanup(t, "sandbox", "volume-copy-up")
 | |
| 
 | |
| 	EnsureImageExists(t, testImage)
 | |
| 
 | |
| 	t.Logf("Create a container with volume-copy-up test image")
 | |
| 	cnConfig := ContainerConfig(
 | |
| 		"container",
 | |
| 		testImage,
 | |
| 		WithCommand("sleep", "150"),
 | |
| 	)
 | |
| 	cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	t.Logf("Start the container")
 | |
| 	require.NoError(t, runtimeService.StartContainer(cn))
 | |
| 
 | |
| 	expectedVolumes := []containerVolume{
 | |
| 		{
 | |
| 			containerPath: "/test_dir",
 | |
| 			files: []volumeFile{
 | |
| 				{
 | |
| 					fileName: "test_file",
 | |
| 					contents: "test_content\n",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			containerPath: "/:colon_prefixed",
 | |
| 			files: []volumeFile{
 | |
| 				{
 | |
| 					fileName: "colon_prefixed_file",
 | |
| 					contents: "test_content\n",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			containerPath: "/C:/weird_test_dir",
 | |
| 			files: []volumeFile{
 | |
| 				{
 | |
| 					fileName: "weird_test_file",
 | |
| 					contents: "test_content\n",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	if runtime.GOOS == "windows" {
 | |
| 		expectedVolumes = []containerVolume{
 | |
| 			{
 | |
| 				containerPath: "C:\\test_dir",
 | |
| 				files: []volumeFile{
 | |
| 					{
 | |
| 						fileName: "test_file",
 | |
| 						contents: "test_content\n",
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			{
 | |
| 				containerPath: "D:",
 | |
| 				files:         []volumeFile{},
 | |
| 			},
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	volumeMappings, err := getContainerBindVolumes(t, cn)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	t.Logf("Check host path of the volume")
 | |
| 	for _, vol := range expectedVolumes {
 | |
| 		_, ok := volumeMappings[vol.containerPath]
 | |
| 		assert.Equalf(t, true, ok, "expected to find volume %s", vol.containerPath)
 | |
| 	}
 | |
| 
 | |
| 	// ghcr.io/containerd/volume-copy-up:2.2 contains 3 volumes on Linux and 2 volumes on Windows.
 | |
| 	// On linux, each of the volumes contains a single file, all with the same conrent. On Windows,
 | |
| 	// non C volumes defined in the image start out as empty.
 | |
| 	for _, vol := range expectedVolumes {
 | |
| 		files, err := os.ReadDir(volumeMappings[vol.containerPath])
 | |
| 		require.NoError(t, err)
 | |
| 		assert.Equal(t, len(vol.files), len(files))
 | |
| 
 | |
| 		for _, file := range vol.files {
 | |
| 			t.Logf("Check whether volume %s contains the test file %s", vol.containerPath, file.fileName)
 | |
| 			stdout, stderr, err := runtimeService.ExecSync(cn, []string{
 | |
| 				"cat",
 | |
| 				filepath.ToSlash(filepath.Join(vol.containerPath, file.fileName)),
 | |
| 			}, execTimeout)
 | |
| 			require.NoError(t, err)
 | |
| 			assert.Empty(t, stderr)
 | |
| 			assert.Equal(t, file.contents, string(stdout))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	testFilePath := filepath.Join(volumeMappings[expectedVolumes[0].containerPath], expectedVolumes[0].files[0].fileName)
 | |
| 	inContainerPath := filepath.Join(expectedVolumes[0].containerPath, expectedVolumes[0].files[0].fileName)
 | |
| 	contents, err := os.ReadFile(testFilePath)
 | |
| 	require.NoError(t, err)
 | |
| 	assert.Equal(t, "test_content\n", string(contents))
 | |
| 
 | |
| 	t.Logf("Update volume from inside the container")
 | |
| 	_, _, err = runtimeService.ExecSync(cn, []string{
 | |
| 		"sh",
 | |
| 		"-c",
 | |
| 		fmt.Sprintf("echo new_content > %s", filepath.ToSlash(inContainerPath)),
 | |
| 	}, execTimeout)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	t.Logf("Check whether host path of the volume is updated")
 | |
| 	contents, err = os.ReadFile(testFilePath)
 | |
| 	require.NoError(t, err)
 | |
| 	assert.Equal(t, "new_content\n", string(contents))
 | |
| }
 | |
| 
 | |
| func TestVolumeOwnership(t *testing.T) {
 | |
| 	var (
 | |
| 		testImage   = images.Get(images.VolumeOwnership)
 | |
| 		execTimeout = time.Minute
 | |
| 	)
 | |
| 
 | |
| 	t.Logf("Create a sandbox")
 | |
| 	sb, sbConfig := PodSandboxConfigWithCleanup(t, "sandbox", "volume-ownership")
 | |
| 
 | |
| 	EnsureImageExists(t, testImage)
 | |
| 
 | |
| 	t.Logf("Create a container with volume-ownership test image")
 | |
| 	cnConfig := ContainerConfig(
 | |
| 		"container",
 | |
| 		testImage,
 | |
| 		WithCommand("sleep", "150"),
 | |
| 	)
 | |
| 	cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	t.Logf("Start the container")
 | |
| 	require.NoError(t, runtimeService.StartContainer(cn))
 | |
| 
 | |
| 	// ghcr.io/containerd/volume-ownership:2.1 contains a test_dir
 | |
| 	// volume, which is owned by 65534:65534 (nobody:nogroup, or nobody:nobody).
 | |
| 	// On Windows, the folder is situated in C:\volumes\test_dir and is owned
 | |
| 	// by ContainerUser (SID: S-1-5-93-2-2). A helper tool get_owner.exe should
 | |
| 	// exist inside the container that returns the owner in the form of USERNAME:SID.
 | |
| 	t.Logf("Check ownership of test directory inside container")
 | |
| 
 | |
| 	volumePath := "/test_dir"
 | |
| 	cmd := []string{
 | |
| 		"stat", "-c", "%u:%g", volumePath,
 | |
| 	}
 | |
| 	expectedContainerOutput := "65534:65534\n"
 | |
| 	expectedHostOutput := "65534:65534\n"
 | |
| 	if runtime.GOOS == "windows" {
 | |
| 		volumePath = "C:\\volumes\\test_dir"
 | |
| 		cmd = []string{
 | |
| 			"C:\\bin\\get_owner.exe",
 | |
| 			volumePath,
 | |
| 		}
 | |
| 		expectedContainerOutput = fmt.Sprintf("%s:%s", containerUserName, containerUserSID)
 | |
| 		// The username is unknown on the host, but we can still get the SID.
 | |
| 		expectedHostOutput = containerUserSID
 | |
| 	}
 | |
| 	stdout, stderr, err := runtimeService.ExecSync(cn, cmd, execTimeout)
 | |
| 	require.NoError(t, err)
 | |
| 	assert.Empty(t, stderr)
 | |
| 	assert.Equal(t, expectedContainerOutput, string(stdout))
 | |
| 
 | |
| 	t.Logf("Check ownership of test directory on the host")
 | |
| 	volumePaths, err := getContainerBindVolumes(t, cn)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	output, err := getOwnership(volumePaths[volumePath])
 | |
| 	require.NoError(t, err)
 | |
| 	assert.Equal(t, expectedHostOutput, output)
 | |
| }
 | |
| 
 | |
| func getContainerBindVolumes(t *testing.T, containerID string) (map[string]string, error) {
 | |
| 	client, err := RawRuntimeClient()
 | |
| 	require.NoError(t, err, "failed to get raw grpc runtime service client")
 | |
| 	request := &v1.ContainerStatusRequest{
 | |
| 		ContainerId: containerID,
 | |
| 		Verbose:     true,
 | |
| 	}
 | |
| 	response, err := client.ContainerStatus(context.TODO(), request)
 | |
| 	require.NoError(t, err)
 | |
| 	ret := make(map[string]string)
 | |
| 
 | |
| 	mounts := struct {
 | |
| 		RuntimeSpec struct {
 | |
| 			Mounts []specs.Mount `json:"mounts"`
 | |
| 		} `json:"runtimeSpec"`
 | |
| 	}{}
 | |
| 
 | |
| 	info := response.Info["info"]
 | |
| 	err = json.Unmarshal([]byte(info), &mounts)
 | |
| 	require.NoError(t, err)
 | |
| 	for _, mount := range mounts.RuntimeSpec.Mounts {
 | |
| 		ret[mount.Destination] = mount.Source
 | |
| 	}
 | |
| 	return ret, nil
 | |
| }
 | 
