Bläddra i källkod

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

Remove job from logs
Alexander Morozov 10 år sedan
förälder
incheckning
bfb487dc50

+ 12 - 29
api/server/server.go

@@ -557,43 +557,26 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo
 		return fmt.Errorf("Missing parameter")
 		return fmt.Errorf("Missing parameter")
 	}
 	}
 
 
-	var (
-		inspectJob = eng.Job("container_inspect", vars["name"])
-		logsJob    = eng.Job("logs", vars["name"])
-		c, err     = inspectJob.Stdout.AddEnv()
-	)
-	if err != nil {
-		return err
-	}
-	logsJob.Setenv("follow", r.Form.Get("follow"))
-	logsJob.Setenv("tail", r.Form.Get("tail"))
-	logsJob.Setenv("stdout", r.Form.Get("stdout"))
-	logsJob.Setenv("stderr", r.Form.Get("stderr"))
-	logsJob.Setenv("timestamps", r.Form.Get("timestamps"))
 	// Validate args here, because we can't return not StatusOK after job.Run() call
 	// Validate args here, because we can't return not StatusOK after job.Run() call
-	stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr")
+	stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr"))
 	if !(stdout || stderr) {
 	if !(stdout || stderr) {
 		return fmt.Errorf("Bad parameters: you must choose at least one stream")
 		return fmt.Errorf("Bad parameters: you must choose at least one stream")
 	}
 	}
-	if err = inspectJob.Run(); err != nil {
-		return err
-	}
 
 
-	var outStream, errStream io.Writer
-	outStream = utils.NewWriteFlusher(w)
-
-	if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
-		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
-		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
-	} else {
-		errStream = outStream
+	logsConfig := &daemon.ContainerLogsConfig{
+		Follow:     toBool(r.Form.Get("follow")),
+		Timestamps: toBool(r.Form.Get("timestamps")),
+		Tail:       r.Form.Get("tail"),
+		UseStdout:  stdout,
+		UseStderr:  stderr,
+		OutStream:  utils.NewWriteFlusher(w),
 	}
 	}
 
 
-	logsJob.Stdout.Add(outStream)
-	logsJob.Stderr.Set(errStream)
-	if err := logsJob.Run(); err != nil {
-		fmt.Fprintf(outStream, "Error running logs job: %s\n", err)
+	d := getDaemon(eng)
+	if err := d.ContainerLogs(vars["name"], logsConfig); err != nil {
+		fmt.Fprintf(w, "Error running logs job: %s\n", err)
 	}
 	}
+
 	return nil
 	return nil
 }
 }
 
 

+ 0 - 94
api/server/server_unit_test.go

