Skip to content

Commit 15e35c4

Browse files
author
John Howard
committed
Windows: Adds support for Hyper-V Containers
Signed-off-by: John Howard <[email protected]>
1 parent 2eaa25d commit 15e35c4

13 files changed

Lines changed: 192 additions & 20 deletions

daemon/container.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ func (container *Container) Start() (err error) {
269269
}
270270
}()
271271

272-
if err := container.Mount(); err != nil {
272+
if err := container.conditionalMountOnStart(); err != nil {
273273
return err
274274
}
275275

@@ -341,9 +341,7 @@ func (container *Container) cleanup() {
341341
logrus.Errorf("%s: Failed to umount ipc filesystems: %v", container.ID, err)
342342
}
343343

344-
if err := container.Unmount(); err != nil {
345-
logrus.Errorf("%s: Failed to umount filesystem: %v", container.ID, err)
346-
}
344+
container.conditionalUnmountOnCleanup()
347345

348346
for _, eConfig := range container.execCommands.s {
349347
container.daemon.unregisterExecCommand(eConfig)

daemon/container_unix.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,3 +1433,20 @@ func (container *Container) ipcMounts() []execdriver.Mount {
14331433
func detachMounted(path string) error {
14341434
return syscall.Unmount(path, syscall.MNT_DETACH)
14351435
}
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+
}

daemon/container_windows.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package daemon
55
import (
66
"strings"
77

8+
"github.com/Sirupsen/logrus"
89
"github.com/docker/docker/daemon/execdriver"
910
derr "github.com/docker/docker/errors"
1011
"github.com/docker/docker/volume"
@@ -144,6 +145,7 @@ func populateCommand(c *Container, env []string) error {
144145
LayerFolder: layerFolder,
145146
LayerPaths: layerPaths,
146147
Hostname: c.Config.Hostname,
148+
Isolated: c.hostConfig.Isolation.IsHyperV(),
147149
}
148150

149151
return nil
@@ -194,3 +196,26 @@ func (container *Container) ipcMounts() []execdriver.Mount {
194196
func getDefaultRouteMtu() (int, error) {
195197
return -1, errSystemNotSupported
196198
}
199+
200+
// conditionalMountOnStart is a platform specific helper function during the
201+
// container start to call mount.
202+
func (container *Container) conditionalMountOnStart() error {
203+
// We do not mount if a Hyper-V container
204+
if !container.hostConfig.Isolation.IsHyperV() {
205+
if err := container.Mount(); err != nil {
206+
return err
207+
}
208+
}
209+
return nil
210+
}
211+
212+
// conditionalUnmountOnCleanup is a platform specific helper function called
213+
// during the cleanup of a container to unmount.
214+
func (container *Container) conditionalUnmountOnCleanup() {
215+
// We do not unmount if a Hyper-V container
216+
if !container.hostConfig.Isolation.IsHyperV() {
217+
if err := container.Unmount(); err != nil {
218+
logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
219+
}
220+
}
221+
}

daemon/execdriver/driver.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,5 @@ type Command struct {
210210
LayerPaths []string `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
211211
LayerFolder string `json:"layer_folder"`
212212
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
213214
}

daemon/execdriver/windows/run.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ type containerInit struct {
7777
ProcessorWeight int64 // CPU Shares 1..9 on Windows; or 0 is platform default.
7878
HostName string // Hostname
7979
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
8082
}
8183

8284
// 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
108110
LayerFolderPath: c.LayerFolder,
109111
ProcessorWeight: c.Resources.CPUShares,
110112
HostName: c.Hostname,
113+
HvPartition: c.Isolated,
114+
}
115+
116+
if c.Isolated {
117+
cu.SandboxPath = filepath.Dir(c.LayerFolder)
118+
} else {
119+
cu.VolumePath = c.Rootfs
120+
cu.LayerFolderPath = c.LayerFolder
111121
}
112122

113123
for _, layerPath := range c.LayerPaths {

runconfig/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
7575
return nil, nil, err
7676
}
7777

78+
// Validate the isolation level
79+
if err := ValidateIsolationLevel(hc); err != nil {
80+
return nil, nil, err
81+
}
7882
return w.Config, hc, nil
7983
}
8084

runconfig/config_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package runconfig
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"fmt"
67
"io/ioutil"
78
"runtime"
9+
"strings"
810
"testing"
911

1012
"github.com/docker/docker/pkg/stringutils"
@@ -60,3 +62,58 @@ func TestDecodeContainerConfig(t *testing.T) {
6062
}
6163
}
6264
}
65+
66+
// TestDecodeContainerConfigIsolation validates the isolation level passed
67+
// to the daemon in the hostConfig structure. Note this is platform specific
68+
// as to what level of container isolation is supported.
69+
func TestDecodeContainerConfigIsolation(t *testing.T) {
70+
71+
// An invalid isolation level
72+
if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
73+
if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
74+
t.Fatal(err)
75+
}
76+
}
77+
78+
// Blank isolation level (== default)
79+
if _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
80+
t.Fatal("Blank isolation should have succeeded")
81+
}
82+
83+
// Default isolation level
84+
if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
85+
t.Fatal("default isolation should have succeeded")
86+
}
87+
88+
// Hyper-V Containers isolation level (Valid on Windows only)
89+
if runtime.GOOS == "windows" {
90+
if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
91+
t.Fatal("hyperv isolation should have succeeded")
92+
}
93+
} else {
94+
if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
95+
if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
96+
t.Fatal(err)
97+
}
98+
}
99+
}
100+
}
101+
102+
// callDecodeContainerConfigIsolation is a utility function to call
103+
// DecodeContainerConfig for validating isolation levels
104+
func callDecodeContainerConfigIsolation(isolation string) (*Config, *HostConfig, error) {
105+
var (
106+
b []byte
107+
err error
108+
)
109+
w := ContainerConfigWrapper{
110+
Config: &Config{},
111+
HostConfig: &HostConfig{
112+
NetworkMode: "none",
113+
Isolation: IsolationLevel(isolation)},
114+
}
115+
if b, err = json.Marshal(w); err != nil {
116+
return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
117+
}
118+
return DecodeContainerConfig(bytes.NewReader(b))
119+
}

runconfig/hostconfig.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ type KeyValuePair struct {
1919
// NetworkMode represents the container network stack.
2020
type NetworkMode string
2121

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+
2232
// IpcMode represents the container ipc stack.
2333
type IpcMode string
2434

@@ -254,6 +264,7 @@ type HostConfig struct {
254264
CgroupParent string // Parent cgroup.
255265
ConsoleSize [2]int // Initial console size on Windows
256266
VolumeDriver string // Name of the volume driver used to mount volumes
267+
Isolation IsolationLevel // Isolation level of the container (eg default, hyperv)
257268
}
258269

259270
// DecodeHostConfig creates a HostConfig based on the specified Reader.

runconfig/hostconfig_unix.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import (
66
"strings"
77
)
88

9+
// IsValid indicates is an isolation level is valid
10+
func (i IsolationLevel) IsValid() bool {
11+
return i.IsDefault()
12+
}
13+
914
// IsPrivate indicates whether container uses it's private network stack.
1015
func (n NetworkMode) IsPrivate() bool {
1116
return !(n.IsHost() || n.IsContainer())

runconfig/hostconfig_windows.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
package runconfig
22

3+
import "strings"
4+
35
// IsDefault indicates whether container uses the default network stack.
46
func (n NetworkMode) IsDefault() bool {
57
return n == "default"
68
}
79

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+
821
// DefaultDaemonNetworkMode returns the default network stack the daemon should
922
// use.
1023
func DefaultDaemonNetworkMode() NetworkMode {

0 commit comments

Comments
 (0)