Support named pipe mounts for Windows containers

Adds support to mount named pipes into Windows containers. This support
already exists in hcsshim, so this change just passes them through
correctly in cri. Named pipe mounts must start with "\\.\pipe\".

Signed-off-by: Kevin Parsons <kevpar@microsoft.com>
This commit is contained in:
Kevin Parsons 2020-06-22 13:29:35 -07:00
parent 682d158399
commit 210561a8e3
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)
}