@@ -7,7 +7,6 @@ import (
 	"io"
 	"io"
 	"net/http"
 	"net/http"
 	"net/http/httptest"
 	"net/http/httptest"
-	"strings"
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
@@ -126,99 +125,6 @@ func TestGetContainersByName(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestLogs(t *testing.T) {
-	eng := engine.New()
-	var inspect bool
-	var logs bool
-	eng.Register("container_inspect", func(job *engine.Job) error {
-		inspect = true
-		if len(job.Args) == 0 {
-			t.Fatal("Job arguments is empty")
-		}
-		if job.Args[0] != "test" {
-			t.Fatalf("Container name %s, must be test", job.Args[0])
-		}
-		return nil
-	})
-	expected := "logs"
-	eng.Register("logs", func(job *engine.Job) error {
-		logs = true
-		if len(job.Args) == 0 {
-			t.Fatal("Job arguments is empty")
-		}
-		if job.Args[0] != "test" {
-			t.Fatalf("Container name %s, must be test", job.Args[0])
-		}
-		follow := job.Getenv("follow")
-		if follow != "1" {
-			t.Fatalf("follow: %s, must be 1", follow)
-		}
-		stdout := job.Getenv("stdout")
-		if stdout != "1" {
-			t.Fatalf("stdout %s, must be 1", stdout)
-		}
-		stderr := job.Getenv("stderr")
-		if stderr != "" {
-			t.Fatalf("stderr %s, must be empty", stderr)
-		}
-		timestamps := job.Getenv("timestamps")
-		if timestamps != "1" {
-			t.Fatalf("timestamps %s, must be 1", timestamps)
-		}
-		job.Stdout.Write([]byte(expected))
-		return nil
-	})
-	r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1&timestamps=1", nil, eng, t)
-	if r.Code != http.StatusOK {
-		t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
-	}
-	if !inspect {
-		t.Fatal("container_inspect job was not called")
-	}
-	if !logs {
-		t.Fatal("logs job was not called")
-	}
-	res := r.Body.String()
-	if res != expected {
-		t.Fatalf("Output %s, expected %s", res, expected)
-	}
-}
-
-func TestLogsNoStreams(t *testing.T) {
-	eng := engine.New()
-	var inspect bool
-	var logs bool
-	eng.Register("container_inspect", func(job *engine.Job) error {
-		inspect = true
-		if len(job.Args) == 0 {
-			t.Fatal("Job arguments is empty")
-		}
-		if job.Args[0] != "test" {
-			t.Fatalf("Container name %s, must be test", job.Args[0])
-		}
-		return nil
-	})
-	eng.Register("logs", func(job *engine.Job) error {
-		logs = true
-		return nil
-	})
-	r := serveRequest("GET", "/containers/test/logs", nil, eng, t)
-	if r.Code != http.StatusBadRequest {
-		t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest)
-	}
-	if inspect {
-		t.Fatal("container_inspect job was called, but it shouldn't")
-	}
-	if logs {
-		t.Fatal("logs job was called, but it shouldn't")
-	}
-	res := strings.TrimSpace(r.Body.String())
-	expected := "Bad parameters: you must choose at least one stream"
-	if !strings.Contains(res, expected) {
-		t.Fatalf("Output %s, expected %s in it", res, expected)
-	}
-}
-
 func TestGetImagesByName(t *testing.T) {
 func TestGetImagesByName(t *testing.T) {
 	eng := engine.New()
 	eng := engine.New()
 	name := "image_name"
 	name := "image_name"

+ 0 - 1
daemon/daemon.go

@@ -123,7 +123,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
 		"create":            daemon.ContainerCreate,
 		"create":            daemon.ContainerCreate,
 		"export":            daemon.ContainerExport,
 		"export":            daemon.ContainerExport,
 		"info":              daemon.CmdInfo,
 		"info":              daemon.CmdInfo,
-		"logs":              daemon.ContainerLogs,
 		"restart":           daemon.ContainerRestart,
 		"restart":           daemon.ContainerRestart,
 		"start":             daemon.ContainerStart,
 		"start":             daemon.ContainerStart,
 		"execCreate":        daemon.ContainerExecCreate,
 		"execCreate":        daemon.ContainerExecCreate,

+ 42 - 32
daemon/logs.go

@@ -10,40 +10,50 @@ import (
 	"sync"
 	"sync"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/docker/engine"
 	"github.com/docker/docker/pkg/jsonlog"
 	"github.com/docker/docker/pkg/jsonlog"
+	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/docker/docker/pkg/tailfile"
 	"github.com/docker/docker/pkg/tailfile"
 	"github.com/docker/docker/pkg/timeutils"
 	"github.com/docker/docker/pkg/timeutils"
 )
 )
 
 
-func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
-	if len(job.Args) != 1 {
-		return fmt.Errorf("Usage: %s CONTAINER\n", job.Name)
-	}
+type ContainerLogsConfig struct {
+	Follow, Timestamps   bool
+	Tail                 string
+	UseStdout, UseStderr bool
+	OutStream            io.Writer
+}
 
 
+func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error {
 	var (
 	var (
-		name   = job.Args[0]
-		stdout = job.GetenvBool("stdout")
-		stderr = job.GetenvBool("stderr")
-		tail   = job.Getenv("tail")
-		follow = job.GetenvBool("follow")
-		times  = job.GetenvBool("timestamps")
 		lines  = -1
 		lines  = -1
 		format string
 		format string
 	)
 	)
-	if !(stdout || stderr) {
+	if !(config.UseStdout || config.UseStderr) {
 		return fmt.Errorf("You must choose at least one stream")
 		return fmt.Errorf("You must choose at least one stream")
 	}
 	}
-	if times {
+	if config.Timestamps {
 		format = timeutils.RFC3339NanoFixed
 		format = timeutils.RFC3339NanoFixed
 	}
 	}
-	if tail == "" {
-		tail = "all"
+	if config.Tail == "" {
+		config.Tail = "all"
 	}
 	}
+
 	container, err := daemon.Get(name)
 	container, err := daemon.Get(name)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+
+	var (
+		outStream = config.OutStream
+		errStream io.Writer
+	)
+	if !container.Config.Tty {
+		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
+		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
+	} else {
+		errStream = outStream
+	}
+
 	if container.LogDriverType() != "json-file" {
 	if container.LogDriverType() != "json-file" {
 		return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
 		return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
 	}
 	}
@@ -51,30 +61,30 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
 	if err != nil && os.IsNotExist(err) {
 	if err != nil && os.IsNotExist(err) {
 		// Legacy logs
 		// Legacy logs
 		logrus.Debugf("Old logs format")
 		logrus.Debugf("Old logs format")
-		if stdout {
+		if config.UseStdout {
 			cLog, err := container.ReadLog("stdout")
 			cLog, err := container.ReadLog("stdout")
 			if err != nil {
 			if err != nil {
 				logrus.Errorf("Error reading logs (stdout): %s", err)
 				logrus.Errorf("Error reading logs (stdout): %s", err)
-			} else if _, err := io.Copy(job.Stdout, cLog); err != nil {
+			} else if _, err := io.Copy(outStream, cLog); err != nil {
 				logrus.Errorf("Error streaming logs (stdout): %s", err)
 				logrus.Errorf("Error streaming logs (stdout): %s", err)
 			}
 			}
 		}
 		}
-		if stderr {
+		if config.UseStderr {
 			cLog, err := container.ReadLog("stderr")
 			cLog, err := container.ReadLog("stderr")
 			if err != nil {
 			if err != nil {
 				logrus.Errorf("Error reading logs (stderr): %s", err)
 				logrus.Errorf("Error reading logs (stderr): %s", err)
-			} else if _, err := io.Copy(job.Stderr, cLog); err != nil {
+			} else if _, err := io.Copy(errStream, cLog); err != nil {
 				logrus.Errorf("Error streaming logs (stderr): %s", err)
 				logrus.Errorf("Error streaming logs (stderr): %s", err)
 			}
 			}
 		}
 		}
 	} else if err != nil {
 	} else if err != nil {
 		logrus.Errorf("Error reading logs (json): %s", err)
 		logrus.Errorf("Error reading logs (json): %s", err)
 	} else {
 	} else {
-		if tail != "all" {
+		if config.Tail != "all" {
 			var err error
 			var err error
-			lines, err = strconv.Atoi(tail)
+			lines, err = strconv.Atoi(config.Tail)
 			if err != nil {
 			if err != nil {
-				logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err)
+				logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err)
 				lines = -1
 				lines = -1
 			}
 			}
 		}
 		}
@@ -101,39 +111,39 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
 					break
 					break
 				}
 				}
 				logLine := l.Log
 				logLine := l.Log
-				if times {
+				if config.Timestamps {
 					// format can be "" or time format, so here can't be error
 					// format can be "" or time format, so here can't be error
 					logLine, _ = l.Format(format)
 					logLine, _ = l.Format(format)
 				}
 				}
-				if l.Stream == "stdout" && stdout {
-					io.WriteString(job.Stdout, logLine)
+				if l.Stream == "stdout" && config.UseStdout {
+					io.WriteString(outStream, logLine)
 				}
 				}
-				if l.Stream == "stderr" && stderr {
-					io.WriteString(job.Stderr, logLine)
+				if l.Stream == "stderr" && config.UseStderr {
+					io.WriteString(errStream, logLine)
 				}
 				}
 				l.Reset()
 				l.Reset()
 			}
 			}
 		}
 		}
 	}
 	}
