Skip to content

Commit 94d70d8

Browse files
John Howardtonistiigi
John Howard
authored andcommittedMar 18, 2016
Windows libcontainerd implementation
Signed-off-by: John Howard <[email protected]> Signed-off-by: John Starks <[email protected]> Signed-off-by: Darren Stahl <[email protected]> Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 6eebe85 commit 94d70d8

22 files changed

+1419
-167
lines changed
 

‎container/container_windows.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os"
88
"path/filepath"
99

10-
"github.com/docker/docker/daemon/execdriver"
1110
"github.com/docker/docker/volume"
1211
containertypes "github.com/docker/engine-api/types/container"
1312
)
@@ -23,6 +22,12 @@ type Container struct {
2322
// Fields below here are platform specific.
2423
}
2524

25+
// ExitStatus provides exit reasons for a container.
26+
type ExitStatus struct {
27+
// The exit code with which the container exited.
28+
ExitCode int
29+
}
30+
2631
// CreateDaemonEnvironment creates a new environment variable slice for this container.
2732
func (container *Container) CreateDaemonEnvironment(linkedEnv []string) []string {
2833
// On Windows, nothing to link. Just return the container environment.
@@ -35,7 +40,7 @@ func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
3540
}
3641

3742
// IpcMounts returns the list of Ipc related mounts.
38-
func (container *Container) IpcMounts() []execdriver.Mount {
43+
func (container *Container) IpcMounts() []Mount {
3944
return nil
4045
}
4146

@@ -45,7 +50,7 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun
4550
}
4651

4752
// TmpfsMounts returns the list of tmpfs mounts
48-
func (container *Container) TmpfsMounts() []execdriver.Mount {
53+
func (container *Container) TmpfsMounts() []Mount {
4954
return nil
5055
}
5156

‎container/mounts_windows.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package container
2+
3+
// Mount contains information for a mount operation.
4+
type Mount struct {
5+
Source string `json:"source"`
6+
Destination string `json:"destination"`
7+
Writable bool `json:"writable"`
8+
}

‎container/state_windows.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package container
22

3-
import "github.com/docker/docker/daemon/execdriver"
4-
53
// setFromExitStatus is a platform specific helper function to set the state
64
// based on the ExitStatus structure.
7-
func (s *State) setFromExitStatus(exitStatus *execdriver.ExitStatus) {
5+
func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
86
s.ExitCode = exitStatus.ExitCode
97
}

‎daemon/config_windows.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import (
77
)
88

99
var (
10-
defaultPidFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker.pid"
11-
defaultGraph = os.Getenv("programdata") + string(os.PathSeparator) + "docker"
10+
defaultPidFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker.pid"
11+
defaultGraph = os.Getenv("programdata") + string(os.PathSeparator) + "docker"
12+
defaultExecRoot = defaultGraph
1213
)
1314

1415
// bridgeConfig stores all the bridge driver specific

‎daemon/container_operations_windows.go

+1-135
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,9 @@ package daemon
44

55
import (
66
"fmt"
7-
"strings"
8-
9-
networktypes "github.com/docker/engine-api/types/network"
107

118
"github.com/docker/docker/container"
12-
"github.com/docker/docker/daemon/execdriver"
13-
"github.com/docker/docker/daemon/execdriver/windows"
14-
"github.com/docker/docker/layer"
9+
networktypes "github.com/docker/engine-api/types/network"
1510
"github.com/docker/libnetwork"
1611
)
1712

@@ -29,135 +24,6 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n li
2924
return fmt.Errorf("Windows does not support disconnecting a running container from a network")
3025
}
3126

