Browse code

Merge pull request #12304 from runcom/remove-job-logs

Remove job from logs

Alexander Morozov authored on 2015/04/14 00:38:46
Showing 13 changed files
... ...
@@ -557,43 +557,26 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo
557 557
 		return fmt.Errorf("Missing parameter")
558 558
 	}
559 559
 
560
-	var (
561
-		inspectJob = eng.Job("container_inspect", vars["name"])
562
-		logsJob    = eng.Job("logs", vars["name"])
563
-		c, err     = inspectJob.Stdout.AddEnv()
564
-	)
565
-	if err != nil {
566
-		return err
567
-	}
568
-	logsJob.Setenv("follow", r.Form.Get("follow"))
569
-	logsJob.Setenv("tail", r.Form.Get("tail"))
570
-	logsJob.Setenv("stdout", r.Form.Get("stdout"))
571
-	logsJob.Setenv("stderr", r.Form.Get("stderr"))
572
-	logsJob.Setenv("timestamps", r.Form.Get("timestamps"))
573 560
 	// Validate args here, because we can't return not StatusOK after job.Run() call
574
-	stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr")
561
+	stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr"))
575 562
 	if !(stdout || stderr) {
576 563
 		return fmt.Errorf("Bad parameters: you must choose at least one stream")
577 564
 	}
578
-	if err = inspectJob.Run(); err != nil {
579
-		return err
580
-	}
581 565
 
582
-	var outStream, errStream io.Writer
583
-	outStream = utils.NewWriteFlusher(w)
584
-
585
-	if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
586
-		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
587
-		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
588
-	} else {
589
-		errStream = outStream
566
+	logsConfig := &daemon.ContainerLogsConfig{
567
+		Follow:     toBool(r.Form.Get("follow")),
568
+		Timestamps: toBool(r.Form.Get("timestamps")),
569
+		Tail:       r.Form.Get("tail"),
570
+		UseStdout:  stdout,
571
+		UseStderr:  stderr,
572
+		OutStream:  utils.NewWriteFlusher(w),
590 573
 	}
591 574
 
592
-	logsJob.Stdout.Add(outStream)
593
-	logsJob.Stderr.Set(errStream)
594
-	if err := logsJob.Run(); err != nil {
595
-		fmt.Fprintf(outStream, "Error running logs job: %s\n", err)
575
+	d := getDaemon(eng)
576
+	if err := d.ContainerLogs(vars["name"], logsConfig); err != nil {
577
+		fmt.Fprintf(w, "Error running logs job: %s\n", err)
596 578
 	}
579
+
597 580
 	return nil
598 581
 }
599 582
 
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"io"
8 8
 	"net/http"
9 9
 	"net/http/httptest"
10
-	"strings"
11 10
 	"testing"
12 11
 
13 12
 	"github.com/docker/docker/api"
... ...
@@ -126,99 +125,6 @@ func TestGetContainersByName(t *testing.T) {
126 126
 	}
127 127
 }
128 128
 
