Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -44,14 +44,13 @@ type stateBackend interface {
|
| 44 | 44 |
ContainerUnpause(name string) error |
| 45 | 45 |
ContainerUpdate(name string, hostConfig *container.HostConfig) ([]string, error) |
| 46 | 46 |
ContainerWait(name string, timeout time.Duration) (int, error) |
| 47 |
- Exists(id string) bool |
|
| 48 | 47 |
} |
| 49 | 48 |
|
| 50 | 49 |
// monitorBackend includes functions to implement to provide containers monitoring functionality. |
| 51 | 50 |
type monitorBackend interface {
|
| 52 | 51 |
ContainerChanges(name string) ([]archive.Change, error) |
| 53 | 52 |
ContainerInspect(name string, size bool, version version.Version) (interface{}, error)
|
| 54 |
- ContainerLogs(name string, config *backend.ContainerLogsConfig) error |
|
| 53 |
+ ContainerLogs(name string, config *backend.ContainerLogsConfig, started chan struct{}) error
|
|
| 55 | 54 |
ContainerStats(name string, config *backend.ContainerStatsConfig) error |
| 56 | 55 |
ContainerTop(name string, psArgs string) (*types.ContainerProcessList, error) |
| 57 | 56 |
|
| ... | ... |
@@ -3,7 +3,6 @@ package container |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"fmt" |
| 6 |
- "io" |
|
| 7 | 6 |
"net/http" |
| 8 | 7 |
"strconv" |
| 9 | 8 |
"strings" |
| ... | ... |
@@ -15,7 +14,6 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/api/server/httputils" |
| 16 | 16 |
"github.com/docker/docker/api/types/backend" |
| 17 | 17 |
derr "github.com/docker/docker/errors" |
| 18 |
- "github.com/docker/docker/pkg/ioutils" |
|
| 19 | 18 |
"github.com/docker/docker/pkg/signal" |
| 20 | 19 |
"github.com/docker/docker/pkg/term" |
| 21 | 20 |
"github.com/docker/docker/runconfig" |
| ... | ... |
@@ -66,14 +64,8 @@ func (s *containerRouter) getContainersStats(ctx context.Context, w http.Respons |
| 66 | 66 |
} |
| 67 | 67 |
|
| 68 | 68 |
stream := httputils.BoolValueOrDefault(r, "stream", true) |
| 69 |
- var out io.Writer |
|
| 70 | 69 |
if !stream {
|
| 71 | 70 |
w.Header().Set("Content-Type", "application/json")
|
| 72 |
- out = w |
|
| 73 |
- } else {
|
|
| 74 |
- wf := ioutils.NewWriteFlusher(w) |
|
| 75 |
- out = wf |
|
| 76 |
- defer wf.Close() |
|
| 77 | 71 |
} |
| 78 | 72 |
|
| 79 | 73 |
var closeNotifier <-chan bool |
| ... | ... |
@@ -83,7 +75,7 @@ func (s *containerRouter) getContainersStats(ctx context.Context, w http.Respons |
| 83 | 83 |
|
| 84 | 84 |
config := &backend.ContainerStatsConfig{
|
| 85 | 85 |
Stream: stream, |
| 86 |
- OutStream: out, |
|
| 86 |
+ OutStream: w, |
|
| 87 | 87 |
Stop: closeNotifier, |
| 88 | 88 |
Version: string(httputils.VersionFromContext(ctx)), |
| 89 | 89 |
} |
| ... | ... |
@@ -112,22 +104,6 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response |
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 | 114 |
containerName := vars["name"] |
| 115 |
- |
|
| 116 |
- if !s.backend.Exists(containerName) {
|
|
| 117 |
- return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) |
|
| 118 |
- } |
|
| 119 |
- |
|
| 120 |
- // write an empty chunk of data (this is to ensure that the |
|
| 121 |
- // HTTP Response is sent immediately, even if the container has |
|
| 122 |
- // not yet produced any data) |
|
| 123 |
- w.WriteHeader(http.StatusOK) |
|
| 124 |
- if flusher, ok := w.(http.Flusher); ok {
|
|
| 125 |
- flusher.Flush() |
|
| 126 |
- } |
|
| 127 |
- |
|
| 128 |
- output := ioutils.NewWriteFlusher(w) |
|
| 129 |
- defer output.Close() |
|
| 130 |
- |
|
| 131 | 115 |
logsConfig := &backend.ContainerLogsConfig{
|
| 132 | 116 |
ContainerLogsOptions: types.ContainerLogsOptions{
|
| 133 | 117 |
Follow: httputils.BoolValue(r, "follow"), |
| ... | ... |
@@ -137,15 +113,21 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response |
| 137 | 137 |
ShowStdout: stdout, |
| 138 | 138 |
ShowStderr: stderr, |
| 139 | 139 |
}, |
| 140 |
- OutStream: output, |
|
| 140 |
+ OutStream: w, |
|
| 141 | 141 |
Stop: closeNotifier, |
| 142 | 142 |
} |
| 143 | 143 |
|
| 144 |
- if err := s.backend.ContainerLogs(containerName, logsConfig); err != nil {
|
|
| 145 |
- // The client may be expecting all of the data we're sending to |
|
| 146 |
- // be multiplexed, so send it through OutStream, which will |
|
| 147 |
- // have been set up to handle that if needed. |
|
| 148 |
- fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err)) |
|
| 144 |
+ chStarted := make(chan struct{})
|
|
| 145 |
+ if err := s.backend.ContainerLogs(containerName, logsConfig, chStarted); err != nil {
|
|
| 146 |
+ select {
|
|
| 147 |
+ case <-chStarted: |
|
| 148 |
+ // The client may be expecting all of the data we're sending to |
|
| 149 |
+ // be multiplexed, so send it through OutStream, which will |
|
| 150 |
+ // have been set up to handle that if needed. |
|
| 151 |
+ fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err)) |
|
| 152 |
+ default: |
|
| 153 |
+ return err |
|
| 154 |
+ } |
|
| 149 | 155 |
} |
| 150 | 156 |
|
| 151 | 157 |
return nil |
| ... | ... |
@@ -463,10 +445,6 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons |
| 463 | 463 |
} |
| 464 | 464 |
containerName := vars["name"] |
| 465 | 465 |
|
| 466 |
- if !s.backend.Exists(containerName) {
|
|
| 467 |
- return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) |
|
| 468 |
- } |
|
| 469 |
- |
|
| 470 | 466 |
var keys []byte |
| 471 | 467 |
var err error |
| 472 | 468 |
detachKeys := r.FormValue("detachKeys")
|
| ... | ... |
@@ -68,16 +68,9 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r * |
| 68 | 68 |
} |
| 69 | 69 |
|
| 70 | 70 |
w.Header().Set("Content-Type", "application/json")
|
| 71 |
- |
|
| 72 |
- // This is to ensure that the HTTP status code is sent immediately, |
|
| 73 |
- // so that it will not block the receiver. |
|
| 74 |
- w.WriteHeader(http.StatusOK) |
|
| 75 |
- if flusher, ok := w.(http.Flusher); ok {
|
|
| 76 |
- flusher.Flush() |
|
| 77 |
- } |
|
| 78 |
- |
|
| 79 | 71 |
output := ioutils.NewWriteFlusher(w) |
| 80 | 72 |
defer output.Close() |
| 73 |
+ output.Flush() |
|
| 81 | 74 |
|
| 82 | 75 |
enc := json.NewEncoder(output) |
| 83 | 76 |
|
| ... | ... |
@@ -11,13 +11,14 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/daemon/logger" |
| 12 | 12 |
"github.com/docker/docker/daemon/logger/jsonfilelog" |
| 13 | 13 |
derr "github.com/docker/docker/errors" |
| 14 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 14 | 15 |
"github.com/docker/docker/pkg/stdcopy" |
| 15 | 16 |
timetypes "github.com/docker/engine-api/types/time" |
| 16 | 17 |
) |
| 17 | 18 |
|
| 18 | 19 |
// ContainerLogs hooks up a container's stdout and stderr streams |
| 19 | 20 |
// configured with the given struct. |
| 20 |
-func (daemon *Daemon) ContainerLogs(containerName string, config *backend.ContainerLogsConfig) error {
|
|
| 21 |
+func (daemon *Daemon) ContainerLogs(containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error {
|
|
| 21 | 22 |
container, err := daemon.GetContainer(containerName) |
| 22 | 23 |
if err != nil {
|
| 23 | 24 |
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) |
| ... | ... |
@@ -27,14 +28,6 @@ func (daemon *Daemon) ContainerLogs(containerName string, config *backend.Contai |
| 27 | 27 |
return derr.ErrorCodeNeedStream |
| 28 | 28 |
} |
| 29 | 29 |
|
| 30 |
- outStream := config.OutStream |
|
| 31 |
- errStream := outStream |
|
| 32 |
- if !container.Config.Tty {
|
|
| 33 |
- errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) |
|
| 34 |
- outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) |
|
| 35 |
- } |
|
| 36 |
- config.OutStream = outStream |
|
| 37 |
- |
|
| 38 | 30 |
cLog, err := daemon.getLogger(container) |
| 39 | 31 |
if err != nil {
|
| 40 | 32 |
return err |
| ... | ... |
@@ -67,6 +60,18 @@ func (daemon *Daemon) ContainerLogs(containerName string, config *backend.Contai |
| 67 | 67 |
} |
| 68 | 68 |
logs := logReader.ReadLogs(readConfig) |
| 69 | 69 |
|
| 70 |
+ wf := ioutils.NewWriteFlusher(config.OutStream) |
|
| 71 |
+ defer wf.Close() |
|
| 72 |
+ close(started) |
|
| 73 |
+ wf.Flush() |
|
| 74 |
+ |
|
| 75 |
+ var outStream io.Writer = wf |
|
| 76 |
+ errStream := outStream |
|
| 77 |
+ if !container.Config.Tty {
|
|
| 78 |
+ errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) |
|
| 79 |
+ outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 70 | 82 |
for {
|
| 71 | 83 |
select {
|
| 72 | 84 |
case err := <-logs.Err: |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/api/types/backend" |
| 9 | 9 |
"github.com/docker/docker/daemon/execdriver" |
| 10 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 10 | 11 |
"github.com/docker/docker/pkg/version" |
| 11 | 12 |
"github.com/docker/engine-api/types" |
| 12 | 13 |
"github.com/docker/engine-api/types/versions/v1p20" |
| ... | ... |
@@ -31,11 +32,12 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *backend.Contai |
| 31 | 31 |
return json.NewEncoder(config.OutStream).Encode(&types.Stats{})
|
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 |
+ outStream := config.OutStream |
|
| 34 | 35 |
if config.Stream {
|
| 35 |
- // Write an empty chunk of data. |
|
| 36 |
- // This is to ensure that the HTTP status code is sent immediately, |
|
| 37 |
- // even if the container has not yet produced any data. |
|
| 38 |
- config.OutStream.Write(nil) |
|
| 36 |
+ wf := ioutils.NewWriteFlusher(outStream) |
|
| 37 |
+ defer wf.Close() |
|
| 38 |
+ wf.Flush() |
|
| 39 |
+ outStream = wf |
|
| 39 | 40 |
} |
| 40 | 41 |
|
| 41 | 42 |
var preCPUStats types.CPUStats |
| ... | ... |
@@ -50,7 +52,7 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *backend.Contai |
| 50 | 50 |
return ss |
| 51 | 51 |
} |
| 52 | 52 |
|
| 53 |
- enc := json.NewEncoder(config.OutStream) |
|
| 53 |
+ enc := json.NewEncoder(outStream) |
|
| 54 | 54 |
|
| 55 | 55 |
updates := daemon.subscribeToContainerStats(container) |
| 56 | 56 |
defer daemon.unsubscribeToContainerStats(container, updates) |