|
@@ -1,6 +1,7 @@
|
|
package docker
|
|
package docker
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
+ "bytes"
|
|
"encoding/json"
|
|
"encoding/json"
|
|
"errors"
|
|
"errors"
|
|
"flag"
|
|
"flag"
|
|
@@ -43,6 +44,7 @@ type Container struct {
|
|
ResolvConfPath string
|
|
ResolvConfPath string
|
|
HostnamePath string
|
|
HostnamePath string
|
|
HostsPath string
|
|
HostsPath string
|
|
|
|
+ Name string
|
|
|
|
|
|
cmd *exec.Cmd
|
|
cmd *exec.Cmd
|
|
stdout *utils.WriteBroadcaster
|
|
stdout *utils.WriteBroadcaster
|
|
@@ -58,6 +60,8 @@ type Container struct {
|
|
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
|
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
|
// Easier than migrating older container configs :)
|
|
// Easier than migrating older container configs :)
|
|
VolumesRW map[string]bool
|
|
VolumesRW map[string]bool
|
|
|
|
+
|
|
|
|
+ activeLinks map[string]*Link
|
|
}
|
|
}
|
|
|
|
|
|
type Config struct {
|
|
type Config struct {
|
|
@@ -70,7 +74,8 @@ type Config struct {
|
|
AttachStdin bool
|
|
AttachStdin bool
|
|
AttachStdout bool
|
|
AttachStdout bool
|
|
AttachStderr bool
|
|
AttachStderr bool
|
|
- PortSpecs []string
|
|
|
|
|
|
+ PortSpecs []string // Deprecated - Can be in the format of 8080/tcp
|
|
|
|
+ ExposedPorts map[Port]struct{}
|
|
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
|
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
|
OpenStdin bool // Open stdin
|
|
OpenStdin bool // Open stdin
|
|
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
|
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
|
@@ -90,6 +95,8 @@ type HostConfig struct {
|
|
Binds []string
|
|
Binds []string
|
|
ContainerIDFile string
|
|
ContainerIDFile string
|
|
LxcConf []KeyValuePair
|
|
LxcConf []KeyValuePair
|
|
|
|
+ PortBindings map[Port][]PortBinding
|
|
|
|
+ Links []string
|
|
}
|
|
}
|
|
|
|
|
|
type BindMap struct {
|
|
type BindMap struct {
|
|
@@ -99,7 +106,11 @@ type BindMap struct {
|
|
}
|
|
}
|
|
|
|
|
|
var (
|
|
var (
|
|
- ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
|
|
|
|
|
|
+ ErrContainerStart = errors.New("The container failed to start. Unkown error")
|
|
|
|
+ ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
|
|
|
|
+ ErrInvalidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
|
|
|
|
+ ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d")
|
|
|
|
+ ErrConflictDetachAutoRemove = errors.New("Conflicting options: -rm and -d")
|
|
)
|
|
)
|
|
|
|
|
|
type KeyValuePair struct {
|
|
type KeyValuePair struct {
|
|
@@ -107,6 +118,34 @@ type KeyValuePair struct {
|
|
Value string
|
|
Value string
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+type PortBinding struct {
|
|
|
|
+ HostIp string
|
|
|
|
+ HostPort string
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 80/tcp
|
|
|
|
+type Port string
|
|
|
|
+
|
|
|
|
+func (p Port) Proto() string {
|
|
|
|
+ return strings.Split(string(p), "/")[1]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (p Port) Port() string {
|
|
|
|
+ return strings.Split(string(p), "/")[0]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (p Port) Int() int {
|
|
|
|
+ i, err := parsePort(p.Port())
|
|
|
|
+ if err != nil {
|
|
|
|
+ panic(err)
|
|
|
|
+ }
|
|
|
|
+ return i
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func NewPort(proto, port string) Port {
|
|
|
|
+ return Port(fmt.Sprintf("%s/%s", port, proto))
|
|
|
|
+}
|
|
|
|
+
|
|
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
|
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
|
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
|
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
|
if os.Getenv("TEST") != "" {
|
|
if os.Getenv("TEST") != "" {
|
|
@@ -127,6 +166,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
|
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
|
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
|
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
|
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
|
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
|
|
|
+ cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
|
|
|
+ cmd.String("name", "", "Assign a name to the container")
|
|
|
|
|
|
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
|
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
|
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
|
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
|
@@ -135,35 +176,45 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|
|
|
|
|
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
|
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
|
|
|
|
|
- var flPorts ListOpts
|
|
|
|
- cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
|
|
|
|
|
|
+ var flPublish utils.ListOpts
|
|
|
|
+ cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
|
|
|
|
+
|
|
|
|
+ var flExpose utils.ListOpts
|
|
|
|
+ cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
|
|
|
|
|
- var flEnv ListOpts
|
|
|
|
|
|
+ var flEnv utils.ListOpts
|
|
cmd.Var(&flEnv, "e", "Set environment variables")
|
|
cmd.Var(&flEnv, "e", "Set environment variables")
|
|
|
|
|
|
- var flDns ListOpts
|
|
|
|
|
|
+ var flDns utils.ListOpts
|
|
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
|
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
|
|
|
|
|
flVolumes := NewPathOpts()
|
|
flVolumes := NewPathOpts()
|
|
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
|
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
|
|
|
|
|
- var flVolumesFrom ListOpts
|
|
|
|
|
|
+ var flVolumesFrom utils.ListOpts
|
|
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container")
|
|
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container")
|
|
|
|
|
|
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
|
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
|
|
|
|
|
- var flLxcOpts ListOpts
|
|
|
|
|
|
+ var flLxcOpts utils.ListOpts
|
|
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
|
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
|
|
|
|
|
|
|
+ var flLinks utils.ListOpts
|
|
|
|
+ cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
|
|
|
|
+
|
|
if err := cmd.Parse(args); err != nil {
|
|
if err := cmd.Parse(args); err != nil {
|
|
return nil, nil, cmd, err
|
|
return nil, nil, cmd, err
|
|
}
|
|
}
|
|
if *flDetach && len(flAttach) > 0 {
|
|
if *flDetach && len(flAttach) > 0 {
|
|
- return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
|
|
|
|
|
|
+ return nil, nil, cmd, ErrConflictAttachDetach
|
|
}
|
|
}
|
|
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
|
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
|
- return nil, nil, cmd, ErrInvaidWorikingDirectory
|
|
|
|
|
|
+ return nil, nil, cmd, ErrInvalidWorikingDirectory
|
|
}
|
|
}
|
|
|
|
+ if *flDetach && *flAutoRemove {
|
|
|
|
+ return nil, nil, cmd, ErrConflictDetachAutoRemove
|
|
|
|
+ }
|
|
|
|
+
|
|
// If neither -d or -a are set, attach to everything by default
|
|
// If neither -d or -a are set, attach to everything by default
|
|
if len(flAttach) == 0 && !*flDetach {
|
|
if len(flAttach) == 0 && !*flDetach {
|
|
if !*flDetach {
|
|
if !*flDetach {
|
|
@@ -175,8 +226,16 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if *flDetach && *flAutoRemove {
|
|
|
|
- return nil, nil, cmd, fmt.Errorf("Conflicting options: -rm and -d")
|
|
|
|
|
|
+ envs := []string{}
|
|
|
|
+
|
|
|
|
+ for _, env := range flEnv {
|
|
|
|
+ arr := strings.Split(env, "=")
|
|
|
|
+ if len(arr) > 1 {
|
|
|
|
+ envs = append(envs, env)
|
|
|
|
+ } else {
|
|
|
|
+ v := os.Getenv(env)
|
|
|
|
+ envs = append(envs, env+"="+v)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
var binds []string
|
|
var binds []string
|
|
@@ -220,10 +279,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|
hostname = parts[0]
|
|
hostname = parts[0]
|
|
domainname = parts[1]
|
|
domainname = parts[1]
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ ports, portBindings, err := parsePortSpecs(flPublish)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, nil, cmd, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Merge in exposed ports to the map of published ports
|
|
|
|
+ for _, e := range flExpose {
|
|
|
|
+ if strings.Contains(e, ":") {
|
|
|
|
+ return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
|
|
|
|
+ }
|
|
|
|
+ p := NewPort(splitProtoPort(e))
|
|
|
|
+ if _, exists := ports[p]; !exists {
|
|
|
|
+ ports[p] = struct{}{}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
config := &Config{
|
|
config := &Config{
|
|
- Hostname: hostname,
|
|
|
|
|
|
+ Hostname: *flHostname,
|
|
Domainname: domainname,
|
|
Domainname: domainname,
|
|
- PortSpecs: flPorts,
|
|
|
|
|
|
+ PortSpecs: nil, // Deprecated
|
|
|
|
+ ExposedPorts: ports,
|
|
User: *flUser,
|
|
User: *flUser,
|
|
Tty: *flTty,
|
|
Tty: *flTty,
|
|
NetworkDisabled: !*flNetwork,
|
|
NetworkDisabled: !*flNetwork,
|
|
@@ -233,7 +310,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|
AttachStdin: flAttach.Get("stdin"),
|
|
AttachStdin: flAttach.Get("stdin"),
|
|
AttachStdout: flAttach.Get("stdout"),
|
|
AttachStdout: flAttach.Get("stdout"),
|
|
AttachStderr: flAttach.Get("stderr"),
|
|
AttachStderr: flAttach.Get("stderr"),
|
|
- Env: flEnv,
|
|
|
|
|
|
+ Env: envs,
|
|
Cmd: runCmd,
|
|
Cmd: runCmd,
|
|
Dns: flDns,
|
|
Dns: flDns,
|
|
Image: image,
|
|
Image: image,
|
|
@@ -243,10 +320,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|
Privileged: *flPrivileged,
|
|
Privileged: *flPrivileged,
|
|
WorkingDir: *flWorkingDir,
|
|
WorkingDir: *flWorkingDir,
|
|
}
|
|
}
|
|
|
|
+
|
|
hostConfig := &HostConfig{
|
|
hostConfig := &HostConfig{
|
|
Binds: binds,
|
|
Binds: binds,
|
|
ContainerIDFile: *flContainerIDFile,
|
|
ContainerIDFile: *flContainerIDFile,
|
|
LxcConf: lxcConf,
|
|
LxcConf: lxcConf,
|
|
|
|
+ PortBindings: portBindings,
|
|
|
|
+ Links: flLinks,
|
|
}
|
|
}
|
|
|
|
|
|
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
|
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
|
@@ -261,36 +341,38 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|
return config, hostConfig, cmd, nil
|
|
return config, hostConfig, cmd, nil
|
|
}
|
|
}
|
|
|
|
|
|
-type PortMapping map[string]string
|
|
|
|
|
|
+type PortMapping map[string]string // Deprecated
|
|
|
|
|
|
type NetworkSettings struct {
|
|
type NetworkSettings struct {
|
|
IPAddress string
|
|
IPAddress string
|
|
IPPrefixLen int
|
|
IPPrefixLen int
|
|
Gateway string
|
|
Gateway string
|
|
Bridge string
|
|
Bridge string
|
|
- PortMapping map[string]PortMapping
|
|
|
|
|
|
+ PortMapping map[string]PortMapping // Deprecated
|
|
|
|
+ Ports map[Port][]PortBinding
|
|
}
|
|
}
|
|
|
|
|
|
-// returns a more easy to process description of the port mapping defined in the settings
|
|
|
|
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
|
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
|
var mapping []APIPort
|
|
var mapping []APIPort
|
|
- for private, public := range settings.PortMapping["Tcp"] {
|
|
|
|
- pubint, _ := strconv.ParseInt(public, 0, 0)
|
|
|
|
- privint, _ := strconv.ParseInt(private, 0, 0)
|
|
|
|
- mapping = append(mapping, APIPort{
|
|
|
|
- PrivatePort: privint,
|
|
|
|
- PublicPort: pubint,
|
|
|
|
- Type: "tcp",
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
- for private, public := range settings.PortMapping["Udp"] {
|
|
|
|
- pubint, _ := strconv.ParseInt(public, 0, 0)
|
|
|
|
- privint, _ := strconv.ParseInt(private, 0, 0)
|
|
|
|
- mapping = append(mapping, APIPort{
|
|
|
|
- PrivatePort: privint,
|
|
|
|
- PublicPort: pubint,
|
|
|
|
- Type: "udp",
|
|
|
|
- })
|
|
|
|
|
|
+ for port, bindings := range settings.Ports {
|
|
|
|
+ p, _ := parsePort(port.Port())
|
|
|
|
+ if len(bindings) == 0 {
|
|
|
|
+ mapping = append(mapping, APIPort{
|
|
|
|
+ PublicPort: int64(p),
|
|
|
|
+ Type: port.Proto(),
|
|
|
|
+ })
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ for _, binding := range bindings {
|
|
|
|
+ p, _ := parsePort(port.Port())
|
|
|
|
+ h, _ := parsePort(binding.HostPort)
|
|
|
|
+ mapping = append(mapping, APIPort{
|
|
|
|
+ PrivatePort: int64(p),
|
|
|
|
+ PublicPort: int64(h),
|
|
|
|
+ Type: port.Proto(),
|
|
|
|
+ IP: binding.HostIp,
|
|
|
|
+ })
|
|
|
|
+ }
|
|
}
|
|
}
|
|
return mapping
|
|
return mapping
|
|
}
|
|
}
|
|
@@ -361,6 +443,15 @@ func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) {
|
|
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
|
|
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (container *Container) generateEnvConfig(env []string) error {
|
|
|
|
+ data, err := json.Marshal(env)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ ioutil.WriteFile(container.EnvConfigPath(), data, 0600)
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
func (container *Container) generateLXCConfig(hostConfig *HostConfig) error {
|
|
func (container *Container) generateLXCConfig(hostConfig *HostConfig) error {
|
|
fo, err := os.Create(container.lxcConfigPath())
|
|
fo, err := os.Create(container.lxcConfigPath())
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -390,9 +481,9 @@ func (container *Container) startPty() error {
|
|
// Copy the PTYs to our broadcasters
|
|
// Copy the PTYs to our broadcasters
|
|
go func() {
|
|
go func() {
|
|
defer container.stdout.CloseWriters()
|
|
defer container.stdout.CloseWriters()
|
|
- utils.Debugf("[startPty] Begin of stdout pipe")
|
|
|
|
|
|
+ utils.Debugf("startPty: begin of stdout pipe")
|
|
io.Copy(container.stdout, ptyMaster)
|
|
io.Copy(container.stdout, ptyMaster)
|
|
- utils.Debugf("[startPty] End of stdout pipe")
|
|
|
|
|
|
+ utils.Debugf("startPty: end of stdout pipe")
|
|
}()
|
|
}()
|
|
|
|
|
|
// stdin
|
|
// stdin
|
|
@@ -401,9 +492,9 @@ func (container *Container) startPty() error {
|
|
container.cmd.SysProcAttr.Setctty = true
|
|
container.cmd.SysProcAttr.Setctty = true
|
|
go func() {
|
|
go func() {
|
|
defer container.stdin.Close()
|
|
defer container.stdin.Close()
|
|
- utils.Debugf("[startPty] Begin of stdin pipe")
|
|
|
|
|
|
+ utils.Debugf("startPty: begin of stdin pipe")
|
|
io.Copy(ptyMaster, container.stdin)
|
|
io.Copy(ptyMaster, container.stdin)
|
|
- utils.Debugf("[startPty] End of stdin pipe")
|
|
|
|
|
|
+ utils.Debugf("startPty: end of stdin pipe")
|
|
}()
|
|
}()
|
|
}
|
|
}
|
|
if err := container.cmd.Start(); err != nil {
|
|
if err := container.cmd.Start(); err != nil {
|
|
@@ -423,9 +514,9 @@ func (container *Container) start() error {
|
|
}
|
|
}
|
|
go func() {
|
|
go func() {
|
|
defer stdin.Close()
|
|
defer stdin.Close()
|
|
- utils.Debugf("Begin of stdin pipe [start]")
|
|
|
|
|
|
+ utils.Debugf("start: begin of stdin pipe")
|
|
io.Copy(stdin, container.stdin)
|
|
io.Copy(stdin, container.stdin)
|
|
- utils.Debugf("End of stdin pipe [start]")
|
|
|
|
|
|
+ utils.Debugf("start: end of stdin pipe")
|
|
}()
|
|
}()
|
|
}
|
|
}
|
|
return container.cmd.Start()
|
|
return container.cmd.Start()
|
|
@@ -442,8 +533,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
errors <- err
|
|
errors <- err
|
|
} else {
|
|
} else {
|
|
go func() {
|
|
go func() {
|
|
- utils.Debugf("[start] attach stdin\n")
|
|
|
|
- defer utils.Debugf("[end] attach stdin\n")
|
|
|
|
|
|
+ utils.Debugf("attach: stdin: begin")
|
|
|
|
+ defer utils.Debugf("attach: stdin: end")
|
|
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
if container.Config.StdinOnce && !container.Config.Tty {
|
|
if container.Config.StdinOnce && !container.Config.Tty {
|
|
defer cStdin.Close()
|
|
defer cStdin.Close()
|
|
@@ -460,11 +551,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
} else {
|
|
} else {
|
|
_, err = io.Copy(cStdin, stdin)
|
|
_, err = io.Copy(cStdin, stdin)
|
|
}
|
|
}
|
|
|
|
+ if err == io.ErrClosedPipe {
|
|
|
|
+ err = nil
|
|
|
|
+ }
|
|
if err != nil {
|
|
if err != nil {
|
|
- utils.Debugf("[error] attach stdin: %s\n", err)
|
|
|
|
|
|
+ utils.Errorf("attach: stdin: %s", err)
|
|
}
|
|
}
|
|
- // Discard error, expecting pipe error
|
|
|
|
- errors <- nil
|
|
|
|
|
|
+ errors <- err
|
|
}()
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -475,8 +568,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
} else {
|
|
} else {
|
|
cStdout = p
|
|
cStdout = p
|
|
go func() {
|
|
go func() {
|
|
- utils.Debugf("[start] attach stdout\n")
|
|
|
|
- defer utils.Debugf("[end] attach stdout\n")
|
|
|
|
|
|
+ utils.Debugf("attach: stdout: begin")
|
|
|
|
+ defer utils.Debugf("attach: stdout: end")
|
|
// If we are in StdinOnce mode, then close stdin
|
|
// If we are in StdinOnce mode, then close stdin
|
|
if container.Config.StdinOnce && stdin != nil {
|
|
if container.Config.StdinOnce && stdin != nil {
|
|
defer stdin.Close()
|
|
defer stdin.Close()
|
|
@@ -485,8 +578,11 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
defer stdinCloser.Close()
|
|
defer stdinCloser.Close()
|
|
}
|
|
}
|
|
_, err := io.Copy(stdout, cStdout)
|
|
_, err := io.Copy(stdout, cStdout)
|
|
|
|
+ if err == io.ErrClosedPipe {
|
|
|
|
+ err = nil
|
|
|
|
+ }
|
|
if err != nil {
|
|
if err != nil {
|
|
- utils.Debugf("[error] attach stdout: %s\n", err)
|
|
|
|
|
|
+ utils.Errorf("attach: stdout: %s", err)
|
|
}
|
|
}
|
|
errors <- err
|
|
errors <- err
|
|
}()
|
|
}()
|
|
@@ -496,9 +592,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
if stdinCloser != nil {
|
|
if stdinCloser != nil {
|
|
defer stdinCloser.Close()
|
|
defer stdinCloser.Close()
|
|
}
|
|
}
|
|
-
|
|
|
|
if cStdout, err := container.StdoutPipe(); err != nil {
|
|
if cStdout, err := container.StdoutPipe(); err != nil {
|
|
- utils.Debugf("Error stdout pipe")
|
|
|
|
|
|
+ utils.Errorf("attach: stdout pipe: %s", err)
|
|
} else {
|
|
} else {
|
|
io.Copy(&utils.NopWriter{}, cStdout)
|
|
io.Copy(&utils.NopWriter{}, cStdout)
|
|
}
|
|
}
|
|
@@ -511,8 +606,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
} else {
|
|
} else {
|
|
cStderr = p
|
|
cStderr = p
|
|
go func() {
|
|
go func() {
|
|
- utils.Debugf("[start] attach stderr\n")
|
|
|
|
- defer utils.Debugf("[end] attach stderr\n")
|
|
|
|
|
|
+ utils.Debugf("attach: stderr: begin")
|
|
|
|
+ defer utils.Debugf("attach: stderr: end")
|
|
// If we are in StdinOnce mode, then close stdin
|
|
// If we are in StdinOnce mode, then close stdin
|
|
if container.Config.StdinOnce && stdin != nil {
|
|
if container.Config.StdinOnce && stdin != nil {
|
|
defer stdin.Close()
|
|
defer stdin.Close()
|
|
@@ -521,8 +616,11 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
defer stdinCloser.Close()
|
|
defer stdinCloser.Close()
|
|
}
|
|
}
|
|
_, err := io.Copy(stderr, cStderr)
|
|
_, err := io.Copy(stderr, cStderr)
|
|
|
|
+ if err == io.ErrClosedPipe {
|
|
|
|
+ err = nil
|
|
|
|
+ }
|
|
if err != nil {
|
|
if err != nil {
|
|
- utils.Debugf("[error] attach stderr: %s\n", err)
|
|
|
|
|
|
+ utils.Errorf("attach: stderr: %s", err)
|
|
}
|
|
}
|
|
errors <- err
|
|
errors <- err
|
|
}()
|
|
}()
|
|
@@ -534,7 +632,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
}
|
|
}
|
|
|
|
|
|
if cStderr, err := container.StderrPipe(); err != nil {
|
|
if cStderr, err := container.StderrPipe(); err != nil {
|
|
- utils.Debugf("Error stdout pipe")
|
|
|
|
|
|
+ utils.Errorf("attach: stdout pipe: %s", err)
|
|
} else {
|
|
} else {
|
|
io.Copy(&utils.NopWriter{}, cStderr)
|
|
io.Copy(&utils.NopWriter{}, cStderr)
|
|
}
|
|
}
|
|
@@ -548,24 +646,29 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|
if cStderr != nil {
|
|
if cStderr != nil {
|
|
defer cStderr.Close()
|
|
defer cStderr.Close()
|
|
}
|
|
}
|
|
- // FIXME: how do clean up the stdin goroutine without the unwanted side effect
|
|
|
|
|
|
+ // FIXME: how to clean up the stdin goroutine without the unwanted side effect
|
|
// of closing the passed stdin? Add an intermediary io.Pipe?
|
|
// of closing the passed stdin? Add an intermediary io.Pipe?
|
|
for i := 0; i < nJobs; i += 1 {
|
|
for i := 0; i < nJobs; i += 1 {
|
|
- utils.Debugf("Waiting for job %d/%d\n", i+1, nJobs)
|
|
|
|
|
|
+ utils.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
|
if err := <-errors; err != nil {
|
|
if err := <-errors; err != nil {
|
|
- utils.Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err)
|
|
|
|
|
|
+ utils.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
- utils.Debugf("Job %d completed successfully\n", i+1)
|
|
|
|
|
|
+ utils.Debugf("attach: job %d completed successfully", i+1)
|
|
}
|
|
}
|
|
- utils.Debugf("All jobs completed successfully\n")
|
|
|
|
|
|
+ utils.Debugf("attach: all jobs completed successfully")
|
|
return nil
|
|
return nil
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-func (container *Container) Start(hostConfig *HostConfig) error {
|
|
|
|
|
|
+func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
|
container.State.Lock()
|
|
container.State.Lock()
|
|
defer container.State.Unlock()
|
|
defer container.State.Unlock()
|
|
|
|
+ defer func() {
|
|
|
|
+ if err != nil {
|
|
|
|
+ container.cleanup()
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
|
|
if hostConfig == nil { // in docker start of docker restart we want to reuse previous HostConfigFile
|
|
if hostConfig == nil { // in docker start of docker restart we want to reuse previous HostConfigFile
|
|
hostConfig, _ = container.ReadHostConfig()
|
|
hostConfig, _ = container.ReadHostConfig()
|
|
@@ -581,7 +684,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|
container.Config.NetworkDisabled = true
|
|
container.Config.NetworkDisabled = true
|
|
container.buildHostnameAndHostsFiles("127.0.1.1")
|
|
container.buildHostnameAndHostsFiles("127.0.1.1")
|
|
} else {
|
|
} else {
|
|
- if err := container.allocateNetwork(); err != nil {
|
|
|
|
|
|
+ if err := container.allocateNetwork(hostConfig); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
|
|
container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
|
|
@@ -761,17 +864,65 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|
params = append(params, "-u", container.Config.User)
|
|
params = append(params, "-u", container.Config.User)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Setup environment
|
|
|
|
+ env := []string{
|
|
|
|
+ "HOME=/",
|
|
|
|
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
|
|
+ "container=lxc",
|
|
|
|
+ "HOSTNAME=" + container.Config.Hostname,
|
|
|
|
+ }
|
|
|
|
+
|
|
if container.Config.Tty {
|
|
if container.Config.Tty {
|
|
- params = append(params, "-e", "TERM=xterm")
|
|
|
|
|
|
+ env = append(env, "TERM=xterm")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Init any links between the parent and children
|
|
|
|
+ runtime := container.runtime
|
|
|
|
+
|
|
|
|
+ children, err := runtime.Children(container.Name)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if len(children) > 0 {
|
|
|
|
+ container.activeLinks = make(map[string]*Link, len(children))
|
|
|
|
+
|
|
|
|
+ // If we encounter an error make sure that we rollback any network
|
|
|
|
+ // config and ip table changes
|
|
|
|
+ rollback := func() {
|
|
|
|
+ for _, link := range container.activeLinks {
|
|
|
|
+ link.Disable()
|
|
|
|
+ }
|
|
|
|
+ container.activeLinks = nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for p, child := range children {
|
|
|
|
+ link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface)
|
|
|
|
+ if err != nil {
|
|
|
|
+ rollback()
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ container.activeLinks[link.Alias()] = link
|
|
|
|
+ if err := link.Enable(); err != nil {
|
|
|
|
+ rollback()
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, envVar := range link.ToEnv() {
|
|
|
|
+ env = append(env, envVar)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, elem := range container.Config.Env {
|
|
|
|
+ env = append(env, elem)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := container.generateEnvConfig(env); err != nil {
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
- // Setup environment
|
|
|
|
- params = append(params,
|
|
|
|
- "-e", "HOME=/",
|
|
|
|
- "-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
|
|
- "-e", "container=lxc",
|
|
|
|
- "-e", "HOSTNAME="+container.Config.Hostname,
|
|
|
|
- )
|
|
|
|
if container.Config.WorkingDir != "" {
|
|
if container.Config.WorkingDir != "" {
|
|
workingDir := path.Clean(container.Config.WorkingDir)
|
|
workingDir := path.Clean(container.Config.WorkingDir)
|
|
utils.Debugf("[working dir] working dir is %s", workingDir)
|
|
utils.Debugf("[working dir] working dir is %s", workingDir)
|
|
@@ -785,10 +936,6 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
- for _, elem := range container.Config.Env {
|
|
|
|
- params = append(params, "-e", elem)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
// Program
|
|
// Program
|
|
params = append(params, "--", container.Path)
|
|
params = append(params, "--", container.Path)
|
|
params = append(params, container.Args...)
|
|
params = append(params, container.Args...)
|
|
@@ -805,7 +952,6 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|
|
|
|
|
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
|
|
|
|
- var err error
|
|
|
|
if container.Config.Tty {
|
|
if container.Config.Tty {
|
|
err = container.startPty()
|
|
err = container.startPty()
|
|
} else {
|
|
} else {
|
|
@@ -824,12 +970,43 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|
container.ToDisk()
|
|
container.ToDisk()
|
|
container.SaveHostConfig(hostConfig)
|
|
container.SaveHostConfig(hostConfig)
|
|
go container.monitor(hostConfig)
|
|
go container.monitor(hostConfig)
|
|
- return nil
|
|
|
|
|
|
+
|
|
|
|
+ defer utils.Debugf("Container running: %v", container.State.Running)
|
|
|
|
+ // We wait for the container to be fully running.
|
|
|
|
+ // Timeout after 5 seconds. In case of broken pipe, just retry.
|
|
|
|
+ // Note: The container can run and finish correctly before
|
|
|
|
+ // the end of this loop
|
|
|
|
+ for now := time.Now(); time.Since(now) < 5*time.Second; {
|
|
|
|
+ // If the container dies while waiting for it, just return
|
|
|
|
+ if !container.State.Running {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
|
|
|
|
+ if err != nil {
|
|
|
|
+ utils.Debugf("Error with lxc-info: %s (%s)", err, output)
|
|
|
|
+
|
|
|
|
+ output, err = exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
|
|
|
|
+ if err != nil {
|
|
|
|
+ utils.Debugf("Second Error with lxc-info: %s (%s)", err, output)
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ if strings.Contains(string(output), "RUNNING") {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.Running, bytes.TrimSpace(output))
|
|
|
|
+ time.Sleep(50 * time.Millisecond)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if container.State.Running {
|
|
|
|
+ return ErrContainerStartTimeout
|
|
|
|
+ }
|
|
|
|
+ return ErrContainerStart
|
|
}
|
|
}
|
|
|
|
|
|
func (container *Container) Run() error {
|
|
func (container *Container) Run() error {
|
|
- hostConfig := &HostConfig{}
|
|
|
|
- if err := container.Start(hostConfig); err != nil {
|
|
|
|
|
|
+ if err := container.Start(&HostConfig{}); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
container.Wait()
|
|
container.Wait()
|
|
@@ -851,9 +1028,14 @@ func (container *Container) Output() (output []byte, err error) {
|
|
return output, err
|
|
return output, err
|
|
}
|
|
}
|
|
|
|
|
|
-// StdinPipe() returns a pipe connected to the standard input of the container's
|
|
|
|
-// active process.
|
|
|
|
-//
|
|
|
|
|
|
+// Container.StdinPipe returns a WriteCloser which can be used to feed data
|
|
|
|
+// to the standard input of the container's active process.
|
|
|
|
+// Container.StdoutPipe and Container.StderrPipe each return a ReadCloser
|
|
|
|
+// which can be used to retrieve the standard output (and error) generated
|
|
|
|
+// by the container's active process. The output (and error) are actually
|
|
|
|
+// copied and delivered to all StdoutPipe and StderrPipe consumers, using
|
|
|
|
+// a kind of "broadcaster".
|
|
|
|
+
|
|
func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
|
func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
|
return container.stdinPipe, nil
|
|
return container.stdinPipe, nil
|
|
}
|
|
}
|
|
@@ -894,7 +1076,7 @@ ff02::2 ip6-allrouters
|
|
ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
|
|
ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
|
|
}
|
|
}
|
|
|
|
|
|
-func (container *Container) allocateNetwork() error {
|
|
|
|
|
|
+func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
|
if container.Config.NetworkDisabled {
|
|
if container.Config.NetworkDisabled {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
@@ -921,41 +1103,67 @@ func (container *Container) allocateNetwork() error {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- var portSpecs []string
|
|
|
|
|
|
+ if container.Config.PortSpecs != nil {
|
|
|
|
+ utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
|
|
|
|
+ if err := migratePortMappings(container.Config, hostConfig); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ container.Config.PortSpecs = nil
|
|
|
|
+ if err := container.SaveHostConfig(hostConfig); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ portSpecs := make(map[Port]struct{})
|
|
|
|
+ bindings := make(map[Port][]PortBinding)
|
|
|
|
+
|
|
if !container.State.Ghost {
|
|
if !container.State.Ghost {
|
|
- portSpecs = container.Config.PortSpecs
|
|
|
|
- } else {
|
|
|
|
- for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] {
|
|
|
|
- portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend))
|
|
|
|
|
|
+ if container.Config.ExposedPorts != nil {
|
|
|
|
+ portSpecs = container.Config.ExposedPorts
|
|
|
|
+ }
|
|
|
|
+ if hostConfig.PortBindings != nil {
|
|
|
|
+ bindings = hostConfig.PortBindings
|
|
}
|
|
}
|
|
- for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] {
|
|
|
|
- portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend))
|
|
|
|
|
|
+ } else {
|
|
|
|
+ if container.NetworkSettings.Ports != nil {
|
|
|
|
+ for port, binding := range container.NetworkSettings.Ports {
|
|
|
|
+ portSpecs[port] = struct{}{}
|
|
|
|
+ bindings[port] = binding
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- container.NetworkSettings.PortMapping = make(map[string]PortMapping)
|
|
|
|
- container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping)
|
|
|
|
- container.NetworkSettings.PortMapping["Udp"] = make(PortMapping)
|
|
|
|
- for _, spec := range portSpecs {
|
|
|
|
- nat, err := iface.AllocatePort(spec)
|
|
|
|
- if err != nil {
|
|
|
|
- iface.Release()
|
|
|
|
- return err
|
|
|
|
|
|
+ container.NetworkSettings.PortMapping = nil
|
|
|
|
+
|
|
|
|
+ for port := range portSpecs {
|
|
|
|
+ binding := bindings[port]
|
|
|
|
+ for i := 0; i < len(binding); i++ {
|
|
|
|
+ b := binding[i]
|
|
|
|
+ nat, err := iface.AllocatePort(port, b)
|
|
|
|
+ if err != nil {
|
|
|
|
+ iface.Release()
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort)
|
|
|
|
+ binding[i] = nat.Binding
|
|
}
|
|
}
|
|
- proto := strings.Title(nat.Proto)
|
|
|
|
- backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend)
|
|
|
|
- container.NetworkSettings.PortMapping[proto][backend] = frontend
|
|
|
|
|
|
+ bindings[port] = binding
|
|
}
|
|
}
|
|
|
|
+ container.SaveHostConfig(hostConfig)
|
|
|
|
+
|
|
|
|
+ container.NetworkSettings.Ports = bindings
|
|
container.network = iface
|
|
container.network = iface
|
|
|
|
+
|
|
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
|
|
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
|
|
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
|
|
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
|
|
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
|
|
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
|
|
container.NetworkSettings.Gateway = iface.Gateway.String()
|
|
container.NetworkSettings.Gateway = iface.Gateway.String()
|
|
|
|
+
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
func (container *Container) releaseNetwork() {
|
|
func (container *Container) releaseNetwork() {
|
|
- if container.Config.NetworkDisabled {
|
|
|
|
|
|
+ if container.Config.NetworkDisabled || container.network == nil {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
container.network.Release()
|
|
container.network.Release()
|
|
@@ -963,7 +1171,7 @@ func (container *Container) releaseNetwork() {
|
|
container.NetworkSettings = &NetworkSettings{}
|
|
container.NetworkSettings = &NetworkSettings{}
|
|
}
|
|
}
|
|
|
|
|
|
-// FIXME: replace this with a control socket within docker-init
|
|
|
|
|
|
+// FIXME: replace this with a control socket within dockerinit
|
|
func (container *Container) waitLxc() error {
|
|
func (container *Container) waitLxc() error {
|
|
for {
|
|
for {
|
|
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
|
|
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
|
|
@@ -979,20 +1187,23 @@ func (container *Container) waitLxc() error {
|
|
|
|
|
|
func (container *Container) monitor(hostConfig *HostConfig) {
|
|
func (container *Container) monitor(hostConfig *HostConfig) {
|
|
// Wait for the program to exit
|
|
// Wait for the program to exit
|
|
- utils.Debugf("Waiting for process")
|
|
|
|
|
|
|
|
- // If the command does not exists, try to wait via lxc
|
|
|
|
|
|
+ // If the command does not exist, try to wait via lxc
|
|
|
|
+ // (This probably happens only for ghost containers, i.e. containers that were running when Docker started)
|
|
if container.cmd == nil {
|
|
if container.cmd == nil {
|
|
|
|
+ utils.Debugf("monitor: waiting for container %s using waitLxc", container.ID)
|
|
if err := container.waitLxc(); err != nil {
|
|
if err := container.waitLxc(); err != nil {
|
|
- utils.Debugf("%s: Process: %s", container.ID, err)
|
|
|
|
|
|
+ utils.Errorf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ID, err)
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
|
|
+ utils.Debugf("monitor: waiting for container %s using cmd.Wait", container.ID)
|
|
if err := container.cmd.Wait(); err != nil {
|
|
if err := container.cmd.Wait(); err != nil {
|
|
- // Discard the error as any signals or non 0 returns will generate an error
|
|
|
|
- utils.Debugf("%s: Process: %s", container.ID, err)
|
|
|
|
|
|
+ // Since non-zero exit status and signal terminations will cause err to be non-nil,
|
|
|
|
+ // we have to actually discard it. Still, log it anyway, just in case.
|
|
|
|
+ utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- utils.Debugf("Process finished")
|
|
|
|
|
|
+ utils.Debugf("monitor: container %s finished", container.ID)
|
|
|
|
|
|
exitCode := -1
|
|
exitCode := -1
|
|
if container.cmd != nil {
|
|
if container.cmd != nil {
|
|
@@ -1007,96 +1218,111 @@ func (container *Container) monitor(hostConfig *HostConfig) {
|
|
}
|
|
}
|
|
|
|
|
|
// Cleanup
|
|
// Cleanup
|
|
|
|
+ container.cleanup()
|
|
|
|
+
|
|
|
|
+ // Re-create a brand new stdin pipe once the container exited
|
|
|
|
+ if container.Config.OpenStdin {
|
|
|
|
+ container.stdin, container.stdinPipe = io.Pipe()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Release the lock
|
|
|
|
+ close(container.waitLock)
|
|
|
|
+
|
|
|
|
+ if err := container.ToDisk(); err != nil {
|
|
|
|
+ // FIXME: there is a race condition here which causes this to fail during the unit tests.
|
|
|
|
+ // If another goroutine was waiting for Wait() to return before removing the container's root
|
|
|
|
+ // from the filesystem... At this point it may already have done so.
|
|
|
|
+ // This is because State.setStopped() has already been called, and has caused Wait()
|
|
|
|
+ // to return.
|
|
|
|
+ // FIXME: why are we serializing running state to disk in the first place?
|
|
|
|
+ //log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (container *Container) cleanup() {
|
|
container.releaseNetwork()
|
|
container.releaseNetwork()
|
|
|
|
+
|
|
|
|
+ // Disable all active links
|
|
|
|
+ if container.activeLinks != nil {
|
|
|
|
+ for _, link := range container.activeLinks {
|
|
|
|
+ link.Disable()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
if container.Config.OpenStdin {
|
|
if container.Config.OpenStdin {
|
|
if err := container.stdin.Close(); err != nil {
|
|
if err := container.stdin.Close(); err != nil {
|
|
- utils.Debugf("%s: Error close stdin: %s", container.ID, err)
|
|
|
|
|
|
+ utils.Errorf("%s: Error close stdin: %s", container.ID, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if err := container.stdout.CloseWriters(); err != nil {
|
|
if err := container.stdout.CloseWriters(); err != nil {
|
|
- utils.Debugf("%s: Error close stdout: %s", container.ID, err)
|
|
|
|
|
|
+ utils.Errorf("%s: Error close stdout: %s", container.ID, err)
|
|
}
|
|
}
|
|
if err := container.stderr.CloseWriters(); err != nil {
|
|
if err := container.stderr.CloseWriters(); err != nil {
|
|
- utils.Debugf("%s: Error close stderr: %s", container.ID, err)
|
|
|
|
|
|
+ utils.Errorf("%s: Error close stderr: %s", container.ID, err)
|
|
}
|
|
}
|
|
|
|
|
|
if container.ptyMaster != nil {
|
|
if container.ptyMaster != nil {
|
|
if err := container.ptyMaster.Close(); err != nil {
|
|
if err := container.ptyMaster.Close(); err != nil {
|
|
- utils.Debugf("%s: Error closing Pty master: %s", container.ID, err)
|
|
|
|
|
|
+ utils.Errorf("%s: Error closing Pty master: %s", container.ID, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if err := container.Unmount(); err != nil {
|
|
if err := container.Unmount(); err != nil {
|
|
log.Printf("%v: Failed to umount filesystem: %v", container.ID, err)
|
|
log.Printf("%v: Failed to umount filesystem: %v", container.ID, err)
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- // Re-create a brand new stdin pipe once the container exited
|
|
|
|
- if container.Config.OpenStdin {
|
|
|
|
- container.stdin, container.stdinPipe = io.Pipe()
|
|
|
|
- }
|
|
|
|
|
|
+func (container *Container) kill(sig int) error {
|
|
|
|
+ container.State.Lock()
|
|
|
|
+ defer container.State.Unlock()
|
|
|
|
|
|
- // Release the lock
|
|
|
|
- close(container.waitLock)
|
|
|
|
|
|
+ if !container.State.Running {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
|
|
- if err := container.ToDisk(); err != nil {
|
|
|
|
- // FIXME: there is a race condition here which causes this to fail during the unit tests.
|
|
|
|
- // If another goroutine was waiting for Wait() to return before removing the container's root
|
|
|
|
- // from the filesystem... At this point it may already have done so.
|
|
|
|
- // This is because State.setStopped() has already been called, and has caused Wait()
|
|
|
|
- // to return.
|
|
|
|
- // FIXME: why are we serializing running state to disk in the first place?
|
|
|
|
- //log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err)
|
|
|
|
|
|
+ if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil {
|
|
|
|
+ log.Printf("error killing container %s (%s, %s)", container.ShortID(), output, err)
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (container *Container) kill() error {
|
|
|
|
|
|
+func (container *Container) Kill() error {
|
|
if !container.State.Running {
|
|
if !container.State.Running {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
- // Sending SIGKILL to the process via lxc
|
|
|
|
- output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput()
|
|
|
|
- if err != nil {
|
|
|
|
- log.Printf("error killing container %s (%s, %s)", container.ID, output, err)
|
|
|
|
|
|
+ // 1. Send SIGKILL
|
|
|
|
+ if err := container.kill(9); err != nil {
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
// 2. Wait for the process to die, in last resort, try to kill the process directly
|
|
// 2. Wait for the process to die, in last resort, try to kill the process directly
|
|
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
|
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
|
if container.cmd == nil {
|
|
if container.cmd == nil {
|
|
- return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID)
|
|
|
|
|
|
+ return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ShortID())
|
|
}
|
|
}
|
|
- log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID)
|
|
|
|
|
|
+ log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", container.ShortID())
|
|
if err := container.cmd.Process.Kill(); err != nil {
|
|
if err := container.cmd.Process.Kill(); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Wait for the container to be actually stopped
|
|
|
|
container.Wait()
|
|
container.Wait()
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (container *Container) Kill() error {
|
|
|
|
- container.State.Lock()
|
|
|
|
- defer container.State.Unlock()
|
|
|
|
- if !container.State.Running {
|
|
|
|
- return nil
|
|
|
|
- }
|
|
|
|
- return container.kill()
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
func (container *Container) Stop(seconds int) error {
|
|
func (container *Container) Stop(seconds int) error {
|
|
- container.State.Lock()
|
|
|
|
- defer container.State.Unlock()
|
|
|
|
if !container.State.Running {
|
|
if !container.State.Running {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
// 1. Send a SIGTERM
|
|
// 1. Send a SIGTERM
|
|
- if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil {
|
|
|
|
- log.Print(string(output))
|
|
|
|
|
|
+ if err := container.kill(15); err != nil {
|
|
|
|
+ utils.Debugf("Error sending kill SIGTERM: %s", err)
|
|
log.Print("Failed to send SIGTERM to the process, force killing")
|
|
log.Print("Failed to send SIGTERM to the process, force killing")
|
|
- if err := container.kill(); err != nil {
|
|
|
|
|
|
+ if err := container.kill(9); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1104,7 +1330,8 @@ func (container *Container) Stop(seconds int) error {
|
|
// 2. Wait for the process to exit on its own
|
|
// 2. Wait for the process to exit on its own
|
|
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
|
|
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
|
|
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
|
|
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
|
|
- if err := container.kill(); err != nil {
|
|
|
|
|
|
+ // 3. If it doesn't, then send SIGKILL
|
|
|
|
+ if err := container.Kill(); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1207,6 +1434,12 @@ func (container *Container) Mounted() (bool, error) {
|
|
}
|
|
}
|
|
|
|
|
|
func (container *Container) Unmount() error {
|
|
func (container *Container) Unmount() error {
|
|
|
|
+ if _, err := os.Stat(container.RootfsPath()); err != nil {
|
|
|
|
+ if os.IsNotExist(err) {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
return Unmount(container.RootfsPath())
|
|
return Unmount(container.RootfsPath())
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1234,6 +1467,10 @@ func (container *Container) jsonPath() string {
|
|
return path.Join(container.root, "config.json")
|
|
return path.Join(container.root, "config.json")
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (container *Container) EnvConfigPath() string {
|
|
|
|
+ return path.Join(container.root, "config.env")
|
|
|
|
+}
|
|
|
|
+
|
|
func (container *Container) lxcConfigPath() string {
|
|
func (container *Container) lxcConfigPath() string {
|
|
return path.Join(container.root, "config.lxc")
|
|
return path.Join(container.root, "config.lxc")
|
|
}
|
|
}
|
|
@@ -1297,3 +1534,9 @@ func (container *Container) Copy(resource string) (Archive, error) {
|
|
}
|
|
}
|
|
return TarFilter(basePath, Uncompressed, filter)
|
|
return TarFilter(basePath, Uncompressed, filter)
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+// Returns true if the container exposes a certain port
|
|
|
|
+func (container *Container) Exposes(p Port) bool {
|
|
|
|
+ _, exists := container.Config.ExposedPorts[p]
|
|
|
|
+ return exists
|
|
|
|
+}
|