-	if follow && container.IsRunning() {
+	if config.Follow && container.IsRunning() {
 		errors := make(chan error, 2)
 		errors := make(chan error, 2)
 		wg := sync.WaitGroup{}
 		wg := sync.WaitGroup{}
 
 
-		if stdout {
+		if config.UseStdout {
 			wg.Add(1)
 			wg.Add(1)
 			stdoutPipe := container.StdoutLogPipe()
 			stdoutPipe := container.StdoutLogPipe()
 			defer stdoutPipe.Close()
 			defer stdoutPipe.Close()
 			go func() {
 			go func() {
-				errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format)
+				errors <- jsonlog.WriteLog(stdoutPipe, outStream, format)
 				wg.Done()
 				wg.Done()
 			}()
 			}()
 		}
 		}
-		if stderr {
+		if config.UseStderr {
 			wg.Add(1)
 			wg.Add(1)
 			stderrPipe := container.StderrLogPipe()
 			stderrPipe := container.StderrLogPipe()
 			defer stderrPipe.Close()
 			defer stderrPipe.Close()
 			go func() {
 			go func() {
-				errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format)
+				errors <- jsonlog.WriteLog(stderrPipe, errStream, format)
 				wg.Done()
 				wg.Done()
 			}()
 			}()
 		}
 		}