32-
func (daemon *Daemon) populateCommand(c *container.Container, env []string) error {
33-
en := &execdriver.Network{
34-
Interface: nil,
35-
}
36-
37-
var epList []string
38-
39-
// Connect all the libnetwork allocated networks to the container
40-
if c.NetworkSettings != nil {
41-
for n := range c.NetworkSettings.Networks {
42-
sn, err := daemon.FindNetwork(n)
43-
if err != nil {
44-
continue
45-
}
46-
47-
ep, err := c.GetEndpointInNetwork(sn)
48-
if err != nil {
49-
continue
50-
}
51-
52-
data, err := ep.DriverInfo()
53-
if err != nil {
54-
continue
55-
}
56-
if data["hnsid"] != nil {
57-
epList = append(epList, data["hnsid"].(string))
58-
}
59-
}
60-
}
61-
62-
if daemon.netController == nil {
63-
parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2)
64-
switch parts[0] {
65-
case "none":
66-
case "default", "": // empty string to support existing containers
67-
if !c.Config.NetworkDisabled {
68-
en.Interface = &execdriver.NetworkInterface{
69-
MacAddress: c.Config.MacAddress,
70-
Bridge: daemon.configStore.bridgeConfig.Iface,
71-
PortBindings: c.HostConfig.PortBindings,
72-
73-
// TODO Windows. Include IPAddress. There already is a
74-
// property IPAddress on execDrive.CommonNetworkInterface,
75-
// but there is no CLI option in docker to pass through
76-
// an IPAddress on docker run.
77-
}
78-
}
79-
default:
80-
return fmt.Errorf("invalid network mode: %s", c.HostConfig.NetworkMode)
81-
}
82-
}
83-
84-
// TODO Windows. More resource controls to be implemented later.
85-
resources := &execdriver.Resources{
86-
CommonResources: execdriver.CommonResources{
87-
CPUShares: c.HostConfig.CPUShares,
88-
},
89-
}
90-
91-
processConfig := execdriver.ProcessConfig{
92-
CommonProcessConfig: execdriver.CommonProcessConfig{
93-
Entrypoint: c.Path,
94-
Arguments: c.Args,
95-
Tty: c.Config.Tty,
96-
},
97-
ConsoleSize: c.HostConfig.ConsoleSize,
98-
}
99-
100-
processConfig.Env = env
101-
102-
var layerPaths []string
103-
img, err := daemon.imageStore.Get(c.ImageID)
104-
if err != nil {
105-
return fmt.Errorf("Failed to graph.Get on ImageID %s - %s", c.ImageID, err)
106-
}
107-
108-
if img.RootFS != nil && img.RootFS.Type == "layers+base" {
109-
max := len(img.RootFS.DiffIDs)
110-
for i := 0; i <= max; i++ {
111-
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
112-
path, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID())
113-
if err != nil {
114-
return fmt.Errorf("Failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err)
115-
}
116-
// Reverse order, expecting parent most first
117-
layerPaths = append([]string{path}, layerPaths...)
118-
}
119-
}
120-
121-
m, err := c.RWLayer.Metadata()
122-
if err != nil {
123-
return fmt.Errorf("Failed to get layer metadata - %s", err)
124-
}
125-
layerFolder := m["dir"]
126-
127-
var hvPartition bool
128-
// Work out the isolation (whether it is a hypervisor partition)
129-
if c.HostConfig.Isolation.IsDefault() {
130-
// Not specified by caller. Take daemon default
131-
hvPartition = windows.DefaultIsolation.IsHyperV()
132-
} else {
133-
// Take value specified by caller
134-
hvPartition = c.HostConfig.Isolation.IsHyperV()
135-
}
136-
137-
c.Command = &execdriver.Command{
138-
CommonCommand: execdriver.CommonCommand{
139-
ID: c.ID,
140-
Rootfs: c.BaseFS,
141-
WorkingDir: c.Config.WorkingDir,
142-
Network: en,
143-
MountLabel: c.GetMountLabel(),
144-
Resources: resources,
145-
ProcessConfig: processConfig,
146-
ProcessLabel: c.GetProcessLabel(),
147-
},
148-
FirstStart: !c.HasBeenStartedBefore,
149-
LayerFolder: layerFolder,
150-
LayerPaths: layerPaths,
151-
Hostname: c.Config.Hostname,
152-
Isolation: string(c.HostConfig.Isolation),
153-
ArgsEscaped: c.Config.ArgsEscaped,
154-
HvPartition: hvPartition,
155-
EpList: epList,
156-
}
157-
158-
return nil
159-
}
160-
16127
// getSize returns real size & virtual size
16228
func (daemon *Daemon) getSize(container *container.Container) (int64, int64) {
16329
// TODO Windows

‎daemon/daemon_windows.go

+59-9
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import (
1818
"github.com/docker/docker/layer"
1919
"github.com/docker/docker/reference"
2020
"github.com/docker/docker/runconfig"
21-
containertypes "github.com/docker/engine-api/types/container"
2221
// register the windows graph driver
2322
"github.com/docker/docker/daemon/graphdriver/windows"
2423
"github.com/docker/docker/pkg/idtools"
24+
"github.com/docker/docker/pkg/parsers"
2525
"github.com/docker/docker/pkg/system"
26+
"github.com/docker/engine-api/types"
27+
containertypes "github.com/docker/engine-api/types/container"
2628
"github.com/docker/libnetwork"
2729
nwconfig "github.com/docker/libnetwork/config"
2830
winlibnetwork "github.com/docker/libnetwork/drivers/windows"
@@ -39,27 +41,27 @@ const (
3941
windowsMaxCPUShares = 10000
4042
)
4143

42-
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) {
44+
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]blkiodev.WeightDevice, error) {
4345
return nil, nil
4446
}
4547

4648
func parseSecurityOpt(container *container.Container, config *containertypes.HostConfig) error {
4749
return nil
4850
}
4951

50-
func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
52+
func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
5153
return nil, nil
5254
}
5355

54-
func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
56+
func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
5557
return nil, nil
5658
}
5759

58-
func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
60+
func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
5961
return nil, nil
6062
}
6163

62-
func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
64+
func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
6365
return nil, nil
6466
}
6567

@@ -287,6 +289,10 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
287289
return nil
288290
}
289291

