Remove execdriver package
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
9c4570a958
commit
6eebe85290
32 changed files with 0 additions and 3035 deletions
|
@ -1,9 +0,0 @@
|
|||
This directory contains code pertaining to running containers and storing images
|
||||
|
||||
Code pertaining to running containers:
|
||||
|
||||
- execdriver
|
||||
|
||||
Code pertaining to storing images:
|
||||
|
||||
- graphdriver
|
|
@ -1,133 +0,0 @@
|
|||
package execdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
)
|
||||
|
||||
// Context is a generic key value pair that allows
|
||||
// arbitrary data to be sent
|
||||
type Context map[string]string
|
||||
|
||||
// Define error messages
|
||||
var (
|
||||
ErrNotRunning = errors.New("Container is not running")
|
||||
ErrWaitTimeoutReached = errors.New("Wait timeout reached")
|
||||
ErrDriverAlreadyRegistered = errors.New("A driver already registered this docker init function")
|
||||
ErrDriverNotFound = errors.New("The requested docker init has not been found")
|
||||
)
|
||||
|
||||
// DriverCallback defines a callback function which is used in "Run" and "Exec".
|
||||
// This allows work to be done in the parent process when the child is passing
|
||||
// through PreStart, Start and PostStop events.
|
||||
// Callbacks are provided a processConfig pointer and the pid of the child.
|
||||
// The channel will be used to notify the OOM events.
|
||||
type DriverCallback func(processConfig *ProcessConfig, pid int, chOOM <-chan struct{}) error
|
||||
|
||||
// Hooks is a struct containing function pointers to callbacks
|
||||
// used by any execdriver implementation exploiting hooks capabilities
|
||||
type Hooks struct {
|
||||
// PreStart is called before container's CMD/ENTRYPOINT is executed
|
||||
PreStart []DriverCallback
|
||||
// Start is called after the container's process is full started
|
||||
Start DriverCallback
|
||||
// PostStop is called after the container process exits
|
||||
PostStop []DriverCallback
|
||||
}
|
||||
|
||||
// Terminal represents a pseudo TTY, it is for when
|
||||
// using a container interactively.
|
||||
type Terminal interface {
|
||||
io.Closer
|
||||
Resize(height, width int) error
|
||||
}
|
||||
|
||||
// Driver is an interface for drivers to implement
|
||||
// including all basic functions a driver should have
|
||||
type Driver interface {
|
||||
// Run executes the process, blocks until the process exits and returns
|
||||
// the exit code. It's the last stage on Docker side for running a container.
|
||||
Run(c *Command, pipes *Pipes, hooks Hooks) (ExitStatus, error)
|
||||
|
||||
// Exec executes the process in an existing container, blocks until the
|
||||
// process exits and returns the exit code.
|
||||
Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, hooks Hooks) (int, error)
|
||||
|
||||
// Kill sends signals to process in container.
|
||||
Kill(c *Command, sig int) error
|
||||
|
||||
// Pause pauses a container.
|
||||
Pause(c *Command) error
|
||||
|
||||
// Unpause unpauses a container.
|
||||
Unpause(c *Command) error
|
||||
|
||||
// Name returns the name of the driver.
|
||||
Name() string
|
||||
|
||||
// GetPidsForContainer returns a list of pid for the processes running in a container.
|
||||
GetPidsForContainer(id string) ([]int, error)
|
||||
|
||||
// Terminate kills a container by sending signal SIGKILL.
|
||||
Terminate(c *Command) error
|
||||
|
||||
// Clean removes all traces of container exec.
|
||||
Clean(id string) error
|
||||
|
||||
// Stats returns resource stats for a running container
|
||||
Stats(id string) (*ResourceStats, error)
|
||||
|
||||
// Update updates resource configs for a container
|
||||
Update(c *Command) error
|
||||
|
||||
// SupportsHooks refers to the driver capability to exploit pre/post hook functionality
|
||||
SupportsHooks() bool
|
||||
}
|
||||
|
||||
// CommonResources contains the resource configs for a driver that are
|
||||
// common across platforms.
|
||||
type CommonResources struct {
|
||||
Memory int64 `json:"memory"`
|
||||
MemoryReservation int64 `json:"memory_reservation"`
|
||||
CPUShares int64 `json:"cpu_shares"`
|
||||
BlkioWeight uint16 `json:"blkio_weight"`
|
||||
}
|
||||
|
||||
// ResourceStats contains information about resource usage by a container.
|
||||
type ResourceStats struct {
|
||||
*libcontainer.Stats
|
||||
Read time.Time `json:"read"`
|
||||
MemoryLimit int64 `json:"memory_limit"`
|
||||
SystemUsage uint64 `json:"system_usage"`
|
||||
}
|
||||
|
||||
// CommonProcessConfig is the common platform agnostic part of the ProcessConfig
|
||||
// structure that describes a process that will be run inside a container.
|
||||
type CommonProcessConfig struct {
|
||||
exec.Cmd `json:"-"`
|
||||
|
||||
Tty bool `json:"tty"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Arguments []string `json:"arguments"`
|
||||
Terminal Terminal `json:"-"` // standard or tty terminal
|
||||
}
|
||||
|
||||
// CommonCommand is the common platform agnostic part of the Command structure
|
||||
// which wraps an os/exec.Cmd to add more metadata
|
||||
type CommonCommand struct {
|
||||
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
|
||||
ID string `json:"id"`
|
||||
MountLabel string `json:"mount_label"` // TODO Windows. More involved, but can be factored out
|
||||
Mounts []Mount `json:"mounts"`
|
||||
Network *Network `json:"network"`
|
||||
ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container.
|
||||
ProcessLabel string `json:"process_label"` // TODO Windows. More involved, but can be factored out
|
||||
Resources *Resources `json:"resources"`
|
||||
Rootfs string `json:"rootfs"` // root fs of the container
|
||||
WorkingDir string `json:"working_dir"`
|
||||
TmpDir string `json:"tmpdir"` // Directory used to store docker tmpdirs.
|
||||
}
|
|
@ -1,323 +0,0 @@
|
|||
// +build !windows
|
||||
|
||||
package execdriver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver/native/template"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
blkiodev "github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
// Mount contains information for a mount operation.
|
||||
type Mount struct {
|
||||
Source string `json:"source"`
|
||||
Destination string `json:"destination"`
|
||||
Writable bool `json:"writable"`
|
||||
Data string `json:"data"`
|
||||
Propagation string `json:"mountpropagation"`
|
||||
}
|
||||
|
||||
// Resources contains all resource configs for a driver.
|
||||
// Currently these are all for cgroup configs.
|
||||
type Resources struct {
|
||||
CommonResources
|
||||
|
||||
// Fields below here are platform specific
|
||||
|
||||
BlkioWeightDevice []*blkiodev.WeightDevice `json:"blkio_weight_device"`
|
||||
BlkioThrottleReadBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_bps_device"`
|
||||
BlkioThrottleWriteBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_bps_device"`
|
||||
BlkioThrottleReadIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_iops_device"`
|
||||
BlkioThrottleWriteIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_iops_device"`
|
||||
MemorySwap int64 `json:"memory_swap"`
|
||||
KernelMemory int64 `json:"kernel_memory"`
|
||||
CPUQuota int64 `json:"cpu_quota"`
|
||||
CpusetCpus string `json:"cpuset_cpus"`
|
||||
CpusetMems string `json:"cpuset_mems"`
|
||||
CPUPeriod int64 `json:"cpu_period"`
|
||||
Rlimits []*units.Rlimit `json:"rlimits"`
|
||||
OomKillDisable bool `json:"oom_kill_disable"`
|
||||
PidsLimit int64 `json:"pids_limit"`
|
||||
MemorySwappiness int64 `json:"memory_swappiness"`
|
||||
}
|
||||
|
||||
// ProcessConfig is the platform specific structure that describes a process
|
||||
// that will be run inside a container.
|
||||
type ProcessConfig struct {
|
||||
CommonProcessConfig
|
||||
|
||||
// Fields below here are platform specific
|
||||
Privileged bool `json:"privileged"`
|
||||
User string `json:"user"`
|
||||
Console string `json:"-"` // dev/console path
|
||||
}
|
||||
|
||||
// Ipc settings of the container
|
||||
// It is for IPC namespace setting. Usually different containers
|
||||
// have their own IPC namespace, however this specifies to use
|
||||
// an existing IPC namespace.
|
||||
// You can join the host's or a container's IPC namespace.
|
||||
type Ipc struct {
|
||||
ContainerID string `json:"container_id"` // id of the container to join ipc.
|
||||
HostIpc bool `json:"host_ipc"`
|
||||
}
|
||||
|
||||
// Pid settings of the container
|
||||
// It is for PID namespace setting. Usually different containers
|
||||
// have their own PID namespace, however this specifies to use
|
||||
// an existing PID namespace.
|
||||
// Joining the host's PID namespace is currently the only supported
|
||||
// option.
|
||||
type Pid struct {
|
||||
HostPid bool `json:"host_pid"`
|
||||
}
|
||||
|
||||
// UTS settings of the container
|
||||
// It is for UTS namespace setting. Usually different containers
|
||||
// have their own UTS namespace, however this specifies to use
|
||||
// an existing UTS namespace.
|
||||
// Joining the host's UTS namespace is currently the only supported
|
||||
// option.
|
||||
type UTS struct {
|
||||
HostUTS bool `json:"host_uts"`
|
||||
}
|
||||
|
||||
// Network settings of the container
|
||||
type Network struct {
|
||||
Mtu int `json:"mtu"`
|
||||
ContainerID string `json:"container_id"` // id of the container to join network.
|
||||
NamespacePath string `json:"namespace_path"`
|
||||
HostNetworking bool `json:"host_networking"`
|
||||
}
|
||||
|
||||
// Command wraps an os/exec.Cmd to add more metadata
|
||||
type Command struct {
|
||||
CommonCommand
|
||||
|
||||
// Fields below here are platform specific
|
||||
|
||||
AllowedDevices []*configs.Device `json:"allowed_devices"`
|
||||
AppArmorProfile string `json:"apparmor_profile"`
|
||||
AutoCreatedDevices []*configs.Device `json:"autocreated_devices"`
|
||||
CapAdd []string `json:"cap_add"`
|
||||
CapDrop []string `json:"cap_drop"`
|
||||
CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command.
|
||||
GIDMapping []idtools.IDMap `json:"gidmapping"`
|
||||
GroupAdd []string `json:"group_add"`
|
||||
Ipc *Ipc `json:"ipc"`
|
||||
OomScoreAdj int `json:"oom_score_adj"`
|
||||
Pid *Pid `json:"pid"`
|
||||
ReadonlyRootfs bool `json:"readonly_rootfs"`
|
||||
RemappedRoot *User `json:"remap_root"`
|
||||
SeccompProfile string `json:"seccomp_profile"`
|
||||
UIDMapping []idtools.IDMap `json:"uidmapping"`
|
||||
UTS *UTS `json:"uts"`
|
||||
NoNewPrivileges bool `json:"no_new_privileges"`
|
||||
}
|
||||
|
||||
// SetRootPropagation sets the root mount propagation mode.
|
||||
func SetRootPropagation(config *configs.Config, propagation int) {
|
||||
config.RootPropagation = propagation
|
||||
}
|
||||
|
||||
// InitContainer is the initialization of a container config.
|
||||
// It returns the initial configs for a container. It's mostly
|
||||
// defined by the default template.
|
||||
func InitContainer(c *Command) *configs.Config {
|
||||
container := template.New()
|
||||
|
||||
container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env)
|
||||
container.Cgroups.Name = c.ID
|
||||
container.Cgroups.Resources.AllowedDevices = c.AllowedDevices
|
||||
container.Devices = filterDevices(c.AutoCreatedDevices, (c.RemappedRoot.UID != 0))
|
||||
container.Rootfs = c.Rootfs
|
||||
container.Readonlyfs = c.ReadonlyRootfs
|
||||
// This can be overridden later by driver during mount setup based
|
||||
// on volume options
|
||||
SetRootPropagation(container, mount.RPRIVATE)
|
||||
container.Cgroups.Parent = c.CgroupParent
|
||||
|
||||
// check to see if we are running in ramdisk to disable pivot root
|
||||
container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
func filterDevices(devices []*configs.Device, userNamespacesEnabled bool) []*configs.Device {
|
||||
if !userNamespacesEnabled {
|
||||
return devices
|
||||
}
|
||||
|
||||
filtered := []*configs.Device{}
|
||||
// if we have user namespaces enabled, these devices will not be created
|
||||
// because of the mknod limitation in the kernel for an unprivileged process.
|
||||
// Rather, they will be bind-mounted, which will only work if they exist;
|
||||
// check for existence and remove non-existent entries from the list
|
||||
for _, device := range devices {
|
||||
if _, err := os.Stat(device.Path); err == nil {
|
||||
filtered = append(filtered, device)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func getEnv(key string, env []string) string {
|
||||
for _, pair := range env {
|
||||
parts := strings.SplitN(pair, "=", 2)
|
||||
if parts[0] == key {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetupCgroups setups cgroup resources for a container.
|
||||
func SetupCgroups(container *configs.Config, c *Command) error {
|
||||
if c.Resources != nil {
|
||||
container.Cgroups.Resources.CpuShares = c.Resources.CPUShares
|
||||
container.Cgroups.Resources.Memory = c.Resources.Memory
|
||||
container.Cgroups.Resources.MemoryReservation = c.Resources.MemoryReservation
|
||||
container.Cgroups.Resources.MemorySwap = c.Resources.MemorySwap
|
||||
container.Cgroups.Resources.KernelMemory = c.Resources.KernelMemory
|
||||
container.Cgroups.Resources.CpusetCpus = c.Resources.CpusetCpus
|
||||
container.Cgroups.Resources.CpusetMems = c.Resources.CpusetMems
|
||||
container.Cgroups.Resources.CpuPeriod = c.Resources.CPUPeriod
|
||||
container.Cgroups.Resources.CpuQuota = c.Resources.CPUQuota
|
||||
container.Cgroups.Resources.BlkioWeight = c.Resources.BlkioWeight
|
||||
container.Cgroups.Resources.BlkioWeightDevice = c.Resources.BlkioWeightDevice
|
||||
container.Cgroups.Resources.BlkioThrottleReadBpsDevice = c.Resources.BlkioThrottleReadBpsDevice
|
||||
container.Cgroups.Resources.BlkioThrottleWriteBpsDevice = c.Resources.BlkioThrottleWriteBpsDevice
|
||||
container.Cgroups.Resources.BlkioThrottleReadIOPSDevice = c.Resources.BlkioThrottleReadIOpsDevice
|
||||
container.Cgroups.Resources.BlkioThrottleWriteIOPSDevice = c.Resources.BlkioThrottleWriteIOpsDevice
|
||||
container.Cgroups.Resources.OomKillDisable = c.Resources.OomKillDisable
|
||||
container.Cgroups.Resources.PidsLimit = c.Resources.PidsLimit
|
||||
container.Cgroups.Resources.MemorySwappiness = c.Resources.MemorySwappiness
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo.
|
||||
func getNetworkInterfaceStats(interfaceName string) (*libcontainer.NetworkInterface, error) {
|
||||
out := &libcontainer.NetworkInterface{Name: interfaceName}
|
||||
// This can happen if the network runtime information is missing - possible if the
|
||||
// container was created by an old version of libcontainer.
|
||||
if interfaceName == "" {
|
||||
return out, nil
|
||||
}
|
||||
type netStatsPair struct {
|
||||
// Where to write the output.
|
||||
Out *uint64
|
||||
// The network stats file to read.
|
||||
File string
|
||||
}
|
||||
// Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
|
||||
netStats := []netStatsPair{
|
||||
{Out: &out.RxBytes, File: "tx_bytes"},
|
||||
{Out: &out.RxPackets, File: "tx_packets"},
|
||||
{Out: &out.RxErrors, File: "tx_errors"},
|
||||
{Out: &out.RxDropped, File: "tx_dropped"},
|
||||
|
||||
{Out: &out.TxBytes, File: "rx_bytes"},
|
||||
{Out: &out.TxPackets, File: "rx_packets"},
|
||||
{Out: &out.TxErrors, File: "rx_errors"},
|
||||
{Out: &out.TxDropped, File: "rx_dropped"},
|
||||
}
|
||||
for _, netStat := range netStats {
|
||||
data, err := readSysfsNetworkStats(interfaceName, netStat.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*(netStat.Out) = data
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Reads the specified statistics available under /sys/class/net/<EthInterface>/statistics
|
||||
func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||
}
|
||||
|
||||
// Stats collects all the resource usage information from a container.
|
||||
func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) {
|
||||
f, err := os.Open(filepath.Join(containerDir, "state.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
type network struct {
|
||||
Type string
|
||||
HostInterfaceName string
|
||||
}
|
||||
|
||||
state := struct {
|
||||
CgroupPaths map[string]string `json:"cgroup_paths"`
|
||||
Networks []network
|
||||
}{}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
mgr := fs.Manager{Paths: state.CgroupPaths}
|
||||
cstats, err := mgr.GetStats()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats := &libcontainer.Stats{CgroupStats: cstats}
|
||||
// if the container does not have any memory limit specified set the
|
||||
// limit to the machines memory
|
||||
memoryLimit := containerMemoryLimit
|
||||
if memoryLimit == 0 {
|
||||
memoryLimit = machineMemory
|
||||
}
|
||||
for _, iface := range state.Networks {
|
||||
switch iface.Type {
|
||||
case "veth":
|
||||
istats, err := getNetworkInterfaceStats(iface.HostInterfaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.Interfaces = append(stats.Interfaces, istats)
|
||||
}
|
||||
}
|
||||
return &ResourceStats{
|
||||
Stats: stats,
|
||||
Read: now,
|
||||
MemoryLimit: memoryLimit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// User contains the uid and gid representing a Unix user
|
||||
type User struct {
|
||||
UID int `json:"root_uid"`
|
||||
GID int `json:"root_gid"`
|
||||
}
|
||||
|
||||
// ExitStatus provides exit reasons for a container.
|
||||
type ExitStatus struct {
|
||||
// The exit code with which the container exited.
|
||||
ExitCode int
|
||||
|
||||
// Whether the container encountered an OOM.
|
||||
OOMKilled bool
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package execdriver
|
||||
|
||||
import "github.com/docker/go-connections/nat"
|
||||
|
||||
// Mount contains information for a mount operation.
|
||||
type Mount struct {
|
||||
Source string `json:"source"`
|
||||
Destination string `json:"destination"`
|
||||
Writable bool `json:"writable"`
|
||||
}
|
||||
|
||||
// Resources contains all resource configs for a driver.
|
||||
// Currently these are all for cgroup configs.
|
||||
type Resources struct {
|
||||
CommonResources
|
||||
|
||||
// Fields below here are platform specific
|
||||
}
|
||||
|
||||
// ProcessConfig is the platform specific structure that describes a process
|
||||
// that will be run inside a container.
|
||||
type ProcessConfig struct {
|
||||
CommonProcessConfig
|
||||
|
||||
// Fields below here are platform specific
|
||||
ConsoleSize [2]int `json:"-"` // h,w of initial console size
|
||||
}
|
||||
|
||||
// Network settings of the container
|
||||
type Network struct {
|
||||
Interface *NetworkInterface `json:"interface"`
|
||||
ContainerID string `json:"container_id"` // id of the container to join network.
|
||||
}
|
||||
|
||||
// NetworkInterface contains network configs for a driver
|
||||
type NetworkInterface struct {
|
||||
MacAddress string `json:"mac"`
|
||||
Bridge string `json:"bridge"`
|
||||
IPAddress string `json:"ip"`
|
||||
|
||||
// PortBindings is the port mapping between the exposed port in the
|
||||
// container and the port on the host.
|
||||
PortBindings nat.PortMap `json:"port_bindings"`
|
||||
}
|
||||
|
||||
// Command wraps an os/exec.Cmd to add more metadata
|
||||
type Command struct {
|
||||
CommonCommand
|
||||
|
||||
// Fields below here are platform specific
|
||||
|
||||
FirstStart bool `json:"first_start"` // Optimization for first boot of Windows
|
||||
Hostname string `json:"hostname"` // Windows sets the hostname in the execdriver
|
||||
LayerFolder string `json:"layer_folder"` // Layer folder for a command
|
||||
LayerPaths []string `json:"layer_paths"` // Layer paths for a command
|
||||
Isolation string `json:"isolation"` // Isolation technology for the container
|
||||
ArgsEscaped bool `json:"args_escaped"` // True if args are already escaped
|
||||
HvPartition bool `json:"hv_partition"` // True if it's an hypervisor partition
|
||||
EpList []string `json:"endpoints"` // List of network endpoints for HNS
|
||||
}
|
||||
|
||||
// ExitStatus provides exit reasons for a container.
|
||||
type ExitStatus struct {
|
||||
// The exit code with which the container exited.
|
||||
ExitCode int
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// +build freebsd
|
||||
|
||||
package execdrivers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
|
||||
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("jail driver not yet supported on FreeBSD")
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package execdrivers
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/execdriver/native"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
|
||||
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
return native.NewDriver(path.Join(root, "execdriver", "native"), options)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package execdrivers
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/execdriver/windows"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
|
||||
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
return windows.NewDriver(root, options)
|
||||
}
|
|
@ -1,514 +0,0 @@
|
|||
// +build linux,cgo
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/docker/profiles/seccomp"
|
||||
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
)
|
||||
|
||||
// createContainer populates and configures the container type with the
|
||||
// data provided by the execdriver.Command
|
||||
func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (container *configs.Config, err error) {
|
||||
container = execdriver.InitContainer(c)
|
||||
|
||||
if err := d.createIpc(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.createPid(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.createUTS(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.setupRemappedRoot(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.createNetwork(container, c, hooks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.ProcessConfig.Privileged {
|
||||
if !container.Readonlyfs {
|
||||
// clear readonly for /sys
|
||||
for i := range container.Mounts {
|
||||
if container.Mounts[i].Destination == "/sys" {
|
||||
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
|
||||
}
|
||||
}
|
||||
container.ReadonlyPaths = nil
|
||||
}
|
||||
|
||||
// clear readonly for cgroup
|
||||
for i := range container.Mounts {
|
||||
if container.Mounts[i].Device == "cgroup" {
|
||||
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
|
||||
}
|
||||
}
|
||||
|
||||
container.MaskPaths = nil
|
||||
if err := d.setPrivileged(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := d.setCapabilities(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.SeccompProfile == "" {
|
||||
container.Seccomp, err = seccomp.GetDefaultProfile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
// add CAP_ prefix to all caps for new libcontainer update to match
|
||||
// the spec format.
|
||||
for i, s := range container.Capabilities {
|
||||
if !strings.HasPrefix(s, "CAP_") {
|
||||
container.Capabilities[i] = fmt.Sprintf("CAP_%s", s)
|
||||
}
|
||||
}
|
||||
container.AdditionalGroups = c.GroupAdd
|
||||
|
||||
if c.AppArmorProfile != "" {
|
||||
container.AppArmorProfile = c.AppArmorProfile
|
||||
}
|
||||
|
||||
if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" {
|
||||
container.Seccomp, err = seccomp.LoadProfile(c.SeccompProfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := execdriver.SetupCgroups(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container.OomScoreAdj = c.OomScoreAdj
|
||||
|
||||
if container.Readonlyfs {
|
||||
for i := range container.Mounts {
|
||||
switch container.Mounts[i].Destination {
|
||||
case "/proc", "/dev", "/dev/pts", "/dev/mqueue":
|
||||
continue
|
||||
}
|
||||
container.Mounts[i].Flags |= syscall.MS_RDONLY
|
||||
}
|
||||
|
||||
/* These paths must be remounted as r/o */
|
||||
container.ReadonlyPaths = append(container.ReadonlyPaths, "/dev")
|
||||
}
|
||||
|
||||
if err := d.setupMounts(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.setupLabels(container, c)
|
||||
d.setupRlimits(container, c)
|
||||
|
||||
container.NoNewPrivileges = c.NoNewPrivileges
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command, hooks execdriver.Hooks) error {
|
||||
if c.Network == nil {
|
||||
return nil
|
||||
}
|
||||
if c.Network.ContainerID != "" {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.Network.ContainerID]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil {
|
||||
return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
|
||||
}
|
||||
|
||||
state, err := active.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container.Namespaces.Add(configs.NEWNET, state.NamespacePaths[configs.NEWNET])
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Network.NamespacePath != "" {
|
||||
container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath)
|
||||
return nil
|
||||
}
|
||||
// only set up prestart hook if the namespace path is not set (this should be
|
||||
// all cases *except* for --net=host shared networking)
|
||||
container.Hooks = &configs.Hooks{
|
||||
Prestart: []configs.Hook{
|
||||
configs.NewFunctionHook(func(s configs.HookState) error {
|
||||
if len(hooks.PreStart) > 0 {
|
||||
for _, fnHook := range hooks.PreStart {
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
if err := fnHook(&c.ProcessConfig, s.Pid, chOOM); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) createIpc(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.Ipc.HostIpc {
|
||||
container.Namespaces.Remove(configs.NEWIPC)
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Ipc.ContainerID != "" {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.Ipc.ContainerID]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil {
|
||||
return fmt.Errorf("%s is not a valid running container to join", c.Ipc.ContainerID)
|
||||
}
|
||||
|
||||
state, err := active.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Namespaces.Add(configs.NEWIPC, state.NamespacePaths[configs.NEWIPC])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) createPid(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.Pid.HostPid {
|
||||
container.Namespaces.Remove(configs.NEWPID)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) createUTS(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.UTS.HostUTS {
|
||||
container.Namespaces.Remove(configs.NEWUTS)
|
||||
container.Hostname = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setupRemappedRoot(container *configs.Config, c *execdriver.Command) error {
|
||||
if c.RemappedRoot.UID == 0 {
|
||||
container.Namespaces.Remove(configs.NEWUSER)
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the Docker daemon id map to the libcontainer variant of the same struct
|
||||
// this keeps us from having to import libcontainer code across Docker client + daemon packages
|
||||
cuidMaps := []configs.IDMap{}
|
||||
cgidMaps := []configs.IDMap{}
|
||||
for _, idMap := range c.UIDMapping {
|
||||
cuidMaps = append(cuidMaps, configs.IDMap(idMap))
|
||||
}
|
||||
for _, idMap := range c.GIDMapping {
|
||||
cgidMaps = append(cgidMaps, configs.IDMap(idMap))
|
||||
}
|
||||
container.UidMappings = cuidMaps
|
||||
container.GidMappings = cgidMaps
|
||||
|
||||
for _, node := range container.Devices {
|
||||
node.Uid = uint32(c.RemappedRoot.UID)
|
||||
node.Gid = uint32(c.RemappedRoot.GID)
|
||||
}
|
||||
// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
|
||||
// we must clear the readonly flag for the cgroups mount (@mrunalp concurs)
|
||||
for i := range container.Mounts {
|
||||
if container.Mounts[i].Device == "cgroup" {
|
||||
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setPrivileged(container *configs.Config) (err error) {
|
||||
container.Capabilities = execdriver.GetAllCapabilities()
|
||||
container.Cgroups.Resources.AllowAllDevices = true
|
||||
|
||||
hostDevices, err := devices.HostDevices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Devices = hostDevices
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
container.AppArmorProfile = "unconfined"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setCapabilities(container *configs.Config, c *execdriver.Command) (err error) {
|
||||
container.Capabilities, err = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Driver) setupRlimits(container *configs.Config, c *execdriver.Command) {
|
||||
if c.Resources == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, rlimit := range c.Resources.Rlimits {
|
||||
container.Rlimits = append(container.Rlimits, configs.Rlimit{
|
||||
Type: rlimit.Type,
|
||||
Hard: rlimit.Hard,
|
||||
Soft: rlimit.Soft,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If rootfs mount propagation is RPRIVATE, that means all the volumes are
|
||||
// going to be private anyway. There is no need to apply per volume
|
||||
// propagation on top. This is just an optimization so that cost of per volume
|
||||
// propagation is paid only if user decides to make some volume non-private
|
||||
// which will force rootfs mount propagation to be non RPRIVATE.
|
||||
func checkResetVolumePropagation(container *configs.Config) {
|
||||
if container.RootPropagation != mount.RPRIVATE {
|
||||
return
|
||||
}
|
||||
for _, m := range container.Mounts {
|
||||
m.PropagationFlags = nil
|
||||
}
|
||||
}
|
||||
|
||||
func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info {
|
||||
for _, m := range mountinfo {
|
||||
if m.Mountpoint == dir {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the source mount point of directory passed in as argument. Also return
|
||||
// optional fields.
|
||||
func getSourceMount(source string) (string, string, error) {
|
||||
// Ensure any symlinks are resolved.
|
||||
sourcePath, err := filepath.EvalSymlinks(source)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
mountinfos, err := mount.GetMounts()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
mountinfo := getMountInfo(mountinfos, sourcePath)
|
||||
if mountinfo != nil {
|
||||
return sourcePath, mountinfo.Optional, nil
|
||||
}
|
||||
|
||||
path := sourcePath
|
||||
for {
|
||||
path = filepath.Dir(path)
|
||||
|
||||
mountinfo = getMountInfo(mountinfos, path)
|
||||
if mountinfo != nil {
|
||||
return path, mountinfo.Optional, nil
|
||||
}
|
||||
|
||||
if path == "/" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If we are here, we did not find parent mount. Something is wrong.
|
||||
return "", "", fmt.Errorf("Could not find source mount of %s", source)
|
||||
}
|
||||
|
||||
// Ensure mount point on which path is mounted, is shared.
|
||||
func ensureShared(path string) error {
|
||||
sharedMount := false
|
||||
|
||||
sourceMount, optionalOpts, err := getSourceMount(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure source mount point is shared.
|
||||
optsSplit := strings.Split(optionalOpts, " ")
|
||||
for _, opt := range optsSplit {
|
||||
if strings.HasPrefix(opt, "shared:") {
|
||||
sharedMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !sharedMount {
|
||||
return fmt.Errorf("Path %s is mounted on %s but it is not a shared mount.", path, sourceMount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure mount point on which path is mounted, is either shared or slave.
|
||||
func ensureSharedOrSlave(path string) error {
|
||||
sharedMount := false
|
||||
slaveMount := false
|
||||
|
||||
sourceMount, optionalOpts, err := getSourceMount(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure source mount point is shared.
|
||||
optsSplit := strings.Split(optionalOpts, " ")
|
||||
for _, opt := range optsSplit {
|
||||
if strings.HasPrefix(opt, "shared:") {
|
||||
sharedMount = true
|
||||
break
|
||||
} else if strings.HasPrefix(opt, "master:") {
|
||||
slaveMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !sharedMount && !slaveMount {
|
||||
return fmt.Errorf("Path %s is mounted on %s but it is not a shared or slave mount.", path, sourceMount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) error {
|
||||
userMounts := make(map[string]struct{})
|
||||
for _, m := range c.Mounts {
|
||||
userMounts[m.Destination] = struct{}{}
|
||||
}
|
||||
|
||||
// Filter out mounts that are overridden by user supplied mounts
|
||||
var defaultMounts []*configs.Mount
|
||||
_, mountDev := userMounts["/dev"]
|
||||
for _, m := range container.Mounts {
|
||||
if _, ok := userMounts[m.Destination]; !ok {
|
||||
if mountDev && strings.HasPrefix(m.Destination, "/dev/") {
|
||||
container.Devices = nil
|
||||
continue
|
||||
}
|
||||
defaultMounts = append(defaultMounts, m)
|
||||
}
|
||||
}
|
||||
container.Mounts = defaultMounts
|
||||
|
||||
mountPropagationMap := map[string]int{
|
||||
"private": mount.PRIVATE,
|
||||
"rprivate": mount.RPRIVATE,
|
||||
"shared": mount.SHARED,
|
||||
"rshared": mount.RSHARED,
|
||||
"slave": mount.SLAVE,
|
||||
"rslave": mount.RSLAVE,
|
||||
}
|
||||
|
||||
for _, m := range c.Mounts {
|
||||
for _, cm := range container.Mounts {
|
||||
if cm.Destination == m.Destination {
|
||||
return fmt.Errorf("Duplicate mount point '%s'", m.Destination)
|
||||
}
|
||||
}
|
||||
|
||||
if m.Source == "tmpfs" {
|
||||
var (
|
||||
data = "size=65536k"
|
||||
flags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
|
||||
err error
|
||||
)
|
||||
if m.Data != "" {
|
||||
flags, data, err = mount.ParseTmpfsOptions(m.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
container.Mounts = append(container.Mounts, &configs.Mount{
|
||||
Source: m.Source,
|
||||
Destination: m.Destination,
|
||||
Data: data,
|
||||
Device: "tmpfs",
|
||||
Flags: flags,
|
||||
PropagationFlags: []int{mountPropagationMap[volume.DefaultPropagationMode]},
|
||||
})
|
||||
continue
|
||||
}
|
||||
flags := syscall.MS_BIND | syscall.MS_REC
|
||||
var pFlag int
|
||||
if !m.Writable {
|
||||
flags |= syscall.MS_RDONLY
|
||||
}
|
||||
|
||||
// Determine property of RootPropagation based on volume
|
||||
// properties. If a volume is shared, then keep root propagation
|
||||
// shared. This should work for slave and private volumes too.
|
||||
//
|
||||
// For slave volumes, it can be either [r]shared/[r]slave.
|
||||
//
|
||||
// For private volumes any root propagation value should work.
|
||||
|
||||
pFlag = mountPropagationMap[m.Propagation]
|
||||
if pFlag == mount.SHARED || pFlag == mount.RSHARED {
|
||||
if err := ensureShared(m.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
rootpg := container.RootPropagation
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED {
|
||||
execdriver.SetRootPropagation(container, mount.SHARED)
|
||||
}
|
||||
} else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
|
||||
if err := ensureSharedOrSlave(m.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
rootpg := container.RootPropagation
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
|
||||
execdriver.SetRootPropagation(container, mount.RSLAVE)
|
||||
}
|
||||
}
|
||||
|
||||
mount := &configs.Mount{
|
||||
Source: m.Source,
|
||||
Destination: m.Destination,
|
||||
Device: "bind",
|
||||
Flags: flags,
|
||||
}
|
||||
|
||||
if pFlag != 0 {
|
||||
mount.PropagationFlags = []int{pFlag}
|
||||
}
|
||||
|
||||
container.Mounts = append(container.Mounts, mount)
|
||||
}
|
||||
|
||||
checkResetVolumePropagation(container)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) setupLabels(container *configs.Config, c *execdriver.Command) {
|
||||
container.ProcessLabel = c.ProcessLabel
|
||||
container.MountLabel = c.MountLabel
|
||||
}
|
|
@ -1,606 +0,0 @@
|
|||
// +build linux,cgo
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
sysinfo "github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
aaprofile "github.com/docker/docker/profiles/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
)
|
||||
|
||||
// Define constants for native driver
|
||||
const (
|
||||
DriverName = "native"
|
||||
Version = "0.2"
|
||||
|
||||
defaultApparmorProfile = "docker-default"
|
||||
)
|
||||
|
||||
// Driver contains all information for native driver,
|
||||
// it implements execdriver.Driver.
|
||||
type Driver struct {
|
||||
root string
|
||||
activeContainers map[string]libcontainer.Container
|
||||
machineMemory int64
|
||||
factory libcontainer.Factory
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewDriver returns a new native driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (*Driver, error) {
|
||||
meminfo, err := sysinfo.ReadMemInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := sysinfo.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
|
||||
apparmorProfiles := []string{defaultApparmorProfile}
|
||||
|
||||
// Allow daemon to run if loading failed, but are active
|
||||
// (possibly through another run, manually, or via system startup)
|
||||
for _, policy := range apparmorProfiles {
|
||||
if err := aaprofile.IsLoaded(policy); err != nil {
|
||||
return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// choose cgroup manager
|
||||
// this makes sure there are no breaking changes to people
|
||||
// who upgrade from versions without native.cgroupdriver opt
|
||||
cgm := libcontainer.Cgroupfs
|
||||
|
||||
// parse the options
|
||||
for _, option := range options {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "native.cgroupdriver":
|
||||
// override the default if they set options
|
||||
switch val {
|
||||
case "systemd":
|
||||
if systemd.UseSystemd() {
|
||||
cgm = libcontainer.SystemdCgroups
|
||||
} else {
|
||||
// warn them that they chose the wrong driver
|
||||
logrus.Warn("You cannot use systemd as native.cgroupdriver, using cgroupfs instead")
|
||||
}
|
||||
case "cgroupfs":
|
||||
cgm = libcontainer.Cgroupfs
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown native.cgroupdriver given %q. try cgroupfs or systemd", val)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown option %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := libcontainer.New(
|
||||
root,
|
||||
cgm,
|
||||
libcontainer.InitPath(reexec.Self(), DriverName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Driver{
|
||||
root: root,
|
||||
activeContainers: make(map[string]libcontainer.Container),
|
||||
machineMemory: meminfo.MemTotal,
|
||||
factory: f,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run implements the exec driver Driver interface,
|
||||
// it calls libcontainer APIs to run a container.
|
||||
func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
|
||||
destroyed := false
|
||||
var err error
|
||||
c.TmpDir, err = ioutil.TempDir("", c.ID)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer os.RemoveAll(c.TmpDir)
|
||||
|
||||
// take the Command and populate the libcontainer.Config from it
|
||||
container, err := d.createContainer(c, hooks)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
p := &libcontainer.Process{
|
||||
Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...),
|
||||
Env: c.ProcessConfig.Env,
|
||||
Cwd: c.WorkingDir,
|
||||
User: c.ProcessConfig.User,
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
writers, err := setupPipes(container, &c.ProcessConfig, p, pipes, &wg)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
cont, err := d.factory.Create(c.ID, container)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
if err := cont.Start(p); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
d.Lock()
|
||||
d.activeContainers[c.ID] = cont
|
||||
d.Unlock()
|
||||
defer func() {
|
||||
if !destroyed {
|
||||
cont.Destroy()
|
||||
}
|
||||
d.cleanContainer(c.ID)
|
||||
}()
|
||||
|
||||
//close the write end of any opened pipes now that they are dup'ed into the container
|
||||
for _, writer := range writers {
|
||||
writer.Close()
|
||||
}
|
||||
// 'oom' is used to emit 'oom' events to the eventstream, 'oomKilled' is used
|
||||
// to set the 'OOMKilled' flag in state
|
||||
oom := notifyOnOOM(cont)
|
||||
oomKilled := notifyOnOOM(cont)
|
||||
if hooks.Start != nil {
|
||||
pid, err := p.Pid()
|
||||
if err != nil {
|
||||
p.Signal(os.Kill)
|
||||
p.Wait()
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
hooks.Start(&c.ProcessConfig, pid, oom)
|
||||
}
|
||||
|
||||
waitF := p.Wait
|
||||
if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
|
||||
// we need such hack for tracking processes with inherited fds,
|
||||
// because cmd.Wait() waiting for all streams to be copied
|
||||
waitF = waitInPIDHost(p, cont)
|
||||
}
|
||||
ps, err := waitF()
|
||||
if err != nil {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
ps = execErr.ProcessState
|
||||
}
|
||||
// wait for all IO goroutine copiers to finish
|
||||
wg.Wait()
|
||||
|
||||
cont.Destroy()
|
||||
destroyed = true
|
||||
// oomKilled will have an oom event if any process within the container was
|
||||
// OOM killed at any time, not only if the init process OOMed.
|
||||
//
|
||||
// Perhaps we only want the OOMKilled flag to be set if the OOM
|
||||
// resulted in a container death, but there isn't a good way to do this
|
||||
// because the kernel's cgroup oom notification does not provide information
|
||||
// such as the PID. This could be heuristically done by checking that the OOM
|
||||
// happened within some very small time slice for the container dying (and
|
||||
// optionally exit-code 137), but I don't think the cgroup oom notification
|
||||
// can be used to reliably determine this
|
||||
//
|
||||
// Even if there were multiple OOMs, it's sufficient to read one value
|
||||
// because libcontainer's oom notify will discard the channel after the
|
||||
// cgroup is destroyed
|
||||
_, oomKill := <-oomKilled
|
||||
return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
|
||||
}
|
||||
|
||||
// notifyOnOOM returns a channel that signals if the container received an OOM notification
|
||||
// for any process. If it is unable to subscribe to OOM notifications then a closed
|
||||
// channel is returned as it will be non-blocking and return the correct result when read.
|
||||
func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
|
||||
oom, err := container.NotifyOOM()
|
||||
if err != nil {
|
||||
logrus.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
c := make(chan struct{})
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
return oom
|
||||
}
|
||||
|
||||
func killCgroupProcs(c libcontainer.Container) {
|
||||
var procs []*os.Process
|
||||
if err := c.Pause(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
pids, err := c.Processes()
|
||||
if err != nil {
|
||||
// don't care about childs if we can't get them, this is mostly because cgroup already deleted
|
||||
logrus.Warnf("Failed to get processes from container %s: %v", c.ID(), err)
|
||||
}
|
||||
for _, pid := range pids {
|
||||
if p, err := os.FindProcess(pid); err == nil {
|
||||
procs = append(procs, p)
|
||||
if err := p.Kill(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := c.Resume(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
for _, p := range procs {
|
||||
if _, err := p.Wait(); err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
|
||||
return func() (*os.ProcessState, error) {
|
||||
pid, err := p.Pid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
s, err := process.Wait()
|
||||
if err != nil {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return s, err
|
||||
}
|
||||
s = execErr.ProcessState
|
||||
}
|
||||
killCgroupProcs(c)
|
||||
p.Wait()
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
|
||||
// Kill implements the exec driver Driver interface.
|
||||
func (d *Driver) Kill(c *execdriver.Command, sig int) error {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
state, err := active.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.InitProcessPid == -1 {
|
||||
return fmt.Errorf("avoid sending signal %d to container %s with pid -1", sig, c.ID)
|
||||
}
|
||||
return syscall.Kill(state.InitProcessPid, syscall.Signal(sig))
|
||||
}
|
||||
|
||||
// Pause implements the exec driver Driver interface,
|
||||
// it calls libcontainer API to pause a container.
|
||||
func (d *Driver) Pause(c *execdriver.Command) error {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
return active.Pause()
|
||||
}
|
||||
|
||||
// Unpause implements the exec driver Driver interface,
|
||||
// it calls libcontainer API to unpause a container.
|
||||
func (d *Driver) Unpause(c *execdriver.Command) error {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
return active.Resume()
|
||||
}
|
||||
|
||||
// Terminate implements the exec driver Driver interface.
|
||||
func (d *Driver) Terminate(c *execdriver.Command) error {
|
||||
defer d.cleanContainer(c.ID)
|
||||
container, err := d.factory.Load(c.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Destroy()
|
||||
state, err := container.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid := state.InitProcessPid
|
||||
currentStartTime, err := system.GetProcessStartTime(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.InitProcessStartTime == currentStartTime {
|
||||
err = syscall.Kill(pid, 9)
|
||||
syscall.Wait4(pid, nil, 0, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Name implements the exec driver Driver interface.
|
||||
func (d *Driver) Name() string {
|
||||
return fmt.Sprintf("%s-%s", DriverName, Version)
|
||||
}
|
||||
|
||||
// GetPidsForContainer implements the exec driver Driver interface.
|
||||
func (d *Driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
d.Lock()
|
||||
active := d.activeContainers[id]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil {
|
||||
return nil, fmt.Errorf("active container for %s does not exist", id)
|
||||
}
|
||||
return active.Processes()
|
||||
}
|
||||
|
||||
func (d *Driver) cleanContainer(id string) error {
|
||||
d.Lock()
|
||||
delete(d.activeContainers, id)
|
||||
d.Unlock()
|
||||
return os.RemoveAll(filepath.Join(d.root, id))
|
||||
}
|
||||
|
||||
func (d *Driver) createContainerRoot(id string) error {
|
||||
return os.MkdirAll(filepath.Join(d.root, id), 0655)
|
||||
}
|
||||
|
||||
// Clean implements the exec driver Driver interface.
|
||||
func (d *Driver) Clean(id string) error {
|
||||
return os.RemoveAll(filepath.Join(d.root, id))
|
||||
}
|
||||
|
||||
// Stats implements the exec driver Driver interface.
|
||||
func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
d.Lock()
|
||||
c := d.activeContainers[id]
|
||||
d.Unlock()
|
||||
if c == nil {
|
||||
return nil, execdriver.ErrNotRunning
|
||||
}
|
||||
now := time.Now()
|
||||
stats, err := c.Stats()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memoryLimit := c.Config().Cgroups.Resources.Memory
|
||||
// if the container does not have any memory limit specified set the
|
||||
// limit to the machines memory
|
||||
if memoryLimit == 0 {
|
||||
memoryLimit = d.machineMemory
|
||||
}
|
||||
return &execdriver.ResourceStats{
|
||||
Stats: stats,
|
||||
Read: now,
|
||||
MemoryLimit: memoryLimit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Update updates configs for a container
|
||||
func (d *Driver) Update(c *execdriver.Command) error {
|
||||
d.Lock()
|
||||
cont := d.activeContainers[c.ID]
|
||||
d.Unlock()
|
||||
if cont == nil {
|
||||
return execdriver.ErrNotRunning
|
||||
}
|
||||
config := cont.Config()
|
||||
if err := execdriver.SetupCgroups(&config, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cont.Set(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TtyConsole implements the exec driver Terminal interface.
|
||||
type TtyConsole struct {
|
||||
console libcontainer.Console
|
||||
}
|
||||
|
||||
// NewTtyConsole returns a new TtyConsole struct.
|
||||
func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes, wg *sync.WaitGroup) (*TtyConsole, error) {
|
||||
tty := &TtyConsole{
|
||||
console: console,
|
||||
}
|
||||
|
||||
if err := tty.AttachPipes(pipes, wg); err != nil {
|
||||
tty.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface
|
||||
func (t *TtyConsole) Resize(h, w int) error {
|
||||
return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
|
||||
}
|
||||
|
||||
// AttachPipes attaches given pipes to TtyConsole
|
||||
func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes, wg *sync.WaitGroup) error {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if wb, ok := pipes.Stdout.(interface {
|
||||
CloseWriters() error
|
||||
}); ok {
|
||||
defer wb.CloseWriters()
|
||||
}
|
||||
|
||||
pools.Copy(pipes.Stdout, t.console)
|
||||
}()
|
||||
|
||||
if pipes.Stdin != nil {
|
||||
go func() {
|
||||
pools.Copy(t.console, pipes.Stdin)
|
||||
|
||||
pipes.Stdin.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface
|
||||
func (t *TtyConsole) Close() error {
|
||||
return t.console.Close()
|
||||
}
|
||||
|
||||
func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes, wg *sync.WaitGroup) ([]io.WriteCloser, error) {
|
||||
|
||||
writers := []io.WriteCloser{}
|
||||
|
||||
rootuid, err := container.HostUID()
|
||||
if err != nil {
|
||||
return writers, err
|
||||
}
|
||||
|
||||
if processConfig.Tty {
|
||||
cons, err := p.NewConsole(rootuid)
|
||||
if err != nil {
|
||||
return writers, err
|
||||
}
|
||||
term, err := NewTtyConsole(cons, pipes, wg)
|
||||
if err != nil {
|
||||
return writers, err
|
||||
}
|
||||
processConfig.Terminal = term
|
||||
return writers, nil
|
||||
}
|
||||
// not a tty--set up stdio pipes
|
||||
term := &execdriver.StdConsole{}
|
||||
processConfig.Terminal = term
|
||||
|
||||
// if we are not in a user namespace, there is no reason to go through
|
||||
// the hassle of setting up os-level pipes with proper (remapped) ownership
|
||||
// so we will do the prior shortcut for non-userns containers
|
||||
if rootuid == 0 {
|
||||
p.Stdout = pipes.Stdout
|
||||
p.Stderr = pipes.Stderr
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return writers, err
|
||||
}
|
||||
if pipes.Stdin != nil {
|
||||
go func() {
|
||||
io.Copy(w, pipes.Stdin)
|
||||
w.Close()
|
||||
}()
|
||||
p.Stdin = r
|
||||
}
|
||||
return writers, nil
|
||||
}
|
||||
|
||||
// if we have user namespaces enabled (rootuid != 0), we will set
|
||||
// up os pipes for stderr, stdout, stdin so we can chown them to
|
||||
// the proper ownership to allow for proper access to the underlying
|
||||
// fds
|
||||
var fds []uintptr
|
||||
|
||||
copyPipes := func(out io.Writer, in io.ReadCloser) {
|
||||
defer wg.Done()
|
||||
io.Copy(out, in)
|
||||
in.Close()
|
||||
}
|
||||
|
||||
//setup stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
w.Close()
|
||||
return writers, err
|
||||
}
|
||||
writers = append(writers, w)
|
||||
fds = append(fds, r.Fd(), w.Fd())
|
||||
if pipes.Stdout != nil {
|
||||
wg.Add(1)
|
||||
go copyPipes(pipes.Stdout, r)
|
||||
}
|
||||
term.Closers = append(term.Closers, r)
|
||||
p.Stdout = w
|
||||
|
||||
//setup stderr
|
||||
r, w, err = os.Pipe()
|
||||
if err != nil {
|
||||
w.Close()
|
||||
return writers, err
|
||||
}
|
||||
writers = append(writers, w)
|
||||
fds = append(fds, r.Fd(), w.Fd())
|
||||
if pipes.Stderr != nil {
|
||||
wg.Add(1)
|
||||
go copyPipes(pipes.Stderr, r)
|
||||
}
|
||||
term.Closers = append(term.Closers, r)
|
||||
p.Stderr = w
|
||||
|
||||
//setup stdin
|
||||
r, w, err = os.Pipe()
|
||||
if err != nil {
|
||||
r.Close()
|
||||
return writers, err
|
||||
}
|
||||
fds = append(fds, r.Fd(), w.Fd())
|
||||
if pipes.Stdin != nil {
|
||||
go func() {
|
||||
io.Copy(w, pipes.Stdin)
|
||||
w.Close()
|
||||
}()
|
||||
p.Stdin = r
|
||||
}
|
||||
for _, fd := range fds {
|
||||
if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil {
|
||||
return writers, fmt.Errorf("Failed to chown pipes fd: %v", err)
|
||||
}
|
||||
}
|
||||
return writers, nil
|
||||
}
|
||||
|
||||
// SupportsHooks implements the execdriver Driver interface.
|
||||
// The libcontainer/runC-based native execdriver does exploit the hook mechanism
|
||||
func (d *Driver) SupportsHooks() bool {
|
||||
return true
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// NewDriver returns a new native driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("native driver not supported on non-linux")
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +build linux,!cgo
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// NewDriver returns a new native driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("native driver not supported on non-linux")
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
// Blank import 'nsenter' so that init in that package will call c
|
||||
// function 'nsexec()' to do 'setns' before Go runtime take over,
|
||||
// it's used for join to exist ns like 'docker exec' command.
|
||||
_ "github.com/opencontainers/runc/libcontainer/nsenter"
|
||||
"github.com/opencontainers/runc/libcontainer/utils"
|
||||
)
|
||||
|
||||
// Exec implements the exec driver Driver interface,
|
||||
// it calls libcontainer APIs to execute a container.
|
||||
func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
|
||||
}
|
||||
|
||||
user := processConfig.User
|
||||
if c.RemappedRoot.UID != 0 && user == "" {
|
||||
//if user namespaces are enabled, set user explicitly so uid/gid is set to 0
|
||||
//otherwise we end up with the overflow id and no permissions (65534)
|
||||
user = "0"
|
||||
}
|
||||
|
||||
p := &libcontainer.Process{
|
||||
Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...),
|
||||
Env: c.ProcessConfig.Env,
|
||||
Cwd: c.WorkingDir,
|
||||
User: user,
|
||||
}
|
||||
|
||||
if processConfig.Privileged {
|
||||
p.Capabilities = execdriver.GetAllCapabilities()
|
||||
}
|
||||
// add CAP_ prefix to all caps for new libcontainer update to match
|
||||
// the spec format.
|
||||
for i, s := range p.Capabilities {
|
||||
if !strings.HasPrefix(s, "CAP_") {
|
||||
p.Capabilities[i] = fmt.Sprintf("CAP_%s", s)
|
||||
}
|
||||
}
|
||||
|
||||
config := active.Config()
|
||||
wg := sync.WaitGroup{}
|
||||
writers, err := setupPipes(&config, processConfig, p, pipes, &wg)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if err := active.Start(p); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
//close the write end of any opened pipes now that they are dup'ed into the container
|
||||
for _, writer := range writers {
|
||||
writer.Close()
|
||||
}
|
||||
|
||||
if hooks.Start != nil {
|
||||
pid, err := p.Pid()
|
||||
if err != nil {
|
||||
p.Signal(os.Kill)
|
||||
p.Wait()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
hooks.Start(&c.ProcessConfig, pid, chOOM)
|
||||
}
|
||||
|
||||
ps, err := p.Wait()
|
||||
if err != nil {
|
||||
exitErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return -1, err
|
||||
}
|
||||
ps = exitErr.ProcessState
|
||||
}
|
||||
// wait for all IO goroutine copiers to finish
|
||||
wg.Wait()
|
||||
return utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), nil
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register(DriverName, initializer)
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
if lerr, ok := err.(libcontainer.Error); ok {
|
||||
lerr.Detail(os.Stderr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func initializer() {
|
||||
runtime.GOMAXPROCS(1)
|
||||
runtime.LockOSThread()
|
||||
factory, err := libcontainer.New("")
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
if err := factory.StartInitialization(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
)
|
||||
|
||||
const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
|
||||
|
||||
// New returns the docker default configuration for libcontainer
|
||||
func New() *configs.Config {
|
||||
container := &configs.Config{
|
||||
Capabilities: []string{
|
||||
"CHOWN",
|
||||
"DAC_OVERRIDE",
|
||||
"FSETID",
|
||||
"FOWNER",
|
||||
"MKNOD",
|
||||
"NET_RAW",
|
||||
"SETGID",
|
||||
"SETUID",
|
||||
"SETFCAP",
|
||||
"SETPCAP",
|
||||
"NET_BIND_SERVICE",
|
||||
"SYS_CHROOT",
|
||||
"KILL",
|
||||
"AUDIT_WRITE",
|
||||
},
|
||||
Namespaces: configs.Namespaces([]configs.Namespace{
|
||||
{Type: "NEWNS"},
|
||||
{Type: "NEWUTS"},
|
||||
{Type: "NEWIPC"},
|
||||
{Type: "NEWPID"},
|
||||
{Type: "NEWNET"},
|
||||
{Type: "NEWUSER"},
|
||||
}),
|
||||
Cgroups: &configs.Cgroup{
|
||||
ScopePrefix: "docker", // systemd only
|
||||
Resources: &configs.Resources{
|
||||
AllowAllDevices: false,
|
||||
MemorySwappiness: -1,
|
||||
},
|
||||
},
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "proc",
|
||||
Destination: "/proc",
|
||||
Device: "proc",
|
||||
Flags: defaultMountFlags,
|
||||
},
|
||||
{
|
||||
Source: "tmpfs",
|
||||
Destination: "/dev",
|
||||
Device: "tmpfs",
|
||||
Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME,
|
||||
Data: "mode=755",
|
||||
},
|
||||
{
|
||||
Source: "devpts",
|
||||
Destination: "/dev/pts",
|
||||
Device: "devpts",
|
||||
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
|
||||
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
|
||||
},
|
||||
{
|
||||
Source: "mqueue",
|
||||
Destination: "/dev/mqueue",
|
||||
Device: "mqueue",
|
||||
Flags: defaultMountFlags,
|
||||
},
|
||||
{
|
||||
Source: "sysfs",
|
||||
Destination: "/sys",
|
||||
Device: "sysfs",
|
||||
Flags: defaultMountFlags | syscall.MS_RDONLY,
|
||||
},
|
||||
{
|
||||
Source: "cgroup",
|
||||
Destination: "/sys/fs/cgroup",
|
||||
Device: "cgroup",
|
||||
Flags: defaultMountFlags | syscall.MS_RDONLY,
|
||||
},
|
||||
},
|
||||
MaskPaths: []string{
|
||||
"/proc/kcore",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_stats",
|
||||
},
|
||||
ReadonlyPaths: []string{
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger",
|
||||
},
|
||||
}
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
container.AppArmorProfile = "docker-default"
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
package template
|
|
@ -1,24 +0,0 @@
|
|||
package execdriver
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Pipes is a wrapper around a container's output for
|
||||
// stdin, stdout, stderr
|
||||
type Pipes struct {
|
||||
Stdin io.ReadCloser
|
||||
Stdout, Stderr io.Writer
|
||||
}
|
||||
|
||||
// NewPipes returns a wrapper around a container's output
|
||||
func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes {
|
||||
p := &Pipes{
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}
|
||||
if useStdin {
|
||||
p.Stdin = stdin
|
||||
}
|
||||
return p
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package execdriver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// StdConsole defines standard console operations for execdriver
|
||||
type StdConsole struct {
|
||||
// Closers holds io.Closer references for closing at terminal close time
|
||||
Closers []io.Closer
|
||||
}
|
||||
|
||||
// NewStdConsole returns a new StdConsole struct
|
||||
func NewStdConsole(processConfig *ProcessConfig, pipes *Pipes) (*StdConsole, error) {
|
||||
std := &StdConsole{}
|
||||
|
||||
if err := std.AttachPipes(&processConfig.Cmd, pipes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return std, nil
|
||||
}
|
||||
|
||||
// AttachPipes attaches given pipes to exec.Cmd
|
||||
func (s *StdConsole) AttachPipes(command *exec.Cmd, pipes *Pipes) error {
|
||||
command.Stdout = pipes.Stdout
|
||||
command.Stderr = pipes.Stderr
|
||||
|
||||
if pipes.Stdin != nil {
|
||||
stdin, err := command.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
io.Copy(stdin, pipes.Stdin)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface
|
||||
func (s *StdConsole) Resize(h, w int) error {
|
||||
// we do not need to resize a non tty
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface
|
||||
func (s *StdConsole) Close() error {
|
||||
for _, c := range s.Closers {
|
||||
c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// +build !windows
|
||||
|
||||
package execdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
)
|
||||
|
||||
var capabilityList Capabilities
|
||||
|
||||
func init() {
|
||||
last := capability.CAP_LAST_CAP
|
||||
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
||||
if last == capability.Cap(63) {
|
||||
last = capability.CAP_BLOCK_SUSPEND
|
||||
}
|
||||
for _, cap := range capability.List() {
|
||||
if cap > last {
|
||||
continue
|
||||
}
|
||||
capabilityList = append(capabilityList,
|
||||
&CapabilityMapping{
|
||||
Key: strings.ToUpper(cap.String()),
|
||||
Value: cap,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// CapabilityMapping maps linux capability name to its value of capability.Cap type
|
||||
// Capabilities is one of the security systems in Linux Security Module (LSM)
|
||||
// framework provided by the kernel.
|
||||
// For more details on capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html
|
||||
CapabilityMapping struct {
|
||||
Key string `json:"key,omitempty"`
|
||||
Value capability.Cap `json:"value,omitempty"`
|
||||
}
|
||||
// Capabilities contains all CapabilityMapping
|
||||
Capabilities []*CapabilityMapping
|
||||
)
|
||||
|
||||
// String returns <key> of CapabilityMapping
|
||||
func (c *CapabilityMapping) String() string {
|
||||
return c.Key
|
||||
}
|
||||
|
||||
// GetCapability returns CapabilityMapping which contains specific key
|
||||
func GetCapability(key string) *CapabilityMapping {
|
||||
for _, capp := range capabilityList {
|
||||
if capp.Key == key {
|
||||
cpy := *capp
|
||||
return &cpy
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllCapabilities returns all of the capabilities
|
||||
func GetAllCapabilities() []string {
|
||||
output := make([]string, len(capabilityList))
|
||||
for i, capability := range capabilityList {
|
||||
output[i] = capability.String()
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// TweakCapabilities can tweak capabilities by adding or dropping capabilities
|
||||
// based on the basics capabilities.
|
||||
func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
|
||||
var (
|
||||
newCaps []string
|
||||
allCaps = GetAllCapabilities()
|
||||
)
|
||||
|
||||
// look for invalid cap in the drop list
|
||||
for _, cap := range drops {
|
||||
if strings.ToLower(cap) == "all" {
|
||||
continue
|
||||
}
|
||||
if !stringutils.InSlice(allCaps, cap) {
|
||||
return nil, fmt.Errorf("Unknown capability drop: %q", cap)
|
||||
}
|
||||
}
|
||||
|
||||
// handle --cap-add=all
|
||||
if stringutils.InSlice(adds, "all") {
|
||||
basics = allCaps
|
||||
}
|
||||
|
||||
if !stringutils.InSlice(drops, "all") {
|
||||
for _, cap := range basics {
|
||||
// skip `all` already handled above
|
||||
if strings.ToLower(cap) == "all" {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we don't drop `all`, add back all the non-dropped caps
|
||||
if !stringutils.InSlice(drops, cap) {
|
||||
newCaps = append(newCaps, strings.ToUpper(cap))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, cap := range adds {
|
||||
// skip `all` already handled above
|
||||
if strings.ToLower(cap) == "all" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !stringutils.InSlice(allCaps, cap) {
|
||||
return nil, fmt.Errorf("Unknown capability to add: %q", cap)
|
||||
}
|
||||
|
||||
// add cap if not already in the list
|
||||
if !stringutils.InSlice(newCaps, cap) {
|
||||
newCaps = append(newCaps, strings.ToUpper(cap))
|
||||
}
|
||||
}
|
||||
return newCaps, nil
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
// Clean implements the exec driver Driver interface.
|
||||
func (d *Driver) Clean(id string) error {
|
||||
return nil
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
//+build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// createCommandLine creates a command line from the Entrypoint and args
|
||||
// of the ProcessConfig. It escapes the arguments if they are not already
|
||||
// escaped
|
||||
func createCommandLine(processConfig *execdriver.ProcessConfig, alreadyEscaped bool) (commandLine string, err error) {
|
||||
// While this should get caught earlier, just in case, validate that we
|
||||
// have something to run.
|
||||
if processConfig.Entrypoint == "" {
|
||||
return "", errors.New("No entrypoint specified")
|
||||
}
|
||||
|
||||
// Build the command line of the process
|
||||
commandLine = processConfig.Entrypoint
|
||||
logrus.Debugf("Entrypoint: %s", processConfig.Entrypoint)
|
||||
for _, arg := range processConfig.Arguments {
|
||||
logrus.Debugf("appending %s", arg)
|
||||
if !alreadyEscaped {
|
||||
arg = syscall.EscapeArg(arg)
|
||||
}
|
||||
commandLine += " " + arg
|
||||
}
|
||||
|
||||
logrus.Debugf("commandLine: %s", commandLine)
|
||||
return commandLine, nil
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Exec implements the exec driver Driver interface.
|
||||
func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
|
||||
|
||||
var (
|
||||
term execdriver.Terminal
|
||||
err error
|
||||
exitCode int32
|
||||
)
|
||||
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID)
|
||||
}
|
||||
|
||||
createProcessParms := hcsshim.CreateProcessParams{
|
||||
EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty
|
||||
WorkingDirectory: c.WorkingDir,
|
||||
}
|
||||
|
||||
// Configure the environment for the process // Note NOT c.ProcessConfig.Env
|
||||
createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env)
|
||||
|
||||
// Create the commandline for the process // Note NOT c.ProcessConfig
|
||||
createProcessParms.CommandLine, err = createCommandLine(processConfig, false)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Start the command running in the container.
|
||||
pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms)
|
||||
if err != nil {
|
||||
// TODO Windows: TP4 Workaround. In Hyper-V containers, there is a limitation
|
||||
// of one exec per container. This should be fixed post TP4. CreateProcessInComputeSystem
|
||||
// will return a specific error which we handle here to give a good error message
|
||||
// back to the user instead of an inactionable "An invalid argument was supplied"
|
||||
if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == hcsshim.WSAEINVAL {
|
||||
return -1, fmt.Errorf("The limit of docker execs per Hyper-V container has been exceeded")
|
||||
}
|
||||
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Now that the process has been launched, begin copying data to and from
|
||||
// the named pipes for the std handles.
|
||||
setupPipes(stdin, stdout, stderr, pipes)
|
||||
|
||||
// Note NOT c.ProcessConfig.Tty
|
||||
if processConfig.Tty {
|
||||
term = NewTtyConsole(c.ID, pid)
|
||||
} else {
|
||||
term = NewStdConsole()
|
||||
}
|
||||
processConfig.Terminal = term
|
||||
|
||||
// Invoke the start callback
|
||||
if hooks.Start != nil {
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
hooks.Start(&c.ProcessConfig, int(pid), chOOM)
|
||||
}
|
||||
|
||||
if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite); err != nil {
|
||||
if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == syscall.ERROR_BROKEN_PIPE {
|
||||
logrus.Debugf("Exiting Run() after WaitForProcessInComputeSystem failed with recognised error %s", err)
|
||||
return hcsshim.WaitErrExecFailed, nil
|
||||
}
|
||||
logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
logrus.Debugln("Exiting Run()", c.ID)
|
||||
return int(exitCode), nil
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import "fmt"
|
||||
|
||||
// GetPidsForContainer implements the exec driver Driver interface.
|
||||
func (d *Driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
// TODO Windows: Implementation required.
|
||||
return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented")
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// General comment. Handling I/O for a container is very different to Linux.
|
||||
// We use a named pipe to HCS to copy I/O both in and out of the container,
|
||||
// very similar to how docker daemon communicates with a CLI.
|
||||
|
||||
// startStdinCopy asynchronously copies an io.Reader to the container's
|
||||
// process's stdin pipe and closes the pipe when there is no more data to copy.
|
||||
func startStdinCopy(dst io.WriteCloser, src io.Reader) {
|
||||
|
||||
// Anything that comes from the client stdin should be copied
|
||||
// across to the stdin named pipe of the container.
|
||||
go func() {
|
||||
defer dst.Close()
|
||||
bytes, err := io.Copy(dst, src)
|
||||
log := fmt.Sprintf("Copied %d bytes from stdin.", bytes)
|
||||
if err != nil {
|
||||
log = log + " err=" + err.Error()
|
||||
}
|
||||
logrus.Debugf(log)
|
||||
}()
|
||||
}
|
||||
|
||||
// startStdouterrCopy asynchronously copies data from the container's process's
|
||||
// stdout or stderr pipe to an io.Writer and closes the pipe when there is no
|
||||
// more data to copy.
|
||||
func startStdouterrCopy(dst io.Writer, src io.ReadCloser, name string) {
|
||||
// Anything that comes from the container named pipe stdout/err should be copied
|
||||
// across to the stdout/err of the client
|
||||
go func() {
|
||||
defer src.Close()
|
||||
bytes, err := io.Copy(dst, src)
|
||||
log := fmt.Sprintf("Copied %d bytes from %s.", bytes, name)
|
||||
if err != nil {
|
||||
log = log + " err=" + err.Error()
|
||||
}
|
||||
logrus.Debugf(log)
|
||||
}()
|
||||
}
|
||||
|
||||
// setupPipes starts the asynchronous copying of data to and from the named
|
||||
// pipes used byt he HCS for the std handles.
|
||||
func setupPipes(stdin io.WriteCloser, stdout, stderr io.ReadCloser, pipes *execdriver.Pipes) {
|
||||
if stdin != nil {
|
||||
startStdinCopy(stdin, pipes.Stdin)
|
||||
}
|
||||
if stdout != nil {
|
||||
startStdouterrCopy(pipes.Stdout, stdout, "stdout")
|
||||
}
|
||||
if stderr != nil {
|
||||
startStdouterrCopy(pipes.Stderr, stderr, "stderr")
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Pause implements the exec driver Driver interface.
|
||||
func (d *Driver) Pause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
||||
|
||||
// Unpause implements the exec driver Driver interface.
|
||||
func (d *Driver) Unpause(c *execdriver.Command) error {
|
||||
return fmt.Errorf("Windows: Containers cannot be paused")
|
||||
}
|
|
@ -1,366 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// defaultContainerNAT is the default name of the container NAT device that is
|
||||
// preconfigured on the server.
|
||||
const defaultContainerNAT = "ContainerNAT"
|
||||
|
||||
// Win32 error codes that are used for various workarounds
|
||||
// These really should be ALL_CAPS to match golangs syscall library and standard
|
||||
// Win32 error conventions, but golint insists on CamelCase.
|
||||
const (
|
||||
CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string
|
||||
ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started
|
||||
ErrorBadPathname = syscall.Errno(161) // The specified path is invalid
|
||||
ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
|
||||
)
|
||||
|
||||
type layer struct {
|
||||
ID string
|
||||
Path string
|
||||
}
|
||||
|
||||
type portBinding struct {
|
||||
Protocol string
|
||||
InternalPort int
|
||||
ExternalPort int
|
||||
}
|
||||
|
||||
type natSettings struct {
|
||||
Name string
|
||||
PortBindings []portBinding
|
||||
}
|
||||
|
||||
type networkConnection struct {
|
||||
NetworkName string
|
||||
// TODO Windows: Add Ip4Address string to this structure when hooked up in
|
||||
// docker CLI. This is present in the HCS JSON handler.
|
||||
EnableNat bool
|
||||
Nat natSettings
|
||||
}
|
||||
type networkSettings struct {
|
||||
MacAddress string
|
||||
}
|
||||
|
||||
type device struct {
|
||||
DeviceType string
|
||||
Connection interface{}
|
||||
Settings interface{}
|
||||
}
|
||||
|
||||
type mappedDir struct {
|
||||
HostPath string
|
||||
ContainerPath string
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
type containerInit struct {
|
||||
SystemType string // HCS requires this to be hard-coded to "Container"
|
||||
Name string // Name of the container. We use the docker ID.
|
||||
Owner string // The management platform that created this container
|
||||
IsDummy bool // Used for development purposes.
|
||||
VolumePath string // Windows volume path for scratch space
|
||||
Devices []device // Devices used by the container
|
||||
IgnoreFlushesDuringBoot bool // Optimization hint for container startup in Windows
|
||||
LayerFolderPath string // Where the layer folders are located
|
||||
Layers []layer // List of storage layers
|
||||
ProcessorWeight int64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default.
|
||||
HostName string // Hostname
|
||||
MappedDirectories []mappedDir // List of mapped directories (volumes/mounts)
|
||||
SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers)
|
||||
HvPartition bool // True if it a Hyper-V Container
|
||||
EndpointList []string // List of endpoints to be attached to container
|
||||
}
|
||||
|
||||
// defaultOwner is a tag passed to HCS to allow it to differentiate between
|
||||
// container creator management stacks. We hard code "docker" in the case
|
||||
// of docker.
|
||||
const defaultOwner = "docker"
|
||||
|
||||
// Run implements the exec driver Driver interface
|
||||
func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
|
||||
|
||||
var (
|
||||
term execdriver.Terminal
|
||||
err error
|
||||
)
|
||||
|
||||
// Allocate Network only if there is no network interface
|
||||
cu := &containerInit{
|
||||
SystemType: "Container",
|
||||
Name: c.ID,
|
||||
Owner: defaultOwner,
|
||||
IsDummy: dummyMode,
|
||||
VolumePath: c.Rootfs,
|
||||
IgnoreFlushesDuringBoot: c.FirstStart,
|
||||
LayerFolderPath: c.LayerFolder,
|
||||
ProcessorWeight: c.Resources.CPUShares,
|
||||
HostName: c.Hostname,
|
||||
EndpointList: c.EpList,
|
||||
}
|
||||
|
||||
cu.HvPartition = c.HvPartition
|
||||
|
||||
if cu.HvPartition {
|
||||
cu.SandboxPath = filepath.Dir(c.LayerFolder)
|
||||
} else {
|
||||
cu.VolumePath = c.Rootfs
|
||||
cu.LayerFolderPath = c.LayerFolder
|
||||
}
|
||||
|
||||
for _, layerPath := range c.LayerPaths {
|
||||
_, filename := filepath.Split(layerPath)
|
||||
g, err := hcsshim.NameToGuid(filename)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
cu.Layers = append(cu.Layers, layer{
|
||||
ID: g.ToString(),
|
||||
Path: layerPath,
|
||||
})
|
||||
}
|
||||
|
||||
// Add the mounts (volumes, bind mounts etc) to the structure
|
||||
mds := make([]mappedDir, len(c.Mounts))
|
||||
for i, mount := range c.Mounts {
|
||||
mds[i] = mappedDir{
|
||||
HostPath: mount.Source,
|
||||
ContainerPath: mount.Destination,
|
||||
ReadOnly: !mount.Writable}
|
||||
}
|
||||
cu.MappedDirectories = mds
|
||||
|
||||
// TODO Windows. At some point, when there is CLI on docker run to
|
||||
// enable the IP Address of the container to be passed into docker run,
|
||||
// the IP Address needs to be wired through to HCS in the JSON. It
|
||||
// would be present in c.Network.Interface.IPAddress. See matching
|
||||
// TODO in daemon\container_windows.go, function populateCommand.
|
||||
|
||||
if c.Network.Interface != nil {
|
||||
|
||||
var pbs []portBinding
|
||||
|
||||
// Enumerate through the port bindings specified by the user and convert
|
||||
// them into the internal structure matching the JSON blob that can be
|
||||
// understood by the HCS.
|
||||
for i, v := range c.Network.Interface.PortBindings {
|
||||
proto := strings.ToUpper(i.Proto())
|
||||
if proto != "TCP" && proto != "UDP" {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto())
|
||||
}
|
||||
|
||||
if len(v) > 1 {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings")
|
||||
}
|
||||
|
||||
for _, v2 := range v {
|
||||
var (
|
||||
iPort, ePort int
|
||||
err error
|
||||
)
|
||||
if len(v2.HostIP) != 0 {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings")
|
||||
}
|
||||
if ePort, err = strconv.Atoi(v2.HostPort); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err)
|
||||
}
|
||||
if iPort, err = strconv.Atoi(i.Port()); err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err)
|
||||
}
|
||||
if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range")
|
||||
}
|
||||
pbs = append(pbs,
|
||||
portBinding{ExternalPort: ePort,
|
||||
InternalPort: iPort,
|
||||
Protocol: proto})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Windows: TP3 workaround. Allow the user to override the name of
|
||||
// the Container NAT device through an environment variable. This will
|
||||
// ultimately be a global daemon parameter on Windows, similar to -b
|
||||
// for the name of the virtual switch (aka bridge).
|
||||
cn := os.Getenv("DOCKER_CONTAINER_NAT")
|
||||
if len(cn) == 0 {
|
||||
cn = defaultContainerNAT
|
||||
}
|
||||
|
||||
dev := device{
|
||||
DeviceType: "Network",
|
||||
Connection: &networkConnection{
|
||||
NetworkName: c.Network.Interface.Bridge,
|
||||
// TODO Windows: Fixme, next line. Needs HCS fix.
|
||||
EnableNat: false,
|
||||
Nat: natSettings{
|
||||
Name: cn,
|
||||
PortBindings: pbs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if c.Network.Interface.MacAddress != "" {
|
||||
windowsStyleMAC := strings.Replace(
|
||||
c.Network.Interface.MacAddress, ":", "-", -1)
|
||||
dev.Settings = networkSettings{
|
||||
MacAddress: windowsStyleMAC,
|
||||
}
|
||||
}
|
||||
cu.Devices = append(cu.Devices, dev)
|
||||
} else {
|
||||
logrus.Debugln("No network interface")
|
||||
}
|
||||
|
||||
configurationb, err := json.Marshal(cu)
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
configuration := string(configurationb)
|
||||
|
||||
// TODO Windows TP5 timeframe. Remove when TP4 is no longer supported.
|
||||
// The following a workaround for Windows TP4 which has a networking
|
||||
// bug which fairly frequently returns an error. Back off and retry.
|
||||
maxAttempts := 5
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
err = hcsshim.CreateComputeSystem(c.ID, configuration)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if !TP4RetryHack {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
if herr, ok := err.(*hcsshim.HcsError); ok {
|
||||
if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found
|
||||
herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified
|
||||
herr.Err != ErrorNoNetwork && // The network is not present or not started
|
||||
herr.Err != ErrorBadPathname && // The specified path is invalid
|
||||
herr.Err != CoEClassstring && // Invalid class string
|
||||
herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object
|
||||
logrus.Debugln("Failed to create temporary container ", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Start the container
|
||||
logrus.Debugln("Starting container ", c.ID)
|
||||
err = hcsshim.StartComputeSystem(c.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to start compute system: %s", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
defer func() {
|
||||
// Stop the container
|
||||
if forceKill {
|
||||
logrus.Debugf("Forcibly terminating container %s", c.ID)
|
||||
if err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
|
||||
logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err)
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("Shutting down container %s", c.ID)
|
||||
if err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
|
||||
if herr, ok := err.(*hcsshim.HcsError); !ok ||
|
||||
(herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS &&
|
||||
herr.Err != ErrorBadPathname &&
|
||||
herr.Err != syscall.ERROR_PATH_NOT_FOUND) {
|
||||
logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
createProcessParms := hcsshim.CreateProcessParams{
|
||||
EmulateConsole: c.ProcessConfig.Tty,
|
||||
WorkingDirectory: c.WorkingDir,
|
||||
ConsoleSize: c.ProcessConfig.ConsoleSize,
|
||||
}
|
||||
|
||||
// Configure the environment for the process
|
||||
createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
|
||||
|
||||
createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped)
|
||||
|
||||
if err != nil {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
// Start the command running in the container.
|
||||
pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms)
|
||||
if err != nil {
|
||||
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
}
|
||||
|
||||
// Now that the process has been launched, begin copying data to and from
|
||||
// the named pipes for the std handles.
|
||||
setupPipes(stdin, stdout, stderr, pipes)
|
||||
|
||||
//Save the PID as we'll need this in Kill()
|
||||
logrus.Debugf("PID %d", pid)
|
||||
c.ContainerPid = int(pid)
|
||||
|
||||
if c.ProcessConfig.Tty {
|
||||
term = NewTtyConsole(c.ID, pid)
|
||||
} else {
|
||||
term = NewStdConsole()
|
||||
}
|
||||
c.ProcessConfig.Terminal = term
|
||||
|
||||
// Maintain our list of active containers. We'll need this later for exec
|
||||
// and other commands.
|
||||
d.Lock()
|
||||
d.activeContainers[c.ID] = &activeContainer{
|
||||
command: c,
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
if hooks.Start != nil {
|
||||
// A closed channel for OOM is returned here as it will be
|
||||
// non-blocking and return the correct result when read.
|
||||
chOOM := make(chan struct{})
|
||||
close(chOOM)
|
||||
hooks.Start(&c.ProcessConfig, int(pid), chOOM)
|
||||
}
|
||||
|
||||
exitCode, err := hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite)
|
||||
if err != nil {
|
||||
if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
|
||||
logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
|
||||
}
|
||||
// Do NOT return err here as the container would have
|
||||
// started, otherwise docker will deadlock. It's perfectly legitimate
|
||||
// for WaitForProcessInComputeSystem to fail in situations such
|
||||
// as the container being killed on another thread.
|
||||
return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
|
||||
return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
|
||||
}
|
||||
|
||||
// SupportsHooks implements the execdriver Driver interface.
|
||||
// The windows driver does not support the hook mechanism
|
||||
func (d *Driver) SupportsHooks() bool {
|
||||
return false
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Stats implements the exec driver Driver interface.
|
||||
func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
return nil, fmt.Errorf("Windows: Stats not implemented")
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
// StdConsole is for when using a container non-interactively
|
||||
type StdConsole struct {
|
||||
}
|
||||
|
||||
// NewStdConsole returns a new StdConsole struct.
|
||||
func NewStdConsole() *StdConsole {
|
||||
return &StdConsole{}
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface.
|
||||
func (s *StdConsole) Resize(h, w int) error {
|
||||
// we do not need to resize a non tty
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface.
|
||||
func (s *StdConsole) Close() error {
|
||||
// nothing to close here
|
||||
return nil
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Terminate implements the exec driver Driver interface.
|
||||
func (d *Driver) Terminate(p *execdriver.Command) error {
|
||||
return kill(p.ID, p.ContainerPid, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
// Kill implements the exec driver Driver interface.
|
||||
func (d *Driver) Kill(p *execdriver.Command, sig int) error {
|
||||
return kill(p.ID, p.ContainerPid, syscall.Signal(sig))
|
||||
}
|
||||
|
||||
func kill(id string, pid int, sig syscall.Signal) error {
|
||||
logrus.Debugf("WindowsExec: kill() id=%s pid=%d sig=%d", id, pid, sig)
|
||||
var err error
|
||||
context := fmt.Sprintf("kill: sig=%d pid=%d", sig, pid)
|
||||
|
||||
if sig == syscall.SIGKILL || forceKill {
|
||||
// Terminate the compute system
|
||||
if err := hcsshim.TerminateComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil {
|
||||
logrus.Errorf("Failed to terminate %s - %q", id, err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Terminate Process
|
||||
if err = hcsshim.TerminateProcessInComputeSystem(id, uint32(pid)); err != nil {
|
||||
logrus.Warnf("Failed to terminate pid %d in %s: %q", pid, id, err)
|
||||
// Ignore errors
|
||||
err = nil
|
||||
}
|
||||
|
||||
// Shutdown the compute system
|
||||
if err := hcsshim.ShutdownComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil {
|
||||
logrus.Errorf("Failed to shutdown %s - %q", id, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim"
|
||||
)
|
||||
|
||||
// TtyConsole implements the exec driver Terminal interface.
|
||||
type TtyConsole struct {
|
||||
id string
|
||||
processid uint32
|
||||
}
|
||||
|
||||
// NewTtyConsole returns a new TtyConsole struct.
|
||||
func NewTtyConsole(id string, processid uint32) *TtyConsole {
|
||||
tty := &TtyConsole{
|
||||
id: id,
|
||||
processid: processid,
|
||||
}
|
||||
return tty
|
||||
}
|
||||
|
||||
// Resize implements Resize method of Terminal interface.
|
||||
func (t *TtyConsole) Resize(h, w int) error {
|
||||
return hcsshim.ResizeConsoleInComputeSystem(t.id, t.processid, h, w)
|
||||
}
|
||||
|
||||
// Close implements Close method of Terminal interface.
|
||||
func (t *TtyConsole) Close() error {
|
||||
return nil
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +build !windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// NewDriver returns a new execdriver.Driver
|
||||
func NewDriver(root, initPath string) (execdriver.Driver, error) {
|
||||
return nil, fmt.Errorf("Windows driver not supported on non-Windows")
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
)
|
||||
|
||||
// Update updates resource configs for a container.
|
||||
func (d *Driver) Update(c *execdriver.Command) error {
|
||||
// Updating resource isn't supported on Windows
|
||||
// but we should return nil for enabling updating container
|
||||
return nil
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
// TP4RetryHack is a hack to retry CreateComputeSystem if it fails with
|
||||
// known return codes from Windows due to bugs in TP4.
|
||||
var TP4RetryHack bool
|
||||
|
||||
// This is a daemon development variable only and should not be
|
||||
// used for running production containers on Windows.
|
||||
var dummyMode bool
|
||||
|
||||
// This allows the daemon to terminate containers rather than shutdown
|
||||
// This allows the daemon to force kill (HCS terminate) rather than shutdown
|
||||
var forceKill bool
|
||||
|
||||
// DefaultIsolation allows users to specify a default isolation technology for
|
||||
// when running a container on Windows. For example docker daemon -D
|
||||
// --exec-opt isolation=hyperv will cause Windows to always run containers
|
||||
// as Hyper-V containers unless otherwise specified.
|
||||
var DefaultIsolation container.Isolation = "process"
|
||||
|
||||
// Define name and version for windows
|
||||
var (
|
||||
DriverName = "Windows 1854"
|
||||
Version = dockerversion.Version + " " + dockerversion.GitCommit
|
||||
)
|
||||
|
||||
type activeContainer struct {
|
||||
command *execdriver.Command
|
||||
}
|
||||
|
||||
// Driver contains all information for windows driver,
|
||||
// it implements execdriver.Driver
|
||||
type Driver struct {
|
||||
root string
|
||||
activeContainers map[string]*activeContainer
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Name implements the exec driver Driver interface.
|
||||
func (d *Driver) Name() string {
|
||||
return fmt.Sprintf("\n Name: %s\n Build: %s \n Default Isolation: %s", DriverName, Version, DefaultIsolation)
|
||||
}
|
||||
|
||||
// NewDriver returns a new windows driver, called from NewDriver of execdriver.
|
||||
func NewDriver(root string, options []string) (*Driver, error) {
|
||||
|
||||
for _, option := range options {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
|
||||
case "dummy":
|
||||
switch val {
|
||||
case "1":
|
||||
dummyMode = true
|
||||
logrus.Warn("Using dummy mode in Windows exec driver. This is for development use only!")
|
||||
}
|
||||
|
||||
case "forcekill":
|
||||
switch val {
|
||||
case "1":
|
||||
forceKill = true
|
||||
logrus.Warn("Using force kill mode in Windows exec driver. This is for testing purposes only.")
|
||||
}
|
||||
|
||||
case "isolation":
|
||||
if !container.Isolation(val).IsValid() {
|
||||
return nil, fmt.Errorf("Unrecognised exec driver option 'isolation':'%s'", val)
|
||||
}
|
||||
if container.Isolation(val).IsHyperV() {
|
||||
DefaultIsolation = "hyperv"
|
||||
}
|
||||
logrus.Infof("Windows default isolation: '%s'", val)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unrecognised exec driver option %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Windows TP5 timeframe. Remove this next block of code once TP4
|
||||
// is no longer supported. Also remove the workaround in run.go.
|
||||
//
|
||||
// Hack for TP4.
|
||||
// This overcomes an issue on TP4 which causes CreateComputeSystem to
|
||||
// intermittently fail. It's predominantly here to make Windows to Windows
|
||||
// CI more reliable.
|
||||
TP4RetryHack = hcsshim.IsTP4()
|
||||
|
||||
return &Driver{
|
||||
root: root,
|
||||
activeContainers: make(map[string]*activeContainer),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// setupEnvironmentVariables convert a string array of environment variables
|
||||
// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
|
||||
func setupEnvironmentVariables(a []string) map[string]string {
|
||||
r := make(map[string]string)
|
||||
for _, s := range a {
|
||||
arr := strings.Split(s, "=")
|
||||
if len(arr) == 2 {
|
||||
r[arr[0]] = arr[1]
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
Loading…
Add table
Reference in a new issue