Browse code

Move stdio attach from libcontainerd backend to callback

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2016/10/18 06:39:52
Showing 16 changed files
... ...
@@ -25,6 +25,7 @@ import (
25 25
 	"github.com/docker/docker/daemon/network"
26 26
 	"github.com/docker/docker/image"
27 27
 	"github.com/docker/docker/layer"
28
+	"github.com/docker/docker/libcontainerd"
28 29
 	"github.com/docker/docker/pkg/idtools"
29 30
 	"github.com/docker/docker/pkg/ioutils"
30 31
 	"github.com/docker/docker/pkg/promise"
... ...
@@ -1012,3 +1013,46 @@ func (container *Container) CancelAttachContext() {
1012 1012
 	}
1013 1013
 	container.attachContext.mu.Unlock()
1014 1014
 }
1015
+
1016
+func (container *Container) startLogging() error {
1017
+	if container.HostConfig.LogConfig.Type == "none" {
1018
+		return nil // do not start logging routines
1019
+	}
1020
+
1021
+	l, err := container.StartLogger(container.HostConfig.LogConfig)
1022
+	if err != nil {
1023
+		return fmt.Errorf("Failed to initialize logging driver: %v", err)
1024
+	}
1025
+
1026
+	copier := logger.NewCopier(map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l)
1027
+	container.LogCopier = copier
1028
+	copier.Run()
1029
+	container.LogDriver = l
1030
+
1031
+	// set LogPath field only for json-file logdriver
1032
+	if jl, ok := l.(*jsonfilelog.JSONFileLogger); ok {
1033
+		container.LogPath = jl.LogPath()
1034
+	}
1035
+
1036
+	return nil
1037
+}
1038
+
1039
+// InitializeStdio is called by libcontainerd to connect the stdio.
1040
+func (container *Container) InitializeStdio(iop libcontainerd.IOPipe) error {
1041
+	if err := container.startLogging(); err != nil {
1042
+		container.Reset(false)
1043
+		return err
1044
+	}
1045
+
1046
+	container.StreamConfig.CopyToPipe(iop)
1047
+
1048
+	if container.Stdin() == nil && !container.Config.Tty {
1049
+		if iop.Stdin != nil {
1050
+			if err := iop.Stdin.Close(); err != nil {
1051
+				logrus.Error("error closing stdin: %+v", err)
1052
+			}
1053
+		}
1054
+	}
1055
+
1056
+	return nil
1057
+}
... ...
@@ -185,7 +185,7 @@ func (daemon *Daemon) restore() error {
185 185
 
186 186
 			if c.IsRunning() || c.IsPaused() {
187 187
 				c.RestartManager().Cancel() // manually start containers because some need to wait for swarm networking
188
-				if err := daemon.containerd.Restore(c.ID); err != nil {
188
+				if err := daemon.containerd.Restore(c.ID, c.InitializeStdio); err != nil {
189 189
 					logrus.Errorf("Failed to restore %s with containerd: %s", c.ID, err)
190 190
 					return
191 191
 				}
... ...
@@ -212,7 +212,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
212 212
 
213 213
 	attachErr := container.AttachStreams(ctx, ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
214 214
 
215
-	systemPid, err := d.containerd.AddProcess(ctx, c.ID, name, p)
215
+	systemPid, err := d.containerd.AddProcess(ctx, c.ID, name, p, ec.InitializeStdio)
216 216
 	if err != nil {
217 217
 		return err
218 218
 	}
... ...
@@ -1,8 +1,11 @@
1 1
 package exec
2 2
 
3 3
 import (
4
+	"runtime"
4 5
 	"sync"
5 6
 
7
+	"github.com/Sirupsen/logrus"
8
+	"github.com/docker/docker/libcontainerd"
6 9
 	"github.com/docker/docker/pkg/stringid"
7 10
 	"github.com/docker/docker/runconfig"
8 11
 )
... ...
@@ -39,6 +42,21 @@ func NewConfig() *Config {
39 39
 	}
40 40
 }
41 41
 
42
+// InitializeStdio is called by libcontainerd to connect the stdio.
43
+func (c *Config) InitializeStdio(iop libcontainerd.IOPipe) error {
44
+	c.StreamConfig.CopyToPipe(iop)
45
+
46
+	if c.Stdin() == nil && !c.Tty && runtime.GOOS == "windows" {
47
+		if iop.Stdin != nil {
48
+			if err := iop.Stdin.Close(); err != nil {
49
+				logrus.Error("error closing exec stdin: %+v", err)
50
+			}
51
+		}
52
+	}
53
+
54
+	return nil
55
+}
56
+
42 57
 // Store keeps track of the exec configurations.
43 58
 type Store struct {
44 59
 	commands map[string]*Config
... ...
@@ -14,7 +14,6 @@ import (
14 14
 	timetypes "github.com/docker/docker/api/types/time"
15 15
 	"github.com/docker/docker/container"
16 16
 	"github.com/docker/docker/daemon/logger"
17
-	"github.com/docker/docker/daemon/logger/jsonfilelog"
18 17
 	"github.com/docker/docker/pkg/ioutils"
19 18
 	"github.com/docker/docker/pkg/stdcopy"
20 19
 )
... ...
@@ -121,30 +120,6 @@ func (daemon *Daemon) getLogger(container *container.Container) (logger.Logger,
121 121
 	return container.StartLogger(container.HostConfig.LogConfig)
122 122
 }
123 123
 
124
-// StartLogging initializes and starts the container logging stream.
125
-func (daemon *Daemon) StartLogging(container *container.Container) error {
126
-	if container.HostConfig.LogConfig.Type == "none" {
127
-		return nil // do not start logging routines
128
-	}
129
-
130
-	l, err := container.StartLogger(container.HostConfig.LogConfig)
131
-	if err != nil {
132
-		return fmt.Errorf("Failed to initialize logging driver: %v", err)
133
-	}
134
-
135
-	copier := logger.NewCopier(map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l)
136
-	container.LogCopier = copier
137
-	copier.Run()
138
-	container.LogDriver = l
139
-
140
-	// set LogPath field only for json-file logdriver
141
-	if jl, ok := l.(*jsonfilelog.JSONFileLogger); ok {
142
-		container.LogPath = jl.LogPath()
143
-	}
144
-
145
-	return nil
146
-}
147
-
148 124
 // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
149 125
 func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
150 126
 	if cfg.Type == "" {
... ...
@@ -3,17 +3,14 @@ package daemon
3 3
 import (
4 4
 	"errors"
5 5
 	"fmt"
6
-	"io"
7 6
 	"runtime"
8 7
 	"strconv"
9 8
 	"time"
10 9
 
11 10
 	"github.com/Sirupsen/logrus"
12 11
 	"github.com/docker/docker/api/types"
13
-	"github.com/docker/docker/daemon/exec"
14 12
 	"github.com/docker/docker/libcontainerd"
15 13
 	"github.com/docker/docker/restartmanager"
16
-	"github.com/docker/docker/runconfig"
17 14
 )
18 15
 
19 16
 // StateChanged updates daemon state changes from containerd
... ...
@@ -133,69 +130,3 @@ func (daemon *Daemon) StateChanged(id string, e libcontainerd.StateInfo) error {
133 133
 
134 134
 	return nil
135 135
 }
136
-
137
-// AttachStreams is called by libcontainerd to connect the stdio.
138
-func (daemon *Daemon) AttachStreams(id string, iop libcontainerd.IOPipe) error {
139
-	var (
140
-		s  *runconfig.StreamConfig
141
-		ec *exec.Config
142
-	)
143
-
144
-	c := daemon.containers.Get(id)
145
-	if c == nil {
146
-		var err error
147
-		ec, err = daemon.getExecConfig(id)
148
-		if err != nil {
149
-			return fmt.Errorf("no such exec/container: %s", id)
150
-		}
151
-		s = ec.StreamConfig
152
-	} else {
153
-		s = c.StreamConfig
154
-		if err := daemon.StartLogging(c); err != nil {
155
-			c.Reset(false)
156
-			return err
157
-		}
158
-	}
159
-
160
-	copyFunc := func(w io.Writer, r io.Reader) {
161
-		s.Add(1)
162
-		go func() {
163
-			if _, err := io.Copy(w, r); err != nil {
164
-				logrus.Errorf("%v stream copy error: %v", id, err)
165
-			}
166
-			s.Done()
167
-		}()
168
-	}
169
-
170
-	if iop.Stdout != nil {
171
-		copyFunc(s.Stdout(), iop.Stdout)
172
-	}
173
-	if iop.Stderr != nil {
174
-		copyFunc(s.Stderr(), iop.Stderr)
175
-	}
176
-
177
-	if stdin := s.Stdin(); stdin != nil {
178
-		if iop.Stdin != nil {
179
-			go func() {
180
-				io.Copy(iop.Stdin, stdin)
181
-				if err := iop.Stdin.Close(); err != nil {
182
-					logrus.Error(err)
183
-				}
184
-			}()
185
-		}
186
-	} else {
187
-		//TODO(swernli): On Windows, not closing stdin when no tty is requested by the exec Config
188
-		// results in a hang. We should re-evaluate generalizing this fix for all OSes if
189
-		// we can determine that is the right thing to do more generally.
190
-		if (c != nil && !c.Config.Tty) || (ec != nil && !ec.Tty && runtime.GOOS == "windows") {
191
-			// tty is enabled, so dont close containerd's iopipe stdin.
192
-			if iop.Stdin != nil {
193
-				if err := iop.Stdin.Close(); err != nil {
194
-					logrus.Error(err)
195
-				}
196
-			}
197
-		}
198
-	}
199
-
200
-	return nil
201
-}
... ...
@@ -37,7 +37,7 @@ func (daemon *Daemon) postRunProcessing(container *container.Container, e libcon
37 37
 
38 38
 		// Create a new servicing container, which will start, complete the update, and merge back the
39 39
 		// results if it succeeded, all as part of the below function call.
40
-		if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, newOpts...); err != nil {
40
+		if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, container.InitializeStdio, newOpts...); err != nil {
41 41
 			container.SetExitCode(-1)
42 42
 			return fmt.Errorf("Post-run update servicing failed: %s", err)
43 43
 		}
... ...
@@ -149,7 +149,7 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
149 149
 		container.ResetRestartManager(true)
150 150
 	}
151 151
 
152
-	if err := daemon.containerd.Create(container.ID, checkpoint, container.CheckpointDir(), *spec, createOptions...); err != nil {
152
+	if err := daemon.containerd.Create(container.ID, checkpoint, container.CheckpointDir(), *spec, container.InitializeStdio, createOptions...); err != nil {
153 153
 		errDesc := grpc.ErrorDesc(err)
154 154
 		logrus.Errorf("Create container failed with error: %s", errDesc)
155 155
 		// if we receive an internal error from the initial start of a container then lets
... ...
@@ -34,7 +34,7 @@ type client struct {
34 34
 // AddProcess is the handler for adding a process to an already running
35 35
 // container. It's called through docker exec. It returns the system pid of the
36 36
 // exec'd process.
37
-func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, specp Process) (int, error) {
37
+func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, specp Process, attachStdio StdioCallback) (int, error) {
38 38
 	clnt.lock(containerID)
39 39
 	defer clnt.unlock(containerID)
40 40
 	container, err := clnt.getContainer(containerID)
... ...
@@ -116,14 +116,10 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
116 116
 
117 117
 	container.processes[processFriendlyName] = p
118 118
 
119
-	clnt.unlock(containerID)
120
-
121
-	if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil {
122
-		clnt.lock(containerID)
119
+	if err := attachStdio(*iopipe); err != nil {
123 120
 		p.closeFifos(iopipe)
124 121
 		return -1, err
125 122
 	}
126
-	clnt.lock(containerID)
127 123
 
128 124
 	return int(resp.SystemPid), nil
129 125
 }
... ...
@@ -153,7 +149,7 @@ func (clnt *client) prepareBundleDir(uid, gid int) (string, error) {
153 153
 	return p, nil
154 154
 }
155 155
 
156
-func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, options ...CreateOption) (err error) {
156
+func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) (err error) {
157 157
 	clnt.lock(containerID)
158 158
 	defer clnt.unlock(containerID)
159 159
 
... ...
@@ -195,7 +191,7 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
195 195
 		return err
196 196
 	}
197 197
 
198
-	return container.start(checkpoint, checkpointDir)
198
+	return container.start(checkpoint, checkpointDir, attachStdio)
199 199
 }
200 200
 
201 201
 func (clnt *client) Signal(containerID string, sig int) error {
... ...
@@ -404,7 +400,7 @@ func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier {
404 404
 	return w
405 405
 }
406 406
 
407
-func (clnt *client) restore(cont *containerd.Container, lastEvent *containerd.Event, options ...CreateOption) (err error) {
407
+func (clnt *client) restore(cont *containerd.Container, lastEvent *containerd.Event, attachStdio StdioCallback, options ...CreateOption) (err error) {
408 408
 	clnt.lock(cont.Id)
409 409
 	defer clnt.unlock(cont.Id)
410 410
 
... ...
@@ -445,7 +441,7 @@ func (clnt *client) restore(cont *containerd.Container, lastEvent *containerd.Ev
445 445
 		return err
446 446
 	})
447 447
 
448
-	if err := clnt.backend.AttachStreams(containerID, *iopipe); err != nil {
448
+	if err := attachStdio(*iopipe); err != nil {
449 449
 		container.closeFifos(iopipe)
450 450
 		return err
451 451
 	}
... ...
@@ -537,7 +533,7 @@ func (clnt *client) getContainerLastEvent(id string) (*containerd.Event, error)
537 537
 	return ev, err
538 538
 }
539 539
 
540
-func (clnt *client) Restore(containerID string, options ...CreateOption) error {
540
+func (clnt *client) Restore(containerID string, attachStdio StdioCallback, options ...CreateOption) error {
541 541
 	// Synchronize with live events
542 542
 	clnt.remote.Lock()
543 543
 	defer clnt.remote.Unlock()
... ...
@@ -585,7 +581,7 @@ func (clnt *client) Restore(containerID string, options ...CreateOption) error {
585 585
 
586 586
 	// container is still alive
587 587
 	if clnt.liveRestore {
588
-		if err := clnt.restore(cont, ev, options...); err != nil {
588
+		if err := clnt.restore(cont, ev, attachStdio, options...); err != nil {
589 589
 			logrus.Errorf("libcontainerd: error restoring %s: %v", containerID, err)
590 590
 		}
591 591
 		return nil
... ...
@@ -94,7 +94,7 @@ const defaultOwner = "docker"
94 94
 //	},
95 95
 //	"Servicing": false
96 96
 //}
97
-func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, options ...CreateOption) error {
97
+func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
98 98
 	clnt.lock(containerID)
99 99
 	defer clnt.unlock(containerID)
100 100
 	logrus.Debugln("libcontainerd: client.Create() with spec", spec)
... ...
@@ -253,7 +253,7 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
253 253
 	// internal structure, start will keep HCS in sync by deleting the
254 254
 	// container there.
255 255
 	logrus.Debugf("libcontainerd: Create() id=%s, Calling start()", containerID)
256
-	if err := container.start(); err != nil {
256
+	if err := container.start(attachStdio); err != nil {
257 257
 		clnt.deleteContainer(containerID)
258 258
 		return err
259 259
 	}
... ...
@@ -266,7 +266,7 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
266 266
 // AddProcess is the handler for adding a process to an already running
267 267
 // container. It's called through docker exec. It returns the system pid of the
268 268
 // exec'd process.
269
-func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, procToAdd Process) (int, error) {
269
+func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, procToAdd Process, attachStdio StdioCallback) (int, error) {
270 270
 	clnt.lock(containerID)
271 271
 	defer clnt.unlock(containerID)
272 272
 	container, err := clnt.getContainer(containerID)
... ...
@@ -343,18 +343,11 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
343 343
 	// Add the process to the container's list of processes
344 344
 	container.processes[processFriendlyName] = proc
345 345
 
346
-	// Make sure the lock is not held while calling back into the daemon
347
-	clnt.unlock(containerID)
348
-
349 346
 	// Tell the engine to attach streams back to the client
350
-	if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil {
351
-		clnt.lock(containerID)
347
+	if err := attachStdio(*iopipe); err != nil {
352 348
 		return -1, err
353 349
 	}
354 350
 
355
-	// Lock again so that the defer unlock doesn't fail. (I really don't like this code)
356
-	clnt.lock(containerID)
357
-
358 351
 	// Spin up a go routine waiting for exit to handle cleanup
359 352
 	go container.waitExit(proc, false)
360 353
 
... ...
@@ -544,7 +537,7 @@ func (clnt *client) Stats(containerID string) (*Stats, error) {
544 544
 }
545 545
 
546 546
 // Restore is the handler for restoring a container
547
-func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error {
547
+func (clnt *client) Restore(containerID string, _ StdioCallback, unusedOnWindows ...CreateOption) error {
548 548
 	// TODO Windows: Implement this. For now, just tell the backend the container exited.
549 549
 	logrus.Debugf("libcontainerd: Restore(%s)", containerID)
550 550
 	return clnt.backend.StateChanged(containerID, StateInfo{
... ...
@@ -88,7 +88,7 @@ func (ctr *container) spec() (*specs.Spec, error) {
88 88
 	return &spec, nil
89 89
 }
90 90
 
91
-func (ctr *container) start(checkpoint string, checkpointDir string) error {
91
+func (ctr *container) start(checkpoint string, checkpointDir string, attachStdio StdioCallback) error {
92 92
 	spec, err := ctr.spec()
93 93
 	if err != nil {
94 94
 		return nil
... ...
@@ -107,7 +107,7 @@ func (ctr *container) start(checkpoint string, checkpointDir string) error {
107 107
 
108 108
 	// we need to delay stdin closure after container start or else "stdin close"
109 109
 	// event will be rejected by containerd.
110
-	// stdin closure happens in AttachStreams
110
+	// stdin closure happens in attachStdio
111 111
 	stdin := iopipe.Stdin
112 112
 	iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
113 113
 		var err error
... ...
@@ -141,7 +141,7 @@ func (ctr *container) start(checkpoint string, checkpointDir string) error {
141 141
 	}
142 142
 	ctr.client.appendContainer(ctr)
143 143
 
144
-	if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
144
+	if err := attachStdio(*iopipe); err != nil {
145 145
 		ctr.closeFifos(iopipe)
146 146
 		return err
147 147
 	}
... ...
@@ -40,7 +40,7 @@ func (ctr *container) newProcess(friendlyName string) *process {
40 40
 
41 41
 // start starts a created container.
42 42
 // Caller needs to lock container ID before calling this method.
43
-func (ctr *container) start() error {
43
+func (ctr *container) start(attachStdio StdioCallback) error {
44 44
 	var err error
45 45
 	isServicing := false
46 46
 
... ...
@@ -147,7 +147,7 @@ func (ctr *container) start() error {
147 147
 
148 148
 	ctr.client.appendContainer(ctr)
149 149
 
150
-	if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
150
+	if err := attachStdio(*iopipe); err != nil {
151 151
 		// OK to return the error here, as waitExit will handle tear-down in HCS
152 152
 		return err
153 153
 	}
... ...
@@ -31,19 +31,18 @@ type CommonStateInfo struct { // FIXME: event?
31 31
 // Backend defines callbacks that the client of the library needs to implement.
32 32
 type Backend interface {
33 33
 	StateChanged(containerID string, state StateInfo) error
34
-	AttachStreams(processFriendlyName string, io IOPipe) error
35 34
 }
36 35
 
37 36
 // Client provides access to containerd features.
38 37
 type Client interface {
39
-	Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, options ...CreateOption) error
38
+	Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error
40 39
 	Signal(containerID string, sig int) error
41 40
 	SignalProcess(containerID string, processFriendlyName string, sig int) error
42
-	AddProcess(ctx context.Context, containerID, processFriendlyName string, process Process) (int, error)
41
+	AddProcess(ctx context.Context, containerID, processFriendlyName string, process Process, attachStdio StdioCallback) (int, error)
43 42
 	Resize(containerID, processFriendlyName string, width, height int) error
44 43
 	Pause(containerID string) error
45 44
 	Resume(containerID string) error
46
-	Restore(containerID string, options ...CreateOption) error
45
+	Restore(containerID string, attachStdio StdioCallback, options ...CreateOption) error
47 46
 	Stats(containerID string) (*Stats, error)
48 47
 	GetPidsForContainer(containerID string) ([]int, error)
49 48
 	Summary(containerID string) ([]Summary, error)
... ...
@@ -58,6 +57,9 @@ type CreateOption interface {
58 58
 	Apply(interface{}) error
59 59
 }
60 60
 
61
+// StdioCallback is called to connect a container or process stdio.
62
+type StdioCallback func(IOPipe) error
63
+
61 64
 // IOPipe contains the stdio streams.
62 65
 type IOPipe struct {
63 66
 	Stdin    io.WriteCloser
... ...
@@ -100,24 +100,6 @@ func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
100 100
 	return nil
101 101
 }
102 102
 
103
-// AttachStreams attaches io streams to the plugin
104
-func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error {
105
-	iop.Stdin.Close()
106
-
107
-	logger := logrus.New()
108
-	logger.Hooks.Add(logHook{id})
109
-	// TODO: cache writer per id
110
-	w := logger.Writer()
111
-	go func() {
112
-		io.Copy(w, iop.Stdout)
113
-	}()
114
-	go func() {
115
-		// TODO: update logrus and use logger.WriterLevel
116
-		io.Copy(w, iop.Stderr)
117
-	}()
118
-	return nil
119
-}
120
-
121 103
 func (pm *Manager) init() error {
122 104
 	dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
123 105
 	if err != nil {
... ...
@@ -169,3 +151,22 @@ func (l logHook) Fire(entry *logrus.Entry) error {
169 169
 	entry.Data = logrus.Fields{"plugin": l.id}
170 170
 	return nil
171 171
 }
172
+
173
+func attachToLog(id string) func(libcontainerd.IOPipe) error {
174
+	return func(iop libcontainerd.IOPipe) error {
175
+		iop.Stdin.Close()
176
+
177
+		logger := logrus.New()
178
+		logger.Hooks.Add(logHook{id})
179
+		// TODO: cache writer per id
180
+		w := logger.Writer()
181
+		go func() {
182
+			io.Copy(w, iop.Stdout)
183
+		}()
184
+		go func() {
185
+			// TODO: update logrus and use logger.WriterLevel
186
+			io.Copy(w, iop.Stderr)
187
+		}()
188
+		return nil
189
+	}
190
+}
... ...
@@ -26,7 +26,7 @@ func (pm *Manager) enable(p *v2.Plugin, force bool) error {
26 26
 	p.Lock()
27 27
 	p.Restart = true
28 28
 	p.Unlock()
29
-	if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec)); err != nil {
29
+	if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
30 30
 		return err
31 31
 	}
32 32
 
... ...
@@ -45,7 +45,7 @@ func (pm *Manager) enable(p *v2.Plugin, force bool) error {
45 45
 }
46 46
 
47 47
 func (pm *Manager) restore(p *v2.Plugin) error {
48
-	return pm.containerdClient.Restore(p.GetID())
48
+	return pm.containerdClient.Restore(p.GetID(), attachToLog(p.GetID()))
49 49
 }
50 50
 
51 51
 func (pm *Manager) disable(p *v2.Plugin) error {
... ...
@@ -7,8 +7,11 @@ import (
7 7
 	"strings"
8 8
 	"sync"
9 9
 
10
+	"github.com/Sirupsen/logrus"
11
+	"github.com/docker/docker/libcontainerd"
10 12
 	"github.com/docker/docker/pkg/broadcaster"
11 13
 	"github.com/docker/docker/pkg/ioutils"
14
+	"github.com/docker/docker/pkg/pools"
12 15
 )
13 16
 
14 17
 // StreamConfig holds information about I/O streams managed together.
... ...
@@ -107,3 +110,34 @@ func (streamConfig *StreamConfig) CloseStreams() error {
107 107
 
108 108
 	return nil
109 109
 }
110
+
111
+// CopyToPipe connects streamconfig with a libcontainerd.IOPipe
112
+func (streamConfig *StreamConfig) CopyToPipe(iop libcontainerd.IOPipe) {
113
+	copyFunc := func(w io.Writer, r io.Reader) {
114
+		streamConfig.Add(1)
115
+		go func() {
116
+			if _, err := pools.Copy(w, r); err != nil {
117
+				logrus.Errorf("stream copy error: %+v", err)
118
+			}
119
+			streamConfig.Done()
120
+		}()
121
+	}
122
+
123
+	if iop.Stdout != nil {
124
+		copyFunc(streamConfig.Stdout(), iop.Stdout)
125
+	}
126
+	if iop.Stderr != nil {
127
+		copyFunc(streamConfig.Stderr(), iop.Stderr)
128
+	}
129
+
130
+	if stdin := streamConfig.Stdin(); stdin != nil {
131
+		if iop.Stdin != nil {
132
+			go func() {
133
+				pools.Copy(iop.Stdin, stdin)
134
+				if err := iop.Stdin.Close(); err != nil {
135
+					logrus.Error("failed to clise stdin: %+v", err)
136
+				}
137
+			}()
138
+		}
139
+	}
140
+}