From 55d539294f7e768445c9dfdd0137872221ba41a3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Apr 2014 06:15:31 +0000 Subject: [PATCH 1/5] Move network settings into separate file Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: f00ca72baa6c43de35d622caa728587a752d6e5e Component: engine --- components/engine/runtime/container.go | 36 ---------------- components/engine/runtime/network_settings.go | 42 +++++++++++++++++++ 2 files changed, 42 insertions(+), 36 deletions(-) create mode 100644 components/engine/runtime/network_settings.go diff --git a/components/engine/runtime/container.go b/components/engine/runtime/container.go index c8053b146c..f2d4ddc79d 100644 --- a/components/engine/runtime/container.go +++ b/components/engine/runtime/container.go @@ -76,42 +76,6 @@ type Container struct { activeLinks map[string]*links.Link } -// FIXME: move deprecated port stuff to nat to clean up the core. -type PortMapping map[string]string // Deprecated - -type NetworkSettings struct { - IPAddress string - IPPrefixLen int - Gateway string - Bridge string - PortMapping map[string]PortMapping // Deprecated - Ports nat.PortMap -} - -func (settings *NetworkSettings) PortMappingAPI() *engine.Table { - var outs = engine.NewTable("", 0) - for port, bindings := range settings.Ports { - p, _ := nat.ParsePort(port.Port()) - if len(bindings) == 0 { - out := &engine.Env{} - out.SetInt("PublicPort", p) - out.Set("Type", port.Proto()) - outs.Add(out) - continue - } - for _, binding := range bindings { - out := &engine.Env{} - h, _ := nat.ParsePort(binding.HostPort) - out.SetInt("PrivatePort", p) - out.SetInt("PublicPort", h) - out.Set("Type", port.Proto()) - out.Set("IP", binding.HostIp) - outs.Add(out) - } - } - return outs -} - // Inject the io.Reader at the given path. Note: do not close the reader func (container *Container) Inject(file io.Reader, pth string) error { if err := container.Mount(); err != nil { diff --git a/components/engine/runtime/network_settings.go b/components/engine/runtime/network_settings.go new file mode 100644 index 0000000000..dc009e246d --- /dev/null +++ b/components/engine/runtime/network_settings.go @@ -0,0 +1,42 @@ +package runtime + +import ( + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" +) + +// FIXME: move deprecated port stuff to nat to clean up the core. +type PortMapping map[string]string // Deprecated + +type NetworkSettings struct { + IPAddress string + IPPrefixLen int + Gateway string + Bridge string + PortMapping map[string]PortMapping // Deprecated + Ports nat.PortMap +} + +func (settings *NetworkSettings) PortMappingAPI() *engine.Table { + var outs = engine.NewTable("", 0) + for port, bindings := range settings.Ports { + p, _ := nat.ParsePort(port.Port()) + if len(bindings) == 0 { + out := &engine.Env{} + out.SetInt("PublicPort", p) + out.Set("Type", port.Proto()) + outs.Add(out) + continue + } + for _, binding := range bindings { + out := &engine.Env{} + h, _ := nat.ParsePort(binding.HostPort) + out.SetInt("PrivatePort", p) + out.SetInt("PublicPort", h) + out.Set("Type", port.Proto()) + out.Set("IP", binding.HostIp) + outs.Add(out) + } + } + return outs +} From f2bd1e6d7dc94965519dee79a77f6871a6fdc5d0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Apr 2014 06:40:49 +0000 Subject: [PATCH 2/5] Refactor container start to make it more manageable Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: c99ba05c844d4099b85c653edc0c0f5122ccb522 Component: engine --- components/engine/runtime/container.go | 337 ++++++++++++++----------- components/engine/runtime/volumes.go | 7 +- 2 files changed, 190 insertions(+), 154 deletions(-) diff --git a/components/engine/runtime/container.go b/components/engine/runtime/container.go index f2d4ddc79d..a9b81819d6 100644 --- a/components/engine/runtime/container.go +++ b/components/engine/runtime/container.go @@ -322,7 +322,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s }) } -func populateCommand(c *Container) { +func populateCommand(c *Container, env []string) { var ( en *execdriver.Network driverConfig = make(map[string][]string) @@ -366,6 +366,7 @@ func populateCommand(c *Container) { Resources: resources, } c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + c.command.Env = env } func (container *Container) ArgsAsString() string { @@ -387,186 +388,50 @@ func (container *Container) Start() (err error) { if container.State.IsRunning() { return nil } - + // if we encounter and error during start we need to ensure that any other + // setup has been cleaned up properly defer func() { if err != nil { container.cleanup() } }() - if container.ResolvConfPath == "" { - if err := container.setupContainerDns(); err != nil { - return err - } + if err := container.setupContainerDns(); err != nil { + return err } - if err := container.Mount(); err != nil { return err } - - if container.runtime.config.DisableNetwork { - container.Config.NetworkDisabled = true - container.buildHostnameAndHostsFiles("127.0.1.1") - } else { - if err := container.allocateNetwork(); err != nil { - return err - } - container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) + if err := container.initializeNetworking(); err != nil { + return err } - - // Make sure the config is compatible with the current kernel - if container.Config.Memory > 0 && !container.runtime.sysInfo.MemoryLimit { - log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") - container.Config.Memory = 0 - } - if container.Config.Memory > 0 && !container.runtime.sysInfo.SwapLimit { - log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") - container.Config.MemorySwap = -1 - } - - if container.runtime.sysInfo.IPv4ForwardingDisabled { - log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") - } - + container.verifyRuntimeSettings() if err := prepareVolumesForContainer(container); err != nil { return err } - - // Setup environment - env := []string{ - "HOME=/", - "PATH=" + DefaultPathEnv, - "HOSTNAME=" + container.Config.Hostname, - } - - if container.Config.Tty { - env = append(env, "TERM=xterm") - } - - // Init any links between the parent and children - runtime := container.runtime - - children, err := runtime.Children(container.Name) + linkedEnv, err := container.setupLinkedContainers() if err != nil { return err } - - if len(children) > 0 { - container.activeLinks = make(map[string]*links.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 linkAlias, child := range children { - if !child.State.IsRunning() { - return fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias) - } - - link, err := links.NewLink( - container.NetworkSettings.IPAddress, - child.NetworkSettings.IPAddress, - linkAlias, - child.Config.Env, - child.Config.ExposedPorts, - runtime.eng) - - 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) - } - } - } - - // because the env on the container can override certain default values - // we need to replace the 'env' keys where they match and append anything - // else. - env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env) + env := container.createRuntimeEnvironment(linkedEnv) + // TODO: This is only needed for lxc so we should look for a way to + // remove this dep if err := container.generateEnvConfig(env); err != nil { return err } - - if container.Config.WorkingDir != "" { - container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) - - pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir)) - if err != nil { - if !os.IsNotExist(err) { - return err - } - if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil { - return err - } - } - if pthInfo != nil && !pthInfo.IsDir() { - return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir) - } - } - - envPath, err := container.EnvConfigPath() - if err != nil { + if err := container.setupWorkingDirectory(); err != nil { return err } - - populateCommand(container) - container.command.Env = env - - if err := setupMountsForContainer(container, envPath); err != nil { + populateCommand(container, env) + if err := setupMountsForContainer(container); err != nil { return err } - - // Setup logging of stdout and stderr to disk - if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { - return err - } - if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { + if err := container.startLoggingToDisk(); err != nil { return err } container.waitLock = make(chan struct{}) - callbackLock := make(chan struct{}) - callback := func(command *execdriver.Command) { - container.State.SetRunning(command.Pid()) - if command.Tty { - // The callback is called after the process Start() - // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace - // which we close here. - if c, ok := command.Stdout.(io.Closer); ok { - c.Close() - } - } - if err := container.ToDisk(); err != nil { - utils.Debugf("%s", err) - } - close(callbackLock) - } - - // We use a callback here instead of a goroutine and an chan for - // syncronization purposes - cErr := utils.Go(func() error { return container.monitor(callback) }) - - // Start should not return until the process is actually running - select { - case <-callbackLock: - case err := <-cErr: - return err - } - return nil + return container.waitForStart() } func (container *Container) Run() error { @@ -1146,6 +1011,9 @@ func (container *Container) DisableLink(name string) { } func (container *Container) setupContainerDns() error { + if container.ResolvConfPath == "" { + return nil + } var ( config = container.hostConfig runtime = container.runtime @@ -1191,3 +1059,166 @@ func (container *Container) setupContainerDns() error { } return nil } + +func (container *Container) initializeNetworking() error { + if container.runtime.config.DisableNetwork { + container.Config.NetworkDisabled = true + container.buildHostnameAndHostsFiles("127.0.1.1") + } else { + if err := container.allocateNetwork(); err != nil { + return err + } + container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) + } + return nil +} + +// Make sure the config is compatible with the current kernel +func (container *Container) verifyRuntimeSettings() { + if container.Config.Memory > 0 && !container.runtime.sysInfo.MemoryLimit { + log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") + container.Config.Memory = 0 + } + if container.Config.Memory > 0 && !container.runtime.sysInfo.SwapLimit { + log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + container.Config.MemorySwap = -1 + } + if container.runtime.sysInfo.IPv4ForwardingDisabled { + log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") + } +} + +func (container *Container) setupLinkedContainers() ([]string, error) { + var ( + env []string + runtime = container.runtime + ) + children, err := runtime.Children(container.Name) + if err != nil { + return nil, err + } + + if len(children) > 0 { + container.activeLinks = make(map[string]*links.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 linkAlias, child := range children { + if !child.State.IsRunning() { + return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias) + } + + link, err := links.NewLink( + container.NetworkSettings.IPAddress, + child.NetworkSettings.IPAddress, + linkAlias, + child.Config.Env, + child.Config.ExposedPorts, + runtime.eng) + + if err != nil { + rollback() + return nil, err + } + + container.activeLinks[link.Alias()] = link + if err := link.Enable(); err != nil { + rollback() + return nil, err + } + + for _, envVar := range link.ToEnv() { + env = append(env, envVar) + } + } + } + return env, nil +} + +func (container *Container) createRuntimeEnvironment(linkedEnv []string) []string { + // Setup environment + env := []string{ + "HOME=/", + "PATH=" + DefaultPathEnv, + "HOSTNAME=" + container.Config.Hostname, + } + if container.Config.Tty { + env = append(env, "TERM=xterm") + } + env = append(env, linkedEnv...) + // because the env on the container can override certain default values + // we need to replace the 'env' keys where they match and append anything + // else. + env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env) + + return env +} + +func (container *Container) setupWorkingDirectory() error { + if container.Config.WorkingDir != "" { + container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) + + pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir)) + if err != nil { + if !os.IsNotExist(err) { + return err + } + if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil { + return err + } + } + if pthInfo != nil && !pthInfo.IsDir() { + return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir) + } + } + return nil +} + +func (container *Container) startLoggingToDisk() error { + // Setup logging of stdout and stderr to disk + if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { + return err + } + if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { + return err + } + return nil +} + +func (container *Container) waitForStart() error { + callbackLock := make(chan struct{}) + callback := func(command *execdriver.Command) { + container.State.SetRunning(command.Pid()) + if command.Tty { + // The callback is called after the process Start() + // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace + // which we close here. + if c, ok := command.Stdout.(io.Closer); ok { + c.Close() + } + } + if err := container.ToDisk(); err != nil { + utils.Debugf("%s", err) + } + close(callbackLock) + } + + // We use a callback here instead of a goroutine and an chan for + // syncronization purposes + cErr := utils.Go(func() error { return container.monitor(callback) }) + + // Start should not return until the process is actually running + select { + case <-callbackLock: + case err := <-cErr: + return err + } + return nil +} diff --git a/components/engine/runtime/volumes.go b/components/engine/runtime/volumes.go index 004f1bb024..38326ad687 100644 --- a/components/engine/runtime/volumes.go +++ b/components/engine/runtime/volumes.go @@ -33,7 +33,12 @@ func prepareVolumesForContainer(container *Container) error { return nil } -func setupMountsForContainer(container *Container, envPath string) error { +func setupMountsForContainer(container *Container) error { + envPath, err := container.EnvConfigPath() + if err != nil { + return err + } + mounts := []execdriver.Mount{ {container.runtime.sysInitPath, "/.dockerinit", false, true}, {envPath, "/.dockerenv", false, true}, From 757f2fd1a015318fbfac52a2825c66758ce334ba Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Apr 2014 06:54:40 +0000 Subject: [PATCH 3/5] Remove container.When method Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 71b241cbfe5b0100ee2ee0fd0e651beccd965746 Component: engine --- components/engine/runtime/container.go | 4 ---- components/engine/runtime/history.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/components/engine/runtime/container.go b/components/engine/runtime/container.go index a9b81819d6..a2b75d2394 100644 --- a/components/engine/runtime/container.go +++ b/components/engine/runtime/container.go @@ -112,10 +112,6 @@ func (container *Container) Inject(file io.Reader, pth string) error { return nil } -func (container *Container) When() time.Time { - return container.Created -} - func (container *Container) FromDisk() error { data, err := ioutil.ReadFile(container.jsonPath()) if err != nil { diff --git a/components/engine/runtime/history.go b/components/engine/runtime/history.go index 835ac9c11e..bce3d7005a 100644 --- a/components/engine/runtime/history.go +++ b/components/engine/runtime/history.go @@ -14,7 +14,7 @@ func (history *History) Len() int { func (history *History) Less(i, j int) bool { containers := *history - return containers[j].When().Before(containers[i].When()) + return containers[j].Created.Before(containers[i].Created) } func (history *History) Swap(i, j int) { From f02d8ea2c4f30e7ead13f6fc82709d47a9d767eb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Apr 2014 06:58:04 +0000 Subject: [PATCH 4/5] Fix resolvconf logic Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 35a88f2c291c882e49f7c448c1581dfdd3e35a9c Component: engine --- components/engine/runtime/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/runtime/container.go b/components/engine/runtime/container.go index a2b75d2394..6918f1c184 100644 --- a/components/engine/runtime/container.go +++ b/components/engine/runtime/container.go @@ -1007,7 +1007,7 @@ func (container *Container) DisableLink(name string) { } func (container *Container) setupContainerDns() error { - if container.ResolvConfPath == "" { + if container.ResolvConfPath != "" { return nil } var ( From c523a86079beb44c248c67e7e2502c312e5ac9f6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Apr 2014 07:08:54 +0000 Subject: [PATCH 5/5] Remove ArgsAsString because its a util function Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 9bd7d09871d8495d06af94a8d866404569d75b8e Component: engine --- components/engine/runtime/container.go | 12 ------------ components/engine/server/server.go | 12 +++++++++++- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/components/engine/runtime/container.go b/components/engine/runtime/container.go index 6918f1c184..ac22800803 100644 --- a/components/engine/runtime/container.go +++ b/components/engine/runtime/container.go @@ -365,18 +365,6 @@ func populateCommand(c *Container, env []string) { c.command.Env = env } -func (container *Container) ArgsAsString() string { - var args []string - for _, arg := range container.Args { - if strings.Contains(arg, " ") { - args = append(args, fmt.Sprintf("'%s'", arg)) - } else { - args = append(args, arg) - } - } - return strings.Join(args, " ") -} - func (container *Container) Start() (err error) { container.Lock() defer container.Unlock() diff --git a/components/engine/server/server.go b/components/engine/server/server.go index 2de7dbc872..613df9a32f 100644 --- a/components/engine/server/server.go +++ b/components/engine/server/server.go @@ -1063,7 +1063,17 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { out.SetList("Names", names[container.ID]) out.Set("Image", srv.runtime.Repositories().ImageName(container.Image)) if len(container.Args) > 0 { - out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, container.ArgsAsString())) + args := []string{} + for _, arg := range container.Args { + if strings.Contains(arg, " ") { + args = append(args, fmt.Sprintf("'%s'", arg)) + } else { + args = append(args, arg) + } + } + argsAsString := strings.Join(args, " ") + + out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, argsAsString)) } else { out.Set("Command", fmt.Sprintf("\"%s\"", container.Path)) }