292+
func (daemon *Daemon) cleanupMountsByID(in string) error {
293+
return nil
294+
}
295+
290296
func (daemon *Daemon) cleanupMounts() error {
291297
return nil
292298
}
@@ -307,8 +313,19 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error
307313
// conditionalMountOnStart is a platform specific helper function during the
308314
// container start to call mount.
309315
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
316+
317+
// Are we going to run as a Hyper-V container?
318+
hv := false
319+
if container.HostConfig.Isolation.IsDefault() {
320+
// Container is set to use the default, so take the default from the daemon configuration
321+
hv = daemon.defaultIsolation.IsHyperV()
322+
} else {
323+
// Container is requesting an isolation mode. Honour it.
324+
hv = container.HostConfig.Isolation.IsHyperV()
325+
}
326+
310327
// We do not mount if a Hyper-V container
311-
if !container.HostConfig.Isolation.IsHyperV() {
328+
if !hv {
312329
if err := daemon.Mount(container); err != nil {
313330
return err
314331
}
@@ -318,11 +335,12 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
318335

319336
// conditionalUnmountOnCleanup is a platform specific helper function called
320337
// during the cleanup of a container to unmount.
321-
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) {
338+
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
322339
// We do not unmount if a Hyper-V container
323340
if !container.HostConfig.Isolation.IsHyperV() {
324-
daemon.Unmount(container)
341+
return daemon.Unmount(container)
325342
}
343+
return nil
326344
}
327345

328346
func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error {
@@ -404,3 +422,35 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro
404422
func driverOptions(config *Config) []nwconfig.Option {
405423
return []nwconfig.Option{}
406424
}
425+
426+
func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
427+
return nil, nil
428+
}
429+
430+
// setDefaultIsolation determine the default isolation mode for the
431+
// daemon to run in. This is only applicable on Windows
432+
func (daemon *Daemon) setDefaultIsolation() error {
433+
daemon.defaultIsolation = containertypes.Isolation("process")
434+
for _, option := range daemon.configStore.ExecOptions {
435+
key, val, err := parsers.ParseKeyValueOpt(option)
436+
if err != nil {
437+
return err
438+
}
439+
key = strings.ToLower(key)
440+
switch key {
441+
442+
case "isolation":
443+
if !containertypes.Isolation(val).IsValid() {
444+
return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val)
445+
}
446+
if containertypes.Isolation(val).IsHyperV() {
447+
daemon.defaultIsolation = containertypes.Isolation("hyperv")
448+
}
449+
default:
450+
return fmt.Errorf("Unrecognised exec-opt '%s'\n", key)
451+
}
452+
}
453+
454+
logrus.Infof("Windows default isolation mode: %s", daemon.defaultIsolation)
455+
return nil
456+
}

‎daemon/exec_windows.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package daemon
22

33
import (
44
"github.com/docker/docker/container"
5-
"github.com/docker/docker/daemon/execdriver"
6-
"github.com/docker/engine-api/types"
5+
"github.com/docker/docker/daemon/exec"
6+
"github.com/docker/docker/libcontainerd"
77
)
88

