Browse code

Adding Exec method to native execdriver. Modified Attach() method to support docker exec.

Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)

Vishnu Kannan authored on 2014/09/04 14:29:19
Showing 6 changed files
... ...
@@ -407,7 +407,7 @@ func (b *Builder) run(c *daemon.Container) error {
407 407
 			// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
408 408
 			// but without hijacking for stdin. Also, with attach there can be race
409 409
 			// condition because of some output already was printed before it.
410
-			return <-b.Daemon.Attach(c, nil, nil, b.OutStream, b.ErrStream)
410
+			return <-b.Daemon.Attach(c, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream)
411 411
 		})
412 412
 	}
413 413
 
... ...
@@ -8,9 +8,9 @@ import (
8 8
 	"time"
9 9
 
10 10
 	"github.com/docker/docker/engine"
11
-	"github.com/docker/docker/pkg/ioutils"
12 11
 	"github.com/docker/docker/pkg/jsonlog"
13 12
 	"github.com/docker/docker/pkg/log"
13
+	"github.com/docker/docker/pkg/ioutils"
14 14
 	"github.com/docker/docker/utils"
15 15
 )
16 16
 
... ...
@@ -103,7 +103,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
103 103
 			cStderr = job.Stderr
104 104
 		}
105 105
 
106
-		<-daemon.Attach(container, cStdin, cStdinCloser, cStdout, cStderr)
106
+		<-daemon.Attach(container, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
107 107
 		// If we are in stdinonce mode, wait for the process to end
108 108
 		// otherwise, simply return
109 109
 		if container.Config.StdinOnce && !container.Config.Tty {
... ...
@@ -119,15 +119,17 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
119 119
 // Attach and ContainerAttach.
120 120
 //
121 121
 // This method is in use by builder/builder.go.
122
-func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
122
+func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
123 123
 	var (
124 124
 		cStdout, cStderr io.ReadCloser
125 125
 		nJobs            int
126 126
 		errors           = make(chan error, 3)
127 127
 	)
128 128
 
129
-	if stdin != nil && container.Config.OpenStdin {
130
-		nJobs++
129
+	// Connect stdin of container to the http conn.
130
+	if stdin != nil && openStdin {
131
+		nJobs += 1
132
+		// Get the stdin pipe.
131 133
 		if cStdin, err := container.StdinPipe(); err != nil {
132 134
 			errors <- err
133 135
 		} else {
... ...
@@ -135,7 +137,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
135 135
 				log.Debugf("attach: stdin: begin")
136 136
 				defer log.Debugf("attach: stdin: end")
137 137
 				// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
138
-				if container.Config.StdinOnce && !container.Config.Tty {
138
+				if stdinOnce && !tty {
139 139
 					defer cStdin.Close()
140 140
 				} else {
141 141
 					defer func() {
... ...
@@ -147,10 +149,11 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
147 147
 						}
148 148
 					}()
149 149
 				}
150
-				if container.Config.Tty {
150
+				if tty {
151 151
 					_, err = utils.CopyEscapable(cStdin, stdin)
152 152
 				} else {
153 153
 					_, err = io.Copy(cStdin, stdin)
154
+
154 155
 				}
155 156
 				if err == io.ErrClosedPipe {
156 157
 					err = nil
... ...
@@ -163,7 +166,8 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
163 163
 		}
164 164
 	}
165 165
 	if stdout != nil {
166
-		nJobs++
166
+		nJobs += 1
167
+		// Get a reader end of a pipe that is attached as stdout to the container.
167 168
 		if p, err := container.StdoutPipe(); err != nil {
168 169
 			errors <- err
169 170
 		} else {
... ...
@@ -172,7 +176,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
172 172
 				log.Debugf("attach: stdout: begin")
173 173
 				defer log.Debugf("attach: stdout: end")
174 174
 				// If we are in StdinOnce mode, then close stdin
175
-				if container.Config.StdinOnce && stdin != nil {
175
+				if stdinOnce && stdin != nil {
176 176
 					defer stdin.Close()
177 177
 				}
178 178
 				if stdinCloser != nil {
... ...
@@ -189,6 +193,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
189 189
 			}()
190 190
 		}
191 191
 	} else {
192
+		// Point stdout of container to a no-op writer.
192 193
 		go func() {
193 194
 			if stdinCloser != nil {
194 195
 				defer stdinCloser.Close()
... ...
@@ -201,7 +206,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
201 201
 		}()
202 202
 	}
203 203
 	if stderr != nil {
204
-		nJobs++
204
+		nJobs += 1
205 205
 		if p, err := container.StderrPipe(); err != nil {
206 206
 			errors <- err
207 207
 		} else {
... ...
@@ -210,7 +215,8 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
210 210
 				log.Debugf("attach: stderr: begin")
211 211
 				defer log.Debugf("attach: stderr: end")
212 212
 				// If we are in StdinOnce mode, then close stdin
213
-				if container.Config.StdinOnce && stdin != nil {
213
+				// Why are we closing stdin here and above while handling stdout?
214
+				if stdinOnce && stdin != nil {
214 215
 					defer stdin.Close()
215 216
 				}
216 217
 				if stdinCloser != nil {
... ...
@@ -223,10 +229,12 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
223 223
 				if err != nil {
224 224
 					log.Errorf("attach: stderr: %s", err)
225 225
 				}
226
+				log.Debugf("stdout attach end")
226 227
 				errors <- err
227 228
 			}()
228 229
 		}
229 230
 	} else {
231
+		// Point stderr at a no-op writer.
230 232
 		go func() {
231 233
 			if stdinCloser != nil {
232 234
 				defer stdinCloser.Close()
... ...
@@ -252,7 +260,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
252 252
 
253 253
 		// FIXME: how to clean up the stdin goroutine without the unwanted side effect
254 254
 		// of closing the passed stdin? Add an intermediary io.Pipe?
255
-		for i := 0; i < nJobs; i++ {
255
+		for i := 0; i < nJobs; i += 1 {
256 256
 			log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
257 257
 			if err := <-errors; err != nil {
258 258
 				log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
... ...
@@ -42,6 +42,8 @@ type TtyTerminal interface {
42 42
 
43 43
 type Driver interface {
44 44
 	Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code
45
+	// Exec executes the process in an existing container, blocks until the process exits and returns the exit code
46
+	Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error)
45 47
 	Kill(c *Command, sig int) error
46 48
 	Pause(c *Command) error
47 49
 	Unpause(c *Command) error
... ...
@@ -527,3 +527,7 @@ func (t *TtyConsole) Close() error {
527 527
 	t.SlavePty.Close()
528 528
 	return t.MasterPty.Close()
529 529
 }
530
+
531
+func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
532
+	return -1, fmt.Errorf("Unsupported: Exec is not supported by the lxc driver")
533
+}
530 534
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+// +build linux
1
+
2
+package native
3
+
4
+import (
5
+	"fmt"
6
+	"log"
7
+	"os"
8
+	"os/exec"
9
+	"path/filepath"
10
+	"runtime"
11
+
12
+	"github.com/docker/libcontainer"
13
+	"github.com/docker/libcontainer/namespaces"
14
+	"github.com/docker/docker/daemon/execdriver"
15
+	"github.com/docker/docker/reexec"	
16
+)
17
+
18
+const commandName = "nsenter-exec"
19
+
20
+func init() {
21
+	reexec.Register(commandName, nsenterExec)
22
+}
23
+
24
+func nsenterExec() {
25
+	runtime.LockOSThread()
26
+
27
+	userArgs := findUserArgs()
28
+
29
+	config, err := loadConfigFromFd()
30
+	if err != nil {
31
+		log.Fatalf("docker-exec: unable to receive config from sync pipe: %s", err)
32
+	}
33
+
34
+	if err := namespaces.FinalizeSetns(config, userArgs); err != nil {
35
+		log.Fatalf("docker-exec: failed to exec: %s", err)
36
+	}
37
+}
38
+
39
+func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
40
+	active := d.activeContainers[c.ID]
41
+	if active == nil {
42
+		return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
43
+	}
44
+	state, err := libcontainer.GetState(filepath.Join(d.root, c.ID))
45
+	if err != nil {
46
+		return -1, fmt.Errorf("State unavailable for container with ID %s. The container may have been cleaned up already. Error: %s", c.ID, err)
47
+	}
48
+
49
+	var term execdriver.Terminal
50
+
51
+	if processConfig.Tty {
52
+		term, err = NewTtyConsole(processConfig, pipes)
53
+	} else {
54
+		term, err = execdriver.NewStdConsole(processConfig, pipes)
55
+	}
56
+
57
+	processConfig.Terminal = term
58
+
59
+	args := append([]string{processConfig.Entrypoint}, processConfig.Arguments...)
60
+
61
+	return namespaces.ExecIn(active.container, state, args, os.Args[0], "exec", processConfig.Stdin, processConfig.Stdout, processConfig.Stderr, processConfig.Console, 
62
+		func(cmd *exec.Cmd) {
63
+			if startCallback != nil {
64
+				startCallback(&c.ProcessConfig, cmd.Process.Pid)
65
+			}
66
+		})
67
+}
0 68
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+// +build linux
1
+
2
+package native
3
+
4
+import (
5
+	"os"
6
+	
7
+	"github.com/docker/libcontainer"
8
+	"github.com/docker/libcontainer/syncpipe"
9
+)
10
+
11
+func findUserArgs() []string {
12
+	i := 0
13
+	for _, a := range os.Args {
14
+		i++
15
+
16
+		if a == "--" {
17
+			break
18
+		}
19
+	}
20
+
21
+	return os.Args[i:]
22
+}
23
+
24
+// loadConfigFromFd loads a container's config from the sync pipe that is provided by
25
+// fd 3 when running a process
26
+func loadConfigFromFd() (*libcontainer.Config, error) {
27
+	syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
28
+	if err != nil {
29
+		return nil, err
30
+	}
31
+
32
+	var config *libcontainer.Config
33
+	if err := syncPipe.ReadFromParent(&config); err != nil {
34
+		return nil, err
35
+	}
36
+
37
+	return config, nil
38
+}
39
+