+ 26 - 25
integration-cli/docker_api_containers_test.go

@@ -3,14 +3,15 @@ package main
 import (
 import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
-	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/pkg/stringid"
-	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
 	"io"
 	"io"
 	"os/exec"
 	"os/exec"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 	"time"
 	"time"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
 )
 )
 
 
 func TestContainerApiGetAll(t *testing.T) {
 func TestContainerApiGetAll(t *testing.T) {
@@ -28,7 +29,7 @@ func TestContainerApiGetAll(t *testing.T) {
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
 	}
 	}
 
 
-	body, err := sockRequest("GET", "/containers/json?all=1", nil)
+	_, body, err := sockRequest("GET", "/containers/json?all=1", nil)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("GET all containers sockRequest failed: %v", err)
 		t.Fatalf("GET all containers sockRequest failed: %v", err)
 	}
 	}
@@ -61,7 +62,7 @@ func TestContainerApiGetExport(t *testing.T) {
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
 	}
 	}
 
 
-	body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
+	_, body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("GET containers/export sockRequest failed: %v", err)
 		t.Fatalf("GET containers/export sockRequest failed: %v", err)
 	}
 	}
@@ -98,7 +99,7 @@ func TestContainerApiGetChanges(t *testing.T) {
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
 		t.Fatalf("Error on container creation: %v, output: %q", err, out)
 	}
 	}
 
 
-	body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
+	_, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("GET containers/changes sockRequest failed: %v", err)
 		t.Fatalf("GET containers/changes sockRequest failed: %v", err)
 	}
 	}
@@ -133,7 +134,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) {
 		"Volumes": map[string]struct{}{"/tmp": {}},
 		"Volumes": map[string]struct{}{"/tmp": {}},
 	}
 	}
 
 
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
@@ -141,7 +142,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) {
 	config = map[string]interface{}{
 	config = map[string]interface{}{
 		"Binds": []string{bindPath + ":/tmp"},
 		"Binds": []string{bindPath + ":/tmp"},
 	}
 	}
-	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
+	if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
@@ -166,7 +167,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) {
 		"Volumes": map[string]struct{}{"/tmp": {}},
 		"Volumes": map[string]struct{}{"/tmp": {}},
 	}
 	}
 
 
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
@@ -176,7 +177,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) {
 	config = map[string]interface{}{
 	config = map[string]interface{}{
 		"Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"},
 		"Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"},
 	}
 	}
-	if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
+	if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
 		t.Fatal("expected container start to fail when duplicate volume binds to same container path")
 		t.Fatal("expected container start to fail when duplicate volume binds to same container path")
 	} else {
 	} else {
 		if !strings.Contains(string(body), "Duplicate volume") {
 		if !strings.Contains(string(body), "Duplicate volume") {
@@ -201,14 +202,14 @@ func TestContainerApiStartVolumesFrom(t *testing.T) {
 		"Volumes": map[string]struct{}{volPath: {}},
 		"Volumes": map[string]struct{}{volPath: {}},
 	}
 	}
 
 
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
 	config = map[string]interface{}{
 	config = map[string]interface{}{
 		"VolumesFrom": []string{volName},
 		"VolumesFrom": []string{volName},
 	}
 	}
-	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
+	if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
@@ -245,7 +246,7 @@ func TestVolumesFromHasPriority(t *testing.T) {
 		"Volumes": map[string]struct{}{volPath: {}},
 		"Volumes": map[string]struct{}{volPath: {}},
 	}
 	}
 
 
-	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
+	if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
@@ -254,7 +255,7 @@ func TestVolumesFromHasPriority(t *testing.T) {
 		"VolumesFrom": []string{volName},
 		"VolumesFrom": []string{volName},
 		"Binds":       []string{bindPath + ":/tmp"},
 		"Binds":       []string{bindPath + ":/tmp"},
 	}
 	}
