Skip to content

Commit

Permalink
Merge pull request moby#16305 from estesp/hooks-for-the-hooks-gods
Browse files Browse the repository at this point in the history
Use libcontainer hook for network namespace info passing to libnetwork's sandbox
  • Loading branch information
Jess Frazelle committed Sep 16, 2015
2 parents 5b99591 + e148e76 commit ac34ce0
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 40 deletions.
5 changes: 3 additions & 2 deletions daemon/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
23 changes: 21 additions & 2 deletions daemon/container_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions daemon/container_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions daemon/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 21 additions & 6 deletions daemon/execdriver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 10 additions & 4 deletions daemon/execdriver/lxc/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
29 changes: 22 additions & 7 deletions daemon/execdriver/native/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
}

Expand Down
14 changes: 10 additions & 4 deletions daemon/execdriver/native/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions daemon/execdriver/native/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions daemon/execdriver/windows/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 9 additions & 4 deletions daemon/execdriver/windows/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}
3 changes: 2 additions & 1 deletion daemon/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit ac34ce0

Please sign in to comment.