1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/volume/volume_test.go
John Starks 54354db850 Windows: Add named pipe mount support
Current insider builds of Windows have support for mounting individual
named pipe servers from the host to the guest. This allows, for example,
exposing the docker engine's named pipe to a container.

This change allows the user to request such a mount via the normal bind
mount syntax in the CLI:

  docker run -v \\.\pipe\docker_engine:\\.\pipe\docker_engine <args>

Signed-off-by: John Starks <jostarks@microsoft.com>
2017-08-07 11:34:36 -07:00

277 lines
12 KiB
Go

package volume
import (
"io/ioutil"
"os"
"runtime"
"strings"
"testing"
"github.com/docker/docker/api/types/mount"
)
func TestParseMountRaw(t *testing.T) {
var (
valid []string
invalid map[string]string
)
if runtime.GOOS == "windows" {
valid = []string{
`d:\`,
`d:`,
`d:\path`,
`d:\path with space`,
`c:\:d:\`,
`c:\windows\:d:`,
`c:\windows:d:\s p a c e`,
`c:\windows:d:\s p a c e:RW`,
`c:\program files:d:\s p a c e i n h o s t d i r`,
`0123456789name:d:`,
`MiXeDcAsEnAmE:d:`,
`name:D:`,
`name:D::rW`,
`name:D::RW`,
`name:D::RO`,
`c:/:d:/forward/slashes/are/good/too`,
`c:/:d:/including with/spaces:ro`,
`c:\Windows`, // With capital
`c:\Program Files (x86)`, // With capitals and brackets
}
invalid = map[string]string{
``: "invalid volume specification: ",
`.`: "invalid volume specification: ",
`..\`: "invalid volume specification: ",
`c:\:..\`: "invalid volume specification: ",
`c:\:d:\:xyzzy`: "invalid volume specification: ",
`c:`: "cannot be `c:`",
`c:\`: "cannot be `c:`",
`c:\notexist:d:`: `source path does not exist`,
`c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
`name<:d:`: `invalid volume specification`,
`name>:d:`: `invalid volume specification`,
`name::d:`: `invalid volume specification`,
`name":d:`: `invalid volume specification`,
`name\:d:`: `invalid volume specification`,
`name*:d:`: `invalid volume specification`,
`name|:d:`: `invalid volume specification`,
`name?:d:`: `invalid volume specification`,
`name/:d:`: `invalid volume specification`,
`d:\pathandmode:rw`: `invalid volume specification`,
`d:\pathandmode:ro`: `invalid volume specification`,
`con:d:`: `cannot be a reserved word for Windows filenames`,
`PRN:d:`: `cannot be a reserved word for Windows filenames`,
`aUx:d:`: `cannot be a reserved word for Windows filenames`,
`nul:d:`: `cannot be a reserved word for Windows filenames`,
`com1:d:`: `cannot be a reserved word for Windows filenames`,
`com2:d:`: `cannot be a reserved word for Windows filenames`,
`com3:d:`: `cannot be a reserved word for Windows filenames`,
`com4:d:`: `cannot be a reserved word for Windows filenames`,
`com5:d:`: `cannot be a reserved word for Windows filenames`,
`com6:d:`: `cannot be a reserved word for Windows filenames`,
`com7:d:`: `cannot be a reserved word for Windows filenames`,
`com8:d:`: `cannot be a reserved word for Windows filenames`,
`com9:d:`: `cannot be a reserved word for Windows filenames`,
`lpt1:d:`: `cannot be a reserved word for Windows filenames`,
`lpt2:d:`: `cannot be a reserved word for Windows filenames`,
`lpt3:d:`: `cannot be a reserved word for Windows filenames`,
`lpt4:d:`: `cannot be a reserved word for Windows filenames`,
`lpt5:d:`: `cannot be a reserved word for Windows filenames`,
`lpt6:d:`: `cannot be a reserved word for Windows filenames`,
`lpt7:d:`: `cannot be a reserved word for Windows filenames`,
`lpt8:d:`: `cannot be a reserved word for Windows filenames`,
`lpt9:d:`: `cannot be a reserved word for Windows filenames`,
`c:\windows\system32\ntdll.dll`: `Only directories can be mapped on this platform`,
}
} else {
valid = []string{
"/home",
"/home:/home",
"/home:/something/else",
"/with space",
"/home:/with space",
"relative:/absolute-path",
"hostPath:/containerPath:ro",
"/hostPath:/containerPath:rw",
"/rw:/ro",
}
invalid = map[string]string{
"": "invalid volume specification",
"./": "mount path must be absolute",
"../": "mount path must be absolute",
"/:../": "mount path must be absolute",
"/:path": "mount path must be absolute",
":": "invalid volume specification",
"/tmp:": "invalid volume specification",
":test": "invalid volume specification",
":/test": "invalid volume specification",
"tmp:": "invalid volume specification",
":test:": "invalid volume specification",
"::": "invalid volume specification",
":::": "invalid volume specification",
"/tmp:::": "invalid volume specification",
":/tmp::": "invalid volume specification",
"/path:rw": "invalid volume specification",
"/path:ro": "invalid volume specification",
"/rw:rw": "invalid volume specification",
"path:ro": "invalid volume specification",
"/path:/path:sw": `invalid mode`,
"/path:/path:rwz": `invalid mode`,
}
}
for _, path := range valid {
if _, err := ParseMountRaw(path, "local"); err != nil {
t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
}
}
for path, expectedError := range invalid {
if mp, err := ParseMountRaw(path, "local"); err == nil {
t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
} else {
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
}
}
}
}
// testParseMountRaw is a structure used by TestParseMountRawSplit for
// specifying test cases for the ParseMountRaw() function.
type testParseMountRaw struct {
bind string
driver string
expType mount.Type
expDest string
expSource string
expName string
expDriver string
expRW bool
fail bool
}
func TestParseMountRawSplit(t *testing.T) {
var cases []testParseMountRaw
if runtime.GOOS == "windows" {
cases = []testParseMountRaw{
{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
}
} else {
cases = []testParseMountRaw{
{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
}
}
for i, c := range cases {
t.Logf("case %d", i)
m, err := ParseMountRaw(c.bind, c.driver)
if c.fail {
if err == nil {
t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
}
continue
}
if m == nil || err != nil {
t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
continue
}
if m.Type != c.expType {
t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
}
if m.Destination != c.expDest {
t.Fatalf("Expected destination '%s', was '%s', for spec '%s'", c.expDest, m.Destination, c.bind)
}
if m.Source != c.expSource {
t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
}
if m.Name != c.expName {
t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
}
if m.Driver != c.expDriver {
t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
}
if m.RW != c.expRW {
t.Fatalf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
}
}
}
func TestParseMountSpec(t *testing.T) {
type c struct {
input mount.Mount
expected MountPoint
}
testDir, err := ioutil.TempDir("", "test-mount-config")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(testDir)
cases := []c{
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: DefaultPropagationMode}},
{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
}
for i, c := range cases {
t.Logf("case %d", i)
mp, err := ParseMountSpec(c.input)
if err != nil {
t.Fatal(err)
}
if c.expected.Type != mp.Type {
t.Fatalf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
}
if c.expected.Destination != mp.Destination {
t.Fatalf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
}
if c.expected.Source != mp.Source {
t.Fatalf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
}
if c.expected.RW != mp.RW {
t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
}
if c.expected.Propagation != mp.Propagation {
t.Fatalf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
}
if c.expected.Driver != mp.Driver {
t.Fatalf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
}
if c.expected.CopyData != mp.CopyData {
t.Fatalf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
}
}
}