9-
// setPlatformSpecificExecProcessConfig sets platform-specific fields in the
10-
// ProcessConfig structure. This is a no-op on Windows
11-
func setPlatformSpecificExecProcessConfig(config *types.ExecConfig, container *container.Container, pc *execdriver.ProcessConfig) {
9+
func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error {
10+
// Process arguments need to be escaped before sending to OCI.
11+
// TODO (jstarks): escape the entrypoint too once the tests are fixed to not rely on this behavior
12+
p.Args = append([]string{p.Args[0]}, escapeArgs(p.Args[1:])...)
13+
return nil
1214
}

‎daemon/inspect_windows.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ func (daemon *Daemon) containerInspectPre120(name string) (*types.ContainerJSON,
3333

3434
func inspectExecProcessConfig(e *exec.Config) *backend.ExecProcessConfig {
3535
return &backend.ExecProcessConfig{
36-
Tty: e.ProcessConfig.Tty,
37-
Entrypoint: e.ProcessConfig.Entrypoint,
38-
Arguments: e.ProcessConfig.Arguments,
36+
Tty: e.Tty,
37+
Entrypoint: e.Entrypoint,
38+
Arguments: e.Args,
3939
}
4040
}

‎daemon/monitor_windows.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package daemon
2+
3+
import (
4+
"github.com/docker/docker/container"
5+
"github.com/docker/docker/libcontainerd"
6+
)
7+
8+
// platformConstructExitStatus returns a platform specific exit status structure
9+
func platformConstructExitStatus(e libcontainerd.StateInfo) *container.ExitStatus {
10+
return &container.ExitStatus{
11+
ExitCode: int(e.ExitCode),
12+
}
13+
}

‎daemon/oci_windows.go

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package daemon
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"syscall"
7+
8+
"github.com/docker/docker/container"
9+
"github.com/docker/docker/layer"
10+
"github.com/docker/docker/libcontainerd"
11+
"github.com/docker/docker/libcontainerd/windowsoci"
12+
"github.com/docker/docker/oci"
13+
)
14+
15+
func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, error) {
16+
s := oci.DefaultSpec()
17+
18+
linkedEnv, err := daemon.setupLinkedContainers(c)
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
// TODO Windows - this can be removed. Not used (UID/GID)
24+
rootUID, rootGID := daemon.GetRemappedUIDGID()
25+
if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil {
26+
return nil, err
27+
}
28+
29+
img, err := daemon.imageStore.Get(c.ImageID)
30+
if err != nil {
31+
return nil, fmt.Errorf("Failed to graph.Get on ImageID %s - %s", c.ImageID, err)
32+
}
33+
34+
// In base spec
35+
s.Hostname = c.FullHostname()
36+
37+
// In s.Mounts
38+
mounts, err := daemon.setupMounts(c)
39+
if err != nil {
40+
return nil, err
41+
}
42+
for _, mount := range mounts {
43+
s.Mounts = append(s.Mounts, windowsoci.Mount{
44+
Source: mount.Source,
45+
Destination: mount.Destination,
46+
Readonly: !mount.Writable,
47+
})
48+
}
49+
50+
// Are we going to run as a Hyper-V container?
51+
hv := false
52+
if c.HostConfig.Isolation.IsDefault() {
53+
// Container is set to use the default, so take the default from the daemon configuration
54+
hv = daemon.defaultIsolation.IsHyperV()
55+
} else {
56+
// Container is requesting an isolation mode. Honour it.
57+
hv = c.HostConfig.Isolation.IsHyperV()
58+
}
59+
if hv {
60+
// TODO We don't yet have the ImagePath hooked up. But set to
61+
// something non-nil to pickup in libcontainerd.
62+
s.Windows.HvRuntime = &windowsoci.HvRuntime{}
63+
}
64+
65+
// In s.Process
66+
if c.Config.ArgsEscaped {
67+
s.Process.Args = append([]string{c.Path}, c.Args...)
68+
} else {
69+
// TODO (jstarks): escape the entrypoint too once the tests are fixed to not rely on this behavior
70+
s.Process.Args = append([]string{c.Path}, escapeArgs(c.Args)...)
71+
}
72+
s.Process.Cwd = c.Config.WorkingDir
73+
s.Process.Env = c.CreateDaemonEnvironment(linkedEnv)
74+
s.Process.InitialConsoleSize = c.HostConfig.ConsoleSize
75+
s.Process.Terminal = c.Config.Tty
76+
s.Process.User.User = c.Config.User
77+
78+
// In spec.Root
79+
s.Root.Path = c.BaseFS
80+
s.Root.Readonly = c.HostConfig.ReadonlyRootfs
81+
82+
// In s.Windows
83+
s.Windows.FirstStart = !c.HasBeenStartedBefore
84+
85+
// s.Windows.LayerFolder.
86+
m, err := c.RWLayer.Metadata()
87+
if err != nil {
88+
return nil, fmt.Errorf("Failed to get layer metadata - %s", err)
89+
}
90+
s.Windows.LayerFolder = m["dir"]
91+
92+
// s.Windows.LayerPaths
93+
var layerPaths []string
94+
if img.RootFS != nil && img.RootFS.Type == "layers+base" {
95+
max := len(img.RootFS.DiffIDs)
96+
for i := 0; i <= max; i++ {
97+
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
98+
path, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID())
99+
if err != nil {
100+
return nil, fmt.Errorf("Failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err)
101+
}
102+
// Reverse order, expecting parent most first
103+
layerPaths = append([]string{path}, layerPaths...)
104+
}
105+
}
106+
s.Windows.LayerPaths = layerPaths
107+
108+
// In s.Windows.Networking (TP5+ libnetwork way of doing things)
109+
// Connect all the libnetwork allocated networks to the container
110+
var epList []string
111+
if c.NetworkSettings != nil {
112+
for n := range c.NetworkSettings.Networks {
113+
sn, err := daemon.FindNetwork(n)
114+
if err != nil {
115+
continue
116+
}
117+
118+
ep, err := c.GetEndpointInNetwork(sn)
119+
if err != nil {
120+
continue
121+
}
122+
123+
data, err := ep.DriverInfo()
124+
if err != nil {
125+
continue
126+
}
127+
if data["hnsid"] != nil {
128+
epList = append(epList, data["hnsid"].(string))
129+
}
130+
}
131+
}
132+
s.Windows.Networking = &windowsoci.Networking{
133+
EndpointList: epList,
134+
}
135+
136+
// In s.Windows.Networking (TP4 back compat)
137+
// TODO Windows: Post TP4 - Remove this along with definitions from spec
138+
// and changes to libcontainerd to not read these fields.
139+
if daemon.netController == nil {
140+
parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2)
141+
switch parts[0] {
142+
case "none":
143+
case "default", "": // empty string to support existing containers
144+
if !c.Config.NetworkDisabled {
145+
s.Windows.Networking = &windowsoci.Networking{
146+
MacAddress: c.Config.MacAddress,
147+
Bridge: daemon.configStore.bridgeConfig.Iface,
148+
PortBindings: c.HostConfig.PortBindings,
149+
}
150+
}
151+
default:
152+
return nil, fmt.Errorf("invalid network mode: %s", c.HostConfig.NetworkMode)
153+
}
154+
}
155+
156+
// In s.Windows.Resources
157+
// @darrenstahlmsft implement these resources
158+
cpuShares := uint64(c.HostConfig.CPUShares)
159+
s.Windows.Resources = &windowsoci.Resources{
160+
CPU: &windowsoci.CPU{
161+
//TODO Count: ...,
162+
//TODO Percent: ...,
163+
Shares: &cpuShares,
164+
},
165+
Memory: &windowsoci.Memory{
166+
//TODO Limit: ...,
167+
//TODO Reservation: ...,
168+
},
169+
Network: &windowsoci.Network{
170+
//TODO Bandwidth: ...,
171+
},
172+
Storage: &windowsoci.Storage{
173+
//TODO Bps: ...,
174+
//TODO Iops: ...,
175+
//TODO SandboxSize: ...,
176+
},
177+
}
178+
179+
// BUGBUG - Next problem. This was an exec opt. Where do we now get these?
180+
// Come back to this when add Xenon support.
181+
// var hvPartition bool
182+
// // Work out the isolation (whether it is a hypervisor partition)
183+
// if c.HostConfig.Isolation.IsDefault() {
184+
// // Not specified by caller. Take daemon default
185+
// hvPartition = windows.DefaultIsolation.IsHyperV()
186+
// } else {
187+
// // Take value specified by caller
188+
// hvPartition = c.HostConfig.Isolation.IsHyperV()
189+
// }
190+
191+
// Isolation: string(c.HostConfig.Isolation),
192+
// HvPartition: hvPartition,
193+
// }
194+
195+
return (*libcontainerd.Spec)(&s), nil
196+
}
197+
198+
func escapeArgs(args []string) []string {
199+
escapedArgs := make([]string, len(args))
200+
for i, a := range args {
201+
escapedArgs[i] = syscall.EscapeArg(a)
202+
}
203+
return escapedArgs
204+
}