-	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
+	if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
@@ -290,7 +291,7 @@ func TestGetContainerStats(t *testing.T) {
 	}
 	}
 	bc := make(chan b, 1)
 	bc := make(chan b, 1)
 	go func() {
 	go func() {
-		body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
+		_, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
 		bc <- b{body, err}
 		bc <- b{body, err}
 	}()
 	}()
 
 
@@ -334,7 +335,7 @@ func TestGetStoppedContainerStats(t *testing.T) {
 	go func() {
 	go func() {
 		// We'll never get return for GET stats from sockRequest as of now,
 		// We'll never get return for GET stats from sockRequest as of now,
 		// just send request and see if panic or error would happen on daemon side.
 		// just send request and see if panic or error would happen on daemon side.
-		_, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
+		_, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -367,7 +368,7 @@ func TestBuildApiDockerfilePath(t *testing.T) {
 		t.Fatalf("failed to close tar archive: %v", err)
 		t.Fatalf("failed to close tar archive: %v", err)
 	}
 	}
 
 
-	out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
+	_, out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
 	if err == nil {
 	if err == nil {
 		t.Fatalf("Build was supposed to fail: %s", out)
 		t.Fatalf("Build was supposed to fail: %s", out)
 	}
 	}
@@ -391,7 +392,7 @@ RUN find /tmp/`,
 	}
 	}
 	defer server.Close()
 	defer server.Close()
 
 
-	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
+	_, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
 	if err != nil {
 	if err != nil {
 		t.Fatalf("Build failed: %s", err)
 		t.Fatalf("Build failed: %s", err)
 	}
 	}
@@ -417,7 +418,7 @@ RUN echo from dockerfile`,
 	}
 	}
 	defer git.Close()
 	defer git.Close()
 
 
-	buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+	_, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
 	if err != nil {
 	if err != nil {
 		t.Fatalf("Build failed: %s\n%q", err, buf)
 		t.Fatalf("Build failed: %s\n%q", err, buf)
 	}
 	}
@@ -443,7 +444,7 @@ RUN echo from Dockerfile`,
 	defer git.Close()
 	defer git.Close()
 
 
 	// Make sure it tries to 'dockerfile' query param value
 	// Make sure it tries to 'dockerfile' query param value
-	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
+	_, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
 	if err != nil {
 	if err != nil {
 		t.Fatalf("Build failed: %s\n%q", err, buf)
 		t.Fatalf("Build failed: %s\n%q", err, buf)
 	}
 	}
@@ -470,7 +471,7 @@ RUN echo from dockerfile`,
 	defer git.Close()
 	defer git.Close()
 
 
 	// Make sure it tries to 'dockerfile' query param value
 	// Make sure it tries to 'dockerfile' query param value
-	buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+	_, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
 	if err != nil {
 	if err != nil {
 		t.Fatalf("Build failed: %s", err)
 		t.Fatalf("Build failed: %s", err)
 	}
 	}
@@ -501,7 +502,7 @@ func TestBuildApiDockerfileSymlink(t *testing.T) {
 		t.Fatalf("failed to close tar archive: %v", err)
 		t.Fatalf("failed to close tar archive: %v", err)
 	}
 	}
 
 
-	out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
+	_, out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
 	if err == nil {
 	if err == nil {
 		t.Fatalf("Build was supposed to fail: %s", out)
 		t.Fatalf("Build was supposed to fail: %s", out)
 	}
 	}
@@ -537,7 +538,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) {
 	}
 	}
 
 
 	bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
 	bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
-	_, err = sockRequest("POST", "/containers/two/start", bindSpec)
+	_, _, err = sockRequest("POST", "/containers/two/start", bindSpec)
 	if err != nil && !strings.Contains(err.Error(), "204 No Content") {
 	if err != nil && !strings.Contains(err.Error(), "204 No Content") {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -565,7 +566,7 @@ func TestContainerApiPause(t *testing.T) {
 	}
 	}
 	ContainerID := strings.TrimSpace(out)
 	ContainerID := strings.TrimSpace(out)
 
 
-	if _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
+	if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
 		t.Fatalf("POST a container pause: sockRequest failed: %v", err)
 		t.Fatalf("POST a container pause: sockRequest failed: %v", err)
 	}
 	}
 
 
