Browse code

Windows: Adds support for Hyper-V Containers

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2015/09/19 10:21:57
Showing 13 changed files
... ...
@@ -269,7 +269,7 @@ func (container *Container) Start() (err error) {
269 269
 		}
270 270
 	}()
271 271
 
272
-	if err := container.Mount(); err != nil {
272
+	if err := container.conditionalMountOnStart(); err != nil {
273 273
 		return err
274 274
 	}
275 275
 
... ...
@@ -341,9 +341,7 @@ func (container *Container) cleanup() {
341 341
 		logrus.Errorf("%s: Failed to umount ipc filesystems: %v", container.ID, err)
342 342
 	}
343 343
 
344
-	if err := container.Unmount(); err != nil {
345
-		logrus.Errorf("%s: Failed to umount filesystem: %v", container.ID, err)
346
-	}
344
+	container.conditionalUnmountOnCleanup()
347 345
 
348 346
 	for _, eConfig := range container.execCommands.s {
349 347
 		container.daemon.unregisterExecCommand(eConfig)
... ...
@@ -1433,3 +1433,20 @@ func (container *Container) ipcMounts() []execdriver.Mount {
1433 1433
 func detachMounted(path string) error {
1434 1434
 	return syscall.Unmount(path, syscall.MNT_DETACH)
1435 1435
 }
1436
+
1437
+// conditionalMountOnStart is a platform specific helper function during the
1438
+// container start to call mount.
1439
+func (container *Container) conditionalMountOnStart() error {
1440
+	if err := container.Mount(); err != nil {
1441
+		return err
1442
+	}
1443
+	return nil
1444
+}
1445
+
1446
+// conditionalUnmountOnCleanup is a platform specific helper function called
1447
+// during the cleanup of a container to unmount.
1448
+func (container *Container) conditionalUnmountOnCleanup() {
1449
+	if err := container.Unmount(); err != nil {
1450
+		logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
1451
+	}
1452
+}
... ...
@@ -5,6 +5,7 @@ package daemon
5 5
 import (
6 6
 	"strings"
7 7
 
8
+	"github.com/Sirupsen/logrus"
8 9
 	"github.com/docker/docker/daemon/execdriver"
9 10
 	derr "github.com/docker/docker/errors"
10 11
 	"github.com/docker/docker/volume"
... ...
@@ -144,6 +145,7 @@ func populateCommand(c *Container, env []string) error {
144 144
 		LayerFolder:    layerFolder,
145 145
 		LayerPaths:     layerPaths,
146 146
 		Hostname:       c.Config.Hostname,
147
+		Isolated:       c.hostConfig.Isolation.IsHyperV(),
147 148
 	}
148 149
 
149 150
 	return nil
... ...
@@ -194,3 +196,26 @@ func (container *Container) ipcMounts() []execdriver.Mount {
194 194
 func getDefaultRouteMtu() (int, error) {
195 195
 	return -1, errSystemNotSupported
196 196
 }
197
+
198
+// conditionalMountOnStart is a platform specific helper function during the
199
+// container start to call mount.
200
+func (container *Container) conditionalMountOnStart() error {
201
+	// We do not mount if a Hyper-V container
202
+	if !container.hostConfig.Isolation.IsHyperV() {
203
+		if err := container.Mount(); err != nil {
204
+			return err
205
+		}
206
+	}
207
+	return nil
208
+}
209
+
210
+// conditionalUnmountOnCleanup is a platform specific helper function called
211
+// during the cleanup of a container to unmount.
212
+func (container *Container) conditionalUnmountOnCleanup() {
213
+	// We do not unmount if a Hyper-V container
214
+	if !container.hostConfig.Isolation.IsHyperV() {
215
+		if err := container.Unmount(); err != nil {
216
+			logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
217
+		}
218
+	}
219
+}
... ...
@@ -210,4 +210,5 @@ type Command struct {
210 210
 	LayerPaths         []string          `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
211 211
 	LayerFolder        string            `json:"layer_folder"`
212 212
 	Hostname           string            `json:"hostname"` // Windows sets the hostname in the execdriver
213
+	Isolated           bool              `json:"isolated"` // Windows: Isolated is a Hyper-V container rather than Windows Server Container
213 214
 }
... ...
@@ -77,6 +77,8 @@ type containerInit struct {
77 77
 	ProcessorWeight         int64       // CPU Shares 1..9 on Windows; or 0 is platform default.
78 78
 	HostName                string      // Hostname
79 79
 	MappedDirectories       []mappedDir // List of mapped directories (volumes/mounts)
80
+	SandboxPath             string      // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers)
81
+	HvPartition             bool        // True if it a Hyper-V Container
80 82
 }
81 83
 
82 84
 // defaultOwner is a tag passed to HCS to allow it to differentiate between
... ...
@@ -108,6 +110,14 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd
108 108
 		LayerFolderPath:         c.LayerFolder,
109 109
 		ProcessorWeight:         c.Resources.CPUShares,
110 110
 		HostName:                c.Hostname,
111
+		HvPartition:             c.Isolated,
112
+	}
113
+
114
+	if c.Isolated {
115
+		cu.SandboxPath = filepath.Dir(c.LayerFolder)
116
+	} else {
117
+		cu.VolumePath = c.Rootfs
118
+		cu.LayerFolderPath = c.LayerFolder
111 119
 	}
112 120
 
113 121
 	for _, layerPath := range c.LayerPaths {
... ...
@@ -75,6 +75,10 @@ func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
75 75
 		return nil, nil, err
76 76
 	}
77 77
 
78
+	// Validate the isolation level
79
+	if err := ValidateIsolationLevel(hc); err != nil {
80
+		return nil, nil, err
81
+	}
78 82
 	return w.Config, hc, nil
79 83
 }
80 84
 
... ...
@@ -2,9 +2,11 @@ package runconfig
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"encoding/json"
5 6
 	"fmt"
6 7
 	"io/ioutil"
7 8
 	"runtime"
9
+	"strings"
8 10
 	"testing"
9 11
 
10 12
 	"github.com/docker/docker/pkg/stringutils"
... ...
@@ -60,3 +62,58 @@ func TestDecodeContainerConfig(t *testing.T) {
60 60
 		}
61 61
 	}
62 62
 }
63
+
64
+// TestDecodeContainerConfigIsolation validates the isolation level passed
65
+// to the daemon in the hostConfig structure. Note this is platform specific
66
+// as to what level of container isolation is supported.
67
+func TestDecodeContainerConfigIsolation(t *testing.T) {
68
+
69
+	// An invalid isolation level
70
+	if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
71
+		if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
72
+			t.Fatal(err)
73
+		}
74
+	}
75
+
76
+	// Blank isolation level (== default)
77
+	if _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
78
+		t.Fatal("Blank isolation should have succeeded")
79
+	}
80
+
81
+	// Default isolation level
82
+	if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
83
+		t.Fatal("default isolation should have succeeded")
84
+	}
85
+
86
+	// Hyper-V Containers isolation level (Valid on Windows only)
87
+	if runtime.GOOS == "windows" {
88
+		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
89
+			t.Fatal("hyperv isolation should have succeeded")
90
+		}
91
+	} else {
92
+		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
93
+			if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
94
+				t.Fatal(err)
95
+			}
96
+		}
97
+	}
98
+}
99
+
100
+// callDecodeContainerConfigIsolation is a utility function to call
101
+// DecodeContainerConfig for validating isolation levels
102
+func callDecodeContainerConfigIsolation(isolation string) (*Config, *HostConfig, error) {
103
+	var (
104
+		b   []byte
105
+		err error
106
+	)
107
+	w := ContainerConfigWrapper{
108
+		Config: &Config{},
109
+		HostConfig: &HostConfig{
110
+			NetworkMode: "none",
111
+			Isolation:   IsolationLevel(isolation)},
112
+	}
113
+	if b, err = json.Marshal(w); err != nil {
114
+		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
115
+	}
116
+	return DecodeContainerConfig(bytes.NewReader(b))
117
+}
... ...
@@ -19,6 +19,16 @@ type KeyValuePair struct {
19 19
 // NetworkMode represents the container network stack.
20 20
 type NetworkMode string
21 21
 
22
+// IsolationLevel represents the isolation level of a container. The supported
23
+// values are platform specific
24
+type IsolationLevel string
25
+
26
+// IsDefault indicates the default isolation level of a container. On Linux this
27
+// is LXC. On Windows, this is a Windows Server Container.
28
+func (i IsolationLevel) IsDefault() bool {
29
+	return strings.ToLower(string(i)) == "default" || string(i) == ""
30
+}
31
+
22 32
 // IpcMode represents the container ipc stack.
23 33
 type IpcMode string
24 34
 
... ...
@@ -254,6 +264,7 @@ type HostConfig struct {
254 254
 	CgroupParent      string                // Parent cgroup.
255 255
 	ConsoleSize       [2]int                // Initial console size on Windows
256 256
 	VolumeDriver      string                // Name of the volume driver used to mount volumes
257
+	Isolation         IsolationLevel        // Isolation level of the container (eg default, hyperv)
257 258
 }
258 259
 
259 260
 // DecodeHostConfig creates a HostConfig based on the specified Reader.
... ...
@@ -6,6 +6,11 @@ import (
6 6
 	"strings"
7 7
 )
8 8
 
9
+// IsValid indicates is an isolation level is valid
10
+func (i IsolationLevel) IsValid() bool {
11
+	return i.IsDefault()
12
+}
13
+
9 14
 // IsPrivate indicates whether container uses it's private network stack.
10 15
 func (n NetworkMode) IsPrivate() bool {
11 16
 	return !(n.IsHost() || n.IsContainer())
... ...
@@ -1,10 +1,23 @@
1 1
 package runconfig
2 2
 
3
+import "strings"
4
+
3 5
 // IsDefault indicates whether container uses the default network stack.
4 6
 func (n NetworkMode) IsDefault() bool {
5 7
 	return n == "default"
6 8
 }
7 9
 
10
+// IsHyperV indicates the use of Hyper-V Containers for isolation (as opposed
11
+// to Windows Server Containers
12
+func (i IsolationLevel) IsHyperV() bool {
13
+	return strings.ToLower(string(i)) == "hyperv"
14
+}
15
+
16
+// IsValid indicates is an isolation level is valid
17
+func (i IsolationLevel) IsValid() bool {
18
+	return i.IsDefault() || i.IsHyperV()
19
+}
20
+
8 21
 // DefaultDaemonNetworkMode returns the default network stack the daemon should
9 22
 // use.
10 23
 func DefaultDaemonNetworkMode() NetworkMode {
... ...
@@ -55,22 +55,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
55 55
 
56 56
 		flUlimits = opts.NewUlimitOpt(nil)
57 57
 
58
-		flPublish     = opts.NewListOpts(nil)
59
-		flExpose      = opts.NewListOpts(nil)
60
-		flDNS         = opts.NewListOpts(opts.ValidateIPAddress)
61
-		flDNSSearch   = opts.NewListOpts(opts.ValidateDNSSearch)
62
-		flDNSOptions  = opts.NewListOpts(nil)
63
-		flExtraHosts  = opts.NewListOpts(opts.ValidateExtraHost)
64
-		flVolumesFrom = opts.NewListOpts(nil)
65
-		flLxcOpts     = opts.NewListOpts(nil)
66
-		flEnvFile     = opts.NewListOpts(nil)
67
-		flCapAdd      = opts.NewListOpts(nil)
68
-		flCapDrop     = opts.NewListOpts(nil)
69
-		flGroupAdd    = opts.NewListOpts(nil)
70
-		flSecurityOpt = opts.NewListOpts(nil)
71
-		flLabelsFile  = opts.NewListOpts(nil)
72
-		flLoggingOpts = opts.NewListOpts(nil)
73
-
58
+		flPublish           = opts.NewListOpts(nil)
59
+		flExpose            = opts.NewListOpts(nil)
60
+		flDNS               = opts.NewListOpts(opts.ValidateIPAddress)
61
+		flDNSSearch         = opts.NewListOpts(opts.ValidateDNSSearch)
62
+		flDNSOptions        = opts.NewListOpts(nil)
63
+		flExtraHosts        = opts.NewListOpts(opts.ValidateExtraHost)
64
+		flVolumesFrom       = opts.NewListOpts(nil)
65
+		flLxcOpts           = opts.NewListOpts(nil)
66
+		flEnvFile           = opts.NewListOpts(nil)
67
+		flCapAdd            = opts.NewListOpts(nil)
68
+		flCapDrop           = opts.NewListOpts(nil)
69
+		flGroupAdd          = opts.NewListOpts(nil)
70
+		flSecurityOpt       = opts.NewListOpts(nil)
71
+		flLabelsFile        = opts.NewListOpts(nil)
72
+		flLoggingOpts       = opts.NewListOpts(nil)
74 73
 		flNetwork           = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
75 74
 		flPrivileged        = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
76 75
 		flPidMode           = cmd.String([]string{"-pid"}, "", "PID namespace to use")
... ...
@@ -104,6 +103,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
104 104
 		flCgroupParent      = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
105 105
 		flVolumeDriver      = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
106 106
 		flStopSignal        = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
107
+		flIsolation         = cmd.String([]string{"-isolation"}, "default", "Container isolation level")
107 108
 	)
108 109
 
109 110
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
... ...
@@ -377,6 +377,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
377 377
 		LogConfig:         LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
378 378
 		CgroupParent:      *flCgroupParent,
379 379
 		VolumeDriver:      *flVolumeDriver,
380
+		Isolation:         IsolationLevel(*flIsolation),
380 381
 	}
381 382
 
382 383
 	// When allocating stdin in attached mode, close stdin at client disconnect
... ...
@@ -4,6 +4,7 @@ package runconfig
4 4
 
5 5
 import (
6 6
 	"fmt"
7
+	"runtime"
7 8
 	"strings"
8 9
 )
9 10
 
... ...
@@ -58,3 +59,17 @@ func ValidateNetMode(c *Config, hc *HostConfig) error {
58 58
 	}
59 59
 	return nil
60 60
 }
61
+
62
+// ValidateIsolationLevel performs platform specific validation of the
63
+// isolation level in the hostconfig structure. Linux only supports "default"
64
+// which is LXC container isolation
65
+func ValidateIsolationLevel(hc *HostConfig) error {
66
+	// We may not be passed a host config, such as in the case of docker commit
67
+	if hc == nil {
68
+		return nil
69
+	}
70
+	if !hc.Isolation.IsValid() {
71
+		return fmt.Errorf("invalid --isolation: %q - %s only supports 'default'", hc.Isolation, runtime.GOOS)
72
+	}
73
+	return nil
74
+}
... ...
@@ -20,3 +20,18 @@ func ValidateNetMode(c *Config, hc *HostConfig) error {
20 20
 	}
21 21
 	return nil
22 22
 }
23
+
24
+// ValidateIsolationLevel performs platform specific validation of the
25
+// isolation level in the hostconfig structure. Windows supports 'default' (or
26
+// blank), and 'hyperv'. These refer to Windows Server Containers and
27
+// Hyper-V Containers respectively.
28
+func ValidateIsolationLevel(hc *HostConfig) error {
29
+	// We may not be passed a host config, such as in the case of docker commit
30
+	if hc == nil {
31
+		return nil
32
+	}
33
+	if !hc.Isolation.IsValid() {
34
+		return fmt.Errorf("invalid --isolation: %q. Windows supports 'default' (Windows Server Container) or 'hyperv' (Hyper-V Container)", hc.Isolation)
35
+	}
36
+	return nil
37
+}