129
-func TestLogs(t *testing.T) {
130
-	eng := engine.New()
131
-	var inspect bool
132
-	var logs bool
133
-	eng.Register("container_inspect", func(job *engine.Job) error {
134
-		inspect = true
135
-		if len(job.Args) == 0 {
136
-			t.Fatal("Job arguments is empty")
137
-		}
138
-		if job.Args[0] != "test" {
139
-			t.Fatalf("Container name %s, must be test", job.Args[0])
140
-		}
141
-		return nil
142
-	})
143
-	expected := "logs"
144
-	eng.Register("logs", func(job *engine.Job) error {
145
-		logs = true
146
-		if len(job.Args) == 0 {
147
-			t.Fatal("Job arguments is empty")
148
-		}
149
-		if job.Args[0] != "test" {
150
-			t.Fatalf("Container name %s, must be test", job.Args[0])
151
-		}
152
-		follow := job.Getenv("follow")
153
-		if follow != "1" {
154
-			t.Fatalf("follow: %s, must be 1", follow)
155
-		}
156
-		stdout := job.Getenv("stdout")
157
-		if stdout != "1" {
158
-			t.Fatalf("stdout %s, must be 1", stdout)
159
-		}
160
-		stderr := job.Getenv("stderr")
161
-		if stderr != "" {
162
-			t.Fatalf("stderr %s, must be empty", stderr)
163
-		}
164
-		timestamps := job.Getenv("timestamps")
165
-		if timestamps != "1" {
166
-			t.Fatalf("timestamps %s, must be 1", timestamps)
167
-		}
168
-		job.Stdout.Write([]byte(expected))
169
-		return nil
170
-	})
171
-	r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1&timestamps=1", nil, eng, t)
172
-	if r.Code != http.StatusOK {
173
-		t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
174
-	}
175
-	if !inspect {
176
-		t.Fatal("container_inspect job was not called")
177
-	}
178
-	if !logs {
179
-		t.Fatal("logs job was not called")
180
-	}
181
-	res := r.Body.String()
182
-	if res != expected {
183
-		t.Fatalf("Output %s, expected %s", res, expected)
184
-	}
185
-}
186
-
187
-func TestLogsNoStreams(t *testing.T) {
188
-	eng := engine.New()
189
-	var inspect bool
190
-	var logs bool
191
-	eng.Register("container_inspect", func(job *engine.Job) error {
192
-		inspect = true
193
-		if len(job.Args) == 0 {
194
-			t.Fatal("Job arguments is empty")
195
-		}
196
-		if job.Args[0] != "test" {
197
-			t.Fatalf("Container name %s, must be test", job.Args[0])
198
-		}
199
-		return nil
200
-	})
201
-	eng.Register("logs", func(job *engine.Job) error {
202
-		logs = true
203
-		return nil
204
-	})
205
-	r := serveRequest("GET", "/containers/test/logs", nil, eng, t)
206
-	if r.Code != http.StatusBadRequest {
207
-		t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest)
208
-	}
209
-	if inspect {
210
-		t.Fatal("container_inspect job was called, but it shouldn't")
211
-	}
212
-	if logs {
213
-		t.Fatal("logs job was called, but it shouldn't")
214
-	}
215
-	res := strings.TrimSpace(r.Body.String())
216
-	expected := "Bad parameters: you must choose at least one stream"
217
-	if !strings.Contains(res, expected) {
218
-		t.Fatalf("Output %s, expected %s in it", res, expected)
219
-	}
220
-}
221
-
222 129
 func TestGetImagesByName(t *testing.T) {
223 130
 	eng := engine.New()
224 131
 	name := "image_name"
... ...
@@ -123,7 +123,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
123 123
 		"create":            daemon.ContainerCreate,
124 124
 		"export":            daemon.ContainerExport,
125 125
 		"info":              daemon.CmdInfo,
126
-		"logs":              daemon.ContainerLogs,
127 126
 		"restart":           daemon.ContainerRestart,
128 127
 		"start":             daemon.ContainerStart,
129 128
 		"execCreate":        daemon.ContainerExecCreate,
... ...
@@ -10,40 +10,50 @@ import (
10 10
 	"sync"
11 11
 
12 12
 	"github.com/Sirupsen/logrus"
13
-	"github.com/docker/docker/engine"
14 13
 	"github.com/docker/docker/pkg/jsonlog"
14
+	"github.com/docker/docker/pkg/stdcopy"
15 15
 	"github.com/docker/docker/pkg/tailfile"
16 16
 	"github.com/docker/docker/pkg/timeutils"
17 17
 )
18 18
 
19
-func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
20
-	if len(job.Args) != 1 {
21
-		return fmt.Errorf("Usage: %s CONTAINER\n", job.Name)
22
-	}
19
+type ContainerLogsConfig struct {
20
+	Follow, Timestamps   bool
21
+	Tail                 string
22
+	UseStdout, UseStderr bool
23
+	OutStream            io.Writer
24
+}
23 25
 
26
+func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error {
24 27
 	var (
25
-		name   = job.Args[0]
26
-		stdout = job.GetenvBool("stdout")
27
-		stderr = job.GetenvBool("stderr")
28
-		tail   = job.Getenv("tail")
29
-		follow = job.GetenvBool("follow")
30
-		times  = job.GetenvBool("timestamps")
31 28
 		lines  = -1
32 29
 		format string
33 30
 	)
34
-	if !(stdout || stderr) {
31
+	if !(config.UseStdout || config.UseStderr) {
35 32
 		return fmt.Errorf("You must choose at least one stream")
36 33
 	}
37
-	if times {
34
+	if config.Timestamps {
38 35
 		format = timeutils.RFC3339NanoFixed
39 36
 	}
40
-	if tail == "" {
41
-		tail = "all"
37
+	if config.Tail == "" {
38
+		config.Tail = "all"
42 39
 	}
40
+
43 41
 	container, err := daemon.Get(name)
44 42
 	if err != nil {
45 43
 		return err
46 44
 	}
45
+
46
+	var (
47
+		outStream = config.OutStream
48
+		errStream io.Writer
49
+	)
50
+	if !container.Config.Tty {
51
+		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
52
+		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
53
+	} else {
54
+		errStream = outStream
55
+	}
56
+
47 57
 	if container.LogDriverType() != "json-file" {
48 58
 		return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
49 59
 	}
... ...
@@ -51,30 +61,30 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
51 51
 	if err != nil && os.IsNotExist(err) {
52 52
 		// Legacy logs
53 53
 		logrus.Debugf("Old logs format")
54
-		if stdout {
54
+		if config.UseStdout {
55 55
 			cLog, err := container.ReadLog("stdout")
56 56
 			if err != nil {
57 57
 				logrus.Errorf("Error reading logs (stdout): %s", err)
58
-			} else if _, err := io.Copy(job.Stdout, cLog); err != nil {
58
+			} else if _, err := io.Copy(outStream, cLog); err != nil {
59 59
 				logrus.Errorf("Error streaming logs (stdout): %s", err)
60 60
 			}
61 61
 		}
62
-		if stderr {
62
+		if config.UseStderr {
63 63
 			cLog, err := container.ReadLog("stderr")
64 64
 			if err != nil {
65 65
 				logrus.Errorf("Error reading logs (stderr): %s", err)
66
-			} else if _, err := io.Copy(job.Stderr, cLog); err != nil {
66
+			} else if _, err := io.Copy(errStream, cLog); err != nil {
67 67
 				logrus.Errorf("Error streaming logs (stderr): %s", err)
68 68
 			}
69 69
 		}
70 70
 	} else if err != nil {
71 71
 		logrus.Errorf("Error reading logs (json): %s", err)
72 72
 	} else {
73
-		if tail != "all" {
73
+		if config.Tail != "all" {
74 74
 			var err error
75
-			lines, err = strconv.Atoi(tail)
75
+			lines, err = strconv.Atoi(config.Tail)
76 76
 			if err != nil {
77
-				logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err)
77
+				logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err)
78 78
 				lines = -1
79 79
 			}
80 80
 		}
... ...
@@ -101,39 +111,39 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
101 101
 					break
102 102
 				}
103 103
 				logLine := l.Log
104
-				if times {
104
+				if config.Timestamps {
105 105
 					// format can be "" or time format, so here can't be error
106 106
 					logLine, _ = l.Format(format)
107 107
 				}
108
-				if l.Stream == "stdout" && stdout {
109
-					io.WriteString(job.Stdout, logLine)
108
+				if l.Stream == "stdout" && config.UseStdout {
109
+					io.WriteString(outStream, logLine)
110 110
 				}
111
-				if l.Stream == "stderr" && stderr {
112
-					io.WriteString(job.Stderr, logLine)
111
+				if l.Stream == "stderr" && config.UseStderr {
112
+					io.WriteString(errStream, logLine)
113 113
 				}
114 114
 				l.Reset()
115 115
 			}
116 116
 		}
117 117
 	}
118
-	if follow && container.IsRunning() {
118
+	if config.Follow && container.IsRunning() {
119 119
 		errors := make(chan error, 2)
120 120
 		wg := sync.WaitGroup{}
121 121
 
122
-		if stdout {
122
+		if config.UseStdout {
123 123
 			wg.Add(1)
124 124
 			stdoutPipe := container.StdoutLogPipe()
125 125
 			defer stdoutPipe.Close()
126 126
 			go func() {
127
-				errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format)
127
+				errors <- jsonlog.WriteLog(stdoutPipe, outStream, format)
128 128
 				wg.Done()
129 129
 			}()
130 130
 		}
131
-		if stderr {
131
+		if config.UseStderr {
132 132
 			wg.Add(1)
133 133
 			stderrPipe := container.StderrLogPipe()
134 134
 			defer stderrPipe.Close()
135 135
 			go func() {
136
-				errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format)
136
+				errors <- jsonlog.WriteLog(stderrPipe, errStream, format)
137 137
 				wg.Done()
138 138
 			}()
139 139
 		}
... ...
@@ -3,14 +3,15 @@ package main
3 3
 import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6
-	"github.com/docker/docker/api/types"
7
-	"github.com/docker/docker/pkg/stringid"
8
-	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
9 6
 	"io"
10 7
 	"os/exec"
11 8
 	"strings"
12 9
 	"testing"
13 10
 	"time"
11
+
12
+	"github.com/docker/docker/api/types"
13
+	"github.com/docker/docker/pkg/stringid"
14
+	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
14 15
 )
15 16
 
16 17
 func TestContainerApiGetAll(t *testing.T) {
... ...
@@ -28,7 +29,7 @@ func TestContainerApiGetAll(t *testing.T) {
28 28
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
29 29
 	}
30 30
 
31
-	body, err := sockRequest("GET", "/containers/json?all=1", nil)
31
+	_, body, err := sockRequest("GET", "/containers/json?all=1", nil)
32 32
 	if err != nil {
33 33
 		t.Fatalf("GET all containers sockRequest failed: %v", err)
34 34
 	}
... ...
@@ -61,7 +62,7 @@ func TestContainerApiGetExport(t *testing.T) {
61 61
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
62 62
 	}
63 63
 
64
-	body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
64
+	_, body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
65 65
 	if err != nil {
66 66
 		t.Fatalf("GET containers/export sockRequest failed: %v", err)
67 67
 	}
... ...
@@ -98,7 +99,7 @@ func TestContainerApiGetChanges(t *testing.T) {
98 98
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
99 99
 	}
100 100
 
101
-	body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
101
+	_, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
102 102
 	if err != nil {
103 103
 		t.Fatalf("GET containers/changes sockRequest failed: %v", err)
104 104
 	}
... ...
@@ -133,7 +134,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) {
133 133
 		"Volumes": map[string]struct{}{"/tmp": {}},
134 134
 	}
135 135
 
136
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
136
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
137 137
 		t.Fatal(err)
138 138
 	}
139 139
 
... ...
@@ -141,7 +142,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) {
141 141
 	config = map[string]interface{}{
142 142
 		"Binds": []string{bindPath + ":/tmp"},
143 143
 	}
144
-	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
144
+	if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
145 145
 		t.Fatal(err)
146 146
 	}
147 147
 
... ...
@@ -166,7 +167,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) {
166 166
 		"Volumes": map[string]struct{}{"/tmp": {}},
167 167
 	}
168 168
 
169
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
169
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
170 170
 		t.Fatal(err)
171 171
 	}
172 172
 
... ...
@@ -176,7 +177,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) {
176 176
 	config = map[string]interface{}{
177 177
 		"Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"},
178 178
 	}
179
-	if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
179
+	if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
180 180
 		t.Fatal("expected container start to fail when duplicate volume binds to same container path")
181 181
 	} else {
182 182
 		if !strings.Contains(string(body), "Duplicate volume") {
... ...
@@ -201,14 +202,14 @@ func TestContainerApiStartVolumesFrom(t *testing.T) {
201 201
 		"Volumes": map[string]struct{}{volPath: {}},
202 202
 	}
203 203
 
204
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
204
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
205 205
 		t.Fatal(err)
206 206
 	}
207 207
 
208 208
 	config = map[string]interface{}{
209 209
 		"VolumesFrom": []string{volName},
210 210
 	}
211
-	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
211
+	if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
212 212
 		t.Fatal(err)
213 213
 	}
214 214
 
... ...
@@ -245,7 +246,7 @@ func TestVolumesFromHasPriority(t *testing.T) {
245 245
 		"Volumes": map[string]struct{}{volPath: {}},
246 246
 	}
247 247
 
248
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
248
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
249 249
 		t.Fatal(err)
250 250
 	}
