Browse code

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)

Madhu Venugopal authored on 2015/09/12 04:05:57
Showing 13 changed files
... ...
@@ -811,7 +811,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error {
811 811
 	container.Lock()
812 812
 	defer container.Unlock()
813 813
 
814
-	callback := func(processConfig *execdriver.ProcessConfig, pid int) {
814
+	callback := func(processConfig *execdriver.ProcessConfig, pid int) error {
815 815
 		if processConfig.Tty {
816 816
 			// The callback is called after the process Start()
817 817
 			// 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 {
821 821
 			}
822 822
 		}
823 823
 		close(ExecConfig.waitStart)
824
+		return nil
824 825
 	}
825 826
 
826 827
 	// We use a callback here instead of a goroutine and an chan for
... ...
@@ -837,7 +838,7 @@ func (container *Container) exec(ExecConfig *ExecConfig) error {
837 837
 	return nil
838 838
 }
839 839
 
840
-func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.StartCallback) error {
840
+func (container *Container) monitorExec(ExecConfig *ExecConfig, callback execdriver.DriverCallback) error {
841 841
 	var (
842 842
 		err      error
843 843
 		exitCode int
... ...
@@ -174,8 +174,9 @@ func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs.
174 174
 func populateCommand(c *Container, env []string) error {
175 175
 	var en *execdriver.Network
176 176
 	if !c.Config.NetworkDisabled {
177
-		en = &execdriver.Network{
178
-			NamespacePath: c.NetworkSettings.SandboxKey,
177
+		en = &execdriver.Network{}
178
+		if !c.daemon.execDriver.SupportsHooks() || c.hostConfig.NetworkMode.IsHost() {
179
+			en.NamespacePath = c.NetworkSettings.SandboxKey
179 180
 		}
180 181
 
181 182
 		parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
... ...
@@ -405,6 +406,10 @@ func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, e
405 405
 		sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox())
406 406
 		sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts"))
407 407
 		sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"))
408
+	} else if container.daemon.execDriver.SupportsHooks() {
409
+		// OptionUseExternalKey is mandatory for userns support.
410
+		// But optional for non-userns support
411
+		sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey())
408 412
 	}
409 413
 
410 414
 	container.HostsPath, err = container.getRootResourcePath("hosts")
... ...
@@ -947,6 +952,20 @@ func (container *Container) initializeNetworking() error {
947 947
 	return container.buildHostnameFile()
948 948
 }
949 949
 
950
+// called from the libcontainer pre-start hook to set the network
951
+// namespace configuration linkage to the libnetwork "sandbox" entity
952
+func (container *Container) setNetworkNamespaceKey(pid int) error {
953
+	path := fmt.Sprintf("/proc/%d/ns/net", pid)
954
+	var sandbox libnetwork.Sandbox
955
+	search := libnetwork.SandboxContainerWalker(&sandbox, container.ID)
956
+	container.daemon.netController.WalkSandboxes(search)
957
+	if sandbox == nil {
958
+		return fmt.Errorf("no sandbox present for %s", container.ID)
959
+	}
960
+
961
+	return sandbox.SetKey(path)
962
+}
963
+
950 964
 func (container *Container) getIpcContainer() (*Container, error) {
951 965
 	containerID := container.hostConfig.IpcMode.Container()
952 966
 	c, err := container.daemon.Get(containerID)
... ...
@@ -138,6 +138,11 @@ func (container *Container) getSize() (int64, int64) {
138 138
 	return 0, 0
139 139
 }
140 140
 
141
+// setNetworkNamespaceKey is a no-op on Windows.
142
+func (container *Container) setNetworkNamespaceKey(pid int) error {
143
+	return nil
144
+}
145
+
141 146
 // allocateNetwork is a no-op on Windows.
142 147
 func (container *Container) allocateNetwork() error {
143 148
 	return nil
... ...
@@ -875,8 +875,14 @@ func (daemon *Daemon) unmount(container *Container) error {
875 875
 	return nil
876 876
 }
877 877
 
878
-func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
879
-	return daemon.execDriver.Run(c.command, pipes, startCallback)
878
+func (daemon *Daemon) run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error) {
879
+	hooks := execdriver.Hooks{
880
+		Start: startCallback,
881
+	}
882
+	hooks.PreStart = append(hooks.PreStart, func(processConfig *execdriver.ProcessConfig, pid int) error {
883
+		return c.setNetworkNamespaceKey(pid)
884
+	})
885
+	return daemon.execDriver.Run(c.command, pipes, hooks)
880 886
 }
881 887
 
882 888
 func (daemon *Daemon) kill(c *Container, sig int) error {
... ...
@@ -267,8 +267,11 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout
267 267
 }
268 268
 
269 269
 // Exec calls the underlying exec driver to run
270
-func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
271
-	exitStatus, err := d.execDriver.Exec(c.command, ExecConfig.ProcessConfig, pipes, startCallback)
270
+func (d *Daemon) Exec(c *Container, ExecConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (int, error) {
271
+	hooks := execdriver.Hooks{
272
+		Start: startCallback,
273
+	}
274
+	exitStatus, err := d.execDriver.Exec(c.command, ExecConfig.ProcessConfig, pipes, hooks)
272 275
 
273 276
 	// On err, make sure we don't leave ExitCode at zero
274 277
 	if err != nil && exitStatus == 0 {
... ...
@@ -24,10 +24,22 @@ var (
24 24
 	ErrDriverNotFound          = errors.New("The requested docker init has not been found")
25 25
 )
26 26
 
27
-// StartCallback defines a callback function.
28
-// It's used by 'Run' and 'Exec', does some work in parent process
29
-// after child process is started.
30
-type StartCallback func(*ProcessConfig, int)
27
+// DriverCallback defines a callback function which is used in "Run" and "Exec".
28
+// This allows work to be done in the parent process when the child is passing
29
+// through PreStart, Start and PostStop events.
30
+// Callbacks are provided a processConfig pointer and the pid of the child
31
+type DriverCallback func(processConfig *ProcessConfig, pid int) error
32
+
33
+// Hooks is a struct containing function pointers to callbacks
34
+// used by any execdriver implementation exploiting hooks capabilities
35
+type Hooks struct {
36
+	// PreStart is called before container's CMD/ENTRYPOINT is executed
37
+	PreStart []DriverCallback
38
+	// Start is called after the container's process is full started
39
+	Start DriverCallback
40
+	// PostStop is called after the container process exits
41
+	PostStop []DriverCallback
42
+}
31 43
 
32 44
 // Info is driver specific information based on
33 45
 // processes registered with the driver
... ...
@@ -56,11 +68,11 @@ type ExitStatus struct {
56 56
 type Driver interface {
57 57
 	// Run executes the process, blocks until the process exits and returns
58 58
 	// the exit code. It's the last stage on Docker side for running a container.
59
-	Run(c *Command, pipes *Pipes, startCallback StartCallback) (ExitStatus, error)
59
+	Run(c *Command, pipes *Pipes, hooks Hooks) (ExitStatus, error)
60 60
 
61 61
 	// Exec executes the process in an existing container, blocks until the
62 62
 	// process exits and returns the exit code.
63
-	Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error)
63
+	Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, hooks Hooks) (int, error)
64 64
 
65 65
 	// Kill sends signals to process in container.
66 66
 	Kill(c *Command, sig int) error
... ...
@@ -89,6 +101,9 @@ type Driver interface {
89 89
 
90 90
 	// Stats returns resource stats for a running container
91 91
 	Stats(id string) (*ResourceStats, error)
92
+
93
+	// SupportsHooks refers to the driver capability to exploit pre/post hook functionality
94
+	SupportsHooks() bool
92 95
 }
93 96
 
94 97
 // Ipc settings of the container
... ...
@@ -125,7 +125,7 @@ func killNetNsProc(proc *os.Process) {
125 125
 
126 126
 // Run implements the exec driver Driver interface,
127 127
 // it calls 'exec.Cmd' to launch lxc commands to run a container.
128
-func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
128
+func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
129 129
 	var (
130 130
 		term     execdriver.Terminal
131 131
 		err      error
... ...
@@ -324,9 +324,9 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
324 324
 
325 325
 	c.ContainerPid = pid
326 326
 
327
-	if startCallback != nil {
327
+	if hooks.Start != nil {
328 328
 		logrus.Debugf("Invoking startCallback")
329
-		startCallback(&c.ProcessConfig, pid)
329
+		hooks.Start(&c.ProcessConfig, pid)
330 330
 	}
331 331
 
332 332
 	oomKill := false
... ...
@@ -870,7 +870,7 @@ func (t *TtyConsole) Close() error {
870 870
 
871 871
 // Exec implements the exec driver Driver interface,
872 872
 // it is not implemented by lxc.
873
-func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
873
+func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
874 874
 	return -1, ErrExec
875 875
 }
876 876
 
... ...
@@ -883,3 +883,9 @@ func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
883 883
 	}
884 884
 	return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
885 885
 }
886
+
887
+// SupportsHooks implements the execdriver Driver interface.
888
+// The LXC execdriver does not support the hook mechanism, which is currently unique to runC/libcontainer.
889
+func (d *Driver) SupportsHooks() bool {
890
+	return false
891
+}
... ...
@@ -18,7 +18,7 @@ import (
18 18
 
19 19
 // createContainer populates and configures the container type with the
20 20
 // data provided by the execdriver.Command
21
-func (d *Driver) createContainer(c *execdriver.Command) (*configs.Config, error) {
21
+func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (*configs.Config, error) {
22 22
 	container := execdriver.InitContainer(c)
23 23
 
24 24
 	if err := d.createIpc(container, c); err != nil {
... ...
@@ -33,7 +33,7 @@ func (d *Driver) createContainer(c *execdriver.Command) (*configs.Config, error)
33 33
 		return nil, err
34 34
 	}
35 35
 
36
-	if err := d.createNetwork(container, c); err != nil {
36
+	if err := d.createNetwork(container, c, hooks); err != nil {
37 37
 		return nil, err
38 38
 	}
39 39
 
... ...
@@ -113,7 +113,7 @@ func generateIfaceName() (string, error) {
113 113
 	return "", errors.New("Failed to find name for new interface")
114 114
 }
115 115
 
116
-func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command) error {
116
+func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command, hooks execdriver.Hooks) error {
117 117
 	if c.Network == nil {
118 118
 		return nil
119 119
 	}
... ...
@@ -135,11 +135,26 @@ func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command)
135 135
 		return nil
136 136
 	}
137 137
 
138
-	if c.Network.NamespacePath == "" {
139
-		return fmt.Errorf("network namespace path is empty")
138
+	if c.Network.NamespacePath != "" {
139
+		container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath)
140
+		return nil
141
+	}
142
+	// only set up prestart hook if the namespace path is not set (this should be
143
+	// all cases *except* for --net=host shared networking)
144
+	container.Hooks = &configs.Hooks{
145
+		Prestart: []configs.Hook{
146
+			configs.NewFunctionHook(func(s configs.HookState) error {
147
+				if len(hooks.PreStart) > 0 {
148
+					for _, fnHook := range hooks.PreStart {
149
+						if err := fnHook(&c.ProcessConfig, s.Pid); err != nil {
150
+							return err
151
+						}
152
+					}
153
+				}
154
+				return nil
155
+			}),
156
+		},
140 157
 	}
141
-
142
-	container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath)
143 158
 	return nil
144 159
 }
145 160
 
... ...
@@ -131,9 +131,9 @@ type execOutput struct {
131 131
 
132 132
 // Run implements the exec driver Driver interface,
133 133
 // it calls libcontainer APIs to run a container.
134
-func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
134
+func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
135 135
 	// take the Command and populate the libcontainer.Config from it
136
-	container, err := d.createContainer(c)
136
+	container, err := d.createContainer(c, hooks)
137 137
 	if err != nil {
138 138
 		return execdriver.ExitStatus{ExitCode: -1}, err
139 139
 	}
... ...
@@ -165,14 +165,14 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
165 165
 		return execdriver.ExitStatus{ExitCode: -1}, err
166 166
 	}
167 167
 
168
-	if startCallback != nil {
168
+	if hooks.Start != nil {
169 169
 		pid, err := p.Pid()
170 170
 		if err != nil {
171 171
 			p.Signal(os.Kill)
172 172
 			p.Wait()
173 173
 			return execdriver.ExitStatus{ExitCode: -1}, err
174 174
 		}
175
-		startCallback(&c.ProcessConfig, pid)
175
+		hooks.Start(&c.ProcessConfig, pid)
176 176
 	}
177 177
 
178 178
 	oom := notifyOnOOM(cont)
... ...
@@ -477,3 +477,9 @@ func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConf
477 477
 	processConfig.Terminal = term
478 478
 	return nil
479 479
 }
480
+
481
+// SupportsHooks implements the execdriver Driver interface.
482
+// The libcontainer/runC-based native execdriver does exploit the hook mechanism
483
+func (d *Driver) SupportsHooks() bool {
484
+	return true
485
+}
... ...
@@ -19,7 +19,7 @@ import (
19 19
 
20 20
 // Exec implements the exec driver Driver interface,
21 21
 // it calls libcontainer APIs to execute a container.
22
-func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
22
+func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
23 23
 	active := d.activeContainers[c.ID]
24 24
 	if active == nil {
25 25
 		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
45 45
 		return -1, err
46 46
 	}
47 47
 
48
-	if startCallback != nil {
48
+	if hooks.Start != nil {
49 49
 		pid, err := p.Pid()
50 50
 		if err != nil {
51 51
 			p.Signal(os.Kill)
52 52
 			p.Wait()
53 53
 			return -1, err
54 54
 		}
55
-		startCallback(&c.ProcessConfig, pid)
55
+		hooks.Start(&c.ProcessConfig, pid)
56 56
 	}
57 57
 
58 58
 	ps, err := p.Wait()
... ...
@@ -12,7 +12,7 @@ import (
12 12
 )
13 13
 
14 14
 // Exec implements the exec driver Driver interface.
15
-func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
15
+func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
16 16
 
17 17
 	var (
18 18
 		term     execdriver.Terminal
... ...
@@ -69,8 +69,8 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
69 69
 	processConfig.Terminal = term
70 70
 
71 71
 	// Invoke the start callback
72
-	if startCallback != nil {
73
-		startCallback(&c.ProcessConfig, int(pid))
72
+	if hooks.Start != nil {
73
+		hooks.Start(&c.ProcessConfig, int(pid))
74 74
 	}
75 75
 
76 76
 	if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil {
... ...
@@ -77,7 +77,7 @@ type containerInit struct {
77 77
 const defaultOwner = "docker"
78 78
 
79 79
 // Run implements the exec driver Driver interface
80
-func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
80
+func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
81 81
 
82 82
 	var (
83 83
 		term execdriver.Terminal
... ...
@@ -290,9 +290,8 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
290 290
 	}
291 291
 	d.Unlock()
292 292
 
293
-	// Invoke the start callback
294
-	if startCallback != nil {
295
-		startCallback(&c.ProcessConfig, int(pid))
293
+	if hooks.Start != nil {
294
+		hooks.Start(&c.ProcessConfig, int(pid))
296 295
 	}
297 296
 
298 297
 	var exitCode int32
... ...
@@ -305,3 +304,9 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
305 305
 	logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
306 306
 	return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
307 307
 }
308
+
309
+// SupportsHooks implements the execdriver Driver interface.
310
+// The windows driver does not support the hook mechanism
311
+func (d *Driver) SupportsHooks() bool {
312
+	return false
313
+}
... ...
@@ -250,7 +250,7 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool {
250 250
 
251 251
 // callback ensures that the container's state is properly updated after we
252 252
 // received ack from the execution drivers
253
-func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int) {
253
+func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int) error {
254 254
 	if processConfig.Tty {
255 255
 		// The callback is called after the process Start()
256 256
 		// 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
273 273
 	if err := m.container.toDiskLocking(); err != nil {
274 274
 		logrus.Errorf("Error saving container to disk: %v", err)
275 275
 	}
276
+	return nil
276 277
 }
277 278
 
278 279
 // resetContainer resets the container's IO and ensures that the command is able to be executed again