Merge pull request #6275 from gabriel-samfira/enable-volume-ownership-test

Integration: Enable TestVolumeOwnership on Windows
This commit is contained in:
Derek McGowan 2021-12-14 09:33:24 -08:00 committed by GitHub
commit 0def0dbecb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 18 deletions

View File

@ -18,13 +18,22 @@ package integration
import ( import (
"fmt" "fmt"
"io/ioutil"
"os"
"path/filepath"
goruntime "runtime" goruntime "runtime"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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) { func TestVolumeCopyUp(t *testing.T) {
@ -62,12 +71,14 @@ func TestVolumeCopyUp(t *testing.T) {
assert.Equal(t, "test_content\n", string(stdout)) assert.Equal(t, "test_content\n", string(stdout))
t.Logf("Check host path of the volume") t.Logf("Check host path of the volume")
// Windows paths might have spaces in them (e.g.: Program Files), which would volumePaths, err := getHostPathForVolumes(*criRoot, cn)
// 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()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "test_content\n", string(output)) 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(contents))
t.Logf("Update volume from inside the container") t.Logf("Update volume from inside the container")
_, _, err = runtimeService.ExecSync(cn, []string{ _, _, err = runtimeService.ExecSync(cn, []string{
@ -78,15 +89,12 @@ func TestVolumeCopyUp(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Logf("Check whether host path of the volume is updated") 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) 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) { func TestVolumeOwnership(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("Skipped on Windows.")
}
var ( var (
testImage = GetImage(VolumeOwnership) testImage = GetImage(VolumeOwnership)
execTimeout = time.Minute execTimeout = time.Minute
@ -101,7 +109,7 @@ func TestVolumeOwnership(t *testing.T) {
cnConfig := ContainerConfig( cnConfig := ContainerConfig(
"container", "container",
testImage, testImage,
WithCommand("tail", "-f", "/dev/null"), WithCommand("sleep", "150"),
) )
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig) cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
require.NoError(t, err) require.NoError(t, err)
@ -111,17 +119,59 @@ func TestVolumeOwnership(t *testing.T) {
// ghcr.io/containerd/volume-ownership:2.1 contains a test_dir // ghcr.io/containerd/volume-ownership:2.1 contains a test_dir
// volume, which is owned by nobody:nogroup. // 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") t.Logf("Check ownership of test directory inside container")
stdout, stderr, err := runtimeService.ExecSync(cn, []string{
cmd := []string{
"stat", "-c", "%U:%G", "/test_dir", "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) require.NoError(t, err)
assert.Empty(t, stderr) 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") 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) volumePaths, err := getHostPathForVolumes(*criRoot, cn)
output, err := exec.Command("sh", "-c", hostCmd).CombinedOutput()
require.NoError(t, err) 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
} }

View File

@ -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
}

View File

@ -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
}