‎daemon/update_windows.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// +build windows
2+
3+
package daemon
4+
5+
import (
6+
"github.com/docker/docker/libcontainerd"
7+
"github.com/docker/engine-api/types/container"
8+
)
9+
10+
func toContainerdResources(resources container.Resources) libcontainerd.Resources {
11+
var r libcontainerd.Resources
12+
return r
13+
}

‎daemon/volumes_windows.go

+11-7
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,22 @@ import (
77
"sort"
88

99
"github.com/docker/docker/container"
10-
"github.com/docker/docker/daemon/execdriver"
1110
"github.com/docker/docker/volume"
1211
)
1312

1413
// setupMounts configures the mount points for a container by appending each
15-
// of the configured mounts on the container to the execdriver mount structure
14+
// of the configured mounts on the container to the oci mount structure
1615
// which will ultimately be passed into the exec driver during container creation.
1716
// It also ensures each of the mounts are lexographically sorted.
18-
func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.Mount, error) {
19-
var mnts []execdriver.Mount
20-
for _, mount := range container.MountPoints { // type is volume.MountPoint
21-
if err := daemon.lazyInitializeVolume(container.ID, mount); err != nil {
17+
18+
// BUGBUG TODO Windows containerd. This would be much better if it returned
19+
// an array of windowsoci mounts, not container mounts. Then no need to
20+
// do multiple transitions.
21+
22+
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
23+
var mnts []container.Mount
24+
for _, mount := range c.MountPoints { // type is volume.MountPoint
25+
if err := daemon.lazyInitializeVolume(c.ID, mount); err != nil {
2226
return nil, err
2327
}
2428
// If there is no source, take it from the volume path
@@ -29,7 +33,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
2933
if s == "" {
3034
return nil, fmt.Errorf("No source for mount name '%s' driver %q destination '%s'", mount.Name, mount.Driver, mount.Destination)
3135
}
32-
mnts = append(mnts, execdriver.Mount{
36+
mnts = append(mnts, container.Mount{
3337
Source: s,
3438
Destination: mount.Destination,
3539
Writable: mount.RW,

‎docker/daemon_windows.go

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/Sirupsen/logrus"
1111
apiserver "github.com/docker/docker/api/server"
1212
"github.com/docker/docker/daemon"
13+
"github.com/docker/docker/libcontainerd"
1314
"github.com/docker/docker/pkg/mflag"
1415
"github.com/docker/docker/pkg/system"
1516
)
@@ -57,3 +58,7 @@ func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(
5758
}
5859
}()
5960
}
61+
62+
func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption {
63+
return nil
64+
}

‎libcontainerd/client_windows.go

+579
Large diffs are not rendered by default.

‎libcontainerd/container_windows.go

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package libcontainerd
2+
3+
import (
4+
"io"
5+
"strings"
6+
"syscall"
7+
8+
"github.com/Microsoft/hcsshim"
9+
"github.com/Sirupsen/logrus"
10+
)
11+
12+
type container struct {
13+
containerCommon
14+
15+
// Platform specific fields are below here. There are none presently on Windows.
16+
options []CreateOption
17+
18+
// The ociSpec is required, as client.Create() needs a spec,
19+
// but can be called from the RestartManager context which does not
20+
// otherwise have access to the Spec
21+
ociSpec Spec
22+
}
23+
24+
func (ctr *container) newProcess(friendlyName string) *process {
25+
return &process{
26+
processCommon: processCommon{
27+
containerID: ctr.containerID,
28+
friendlyName: friendlyName,
29+
client: ctr.client,
30+
},
31+
}
32+
}
33+
34+
func (ctr *container) start() error {
35+
var err error
36+
37+
// Start the container
38+
logrus.Debugln("Starting container ", ctr.containerID)
39+
if err = hcsshim.StartComputeSystem(ctr.containerID); err != nil {
40+
logrus.Errorf("Failed to start compute system: %s", err)
41+
return err
42+
}
43+
44+
createProcessParms := hcsshim.CreateProcessParams{
45+
EmulateConsole: ctr.ociSpec.Process.Terminal,
46+
WorkingDirectory: ctr.ociSpec.Process.Cwd,
47+
ConsoleSize: ctr.ociSpec.Process.InitialConsoleSize,
48+
}
49+
50+
// Configure the environment for the process
51+
createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
52+
createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
53+
54+
iopipe := &IOPipe{Terminal: ctr.ociSpec.Process.Terminal}
55+
56+
// Start the command running in the container. Note we always tell HCS to
57+
// create stdout as it's required regardless of '-i' or '-t' options, so that
58+
// docker can always grab the output through logs. We also tell HCS to always
59+
// create stdin, even if it's not used - it will be closed shortly. Stderr
60+
// is only created if it we're not -t.
61+
var pid uint32
62+
var stdout, stderr io.ReadCloser
63+
pid, iopipe.Stdin, stdout, stderr, err = hcsshim.CreateProcessInComputeSystem(
64+
ctr.containerID,
65+
true,
66+
true,
67+
!ctr.ociSpec.Process.Terminal,
68+
createProcessParms)
69+
if err != nil {
70+
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
71+
72+
// Explicitly terminate the compute system here.
73+
if err2 := hcsshim.TerminateComputeSystem(ctr.containerID, hcsshim.TimeoutInfinite, "CreateProcessInComputeSystem failed"); err2 != nil {
74+
// Ignore this error, there's not a lot we can do except log it
75+
logrus.Warnf("Failed to TerminateComputeSystem after a failed CreateProcessInComputeSystem. Ignoring this.", err2)
76+
} else {
77+
logrus.Debugln("Cleaned up after failed CreateProcessInComputeSystem by calling TerminateComputeSystem")
78+
}
79+
return err
80+
}
81+
82+
// Convert io.ReadClosers to io.Readers
83+
if stdout != nil {
84+
iopipe.Stdout = openReaderFromPipe(stdout)
85+
}
86+
if stderr != nil {
87+
iopipe.Stderr = openReaderFromPipe(stderr)
88+
}
89+
90+
// Save the PID
91+
logrus.Debugf("Process started - PID %d", pid)
92+
ctr.systemPid = uint32(pid)
93+
94+
// Spin up a go routine waiting for exit to handle cleanup
95+
go ctr.waitExit(pid, InitFriendlyName, true)
96+
97+
ctr.client.appendContainer(ctr)
98+
99+
if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
100+
// OK to return the error here, as waitExit will handle tear-down in HCS
101+
return err
102+
}
103+
104+
// Tell the docker engine that the container has started.
105+
si := StateInfo{
106+
State: StateStart,
107+
Pid: ctr.systemPid, // Not sure this is needed? Double-check monitor.go in daemon BUGBUG @jhowardmsft
108+
}
109+
return ctr.client.backend.StateChanged(ctr.containerID, si)
110+
111+
}
112+
113+
// waitExit runs as a goroutine waiting for the process to exit. It's
114+
// equivalent to (in the linux containerd world) where events come in for
115+
// state change notifications from containerd.
116+
func (ctr *container) waitExit(pid uint32, processFriendlyName string, isFirstProcessToStart bool) error {
117+
logrus.Debugln("waitExit on pid", pid)
118+
119+
// Block indefinitely for the process to exit.
120+
exitCode, err := hcsshim.WaitForProcessInComputeSystem(ctr.containerID, pid, hcsshim.TimeoutInfinite)
121+
if err != nil {
122+
if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
123+
logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
124+
}
125+
// Fall through here, do not return. This ensures we attempt to continue the
126+
// shutdown in HCS nad tell the docker engine that the process/container
127+
// has exited to avoid a container being dropped on the floor.
128+
}
129+
130+
// Assume the container has exited
131+
si := StateInfo{
132+
State: StateExit,
133+
ExitCode: uint32(exitCode),
134+
Pid: pid,
135+
ProcessID: processFriendlyName,
136+
}
137+
138+
// But it could have been an exec'd process which exited
139+
if !isFirstProcessToStart {
140+
si.State = StateExitProcess
141+
}
142+
143+
// If this is the init process, always call into vmcompute.dll to
144+
// shutdown the container after we have completed.
145+
if isFirstProcessToStart {
146+
logrus.Debugf("Shutting down container %s", ctr.containerID)
147+
// Explicit timeout here rather than hcsshim.TimeoutInfinte to avoid a
148+
// (remote) possibility that ShutdownComputeSystem hangs indefinitely.
149+
const shutdownTimeout = 5 * 60 * 1000 // 5 minutes
150+
if err := hcsshim.ShutdownComputeSystem(ctr.containerID, shutdownTimeout, "waitExit"); err != nil {
151+
if herr, ok := err.(*hcsshim.HcsError); !ok ||
152+
(herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS &&
153+
herr.Err != ErrorBadPathname &&
154+
herr.Err != syscall.ERROR_PATH_NOT_FOUND) {
155+
logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err)
156+
}
157+
} else {
158+
logrus.Debugf("Completed shutting down container %s", ctr.containerID)
159+
}
160+
161+
// BUGBUG - Is taking the lock necessary here? Should it just be taken for
162+
// the deleteContainer call, not for the restart logic? @jhowardmsft
163+
ctr.client.lock(ctr.containerID)
164+
defer ctr.client.unlock(ctr.containerID)
165+
166+
if si.State == StateExit && ctr.restartManager != nil {
167+
restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode))
168+
if err != nil {
169+
logrus.Error(err)
170+
} else if restart {
171+
si.State = StateRestart
172+
ctr.restarting = true
173+
go func() {
174+
err := <-wait
175+
ctr.restarting = false
176+
if err != nil {
177+
si.State = StateExit
178+
if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
179+
logrus.Error(err)
180+
}
181+
logrus.Error(err)
182+
} else {
183+
ctr.client.Create(ctr.containerID, ctr.ociSpec, ctr.options...)
184+
}
185+
}()
186+
}
187+
}
188+
189+
// Remove process from list if we have exited
190+
// We need to do so here in case the Message Handler decides to restart it.
191+
if si.State == StateExit {
192+
ctr.client.deleteContainer(ctr.friendlyName)
193+
}
194+
}
195+
196+
// Call into the backend to notify it of the state change.
197+
logrus.Debugf("waitExit() calling backend.StateChanged %v", si)
198+
if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
199+
logrus.Error(err)
200+
}
201+
202+
logrus.Debugln("waitExit() completed OK")
203+
return nil
204+
}

