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

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 <madhu@docker.com>
Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
This commit is contained in:
Madhu Venugopal 2015-09-11 12:05:57 -07:00 committed by Phil Estes
parent e91f2c26ce
commit e148e763b8
13 changed files with 122 additions and 40 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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()

View file

@ -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 {

View file

@ -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
}

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
// 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