From e148e763b8e7879855022690fdea88a6bf869195 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Fri, 11 Sep 2015 12:05:57 -0700 Subject: [PATCH] Update native execdriver to exploit libcontainer hooks Using @mavenugo's patch for enabling the libcontainer pre-start hook to be used for network namespace initialization (correcting the conflict with user namespaces); updated the boolean check to the more generic SupportsHooks() name, and fixed the hook state function signature. Signed-off-by: Madhu Venugopal Docker-DCO-1.1-Signed-off-by: Phil Estes (github: estesp) --- daemon/container.go | 5 +++-- daemon/container_unix.go | 23 +++++++++++++++++++++-- daemon/container_windows.go | 5 +++++ daemon/daemon.go | 10 ++++++++-- daemon/exec.go | 7 +++++-- daemon/execdriver/driver.go | 27 +++++++++++++++++++++------ daemon/execdriver/lxc/driver.go | 14 ++++++++++---- daemon/execdriver/native/create.go | 29 ++++++++++++++++++++++------- daemon/execdriver/native/driver.go | 14 ++++++++++---- daemon/execdriver/native/exec.go | 6 +++--- daemon/execdriver/windows/exec.go | 6 +++--- daemon/execdriver/windows/run.go | 13 +++++++++---- daemon/monitor.go | 3 ++- 13 files changed, 122 insertions(+), 40 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index c73e7aadb7..dbc5458e96 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -811,7 +811,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error { container.Lock() defer container.Unlock() - callback := func(processConfig *execdriver.ProcessConfig, pid int) { + callback := func(processConfig *execdriver.ProcessConfig, pid int) error { if processConfig.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave @@ -821,6 +821,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error { } } close(ExecConfig.waitStart) + return nil } // We use a callback here instead of a goroutine and an chan for @@ -837,7 +838,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error { return nil } -func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.StartCallback) error { +func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.DriverCallback) error { var ( err error exitCode int diff --git a/daemon/container_unix.go b/daemon/container_unix.go index bdeaa190ca..46bcd0f29a 100644 --- a/daemon/container_unix.go +++ b/daemon/container_unix.go @@ -174,8 +174,9 @@ func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs. func populateCommand(c *Container, env []string) error { var en *execdriver.Network if !c.Config.NetworkDisabled { - en = &execdriver.Network{ - NamespacePath: c.NetworkSettings.SandboxKey, + en = &execdriver.Network{} + if !c.daemon.execDriver.SupportsHooks() || c.hostConfig.NetworkMode.IsHost() { + en.NamespacePath = c.NetworkSettings.SandboxKey } parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2) @@ -405,6 +406,10 @@ func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, e sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox()) sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts")) sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf")) + } else if container.daemon.execDriver.SupportsHooks() { + // OptionUseExternalKey is mandatory for userns support. + // But optional for non-userns support + sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey()) } container.HostsPath, err = container.getRootResourcePath("hosts") @@ -947,6 +952,20 @@ func (container *Container) initializeNetworking() error { return container.buildHostnameFile() } +// called from the libcontainer pre-start hook to set the network +// namespace configuration linkage to the libnetwork "sandbox" entity +func (container *Container) setNetworkNamespaceKey(pid int) error { + path := fmt.Sprintf("/proc/%d/ns/net", pid) + var sandbox libnetwork.Sandbox + search := libnetwork.SandboxContainerWalker(&sandbox, container.ID) + container.daemon.netController.WalkSandboxes(search) + if sandbox == nil { + return fmt.Errorf("no sandbox present for %s", container.ID) + } + + return sandbox.SetKey(path) +} + func (container *Container) getIpcContainer() (*Container, error) { containerID := container.hostConfig.IpcMode.Container() c, err := container.daemon.Get(containerID) diff --git a/daemon/container_windows.go b/daemon/container_windows.go index f72b6bb72e..8378acfe5c 100644 --- a/daemon/container_windows.go +++ b/daemon/container_windows.go @@ -138,6 +138,11 @@ func (container *Container) getSize() (int64, int64) { return 0, 0 } +// setNetworkNamespaceKey is a no-op on Windows. +func (container *Container) setNetworkNamespaceKey(pid int) error { + return nil +} + // allocateNetwork is a no-op on Windows. func (container *Container) allocateNetwork() error { return nil diff --git a/daemon/daemon.go b/daemon/daemon.go index 55b7b9d9c7..11e1d2b587 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -875,8 +875,14 @@ func (daemon *Daemon) unmount(container *Container) error { return nil } -func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { - return daemon.execDriver.Run(c.command, pipes, startCallback) +func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error) { + hooks := execdriver.Hooks{ + Start: startCallback, + } + hooks.PreStart = append(hooks.PreStart, func(processConfig *execdriver.ProcessConfig, pid int) error { + return c.setNetworkNamespaceKey(pid) + }) + return daemon.execDriver.Run(c.command, pipes, hooks) } func (daemon *Daemon) kill(c *Container, sig int) error { diff --git a/daemon/exec.go b/daemon/exec.go index c0756bb554..24f717508c 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -267,8 +267,11 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout } // Exec calls the underlying exec driver to run -func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - exitStatus, err := d.execDriver.Exec(c.command, ExecConfig.ProcessConfig, pipes, startCallback) +func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (int, error) { + hooks := execdriver.Hooks{ + Start: startCallback, + } + exitStatus, err := d.execDriver.Exec(c.command, ExecConfig.ProcessConfig, pipes, hooks) // On err, make sure we don't leave ExitCode at zero if err != nil && exitStatus == 0 { diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index b865c76cf2..142003fdd0 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -24,10 +24,22 @@ var ( ErrDriverNotFound = errors.New("The requested docker init has not been found") ) -// StartCallback defines a callback function. -// It's used by 'Run' and 'Exec', does some work in parent process -// after child process is started. -type StartCallback func(*ProcessConfig, int) +// 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 +type DriverCallback func(processConfig *ProcessConfig, pid int) 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 +} // Info is driver specific information based on // processes registered with the driver @@ -56,11 +68,11 @@ type ExitStatus struct { 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, startCallback StartCallback) (ExitStatus, error) + 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, startCallback StartCallback) (int, error) + Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, hooks Hooks) (int, error) // Kill sends signals to process in container. Kill(c *Command, sig int) error @@ -89,6 +101,9 @@ type Driver interface { // Stats returns resource stats for a running container Stats(id string) (*ResourceStats, error) + + // SupportsHooks refers to the driver capability to exploit pre/post hook functionality + SupportsHooks() bool } // Ipc settings of the container diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 27da7c8c24..5a6274d8f2 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -125,7 +125,7 @@ func killNetNsProc(proc *os.Process) { // Run implements the exec driver Driver interface, // it calls 'exec.Cmd' to launch lxc commands to run a container. -func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { +func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { var ( term execdriver.Terminal err error @@ -324,9 +324,9 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba c.ContainerPid = pid - if startCallback != nil { + if hooks.Start != nil { logrus.Debugf("Invoking startCallback") - startCallback(&c.ProcessConfig, pid) + hooks.Start(&c.ProcessConfig, pid) } oomKill := false @@ -870,7 +870,7 @@ func (t *TtyConsole) Close() error { // Exec implements the exec driver Driver interface, // it is not implemented by lxc. -func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { +func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) { return -1, ErrExec } @@ -883,3 +883,9 @@ func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) { } return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory) } + +// SupportsHooks implements the execdriver Driver interface. +// The LXC execdriver does not support the hook mechanism, which is currently unique to runC/libcontainer. +func (d *Driver) SupportsHooks() bool { + return false +} diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 85f72f8c2c..1fbc3cfde0 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -18,7 +18,7 @@ import ( // createContainer populates and configures the container type with the // data provided by the execdriver.Command -func (d *Driver) createContainer(c *execdriver.Command) (*configs.Config, error) { +func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (*configs.Config, error) { container := execdriver.InitContainer(c) if err := d.createIpc(container, c); err != nil { @@ -33,7 +33,7 @@ func (d *Driver) createContainer(c *execdriver.Command) (*configs.Config, error) return nil, err } - if err := d.createNetwork(container, c); err != nil { + if err := d.createNetwork(container, c, hooks); err != nil { return nil, err } @@ -113,7 +113,7 @@ func generateIfaceName() (string, error) { return "", errors.New("Failed to find name for new interface") } -func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command) error { +func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command, hooks execdriver.Hooks) error { if c.Network == nil { return nil } @@ -135,11 +135,26 @@ func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command) return nil } - if c.Network.NamespacePath == "" { - return fmt.Errorf("network namespace path is empty") + 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 { + if err := fnHook(&c.ProcessConfig, s.Pid); err != nil { + return err + } + } + } + return nil + }), + }, } - - container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath) return nil } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 632c4e61fc..49d06ad9a1 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -131,9 +131,9 @@ type execOutput struct { // 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, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { +func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { // take the Command and populate the libcontainer.Config from it - container, err := d.createContainer(c) + container, err := d.createContainer(c, hooks) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } @@ -165,14 +165,14 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba return execdriver.ExitStatus{ExitCode: -1}, err } - if startCallback != nil { + if hooks.Start != nil { pid, err := p.Pid() if err != nil { p.Signal(os.Kill) p.Wait() return execdriver.ExitStatus{ExitCode: -1}, err } - startCallback(&c.ProcessConfig, pid) + hooks.Start(&c.ProcessConfig, pid) } oom := notifyOnOOM(cont) @@ -477,3 +477,9 @@ func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConf processConfig.Terminal = term return 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 +} diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index f50630d8eb..6cd1a92872 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -19,7 +19,7 @@ import ( // 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, startCallback execdriver.StartCallback) (int, error) { +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) @@ -45,14 +45,14 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo return -1, err } - if startCallback != nil { + if hooks.Start != nil { pid, err := p.Pid() if err != nil { p.Signal(os.Kill) p.Wait() return -1, err } - startCallback(&c.ProcessConfig, pid) + hooks.Start(&c.ProcessConfig, pid) } ps, err := p.Wait() diff --git a/daemon/execdriver/windows/exec.go b/daemon/execdriver/windows/exec.go index 36b50ca966..e0896c0e86 100644 --- a/daemon/execdriver/windows/exec.go +++ b/daemon/execdriver/windows/exec.go @@ -12,7 +12,7 @@ import ( ) // Exec implements the exec driver Driver interface. -func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { +func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) { var ( term execdriver.Terminal @@ -69,8 +69,8 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo processConfig.Terminal = term // Invoke the start callback - if startCallback != nil { - startCallback(&c.ProcessConfig, int(pid)) + if hooks.Start != nil { + hooks.Start(&c.ProcessConfig, int(pid)) } if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil { diff --git a/daemon/execdriver/windows/run.go b/daemon/execdriver/windows/run.go index a17ba79320..cc3617c491 100644 --- a/daemon/execdriver/windows/run.go +++ b/daemon/execdriver/windows/run.go @@ -77,7 +77,7 @@ type containerInit struct { const defaultOwner = "docker" // Run implements the exec driver Driver interface -func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { +func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { var ( term execdriver.Terminal @@ -290,9 +290,8 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba } d.Unlock() - // Invoke the start callback - if startCallback != nil { - startCallback(&c.ProcessConfig, int(pid)) + if hooks.Start != nil { + hooks.Start(&c.ProcessConfig, int(pid)) } var exitCode int32 @@ -305,3 +304,9 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba 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 +} diff --git a/daemon/monitor.go b/daemon/monitor.go index dd5d8c6b25..4d34a83bef 100644 --- a/daemon/monitor.go +++ b/daemon/monitor.go @@ -250,7 +250,7 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool { // callback ensures that the container's state is properly updated after we // received ack from the execution drivers -func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int) { +func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int) error { if processConfig.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave @@ -273,6 +273,7 @@ func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid if err := m.container.toDiskLocking(); err != nil { logrus.Errorf("Error saving container to disk: %v", err) } + return nil } // resetContainer resets the container's IO and ensures that the command is able to be executed again