‎libcontainerd/process_windows.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package libcontainerd
2+
3+
import (
4+
"io"
5+
)
6+
7+
// process keeps the state for both main container process and exec process.
8+
9+
// process keeps the state for both main container process and exec process.
10+
type process struct {
11+
processCommon
12+
}
13+
14+
func openReaderFromPipe(p io.ReadCloser) io.Reader {
15+
r, w := io.Pipe()
16+
go func() {
17+
if _, err := io.Copy(w, p); err != nil {
18+
r.CloseWithError(err)
19+
}
20+
w.Close()
21+
p.Close()
22+
}()
23+
return r
24+
}

‎libcontainerd/remote_windows.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package libcontainerd
2+
3+
import "sync"
4+
5+
type remote struct {
6+
}
7+
8+
func (r *remote) Client(b Backend) (Client, error) {
9+
c := &client{
10+
clientCommon: clientCommon{
11+
backend: b,
12+
containerMutexes: make(map[string]*sync.Mutex),
13+
containers: make(map[string]*container),
14+
},
15+
}
16+
return c, nil
17+
}
18+
19+
// Cleanup is a no-op on Windows. It is here to implement the same interface
20+
// to meet compilation requirements.
21+
func (r *remote) Cleanup() {
22+
}
23+
24+
// New creates a fresh instance of libcontainerd remote. This is largely
25+
// a no-op on Windows.
26+
func New(_ string, _ ...RemoteOption) (Remote, error) {
27+
return &remote{}, nil
28+
}

