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

Add WCOW sandbox mount support #1087

Merged
merged 2 commits into from
Sep 24, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/hcsoci/hcsdoc_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ func createMountsConfig(ctx context.Context, coi *createOptionsInternal) (*mount
return nil, err
}
mdv2.HostPath = uvmPath
} else if strings.HasPrefix(mount.Source, "sandbox://") {
// Convert to the path in the guest that was asked for.
mdv2.HostPath = convertToWCOWSandboxMountPath(mount.Source)
} else {
// vsmb mount
uvmPath, err := coi.HostingSystem.GetVSMBUvmPath(ctx, mount.Source, readOnly)
Expand Down
35 changes: 35 additions & 0 deletions internal/hcsoci/resources_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ package hcsoci
// Contains functions relating to a WCOW container, as opposed to a utility VM

import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/Microsoft/hcsshim/internal/cmd"
"github.com/Microsoft/hcsshim/internal/credentials"
"github.com/Microsoft/hcsshim/internal/devices"
"github.com/Microsoft/hcsshim/internal/layers"
Expand All @@ -23,6 +25,8 @@ import (
"github.com/pkg/errors"
)

const wcowSandboxMountPath = "C:\\SandboxMounts"

func allocateWindowsResources(ctx context.Context, coi *createOptionsInternal, r *resources.Resources, isSandbox bool) error {
if coi.Spec == nil || coi.Spec.Windows == nil || coi.Spec.Windows.LayerFolders == nil {
return errors.New("field 'Spec.Windows.Layerfolders' is not populated")
Expand Down Expand Up @@ -179,6 +183,32 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R
return errors.Wrapf(err, "adding SCSI EVD mount failed %+v", mount)
}
r.Add(scsiMount)
} else if strings.HasPrefix(mount.Source, "sandbox://") {
// Mounts that map to a path in the UVM are specified with a 'sandbox://' prefix.
//
// Example: sandbox:///a/dirInUvm destination:C:\\dirInContainer.
//
// so first convert to a path in the sandboxmounts path itself.
sandboxPath := convertToWCOWSandboxMountPath(mount.Source)

// Now we need to exec a process in the vm that will make these directories as theres
// no functionality in the Windows gcs to create an arbitrary directory.
//
// Create the directory, but also run dir afterwards regardless of if mkdir succeeded to handle the case where the directory already exists
// e.g. from a previous container specifying the same mount (and thus creating the same directory).
b := &bytes.Buffer{}
stderr, err := cmd.CreatePipeAndListen(b, false)
if err != nil {
return err
}
req := &cmd.CmdProcessRequest{
Args: []string{"cmd", "/c", "mkdir", sandboxPath, "&", "dir", sandboxPath},
Stderr: stderr,
}
exitCode, err := cmd.ExecInUvm(ctx, coi.HostingSystem, req)
if err != nil {
return errors.Wrapf(err, "failed to create sandbox mount directory in utility VM with exit code %d %q", exitCode, b.String())
}
} else {
if uvm.IsPipe(mount.Source) {
pipe, err := coi.HostingSystem.AddPipe(ctx, mount.Source)
Expand All @@ -201,3 +231,8 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R

return nil
}

func convertToWCOWSandboxMountPath(source string) string {
subPath := strings.TrimPrefix(source, "sandbox://")
return filepath.Join(wcowSandboxMountPath, subPath)
}
124 changes: 124 additions & 0 deletions test/cri-containerd/runpodsandbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,130 @@ func Test_RunPodSandbox_Mount_SandboxDir_LCOW(t *testing.T) {
//TODO: Parse the output of the exec command to make sure the uvm mount was successful
}

func Test_RunPodSandbox_Mount_SandboxDir_WCOW(t *testing.T) {
requireFeatures(t, featureWCOWHypervisor)

pullRequiredImages(t, []string{imageWindowsNanoserver})

client := newTestRuntimeClient(t)
ctx := context.Background()

sbRequest := getRunPodSandboxRequest(t, wcowHypervisorRuntimeHandler, nil)
podID := runPodSandbox(t, client, ctx, sbRequest)
defer removePodSandbox(t, client, ctx, podID)
defer stopPodSandbox(t, client, ctx, podID)

command := []string{
"cmd",
"/c",
"ping",
"-t",
"127.0.0.1",
}

mounts := []*runtime.Mount{
{
HostPath: "sandbox:///test",
ContainerPath: "C:\\test",
},
}
// Create 2 containers with sandbox mounts and verify both can write and see the others files
container1Name := t.Name() + "-Container-" + "1"
container1Id := createContainerInSandbox(t, client, ctx, podID, container1Name, imageWindowsNanoserver, command, nil, mounts, sbRequest.Config)
defer removeContainer(t, client, ctx, container1Id)

startContainer(t, client, ctx, container1Id)
defer stopContainer(t, client, ctx, container1Id)

execEcho := []string{
"cmd",
"/c",
"echo",
`"test"`,
">",
"C:\\test\\test.txt",
}
_, errorMsg, exitCode := execContainer(t, client, ctx, container1Id, execEcho)
if exitCode != 0 {
t.Fatalf("Exec into container failed with: %v and exit code: %d, %s", errorMsg, exitCode, container1Id)
}

container2Name := t.Name() + "-Container-" + "2"
container2Id := createContainerInSandbox(t, client, ctx, podID, container2Name, imageWindowsNanoserver, command, nil, mounts, sbRequest.Config)
defer removeContainer(t, client, ctx, container2Id)

startContainer(t, client, ctx, container2Id)
defer stopContainer(t, client, ctx, container2Id)

// Test that we can see the file made in the first container in the second one.
execDir := []string{
"cmd",
"/c",
"dir",
"C:\\test\\test.txt",
}
_, errorMsg, exitCode = execContainer(t, client, ctx, container2Id, execDir)
if exitCode != 0 {
t.Fatalf("Exec into container failed with: %v and exit code: %d, %s", errorMsg, exitCode, container2Id)
}
}

func Test_RunPodSandbox_Mount_SandboxDir_NoShare_WCOW(t *testing.T) {
requireFeatures(t, featureWCOWHypervisor)

pullRequiredImages(t, []string{imageWindowsNanoserver})

client := newTestRuntimeClient(t)
ctx := context.Background()

sbRequest := getRunPodSandboxRequest(t, wcowHypervisorRuntimeHandler, nil)
podID := runPodSandbox(t, client, ctx, sbRequest)
defer removePodSandbox(t, client, ctx, podID)
defer stopPodSandbox(t, client, ctx, podID)

command := []string{
"cmd",
"/c",
"ping",
"-t",
"127.0.0.1",
}

mounts := []*runtime.Mount{
{
HostPath: "sandbox:///test",
ContainerPath: "C:\\test",
},
}
// This test case is making sure that the sandbox mount doesn't show up in another container if not
// explicitly asked for. Make first container with the mount and another shortly after without.
container1Name := t.Name() + "-Container-" + "1"
container1Id := createContainerInSandbox(t, client, ctx, podID, container1Name, imageWindowsNanoserver, command, nil, mounts, sbRequest.Config)
defer removeContainer(t, client, ctx, container1Id)

startContainer(t, client, ctx, container1Id)
defer stopContainer(t, client, ctx, container1Id)

container2Name := t.Name() + "-Container-" + "2"
container2Id := createContainerInSandbox(t, client, ctx, podID, container2Name, imageWindowsNanoserver, command, nil, nil, sbRequest.Config)
defer removeContainer(t, client, ctx, container2Id)

startContainer(t, client, ctx, container2Id)
defer stopContainer(t, client, ctx, container2Id)

// Test that we can't see the file made in the first container in the second one.
execDir := []string{
"cmd",
"/c",
"dir",
"C:\\test\\",
}
output, _, exitCode := execContainer(t, client, ctx, container2Id, execDir)
if exitCode == 0 {
t.Fatalf("Found directory in second container when not expected: %s", output)
}
}

func Test_RunPodSandbox_CPUGroup(t *testing.T) {
testutilities.RequiresBuild(t, 20124)
ctx := context.Background()
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.