251 251
 
... ...
@@ -254,7 +255,7 @@ func TestVolumesFromHasPriority(t *testing.T) {
254 254
 		"VolumesFrom": []string{volName},
255 255
 		"Binds":       []string{bindPath + ":/tmp"},
256 256
 	}
257
-	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
257
+	if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
258 258
 		t.Fatal(err)
259 259
 	}
260 260
 
... ...
@@ -290,7 +291,7 @@ func TestGetContainerStats(t *testing.T) {
290 290
 	}
291 291
 	bc := make(chan b, 1)
292 292
 	go func() {
293
-		body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
293
+		_, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
294 294
 		bc <- b{body, err}
295 295
 	}()
296 296
 
... ...
@@ -334,7 +335,7 @@ func TestGetStoppedContainerStats(t *testing.T) {
334 334
 	go func() {
335 335
 		// We'll never get return for GET stats from sockRequest as of now,
336 336
 		// just send request and see if panic or error would happen on daemon side.
337
-		_, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
337
+		_, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
338 338
 		if err != nil {
339 339
 			t.Fatal(err)
340 340
 		}
... ...
@@ -367,7 +368,7 @@ func TestBuildApiDockerfilePath(t *testing.T) {
367 367
 		t.Fatalf("failed to close tar archive: %v", err)
368 368
 	}
369 369
 
370
-	out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
370
+	_, out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
371 371
 	if err == nil {
372 372
 		t.Fatalf("Build was supposed to fail: %s", out)
373 373
 	}
... ...
@@ -391,7 +392,7 @@ RUN find /tmp/`,
391 391
 	}
392 392
 	defer server.Close()
393 393
 
394
-	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
394
+	_, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
395 395
 	if err != nil {
396 396
 		t.Fatalf("Build failed: %s", err)
397 397
 	}
... ...
@@ -417,7 +418,7 @@ RUN echo from dockerfile`,
417 417
 	}
418 418
 	defer git.Close()
419 419
 
420
-	buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
420
+	_, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
421 421
 	if err != nil {
422 422
 		t.Fatalf("Build failed: %s\n%q", err, buf)
423 423
 	}
... ...
@@ -443,7 +444,7 @@ RUN echo from Dockerfile`,
443 443
 	defer git.Close()
444 444
 
445 445
 	// Make sure it tries to 'dockerfile' query param value
446
-	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
446
+	_, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
447 447
 	if err != nil {
448 448
 		t.Fatalf("Build failed: %s\n%q", err, buf)
449 449
 	}
... ...
@@ -470,7 +471,7 @@ RUN echo from dockerfile`,
470 470
 	defer git.Close()
471 471
 
472 472
 	// Make sure it tries to 'dockerfile' query param value
473
-	buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
473
+	_, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
474 474
 	if err != nil {
475 475
 		t.Fatalf("Build failed: %s", err)
476 476
 	}
... ...
@@ -501,7 +502,7 @@ func TestBuildApiDockerfileSymlink(t *testing.T) {
501 501
 		t.Fatalf("failed to close tar archive: %v", err)
502 502
 	}
503 503
 
504
-	out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
504
+	_, out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
505 505
 	if err == nil {
506 506
 		t.Fatalf("Build was supposed to fail: %s", out)
507 507
 	}
... ...
@@ -537,7 +538,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) {
537 537
 	}
