Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration: Enable TestVolumeOwnership on Windows #6275

Merged
merged 2 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
86 changes: 68 additions & 18 deletions integration/volume_copy_up_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@ package integration

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
goruntime "runtime"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
exec "golang.org/x/sys/execabs"
)

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"
)

func TestVolumeCopyUp(t *testing.T) {
Expand Down Expand Up @@ -62,12 +71,14 @@ func TestVolumeCopyUp(t *testing.T) {
assert.Equal(t, "test_content\n", string(stdout))

t.Logf("Check host path of the volume")
// Windows paths might have spaces in them (e.g.: Program Files), which would
// cause issues for this command. This will allow us to bypass them.
hostCmd := fmt.Sprintf("find '%s/containers/%s/volumes/' -type f -print0 | xargs -0 cat", *criRoot, cn)
output, err := exec.Command("sh", "-c", hostCmd).CombinedOutput()
volumePaths, err := getHostPathForVolumes(*criRoot, cn)
require.NoError(t, err)
assert.Equal(t, len(volumePaths), 1, "expected exactly 1 volume")

testFilePath := filepath.Join(volumePaths[0], "test_file")
contents, err := ioutil.ReadFile(testFilePath)
require.NoError(t, err)
assert.Equal(t, "test_content\n", string(output))
assert.Equal(t, "test_content\n", string(contents))

t.Logf("Update volume from inside the container")
_, _, err = runtimeService.ExecSync(cn, []string{
Expand All @@ -78,15 +89,12 @@ func TestVolumeCopyUp(t *testing.T) {
require.NoError(t, err)

t.Logf("Check whether host path of the volume is updated")
output, err = exec.Command("sh", "-c", hostCmd).CombinedOutput()
contents, err = ioutil.ReadFile(testFilePath)
require.NoError(t, err)
assert.Equal(t, "new_content\n", string(output))
assert.Equal(t, "new_content\n", string(contents))
}

func TestVolumeOwnership(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("Skipped on Windows.")
}
var (
testImage = GetImage(VolumeOwnership)
execTimeout = time.Minute
Expand All @@ -101,7 +109,7 @@ func TestVolumeOwnership(t *testing.T) {
cnConfig := ContainerConfig(
"container",
testImage,
WithCommand("tail", "-f", "/dev/null"),
WithCommand("sleep", "150"),
)
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
require.NoError(t, err)
Expand All @@ -111,17 +119,59 @@ func TestVolumeOwnership(t *testing.T) {

// ghcr.io/containerd/volume-ownership:2.1 contains a test_dir
// volume, which is owned by nobody:nogroup.
// 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")
stdout, stderr, err := runtimeService.ExecSync(cn, []string{

cmd := []string{
"stat", "-c", "%U:%G", "/test_dir",
}, execTimeout)
}
expectedContainerOutput := "nobody:nogroup\n"
expectedHostOutput := "nobody:nogroup\n"
if goruntime.GOOS == "windows" {
cmd = []string{
"C:\\bin\\get_owner.exe",
"C:\\volumes\\test_dir",
}
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, "nobody:nogroup\n", string(stdout))
assert.Equal(t, expectedContainerOutput, string(stdout))

t.Logf("Check ownership of test directory on the host")
hostCmd := fmt.Sprintf("find %s/containers/%s/volumes/* | xargs stat -c %%U:%%G", *criRoot, cn)
output, err := exec.Command("sh", "-c", hostCmd).CombinedOutput()
volumePaths, err := getHostPathForVolumes(*criRoot, cn)
require.NoError(t, err)
assert.Equal(t, "nobody:nogroup\n", string(output))
assert.Equal(t, len(volumePaths), 1, "expected exactly 1 volume")

output, err := getOwnership(volumePaths[0])
require.NoError(t, err)
assert.Equal(t, expectedHostOutput, output)
}

func getHostPathForVolumes(criRoot, containerID string) ([]string, error) {
hostPath := filepath.Join(criRoot, "containers", containerID, "volumes")
if _, err := os.Stat(hostPath); err != nil {
return nil, err
}

volumes, err := ioutil.ReadDir(hostPath)
if err != nil {
return nil, err
}

if len(volumes) == 0 {
return []string{}, nil
}

volumePaths := make([]string, len(volumes))
for idx, volume := range volumes {
volumePaths[idx] = filepath.Join(hostPath, volume.Name())
}

return volumePaths, nil
}
35 changes: 35 additions & 0 deletions integration/volume_copy_up_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//go:build !windows
// +build !windows

/*
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 (
"fmt"

exec "golang.org/x/sys/execabs"
)

func getOwnership(path string) (string, error) {
hostCmd := fmt.Sprintf("stat -c %%U:%%G '%s'", path)
output, err := exec.Command("sh", "-c", hostCmd).CombinedOutput()
if err != nil {
return "", err
}
return string(output), nil
}
40 changes: 40 additions & 0 deletions integration/volume_copy_up_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//go:build windows
// +build windows

/*
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 (
"golang.org/x/sys/windows"
)

func getOwnership(path string) (string, error) {
secInfo, err := windows.GetNamedSecurityInfo(
path, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)

if err != nil {
return "", err
}

sid, _, err := secInfo.Owner()
if err != nil {
return "", err
}
return sid.String(), nil
}