@@ -579,7 +580,7 @@ func TestContainerApiPause(t *testing.T) {
 		t.Fatalf("there should be one paused container and not %d", len(pausedContainers))
 		t.Fatalf("there should be one paused container and not %d", len(pausedContainers))
 	}
 	}
 
 
-	if _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
+	if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
 		t.Fatalf("POST a container pause: sockRequest failed: %v", err)
 		t.Fatalf("POST a container pause: sockRequest failed: %v", err)
 	}
 	}
 
 

+ 1 - 1
integration-cli/docker_api_exec_test.go

@@ -18,7 +18,7 @@ func TestExecApiCreateNoCmd(t *testing.T) {
 		t.Fatal(out, err)
 		t.Fatal(out, err)
 	}
 	}
 
 
-	body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
+	_, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
 	if err == nil || !bytes.Contains(body, []byte("No exec command specified")) {
 	if err == nil || !bytes.Contains(body, []byte("No exec command specified")) {
 		t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err)
 		t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err)
 	}
 	}

+ 1 - 1
integration-cli/docker_api_images_test.go

@@ -8,7 +8,7 @@ import (
 )
 )
 
 
 func TestLegacyImages(t *testing.T) {
 func TestLegacyImages(t *testing.T) {
-	body, err := sockRequest("GET", "/v1.6/images/json", nil)
+	_, body, err := sockRequest("GET", "/v1.6/images/json", nil)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("Error on GET: %s", err)
 		t.Fatalf("Error on GET: %s", err)
 	}
 	}

+ 1 - 1
integration-cli/docker_api_inspect_test.go

@@ -27,7 +27,7 @@ func TestInspectApiContainerResponse(t *testing.T) {
 		if testVersion != "latest" {
 		if testVersion != "latest" {
 			endpoint = "/" + testVersion + endpoint
 			endpoint = "/" + testVersion + endpoint
 		}
 		}
-		body, err := sockRequest("GET", endpoint, nil)
+		_, body, err := sockRequest("GET", endpoint, nil)
 		if err != nil {
 		if err != nil {
 			t.Fatalf("sockRequest failed for %s version: %v", testVersion, err)
 			t.Fatalf("sockRequest failed for %s version: %v", testVersion, err)
 		}
 		}

+ 53 - 0
integration-cli/docker_api_logs_test.go

@@ -0,0 +1,53 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"os/exec"
+	"testing"
+)
+
+func TestLogsApiWithStdout(t *testing.T) {
+	defer deleteAllContainers()
+	name := "logs_test"
+
+	runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name)
+	if out, _, err := runCommandWithOutput(runCmd); err != nil {
+		t.Fatal(out, err)
+	}
+
+	statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&timestamps=1", name), nil)
+
+	if err != nil || statusCode != http.StatusOK {
+		t.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode)
+	}
+
+	if !bytes.Contains(body, []byte(name)) {
+		t.Fatalf("Expected %s, got %s", name, string(body[:]))
+	}
+
+	logDone("logs API - with stdout ok")
+}
+
+func TestLogsApiNoStdoutNorStderr(t *testing.T) {
+	defer deleteAllContainers()
+	name := "logs_test"
+	runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
+	if out, _, err := runCommandWithOutput(runCmd); err != nil {
+		t.Fatal(out, err)
+	}
+
+	statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil)
+
+	if err == nil || statusCode != http.StatusBadRequest {
+		t.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode)
+	}
+
+	expected := "Bad parameters: you must choose at least one stream"
+	if !bytes.Contains(body, []byte(expected)) {
+		t.Fatalf("Expected %s, got %s", expected, string(body[:]))
+	}
+
+	logDone("logs API - returns error when no stdout nor stderr specified")
+}

+ 2 - 2
integration-cli/docker_api_resize_test.go

@@ -16,7 +16,7 @@ func TestResizeApiResponse(t *testing.T) {
 	cleanedContainerID := strings.TrimSpace(out)
 	cleanedContainerID := strings.TrimSpace(out)
 
 
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
-	_, err = sockRequest("POST", endpoint, nil)
+	_, _, err = sockRequest("POST", endpoint, nil)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("resize Request failed %v", err)
 		t.Fatalf("resize Request failed %v", err)
 	}
 	}