538 538
 
539 539
 	bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
540
-	_, err = sockRequest("POST", "/containers/two/start", bindSpec)
540
+	_, _, err = sockRequest("POST", "/containers/two/start", bindSpec)
541 541
 	if err != nil && !strings.Contains(err.Error(), "204 No Content") {
542 542
 		t.Fatal(err)
543 543
 	}
... ...
@@ -565,7 +566,7 @@ func TestContainerApiPause(t *testing.T) {
565 565
 	}
566 566
 	ContainerID := strings.TrimSpace(out)
567 567
 
568
-	if _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
568
+	if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
569 569
 		t.Fatalf("POST a container pause: sockRequest failed: %v", err)
570 570
 	}
571 571
 
... ...
@@ -579,7 +580,7 @@ func TestContainerApiPause(t *testing.T) {
579 579
 		t.Fatalf("there should be one paused container and not %d", len(pausedContainers))
580 580
 	}
581 581
 
582
-	if _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
582
+	if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
583 583
 		t.Fatalf("POST a container pause: sockRequest failed: %v", err)
584 584
 	}
585 585
 
... ...
@@ -18,7 +18,7 @@ func TestExecApiCreateNoCmd(t *testing.T) {
18 18
 		t.Fatal(out, err)
19 19
 	}
20 20
 
