1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #16305 from estesp/hooks-for-the-hooks-gods

Use libcontainer hook for network namespace info passing to libnetwork's sandbox
This commit is contained in:
Jess Frazelle 2015-09-16 13:55:16 -07:00
commit ac34ce0307
13 changed files with 122 additions and 40 deletions

View file

@ -811,7 +811,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error {
container.Lock() container.Lock()
defer container.Unlock() defer container.Unlock()
callback := func(processConfig *execdriver.ProcessConfig, pid int) { callback := func(processConfig *execdriver.ProcessConfig, pid int) error {
if processConfig.Tty { if processConfig.Tty {
// The callback is called after the process Start() // The callback is called after the process Start()
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave // 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) close(ExecConfig.waitStart)
return nil
} }
// We use a callback here instead of a goroutine and an chan for // 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 return nil
} }
func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.StartCallback) error { func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.DriverCallback) error {
var ( var (
err error err error
exitCode int exitCode int

View file

@ -174,8 +174,9 @@ func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs.
func populateCommand(c *Container, env []string) error { func populateCommand(c *Container, env []string) error {
var en *execdriver.Network var en *execdriver.Network
if !c.Config.NetworkDisabled { if !c.Config.NetworkDisabled {
en = &execdriver.Network{ en = &execdriver.Network{}
NamespacePath: c.NetworkSettings.SandboxKey, if !c.daemon.execDriver.SupportsHooks() || c.hostConfig.NetworkMode.IsHost() {
en.NamespacePath = c.NetworkSettings.SandboxKey
} }
parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2) 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.OptionUseDefaultSandbox())
sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts")) sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts"))
sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf")) 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") container.HostsPath, err = container.getRootResourcePath("hosts")
@ -947,6 +952,20 @@ func (container *Container) initializeNetworking() error {
return container.buildHostnameFile() 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) { func (container *Container) getIpcContainer() (*Container, error) {
containerID := container.hostConfig.IpcMode.Container() containerID := container.hostConfig.IpcMode.Container()
c, err := container.daemon.Get(containerID) c, err := container.daemon.Get(containerID)

View file

@ -138,6 +138,11 @@ func (container *Container) getSize() (int64, int64) {
return 0, 0 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. // allocateNetwork is a no-op on Windows.
func (container *Container) allocateNetwork() error { func (container *Container) allocateNetwork() error {
return nil return nil

View file

@ -875,8 +875,14 @@ func (daemon *Daemon) unmount(container *Container) error {
return nil return nil
} }
func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error) {
return daemon.execDriver.Run(c.command, pipes, startCallback) 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 { func (daemon *Daemon) kill(c *Container, sig int) error {

View file

@ -267,8 +267,11 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout
} }
// Exec calls the underlying exec driver to run // Exec calls the underlying exec driver to run
func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (int, error) {
exitStatus, err := d.execDriver.Exec(c.command, ExecConfig.ProcessConfig, pipes, startCallback) 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 // On err, make sure we don't leave ExitCode at zero
if err != nil && exitStatus == 0 { if err != nil && exitStatus == 0 {

View file

@ -24,10 +24,22 @@ var (
ErrDriverNotFound = errors.New("The requested docker init has not been found") ErrDriverNotFound = errors.New("The requested docker init has not been found")
) )
// StartCallback defines a callback function. // DriverCallback defines a callback function which is used in "Run" and "Exec".
// It's used by 'Run' and 'Exec', does some work in parent process // This allows work to be done in the parent process when the child is passing
// after child process is started. // through PreStart, Start and PostStop events.
type StartCallback func(*ProcessConfig, int) // 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 // Info is driver specific information based on
// processes registered with the driver // processes registered with the driver
@ -56,11 +68,11 @@ type ExitStatus struct {
type Driver interface { type Driver interface {
// Run executes the process, blocks until the process exits and returns // 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. // 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 // Exec executes the process in an existing container, blocks until the
// process exits and returns the exit code. // 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 sends signals to process in container.
Kill(c *Command, sig int) error Kill(c *Command, sig int) error
@ -89,6 +101,9 @@ type Driver interface {
// Stats returns resource stats for a running container // Stats returns resource stats for a running container
Stats(id string) (*ResourceStats, error) Stats(id string) (*ResourceStats, error)
// SupportsHooks refers to the driver capability to exploit pre/post hook functionality
SupportsHooks() bool
} }
// Ipc settings of the container // Ipc settings of the container

View file

@ -125,7 +125,7 @@ func killNetNsProc(proc *os.Process) {
// Run implements the exec driver Driver interface, // Run implements the exec driver Driver interface,
// it calls 'exec.Cmd' to launch lxc commands to run a container. // 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 ( var (
term execdriver.Terminal term execdriver.Terminal
err error err error
@ -324,9 +324,9 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
c.ContainerPid = pid c.ContainerPid = pid
if startCallback != nil { if hooks.Start != nil {
logrus.Debugf("Invoking startCallback") logrus.Debugf("Invoking startCallback")
startCallback(&c.ProcessConfig, pid) hooks.Start(&c.ProcessConfig, pid)
} }
oomKill := false oomKill := false
@ -870,7 +870,7 @@ func (t *TtyConsole) Close() error {
// Exec implements the exec driver Driver interface, // Exec implements the exec driver Driver interface,
// it is not implemented by lxc. // 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 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) 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
}

View file

@ -18,7 +18,7 @@ import (
// createContainer populates and configures the container type with the // createContainer populates and configures the container type with the
// data provided by the execdriver.Command // 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) container := execdriver.InitContainer(c)
if err := d.createIpc(container, c); err != nil { 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 return nil, err
} }
if err := d.createNetwork(container, c); err != nil { if err := d.createNetwork(container, c, hooks); err != nil {
return nil, err return nil, err
} }
@ -113,7 +113,7 @@ func generateIfaceName() (string, error) {
return "", errors.New("Failed to find name for new interface") 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 { if c.Network == nil {
return nil return nil
} }
@ -135,11 +135,26 @@ func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command)
return nil return nil
} }
if c.Network.NamespacePath == "" { if c.Network.NamespacePath != "" {
return fmt.Errorf("network namespace path is empty") 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 return nil
} }

View file

@ -131,9 +131,9 @@ type execOutput struct {
// Run implements the exec driver Driver interface, // Run implements the exec driver Driver interface,
// it calls libcontainer APIs to run a container. // 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 // take the Command and populate the libcontainer.Config from it
container, err := d.createContainer(c) container, err := d.createContainer(c, hooks)
if err != nil { if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err 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 return execdriver.ExitStatus{ExitCode: -1}, err
} }
if startCallback != nil { if hooks.Start != nil {
pid, err := p.Pid() pid, err := p.Pid()
if err != nil { if err != nil {
p.Signal(os.Kill) p.Signal(os.Kill)
p.Wait() p.Wait()
return execdriver.ExitStatus{ExitCode: -1}, err return execdriver.ExitStatus{ExitCode: -1}, err
} }
startCallback(&c.ProcessConfig, pid) hooks.Start(&c.ProcessConfig, pid)
} }
oom := notifyOnOOM(cont) oom := notifyOnOOM(cont)
@ -477,3 +477,9 @@ func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConf
processConfig.Terminal = term processConfig.Terminal = term
return nil 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
}

View file

@ -19,7 +19,7 @@ import (
// Exec implements the exec driver Driver interface, // Exec implements the exec driver Driver interface,
// it calls libcontainer APIs to execute a container. // 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] active := d.activeContainers[c.ID]
if active == nil { if active == nil {
return -1, fmt.Errorf("No active container exists with ID %s", c.ID) 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 return -1, err
} }
if startCallback != nil { if hooks.Start != nil {
pid, err := p.Pid() pid, err := p.Pid()
if err != nil { if err != nil {
p.Signal(os.Kill) p.Signal(os.Kill)
p.Wait() p.Wait()
return -1, err return -1, err
} }
startCallback(&c.ProcessConfig, pid) hooks.Start(&c.ProcessConfig, pid)
} }
ps, err := p.Wait() ps, err := p.Wait()

View file

@ -12,7 +12,7 @@ import (
) )
// Exec implements the exec driver Driver interface. // 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 ( var (
term execdriver.Terminal term execdriver.Terminal
@ -69,8 +69,8 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
processConfig.Terminal = term processConfig.Terminal = term
// Invoke the start callback // Invoke the start callback
if startCallback != nil { if hooks.Start != nil {
startCallback(&c.ProcessConfig, int(pid)) hooks.Start(&c.ProcessConfig, int(pid))
} }
if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil { if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil {

View file

@ -77,7 +77,7 @@ type containerInit struct {
const defaultOwner = "docker" const defaultOwner = "docker"
// Run implements the exec driver Driver interface // 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 ( var (
term execdriver.Terminal term execdriver.Terminal
@ -290,9 +290,8 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
} }
d.Unlock() d.Unlock()
// Invoke the start callback if hooks.Start != nil {
if startCallback != nil { hooks.Start(&c.ProcessConfig, int(pid))
startCallback(&c.ProcessConfig, int(pid))
} }
var exitCode int32 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) logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil 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
}

View file

@ -250,7 +250,7 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool {
// callback ensures that the container's state is properly updated after we // callback ensures that the container's state is properly updated after we
// received ack from the execution drivers // 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 { if processConfig.Tty {
// The callback is called after the process Start() // The callback is called after the process Start()
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave // 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 { if err := m.container.toDiskLocking(); err != nil {
logrus.Errorf("Error saving container to disk: %v", err) 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 // resetContainer resets the container's IO and ensures that the command is able to be executed again