@@ -41,7 +41,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) {
 	}
 	}
 
 
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
-	body, err := sockRequest("POST", endpoint, nil)
+	_, body, err := sockRequest("POST", endpoint, nil)
 	if err == nil {
 	if err == nil {
 		t.Fatalf("resize should fail when container is not started")
 		t.Fatalf("resize should fail when container is not started")
 	}
 	}

+ 1 - 1
integration-cli/docker_cli_rm_test.go

@@ -64,7 +64,7 @@ func TestRmRunningContainerCheckError409(t *testing.T) {
 	createRunningContainer(t, "foo")
 	createRunningContainer(t, "foo")
 
 
 	endpoint := "/containers/foo"
 	endpoint := "/containers/foo"
-	_, err := sockRequest("DELETE", endpoint, nil)
+	_, _, err := sockRequest("DELETE", endpoint, nil)
 
 
 	if err == nil {
 	if err == nil {
 		t.Fatalf("Expected error, can't rm a running container")
 		t.Fatalf("Expected error, can't rm a running container")

+ 11 - 9
integration-cli/docker_utils.go

@@ -298,19 +298,19 @@ func sockConn(timeout time.Duration) (net.Conn, error) {
 	}
 	}
 }
 }
 
 
-func sockRequest(method, endpoint string, data interface{}) ([]byte, error) {
+func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) {
 	jsonData := bytes.NewBuffer(nil)
 	jsonData := bytes.NewBuffer(nil)
 	if err := json.NewEncoder(jsonData).Encode(data); err != nil {
 	if err := json.NewEncoder(jsonData).Encode(data); err != nil {
-		return nil, err
+		return -1, nil, err
 	}
 	}
 
 
 	return sockRequestRaw(method, endpoint, jsonData, "application/json")
 	return sockRequestRaw(method, endpoint, jsonData, "application/json")
 }
 }
 
 
-func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, error) {
+func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, []byte, error) {
 	c, err := sockConn(time.Duration(10 * time.Second))
 	c, err := sockConn(time.Duration(10 * time.Second))
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("could not dial docker daemon: %v", err)
+		return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err)
 	}
 	}
 
 
 	client := httputil.NewClientConn(c, nil)
 	client := httputil.NewClientConn(c, nil)
@@ -318,7 +318,7 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte,
 
 
 	req, err := http.NewRequest(method, endpoint, data)
 	req, err := http.NewRequest(method, endpoint, data)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("could not create new request: %v", err)
+		return -1, nil, fmt.Errorf("could not create new request: %v", err)
 	}
 	}
 
 
 	if ct == "" {
 	if ct == "" {
@@ -328,15 +328,17 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte,
 
 
 	resp, err := client.Do(req)
 	resp, err := client.Do(req)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("could not perform request: %v", err)
+		return -1, nil, fmt.Errorf("could not perform request: %v", err)
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 	if resp.StatusCode != http.StatusOK {
 	if resp.StatusCode != http.StatusOK {
 		body, _ := ioutil.ReadAll(resp.Body)
 		body, _ := ioutil.ReadAll(resp.Body)
-		return body, fmt.Errorf("received status != 200 OK: %s", resp.Status)
+		return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status)
 	}
 	}
 
 
-	return ioutil.ReadAll(resp.Body)
+	b, err := ioutil.ReadAll(resp.Body)
+
+	return resp.StatusCode, b, err
 }
 }
 
 
 func deleteContainer(container string) error {
 func deleteContainer(container string) error {
@@ -1041,7 +1043,7 @@ func daemonTime(t *testing.T) time.Time {
 		return time.Now()
 		return time.Now()
 	}
 	}
 
 
-	body, err := sockRequest("GET", "/info", nil)
+	_, body, err := sockRequest("GET", "/info", nil)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("daemonTime: failed to get /info: %v", err)
 		t.Fatalf("daemonTime: failed to get /info: %v", err)
 	}
 	}

+ 1 - 1
integration-cli/requirements.go

@@ -57,7 +57,7 @@ var (
 		func() bool {
 		func() bool {
 			if daemonExecDriver == "" {
 			if daemonExecDriver == "" {
 				// get daemon info
 				// get daemon info
-				body, err := sockRequest("GET", "/info", nil)
+				_, body, err := sockRequest("GET", "/info", nil)
 				if err != nil {
 				if err != nil {
 					log.Fatalf("sockRequest failed for /info: %v", err)
 					log.Fatalf("sockRequest failed for /info: %v", err)
 				}
 				}