‎libcontainerd/types_windows.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package libcontainerd
2+
3+
import "github.com/docker/docker/libcontainerd/windowsoci"
4+
5+
// Spec is the base configuration for the container.
6+
type Spec windowsoci.WindowsSpec
7+
8+
// Process contains information to start a specific application inside the container.
9+
type Process windowsoci.Process
10+
11+
// User specifies user information for the containers main process.
12+
type User windowsoci.User
13+
14+
// Stats contains a stats properties from containerd.
15+
type Stats struct{}
16+
17+
// Resources defines updatable container resource values.
18+
type Resources struct{}

‎libcontainerd/utils_windows.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package libcontainerd
2+
3+
import "strings"
4+
5+
// setupEnvironmentVariables convert a string array of environment variables
6+
// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
7+
func setupEnvironmentVariables(a []string) map[string]string {
8+
r := make(map[string]string)
9+
for _, s := range a {
10+
arr := strings.Split(s, "=")
11+
if len(arr) == 2 {
12+
r[arr[0]] = arr[1]
13+
}
14+
}
15+
return r
16+
}
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package windowsoci
2+
3+
// This file is a hack - essentially a mirror of OCI spec for Windows.
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/docker/go-connections/nat"
9+
)
10+
11+
// WindowsSpec is the full specification for Windows containers.
12+
type WindowsSpec struct {
13+
Spec
14+
15+
// Windows is platform specific configuration for Windows based containers.
16+
Windows Windows `json:"windows"`
17+
}
18+
19+
// Spec is the base configuration for the container. It specifies platform
20+
// independent configuration. This information must be included when the
21+
// bundle is packaged for distribution.
22+
type Spec struct {
23+
24+
// Version is the version of the specification that is supported.
25+
Version string `json:"ociVersion"`
26+
// Platform is the host information for OS and Arch.
27+
Platform Platform `json:"platform"`
28+
// Process is the container's main process.
29+
Process Process `json:"process"`
30+
// Root is the root information for the container's filesystem.
31+
Root Root `json:"root"`
32+
// Hostname is the container's host name.
33+
Hostname string `json:"hostname,omitempty"`
34+
// Mounts profile configuration for adding mounts to the container's filesystem.
35+
Mounts []Mount `json:"mounts"`
36+
}
37+
38+
// Windows contains platform specific configuration for Windows based containers.
39+
type Windows struct {
40+
// Resources contain information for handling resource constraints for the container
41+
Resources *Resources `json:"resources,omitempty"`
42+
// Networking contains the platform specific network settings for the container.
43+
Networking *Networking `json:"networking,omitempty"`
44+
// FirstStart is used for an optimization on first boot of Windows
45+
FirstStart bool `json:"first_start,omitempty"`
46+
// LayerFolder is the path to the current layer folder
47+
LayerFolder string `json:"layer_folder,omitempty"`
48+
// Layer paths of the parent layers
49+
LayerPaths []string `json:"layer_paths,omitempty"`
50+
// HvRuntime contains settings specific to Hyper-V containers, omitted if not using Hyper-V isolation
51+
HvRuntime *HvRuntime `json:"hv_runtime,omitempty"`
52+
}
53+
54+
// Process contains information to start a specific application inside the container.
55+
type Process struct {
56+
// Terminal indicates if stderr should NOT be attached for the container.
57+
Terminal bool `json:"terminal"`
58+
// ConsoleSize contains the initial h,w of the console size
59+
InitialConsoleSize [2]int `json:"-"`
60+
// User specifies user information for the process.
61+
User User `json:"user"`
62+
// Args specifies the binary and arguments for the application to execute.
63+
Args []string `json:"args"`
64+
// Env populates the process environment for the process.
65+
Env []string `json:"env,omitempty"`
66+
// Cwd is the current working directory for the process and must be
67+
// relative to the container's root.
68+
Cwd string `json:"cwd"`
69+
}
70+
71+
// User contains the user information for Windows
72+
type User struct {
73+
User string `json:"user,omitempty"`
74+
}
75+
76+
// Root contains information about the container's root filesystem on the host.
77+
type Root struct {
78+
// Path is the absolute path to the container's root filesystem.
79+
Path string `json:"path"`
80+
// Readonly makes the root filesystem for the container readonly before the process is executed.
81+
Readonly bool `json:"readonly"`
82+
}
83+
84+
// Platform specifies OS and arch information for the host system that the container
85+
// is created for.
86+
type Platform struct {
87+
// OS is the operating system.
88+
OS string `json:"os"`
89+
// Arch is the architecture
90+
Arch string `json:"arch"`
91+
}
92+
93+
// Mount specifies a mount for a container.
94+
type Mount struct {
95+
// Destination is the path where the mount will be placed relative to the container's root. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point.
96+
Destination string `json:"destination"`
97+
// Type specifies the mount kind.
98+
Type string `json:"type"`
99+
// Source specifies the source path of the mount. In the case of bind mounts
100+
// this would be the file on the host.
101+
Source string `json:"source"`
102+
// Readonly specifies if the mount should be read-only
103+
Readonly bool `json:"readonly"`
104+
}
105+
106+
// HvRuntime contains settings specific to Hyper-V containers
107+
type HvRuntime struct {
108+
// ImagePath is the path to the Utility VM image for this container
109+
ImagePath string `json:"image_path,omitempty"`
110+
}
111+
112+
// Networking contains the platform specific network settings for the container
113+
type Networking struct {
114+
// TODO Windows TP5. The following three fields are for 'legacy' non-
115+
// libnetwork networking through HCS. They can be removed once TP4 is
116+
// no longer supported. Also remove in libcontainerd\client_windows.go,
117+
// function Create(), and in daemon\oci_windows.go, function CreateSpec()
118+
MacAddress string `json:"mac,omitempty"`
119+
Bridge string `json:"bridge,omitempty"`
120+
PortBindings nat.PortMap `json:"port_bindings,omitempty"`
121+
// End of TODO Windows TP5.
122+
123+
// List of endpoints to be attached to the container
124+
EndpointList []string `json:"endpoints,omitempty"`
125+
}
126+
127+
// Storage contains storage resource management settings
128+
type Storage struct {
129+
// Specifies maximum Iops for the system drive
130+
Iops *uint64 `json:"iops,omitempty"`
131+
// Specifies maximum bytes per second for the system drive
132+
Bps *uint64 `json:"bps,omitempty"`
133+
// Sandbox size indicates the size to expand the system drive to if it is currently smaller
134+
SandboxSize *uint64 `json:"sandbox_size,omitempty"`
135+
}
136+
137+
// Memory contains memory settings for the container
138+
type Memory struct {
139+
// Memory limit (in bytes).
140+
Limit *int64 `json:"limit,omitempty"`
141+
// Memory reservation (in bytes).
142+
Reservation *uint64 `json:"reservation,omitempty"`
143+
}
144+
145+
// CPU contains information for cpu resource management
146+
type CPU struct {
147+
// Number of CPUs available to the container. This is an appoximation for Windows Server Containers.
148+
Count *uint64 `json:"count,omitempty"`
149+
// CPU shares (relative weight (ratio) vs. other containers with cpu shares). Range is from 1 to 10000.
150+
Shares *uint64 `json:"shares,omitempty"`
151+
// Percent of available CPUs usable by the container.
152+
Percent *int64 `json:"percent,omitempty"`
153+
}
154+
155+
// Network network resource management information
156+
type Network struct {
157+
// Bandwidth is the maximum egress bandwidth in bytes per second
158+
Bandwidth *uint64 `json:"bandwidth,omitempty"`
159+
}
160+
161+
// Resources has container runtime resource constraints
162+
// TODO Windows containerd. This structure needs ratifying with the old resources
163+
// structure used on Windows and the latest OCI spec.
164+
type Resources struct {
165+
// Memory restriction configuration
166+
Memory *Memory `json:"memory,omitempty"`
167+
// CPU resource restriction configuration
168+
CPU *CPU `json:"cpu,omitempty"`
169+
// Storage restriction configuration
170+
Storage *Storage `json:"storage,omitempty"`
171+
// Network restriction configuration
172+
Network *Network `json:"network,omitempty"`
173+
}
174+
175+
const (
176+
// VersionMajor is for an API incompatible changes
177+
VersionMajor = 0
178+
// VersionMinor is for functionality in a backwards-compatible manner
179+
VersionMinor = 3
180+
// VersionPatch is for backwards-compatible bug fixes
181+
VersionPatch = 0
182+
183+
// VersionDev indicates development branch. Releases will be empty string.
184+
VersionDev = ""
185+
)
186+
187+
// Version is the specification version that the package types support.
188+
var Version = fmt.Sprintf("%d.%d.%d%s (Windows)", VersionMajor, VersionMinor, VersionPatch, VersionDev)
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// +build !windows
2+
3+
package windowsoci

‎oci/defaults_windows.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package oci
2+
3+
import (
4+
"runtime"
5+
6+
"github.com/docker/docker/libcontainerd/windowsoci"
7+
)
8+
9+
// DefaultSpec returns default spec used by docker.
10+
func DefaultSpec() windowsoci.WindowsSpec {
11+
s := windowsoci.Spec{
12+
Version: windowsoci.Version,
13+
Platform: windowsoci.Platform{
14+
OS: runtime.GOOS,
15+
Arch: runtime.GOARCH,
16+
},
17+
}
18+
19+
return windowsoci.WindowsSpec{
20+
Spec: s,
21+
Windows: windowsoci.Windows{},
22+
}
23+
}

0 commit comments

Comments
 (0)
Please sign in to comment.