21
-	body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
21
+	_, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
22 22
 	if err == nil || !bytes.Contains(body, []byte("No exec command specified")) {
23 23
 		t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err)
24 24
 	}
... ...
@@ -8,7 +8,7 @@ import (
8 8
 )
9 9
 
10 10
 func TestLegacyImages(t *testing.T) {
11
-	body, err := sockRequest("GET", "/v1.6/images/json", nil)
11
+	_, body, err := sockRequest("GET", "/v1.6/images/json", nil)
12 12
 	if err != nil {
13 13
 		t.Fatalf("Error on GET: %s", err)
14 14
 	}
... ...
@@ -27,7 +27,7 @@ func TestInspectApiContainerResponse(t *testing.T) {
27 27
 		if testVersion != "latest" {
28 28
 			endpoint = "/" + testVersion + endpoint
29 29
 		}
30
-		body, err := sockRequest("GET", endpoint, nil)
30
+		_, body, err := sockRequest("GET", endpoint, nil)
31 31
 		if err != nil {
32 32
 			t.Fatalf("sockRequest failed for %s version: %v", testVersion, err)
33 33
 		}
34 34
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+package main
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"net/http"
6
+	"os/exec"
7
+	"testing"
8
+)
9
+
10
+func TestLogsApiWithStdout(t *testing.T) {
11
+	defer deleteAllContainers()
12
+	name := "logs_test"
13
+
14
+	runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name)
15
+	if out, _, err := runCommandWithOutput(runCmd); err != nil {
16
+		t.Fatal(out, err)
17
+	}
18
+
19
+	statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&timestamps=1", name), nil)
20
+
21
+	if err != nil || statusCode != http.StatusOK {
22
+		t.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode)
23
+	}
24
+
25
+	if !bytes.Contains(body, []byte(name)) {
26
+		t.Fatalf("Expected %s, got %s", name, string(body[:]))
27
+	}
28
+
29
+	logDone("logs API - with stdout ok")
30
+}
31
+
32
+func TestLogsApiNoStdoutNorStderr(t *testing.T) {
33
+	defer deleteAllContainers()
34
+	name := "logs_test"
35
+	runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
36
+	if out, _, err := runCommandWithOutput(runCmd); err != nil {
37
+		t.Fatal(out, err)
38
+	}
39
+
40
+	statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil)
41
+
42
+	if err == nil || statusCode != http.StatusBadRequest {
43
+		t.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode)
44
+	}
45
+
46
+	expected := "Bad parameters: you must choose at least one stream"
47
+	if !bytes.Contains(body, []byte(expected)) {
48
+		t.Fatalf("Expected %s, got %s", expected, string(body[:]))
49
+	}
50
+
51
+	logDone("logs API - returns error when no stdout nor stderr specified")
52
+}
... ...
@@ -16,7 +16,7 @@ func TestResizeApiResponse(t *testing.T) {
16 16
 	cleanedContainerID := strings.TrimSpace(out)
17 17
 
18 18
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
19
-	_, err = sockRequest("POST", endpoint, nil)
19
+	_, _, err = sockRequest("POST", endpoint, nil)
20 20
 	if err != nil {
21 21
 		t.Fatalf("resize Request failed %v", err)
22 22
 	}
... ...
@@ -41,7 +41,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) {
41 41
 	}
