Merge pull request #1511 from kevpar/named-pipe-mounts

Support named pipe mounts for Windows containers
This commit is contained in:
Mike Brown 2020-06-25 15:31:38 -05:00 committed by GitHub
commit 09d6426f33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 17 deletions

View File

@ -22,6 +22,7 @@ import (
"context" "context"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci" "github.com/containerd/containerd/oci"
@ -47,6 +48,20 @@ func WithWindowsNetworkNamespace(path string) oci.SpecOpts {
} }
} }
// namedPipePath returns true if the given path is to a named pipe.
func namedPipePath(p string) bool {
return strings.HasPrefix(p, `\\.\pipe\`)
}
// cleanMount returns a cleaned version of the mount path. The input is returned
// as-is if it is a named pipe path.
func cleanMount(p string) string {
if namedPipePath(p) {
return p
}
return filepath.Clean(p)
}
// WithWindowsMounts sorts and adds runtime and CRI mounts to the spec for // WithWindowsMounts sorts and adds runtime and CRI mounts to the spec for
// windows container. // windows container.
func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount) oci.SpecOpts { func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount) oci.SpecOpts {
@ -62,7 +77,7 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
for _, e := range extra { for _, e := range extra {
found := false found := false
for _, c := range criMounts { for _, c := range criMounts {
if filepath.Clean(e.ContainerPath) == filepath.Clean(c.ContainerPath) { if cleanMount(e.ContainerPath) == cleanMount(c.ContainerPath) {
found = true found = true
break break
} }
@ -80,14 +95,14 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
// mounts overridden by supplied mount; // mounts overridden by supplied mount;
mountSet := make(map[string]struct{}) mountSet := make(map[string]struct{})
for _, m := range mounts { for _, m := range mounts {
mountSet[filepath.Clean(m.ContainerPath)] = struct{}{} mountSet[cleanMount(m.ContainerPath)] = struct{}{}
} }
defaultMounts := s.Mounts defaultMounts := s.Mounts
s.Mounts = nil s.Mounts = nil
for _, m := range defaultMounts { for _, m := range defaultMounts {
dst := filepath.Clean(m.Destination) dst := cleanMount(m.Destination)
if _, ok := mountSet[dst]; ok { if _, ok := mountSet[dst]; ok {
// filter out mount overridden by a supplied mount // filter out mount overridden by a supplied mount
continue continue
@ -100,18 +115,26 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
dst = mount.GetContainerPath() dst = mount.GetContainerPath()
src = mount.GetHostPath() src = mount.GetHostPath()
) )
// TODO(windows): Support special mount sources, e.g. named pipe. // In the case of a named pipe mount on Windows, don't stat the file
// Create the host path if it doesn't exist. // or do other operations that open it, as that could interfere with
// the listening process. filepath.Clean also breaks named pipe
// paths, so don't use it.
if !namedPipePath(src) {
if _, err := osi.Stat(src); err != nil { if _, err := osi.Stat(src); err != nil {
// If the source doesn't exist, return an error instead // If the source doesn't exist, return an error instead
// of creating the source. This aligns with Docker's // of creating the source. This aligns with Docker's
// behavior on windows. // behavior on windows.
return errors.Wrapf(err, "failed to stat %q", src) return errors.Wrapf(err, "failed to stat %q", src)
} }
src, err := osi.ResolveSymbolicLink(src) var err error
src, err = osi.ResolveSymbolicLink(src)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to resolve symlink %q", src) return errors.Wrapf(err, "failed to resolve symlink %q", src)
} }
// hcsshim requires clean path, especially '/' -> '\'.
src = filepath.Clean(src)
dst = filepath.Clean(dst)
}
var options []string var options []string
// NOTE(random-liu): we don't change all mounts to `ro` when root filesystem // NOTE(random-liu): we don't change all mounts to `ro` when root filesystem
@ -122,9 +145,8 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
options = append(options, "rw") options = append(options, "rw")
} }
s.Mounts = append(s.Mounts, runtimespec.Mount{ s.Mounts = append(s.Mounts, runtimespec.Mount{
// hcsshim requires clean path, especially '/' -> '\'. Source: src,
Source: filepath.Clean(src), Destination: dst,
Destination: filepath.Clean(dst),
Options: options, Options: options,
}) })
} }

View File

@ -167,3 +167,23 @@ func TestMountCleanPath(t *testing.T) {
specCheck(t, testID, testSandboxID, testPid, spec) specCheck(t, testID, testSandboxID, testPid, spec)
checkMount(t, spec.Mounts, "c:\\test\\host-path", "c:\\test\\container-path", "", []string{"rw"}, nil) checkMount(t, spec.Mounts, "c:\\test\\host-path", "c:\\test\\container-path", "", []string{"rw"}, nil)
} }
func TestMountNamedPipe(t *testing.T) {
testID := "test-id"
testSandboxID := "sandbox-id"
testContainerName := "container-name"
testPid := uint32(1234)
nsPath := "test-cni"
c := newTestCRIService()
containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
containerConfig.Mounts = append(containerConfig.Mounts, &runtime.Mount{
ContainerPath: `\\.\pipe\foo`,
HostPath: `\\.\pipe\foo`,
})
spec, err := c.containerSpec(testID, testSandboxID, testPid, nsPath, testContainerName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
assert.NoError(t, err)
assert.NotNil(t, spec)
specCheck(t, testID, testSandboxID, testPid, spec)
checkMount(t, spec.Mounts, `\\.\pipe\foo`, `\\.\pipe\foo`, "", []string{"rw"}, nil)
}