github.com/xuyutom/docker@v1.6.0/integration-cli/docker_api_containers_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"os/exec"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
    14  )
    15  
    16  func TestContainerApiGetAll(t *testing.T) {
    17  	defer deleteAllContainers()
    18  
    19  	startCount, err := getContainerCount()
    20  	if err != nil {
    21  		t.Fatalf("Cannot query container count: %v", err)
    22  	}
    23  
    24  	name := "getall"
    25  	runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")
    26  	out, _, err := runCommandWithOutput(runCmd)
    27  	if err != nil {
    28  		t.Fatalf("Error on container creation: %v, output: %q", err, out)
    29  	}
    30  
    31  	body, err := sockRequest("GET", "/containers/json?all=1", nil)
    32  	if err != nil {
    33  		t.Fatalf("GET all containers sockRequest failed: %v", err)
    34  	}
    35  
    36  	var inspectJSON []struct {
    37  		Names []string
    38  	}
    39  	if err = json.Unmarshal(body, &inspectJSON); err != nil {
    40  		t.Fatalf("unable to unmarshal response body: %v", err)
    41  	}
    42  
    43  	if len(inspectJSON) != startCount+1 {
    44  		t.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount)
    45  	}
    46  
    47  	if actual := inspectJSON[0].Names[0]; actual != "/"+name {
    48  		t.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual)
    49  	}
    50  
    51  	logDone("container REST API - check GET json/all=1")
    52  }
    53  
    54  func TestContainerApiGetExport(t *testing.T) {
    55  	defer deleteAllContainers()
    56  
    57  	name := "exportcontainer"
    58  	runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test")
    59  	out, _, err := runCommandWithOutput(runCmd)
    60  	if err != nil {
    61  		t.Fatalf("Error on container creation: %v, output: %q", err, out)
    62  	}
    63  
    64  	body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
    65  	if err != nil {
    66  		t.Fatalf("GET containers/export sockRequest failed: %v", err)
    67  	}
    68  
    69  	found := false
    70  	for tarReader := tar.NewReader(bytes.NewReader(body)); ; {
    71  		h, err := tarReader.Next()
    72  		if err != nil {
    73  			if err == io.EOF {
    74  				break
    75  			}
    76  			t.Fatal(err)
    77  		}
    78  		if h.Name == "test" {
    79  			found = true
    80  			break
    81  		}
    82  	}
    83  
    84  	if !found {
    85  		t.Fatalf("The created test file has not been found in the exported image")
    86  	}
    87  
    88  	logDone("container REST API - check GET containers/export")
    89  }
    90  
    91  func TestContainerApiGetChanges(t *testing.T) {
    92  	defer deleteAllContainers()
    93  
    94  	name := "changescontainer"
    95  	runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "rm", "/etc/passwd")
    96  	out, _, err := runCommandWithOutput(runCmd)
    97  	if err != nil {
    98  		t.Fatalf("Error on container creation: %v, output: %q", err, out)
    99  	}
   100  
   101  	body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
   102  	if err != nil {
   103  		t.Fatalf("GET containers/changes sockRequest failed: %v", err)
   104  	}
   105  
   106  	changes := []struct {
   107  		Kind int
   108  		Path string
   109  	}{}
   110  	if err = json.Unmarshal(body, &changes); err != nil {
   111  		t.Fatalf("unable to unmarshal response body: %v", err)
   112  	}
   113  
   114  	// Check the changelog for removal of /etc/passwd
   115  	success := false
   116  	for _, elem := range changes {
   117  		if elem.Path == "/etc/passwd" && elem.Kind == 2 {
   118  			success = true
   119  		}
   120  	}
   121  	if !success {
   122  		t.Fatalf("/etc/passwd has been removed but is not present in the diff")
   123  	}
   124  
   125  	logDone("container REST API - check GET containers/changes")
   126  }
   127  
   128  func TestContainerApiStartVolumeBinds(t *testing.T) {
   129  	defer deleteAllContainers()
   130  	name := "testing"
   131  	config := map[string]interface{}{
   132  		"Image":   "busybox",
   133  		"Volumes": map[string]struct{}{"/tmp": {}},
   134  	}
   135  
   136  	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
   137  		t.Fatal(err)
   138  	}
   139  
   140  	bindPath := randomUnixTmpDirPath("test")
   141  	config = map[string]interface{}{
   142  		"Binds": []string{bindPath + ":/tmp"},
   143  	}
   144  	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
   145  		t.Fatal(err)
   146  	}
   147  
   148  	pth, err := inspectFieldMap(name, "Volumes", "/tmp")
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  
   153  	if pth != bindPath {
   154  		t.Fatalf("expected volume host path to be %s, got %s", bindPath, pth)
   155  	}
   156  
   157  	logDone("container REST API - check volume binds on start")
   158  }
   159  
   160  // Test for GH#10618
   161  func TestContainerApiStartDupVolumeBinds(t *testing.T) {
   162  	defer deleteAllContainers()
   163  	name := "testdups"
   164  	config := map[string]interface{}{
   165  		"Image":   "busybox",
   166  		"Volumes": map[string]struct{}{"/tmp": {}},
   167  	}
   168  
   169  	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
   170  		t.Fatal(err)
   171  	}
   172  
   173  	bindPath1 := randomUnixTmpDirPath("test1")
   174  	bindPath2 := randomUnixTmpDirPath("test2")
   175  
   176  	config = map[string]interface{}{
   177  		"Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"},
   178  	}
   179  	if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
   180  		t.Fatal("expected container start to fail when duplicate volume binds to same container path")
   181  	} else {
   182  		if !strings.Contains(string(body), "Duplicate volume") {
   183  			t.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err)
   184  		}
   185  	}
   186  
   187  	logDone("container REST API - check for duplicate volume binds error on start")
   188  }
   189  func TestContainerApiStartVolumesFrom(t *testing.T) {
   190  	defer deleteAllContainers()
   191  	volName := "voltst"
   192  	volPath := "/tmp"
   193  
   194  	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
   195  		t.Fatal(out, err)
   196  	}
   197  
   198  	name := "testing"
   199  	config := map[string]interface{}{
   200  		"Image":   "busybox",
   201  		"Volumes": map[string]struct{}{volPath: {}},
   202  	}
   203  
   204  	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
   205  		t.Fatal(err)
   206  	}
   207  
   208  	config = map[string]interface{}{
   209  		"VolumesFrom": []string{volName},
   210  	}
   211  	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
   212  		t.Fatal(err)
   213  	}
   214  
   215  	pth, err := inspectFieldMap(name, "Volumes", volPath)
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	pth2, err := inspectFieldMap(volName, "Volumes", volPath)
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  
   224  	if pth != pth2 {
   225  		t.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
   226  	}
   227  
   228  	logDone("container REST API - check VolumesFrom on start")
   229  }
   230  
   231  // Ensure that volumes-from has priority over binds/anything else
   232  // This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start
   233  func TestVolumesFromHasPriority(t *testing.T) {
   234  	defer deleteAllContainers()
   235  	volName := "voltst"
   236  	volPath := "/tmp"
   237  
   238  	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
   239  		t.Fatal(out, err)
   240  	}
   241  
   242  	name := "testing"
   243  	config := map[string]interface{}{
   244  		"Image":   "busybox",
   245  		"Volumes": map[string]struct{}{volPath: {}},
   246  	}
   247  
   248  	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
   249  		t.Fatal(err)
   250  	}
   251  
   252  	bindPath := randomUnixTmpDirPath("test")
   253  	config = map[string]interface{}{
   254  		"VolumesFrom": []string{volName},
   255  		"Binds":       []string{bindPath + ":/tmp"},
   256  	}
   257  	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
   258  		t.Fatal(err)
   259  	}
   260  
   261  	pth, err := inspectFieldMap(name, "Volumes", volPath)
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	pth2, err := inspectFieldMap(volName, "Volumes", volPath)
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  
   270  	if pth != pth2 {
   271  		t.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
   272  	}
   273  
   274  	logDone("container REST API - check VolumesFrom has priority")
   275  }
   276  
   277  func TestGetContainerStats(t *testing.T) {
   278  	defer deleteAllContainers()
   279  	var (
   280  		name   = "statscontainer"
   281  		runCmd = exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top")
   282  	)
   283  	out, _, err := runCommandWithOutput(runCmd)
   284  	if err != nil {
   285  		t.Fatalf("Error on container creation: %v, output: %q", err, out)
   286  	}
   287  	type b struct {
   288  		body []byte
   289  		err  error
   290  	}
   291  	bc := make(chan b, 1)
   292  	go func() {
   293  		body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
   294  		bc <- b{body, err}
   295  	}()
   296  
   297  	// allow some time to stream the stats from the container
   298  	time.Sleep(4 * time.Second)
   299  	if _, err := runCommand(exec.Command(dockerBinary, "rm", "-f", name)); err != nil {
   300  		t.Fatal(err)
   301  	}
   302  
   303  	// collect the results from the stats stream or timeout and fail
   304  	// if the stream was not disconnected.
   305  	select {
   306  	case <-time.After(2 * time.Second):
   307  		t.Fatal("stream was not closed after container was removed")
   308  	case sr := <-bc:
   309  		if sr.err != nil {
   310  			t.Fatal(sr.err)
   311  		}
   312  
   313  		dec := json.NewDecoder(bytes.NewBuffer(sr.body))
   314  		var s *types.Stats
   315  		// decode only one object from the stream
   316  		if err := dec.Decode(&s); err != nil {
   317  			t.Fatal(err)
   318  		}
   319  	}
   320  	logDone("container REST API - check GET containers/stats")
   321  }
   322  
   323  func TestGetStoppedContainerStats(t *testing.T) {
   324  	defer deleteAllContainers()
   325  	var (
   326  		name   = "statscontainer"
   327  		runCmd = exec.Command(dockerBinary, "create", "--name", name, "busybox", "top")
   328  	)
   329  	out, _, err := runCommandWithOutput(runCmd)
   330  	if err != nil {
   331  		t.Fatalf("Error on container creation: %v, output: %q", err, out)
   332  	}
   333  
   334  	go func() {
   335  		// We'll never get return for GET stats from sockRequest as of now,
   336  		// just send request and see if panic or error would happen on daemon side.
   337  		_, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
   338  		if err != nil {
   339  			t.Fatal(err)
   340  		}
   341  	}()
   342  
   343  	// allow some time to send request and let daemon deal with it
   344  	time.Sleep(1 * time.Second)
   345  
   346  	logDone("container REST API - check GET stopped containers/stats")
   347  }
   348  
   349  func TestBuildApiDockerfilePath(t *testing.T) {
   350  	// Test to make sure we stop people from trying to leave the
   351  	// build context when specifying the path to the dockerfile
   352  	buffer := new(bytes.Buffer)
   353  	tw := tar.NewWriter(buffer)
   354  	defer tw.Close()
   355  
   356  	dockerfile := []byte("FROM busybox")
   357  	if err := tw.WriteHeader(&tar.Header{
   358  		Name: "Dockerfile",
   359  		Size: int64(len(dockerfile)),
   360  	}); err != nil {
   361  		t.Fatalf("failed to write tar file header: %v", err)
   362  	}
   363  	if _, err := tw.Write(dockerfile); err != nil {
   364  		t.Fatalf("failed to write tar file content: %v", err)
   365  	}
   366  	if err := tw.Close(); err != nil {
   367  		t.Fatalf("failed to close tar archive: %v", err)
   368  	}
   369  
   370  	out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
   371  	if err == nil {
   372  		t.Fatalf("Build was supposed to fail: %s", out)
   373  	}
   374  
   375  	if !strings.Contains(string(out), "must be within the build context") {
   376  		t.Fatalf("Didn't complain about leaving build context: %s", out)
   377  	}
   378  
   379  	logDone("container REST API - check build w/bad Dockerfile path")
   380  }
   381  
   382  func TestBuildApiDockerFileRemote(t *testing.T) {
   383  	server, err := fakeStorage(map[string]string{
   384  		"testD": `FROM busybox
   385  COPY * /tmp/
   386  RUN find / -name ba*
   387  RUN find /tmp/`,
   388  	})
   389  	if err != nil {
   390  		t.Fatal(err)
   391  	}
   392  	defer server.Close()
   393  
   394  	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
   395  	if err != nil {
   396  		t.Fatalf("Build failed: %s", err)
   397  	}
   398  
   399  	// Make sure Dockerfile exists.
   400  	// Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL
   401  	out := string(buf)
   402  	if !strings.Contains(out, "/tmp/Dockerfile") ||
   403  		strings.Contains(out, "baz") {
   404  		t.Fatalf("Incorrect output: %s", out)
   405  	}
   406  
   407  	logDone("container REST API - check build with -f from remote")
   408  }
   409  
   410  func TestBuildApiLowerDockerfile(t *testing.T) {
   411  	git, err := fakeGIT("repo", map[string]string{
   412  		"dockerfile": `FROM busybox
   413  RUN echo from dockerfile`,
   414  	}, false)
   415  	if err != nil {
   416  		t.Fatal(err)
   417  	}
   418  	defer git.Close()
   419  
   420  	buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
   421  	if err != nil {
   422  		t.Fatalf("Build failed: %s\n%q", err, buf)
   423  	}
   424  
   425  	out := string(buf)
   426  	if !strings.Contains(out, "from dockerfile") {
   427  		t.Fatalf("Incorrect output: %s", out)
   428  	}
   429  
   430  	logDone("container REST API - check build with lower dockerfile")
   431  }
   432  
   433  func TestBuildApiBuildGitWithF(t *testing.T) {
   434  	git, err := fakeGIT("repo", map[string]string{
   435  		"baz": `FROM busybox
   436  RUN echo from baz`,
   437  		"Dockerfile": `FROM busybox
   438  RUN echo from Dockerfile`,
   439  	}, false)
   440  	if err != nil {
   441  		t.Fatal(err)
   442  	}
   443  	defer git.Close()
   444  
   445  	// Make sure it tries to 'dockerfile' query param value
   446  	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
   447  	if err != nil {
   448  		t.Fatalf("Build failed: %s\n%q", err, buf)
   449  	}
   450  
   451  	out := string(buf)
   452  	if !strings.Contains(out, "from baz") {
   453  		t.Fatalf("Incorrect output: %s", out)
   454  	}
   455  
   456  	logDone("container REST API - check build from git w/F")
   457  }
   458  
   459  func TestBuildApiDoubleDockerfile(t *testing.T) {
   460  	testRequires(t, UnixCli) // dockerfile overwrites Dockerfile on Windows
   461  	git, err := fakeGIT("repo", map[string]string{
   462  		"Dockerfile": `FROM busybox
   463  RUN echo from Dockerfile`,
   464  		"dockerfile": `FROM busybox
   465  RUN echo from dockerfile`,
   466  	}, false)
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  	defer git.Close()
   471  
   472  	// Make sure it tries to 'dockerfile' query param value
   473  	buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
   474  	if err != nil {
   475  		t.Fatalf("Build failed: %s", err)
   476  	}
   477  
   478  	out := string(buf)
   479  	if !strings.Contains(out, "from Dockerfile") {
   480  		t.Fatalf("Incorrect output: %s", out)
   481  	}
   482  
   483  	logDone("container REST API - check build with two dockerfiles")
   484  }
   485  
   486  func TestBuildApiDockerfileSymlink(t *testing.T) {
   487  	// Test to make sure we stop people from trying to leave the
   488  	// build context when specifying a symlink as the path to the dockerfile
   489  	buffer := new(bytes.Buffer)
   490  	tw := tar.NewWriter(buffer)
   491  	defer tw.Close()
   492  
   493  	if err := tw.WriteHeader(&tar.Header{
   494  		Name:     "Dockerfile",
   495  		Typeflag: tar.TypeSymlink,
   496  		Linkname: "/etc/passwd",
   497  	}); err != nil {
   498  		t.Fatalf("failed to write tar file header: %v", err)
   499  	}
   500  	if err := tw.Close(); err != nil {
   501  		t.Fatalf("failed to close tar archive: %v", err)
   502  	}
   503  
   504  	out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
   505  	if err == nil {
   506  		t.Fatalf("Build was supposed to fail: %s", out)
   507  	}
   508  
   509  	// The reason the error is "Cannot locate specified Dockerfile" is because
   510  	// in the builder, the symlink is resolved within the context, therefore
   511  	// Dockerfile -> /etc/passwd becomes etc/passwd from the context which is
   512  	// a nonexistent file.
   513  	if !strings.Contains(string(out), "Cannot locate specified Dockerfile: Dockerfile") {
   514  		t.Fatalf("Didn't complain about leaving build context: %s", out)
   515  	}
   516  
   517  	logDone("container REST API - check build w/bad Dockerfile symlink path")
   518  }
   519  
   520  // #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume
   521  func TestPostContainerBindNormalVolume(t *testing.T) {
   522  	defer deleteAllContainers()
   523  
   524  	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox"))
   525  	if err != nil {
   526  		t.Fatal(err, out)
   527  	}
   528  
   529  	fooDir, err := inspectFieldMap("one", "Volumes", "/foo")
   530  	if err != nil {
   531  		t.Fatal(err)
   532  	}
   533  
   534  	out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox"))
   535  	if err != nil {
   536  		t.Fatal(err, out)
   537  	}
   538  
   539  	bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
   540  	_, err = sockRequest("POST", "/containers/two/start", bindSpec)
   541  	if err != nil && !strings.Contains(err.Error(), "204 No Content") {
   542  		t.Fatal(err)
   543  	}
   544  
   545  	fooDir2, err := inspectFieldMap("two", "Volumes", "/foo")
   546  	if err != nil {
   547  		t.Fatal(err)
   548  	}
   549  
   550  	if fooDir2 != fooDir {
   551  		t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2)
   552  	}
   553  
   554  	logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume")
   555  }