42 42
 
43 43
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
44
-	body, err := sockRequest("POST", endpoint, nil)
44
+	_, body, err := sockRequest("POST", endpoint, nil)
45 45
 	if err == nil {
46 46
 		t.Fatalf("resize should fail when container is not started")
47 47
 	}
... ...
@@ -64,7 +64,7 @@ func TestRmRunningContainerCheckError409(t *testing.T) {
64 64
 	createRunningContainer(t, "foo")
65 65
 
66 66
 	endpoint := "/containers/foo"
67
-	_, err := sockRequest("DELETE", endpoint, nil)
67
+	_, _, err := sockRequest("DELETE", endpoint, nil)
68 68
 
69 69
 	if err == nil {
70 70
 		t.Fatalf("Expected error, can't rm a running container")
... ...
@@ -298,19 +298,19 @@ func sockConn(timeout time.Duration) (net.Conn, error) {
298 298
 	}
299 299
 }
300 300
 
301
-func sockRequest(method, endpoint string, data interface{}) ([]byte, error) {
301
+func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) {
302 302
 	jsonData := bytes.NewBuffer(nil)
303 303
 	if err := json.NewEncoder(jsonData).Encode(data); err != nil {
304
-		return nil, err
304
+		return -1, nil, err
305 305
 	}
306 306
 
307 307
 	return sockRequestRaw(method, endpoint, jsonData, "application/json")
308 308
 }
309 309
 
310
-func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, error) {
310
+func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, []byte, error) {
311 311
 	c, err := sockConn(time.Duration(10 * time.Second))
312 312
 	if err != nil {
313
-		return nil, fmt.Errorf("could not dial docker daemon: %v", err)
313
+		return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err)
314 314
 	}
315 315
 
316 316
 	client := httputil.NewClientConn(c, nil)
... ...
@@ -318,7 +318,7 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte,
318 318
 
319 319
 	req, err := http.NewRequest(method, endpoint, data)
320 320
 	if err != nil {
321
-		return nil, fmt.Errorf("could not create new request: %v", err)
321
+		return -1, nil, fmt.Errorf("could not create new request: %v", err)
322 322
 	}
323 323
 
324 324
 	if ct == "" {
... ...
@@ -328,15 +328,17 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte,
328 328
 
329 329
 	resp, err := client.Do(req)
330 330
 	if err != nil {
331
-		return nil, fmt.Errorf("could not perform request: %v", err)
331
+		return -1, nil, fmt.Errorf("could not perform request: %v", err)
332 332
 	}
333 333
 	defer resp.Body.Close()
334 334
 	if resp.StatusCode != http.StatusOK {
335 335
 		body, _ := ioutil.ReadAll(resp.Body)
336
-		return body, fmt.Errorf("received status != 200 OK: %s", resp.Status)
336
+		return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status)
337 337
 	}
338 338
 
339
-	return ioutil.ReadAll(resp.Body)
339
+	b, err := ioutil.ReadAll(resp.Body)
340
+
341
+	return resp.StatusCode, b, err
340 342
 }
341 343
 
342 344
 func deleteContainer(container string) error {
... ...
@@ -1041,7 +1043,7 @@ func daemonTime(t *testing.T) time.Time {
1041 1041
 		return time.Now()
1042 1042
 	}
1043 1043
 
1044
-	body, err := sockRequest("GET", "/info", nil)
1044
+	_, body, err := sockRequest("GET", "/info", nil)
1045 1045
 	if err != nil {
1046 1046
 		t.Fatalf("daemonTime: failed to get /info: %v", err)
1047 1047
 	}
... ...
@@ -57,7 +57,7 @@ var (
57 57
 		func() bool {
58 58
 			if daemonExecDriver == "" {
59 59
 				// get daemon info
60
-				body, err := sockRequest("GET", "/info", nil)
60
+				_, body, err := sockRequest("GET", "/info", nil)
61 61
 				if err != nil {
62 62
 					log.Fatalf("sockRequest failed for /info: %v", err)
63 63
 				}