github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/integration-cli/docker_api_exec_test.go (about)

     1  // +build !test_no_exec
     2  
     3  package main
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/docker/docker/api/types"
    17  	"github.com/docker/docker/api/types/versions"
    18  	"github.com/docker/docker/client"
    19  	"github.com/docker/docker/integration-cli/checker"
    20  	"github.com/docker/docker/internal/test/request"
    21  	"github.com/go-check/check"
    22  	"gotest.tools/assert"
    23  )
    24  
    25  // Regression test for #9414
    26  func (s *DockerSuite) TestExecAPICreateNoCmd(c *check.C) {
    27  	name := "exec_test"
    28  	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
    29  
    30  	res, body, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": nil}))
    31  	assert.NilError(c, err)
    32  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
    33  		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
    34  	} else {
    35  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
    36  	}
    37  	b, err := request.ReadBody(body)
    38  	assert.NilError(c, err)
    39  	assert.Assert(c, strings.Contains(getErrorMessage(c, b), "No exec command specified"), "Expected message when creating exec command with no Cmd specified")
    40  }
    41  
    42  func (s *DockerSuite) TestExecAPICreateNoValidContentType(c *check.C) {
    43  	name := "exec_test"
    44  	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
    45  
    46  	jsonData := bytes.NewBuffer(nil)
    47  	if err := json.NewEncoder(jsonData).Encode(map[string]interface{}{"Cmd": nil}); err != nil {
    48  		c.Fatalf("Can not encode data to json %s", err)
    49  	}
    50  
    51  	res, body, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.RawContent(ioutil.NopCloser(jsonData)), request.ContentType("test/plain"))
    52  	assert.NilError(c, err)
    53  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
    54  		assert.Equal(c, res.StatusCode, http.StatusInternalServerError)
    55  	} else {
    56  		assert.Equal(c, res.StatusCode, http.StatusBadRequest)
    57  	}
    58  	b, err := request.ReadBody(body)
    59  	assert.NilError(c, err)
    60  	assert.Assert(c, strings.Contains(getErrorMessage(c, b), "Content-Type specified"), "Expected message when creating exec command with invalid Content-Type specified")
    61  }
    62  
    63  func (s *DockerSuite) TestExecAPICreateContainerPaused(c *check.C) {
    64  	// Not relevant on Windows as Windows containers cannot be paused
    65  	testRequires(c, DaemonIsLinux)
    66  	name := "exec_create_test"
    67  	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
    68  
    69  	dockerCmd(c, "pause", name)
    70  
    71  	cli, err := client.NewClientWithOpts(client.FromEnv)
    72  	assert.NilError(c, err)
    73  	defer cli.Close()
    74  
    75  	config := types.ExecConfig{
    76  		Cmd: []string{"true"},
    77  	}
    78  	_, err = cli.ContainerExecCreate(context.Background(), name, config)
    79  	assert.ErrorContains(c, err, "Container "+name+" is paused, unpause the container before exec", "Expected message when creating exec command with Container %s is paused", name)
    80  }
    81  
    82  func (s *DockerSuite) TestExecAPIStart(c *check.C) {
    83  	testRequires(c, DaemonIsLinux) // Uses pause/unpause but bits may be salvageable to Windows to Windows CI
    84  	dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
    85  
    86  	id := createExec(c, "test")
    87  	startExec(c, id, http.StatusOK)
    88  
    89  	var execJSON struct{ PID int }
    90  	inspectExec(c, id, &execJSON)
    91  	assert.Assert(c, execJSON.PID > 1)
    92  
    93  	id = createExec(c, "test")
    94  	dockerCmd(c, "stop", "test")
    95  
    96  	startExec(c, id, http.StatusNotFound)
    97  
    98  	dockerCmd(c, "start", "test")
    99  	startExec(c, id, http.StatusNotFound)
   100  
   101  	// make sure exec is created before pausing
   102  	id = createExec(c, "test")
   103  	dockerCmd(c, "pause", "test")
   104  	startExec(c, id, http.StatusConflict)
   105  	dockerCmd(c, "unpause", "test")
   106  	startExec(c, id, http.StatusOK)
   107  }
   108  
   109  func (s *DockerSuite) TestExecAPIStartEnsureHeaders(c *check.C) {
   110  	testRequires(c, DaemonIsLinux)
   111  	dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
   112  
   113  	id := createExec(c, "test")
   114  	resp, _, err := request.Post(fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
   115  	assert.NilError(c, err)
   116  	assert.Assert(c, resp.Header.Get("Server") != "")
   117  }
   118  
   119  func (s *DockerSuite) TestExecAPIStartBackwardsCompatible(c *check.C) {
   120  	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
   121  	runSleepingContainer(c, "-d", "--name", "test")
   122  	id := createExec(c, "test")
   123  
   124  	resp, body, err := request.Post(fmt.Sprintf("/v1.20/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.ContentType("text/plain"))
   125  	assert.NilError(c, err)
   126  
   127  	b, err := request.ReadBody(body)
   128  	comment := check.Commentf("response body: %s", b)
   129  	assert.NilError(c, err, comment)
   130  	assert.Equal(c, resp.StatusCode, http.StatusOK, comment)
   131  }
   132  
   133  // #19362
   134  func (s *DockerSuite) TestExecAPIStartMultipleTimesError(c *check.C) {
   135  	runSleepingContainer(c, "-d", "--name", "test")
   136  	execID := createExec(c, "test")
   137  	startExec(c, execID, http.StatusOK)
   138  	waitForExec(c, execID)
   139  
   140  	startExec(c, execID, http.StatusConflict)
   141  }
   142  
   143  // #20638
   144  func (s *DockerSuite) TestExecAPIStartWithDetach(c *check.C) {
   145  	name := "foo"
   146  	runSleepingContainer(c, "-d", "-t", "--name", name)
   147  
   148  	config := types.ExecConfig{
   149  		Cmd:          []string{"true"},
   150  		AttachStderr: true,
   151  	}
   152  
   153  	cli, err := client.NewClientWithOpts(client.FromEnv)
   154  	assert.NilError(c, err)
   155  	defer cli.Close()
   156  
   157  	createResp, err := cli.ContainerExecCreate(context.Background(), name, config)
   158  	assert.NilError(c, err)
   159  
   160  	_, body, err := request.Post(fmt.Sprintf("/exec/%s/start", createResp.ID), request.RawString(`{"Detach": true}`), request.JSON)
   161  	assert.NilError(c, err)
   162  
   163  	b, err := request.ReadBody(body)
   164  	comment := check.Commentf("response body: %s", b)
   165  	assert.NilError(c, err, comment)
   166  
   167  	resp, _, err := request.Get("/_ping")
   168  	assert.NilError(c, err)
   169  	if resp.StatusCode != http.StatusOK {
   170  		c.Fatal("daemon is down, it should alive")
   171  	}
   172  }
   173  
   174  // #30311
   175  func (s *DockerSuite) TestExecAPIStartValidCommand(c *check.C) {
   176  	name := "exec_test"
   177  	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
   178  
   179  	id := createExecCmd(c, name, "true")
   180  	startExec(c, id, http.StatusOK)
   181  
   182  	waitForExec(c, id)
   183  
   184  	var inspectJSON struct{ ExecIDs []string }
   185  	inspectContainer(c, name, &inspectJSON)
   186  
   187  	assert.Assert(c, inspectJSON.ExecIDs == nil)
   188  }
   189  
   190  // #30311
   191  func (s *DockerSuite) TestExecAPIStartInvalidCommand(c *check.C) {
   192  	name := "exec_test"
   193  	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
   194  
   195  	id := createExecCmd(c, name, "invalid")
   196  	if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
   197  		startExec(c, id, http.StatusNotFound)
   198  	} else {
   199  		startExec(c, id, http.StatusBadRequest)
   200  	}
   201  	waitForExec(c, id)
   202  
   203  	var inspectJSON struct{ ExecIDs []string }
   204  	inspectContainer(c, name, &inspectJSON)
   205  
   206  	assert.Assert(c, inspectJSON.ExecIDs == nil)
   207  }
   208  
   209  func (s *DockerSuite) TestExecStateCleanup(c *check.C) {
   210  	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
   211  
   212  	// This test checks accidental regressions. Not part of stable API.
   213  
   214  	name := "exec_cleanup"
   215  	cid, _ := dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
   216  	cid = strings.TrimSpace(cid)
   217  
   218  	stateDir := "/var/run/docker/containerd/" + cid
   219  
   220  	checkReadDir := func(c *check.C) (interface{}, check.CommentInterface) {
   221  		fi, err := ioutil.ReadDir(stateDir)
   222  		assert.NilError(c, err)
   223  		return len(fi), nil
   224  	}
   225  
   226  	fi, err := ioutil.ReadDir(stateDir)
   227  	assert.NilError(c, err)
   228  	assert.Assert(c, len(fi) > 1)
   229  
   230  	id := createExecCmd(c, name, "ls")
   231  	startExec(c, id, http.StatusOK)
   232  	waitForExec(c, id)
   233  
   234  	waitAndAssert(c, 5*time.Second, checkReadDir, checker.Equals, len(fi))
   235  
   236  	id = createExecCmd(c, name, "invalid")
   237  	startExec(c, id, http.StatusBadRequest)
   238  	waitForExec(c, id)
   239  
   240  	waitAndAssert(c, 5*time.Second, checkReadDir, checker.Equals, len(fi))
   241  
   242  	dockerCmd(c, "stop", name)
   243  	_, err = os.Stat(stateDir)
   244  	assert.ErrorContains(c, err, "")
   245  	assert.Assert(c, os.IsNotExist(err))
   246  }
   247  
   248  func createExec(c *check.C, name string) string {
   249  	return createExecCmd(c, name, "true")
   250  }
   251  
   252  func createExecCmd(c *check.C, name string, cmd string) string {
   253  	_, reader, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": []string{cmd}}))
   254  	assert.NilError(c, err)
   255  	b, err := ioutil.ReadAll(reader)
   256  	assert.NilError(c, err)
   257  	defer reader.Close()
   258  	createResp := struct {
   259  		ID string `json:"Id"`
   260  	}{}
   261  	assert.NilError(c, json.Unmarshal(b, &createResp), string(b))
   262  	return createResp.ID
   263  }
   264  
   265  func startExec(c *check.C, id string, code int) {
   266  	resp, body, err := request.Post(fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
   267  	assert.NilError(c, err)
   268  
   269  	b, err := request.ReadBody(body)
   270  	assert.NilError(c, err, "response body: %s", b)
   271  	assert.Equal(c, resp.StatusCode, code, "response body: %s", b)
   272  }
   273  
   274  func inspectExec(c *check.C, id string, out interface{}) {
   275  	resp, body, err := request.Get(fmt.Sprintf("/exec/%s/json", id))
   276  	assert.NilError(c, err)
   277  	defer body.Close()
   278  	assert.Equal(c, resp.StatusCode, http.StatusOK)
   279  	err = json.NewDecoder(body).Decode(out)
   280  	assert.NilError(c, err)
   281  }
   282  
   283  func waitForExec(c *check.C, id string) {
   284  	timeout := time.After(60 * time.Second)
   285  	var execJSON struct{ Running bool }
   286  	for {
   287  		select {
   288  		case <-timeout:
   289  			c.Fatal("timeout waiting for exec to start")
   290  		default:
   291  		}
   292  
   293  		inspectExec(c, id, &execJSON)
   294  		if !execJSON.Running {
   295  			break
   296  		}
   297  	}
   298  }
   299  
   300  func inspectContainer(c *check.C, id string, out interface{}) {
   301  	resp, body, err := request.Get(fmt.Sprintf("/containers/%s/json", id))
   302  	assert.NilError(c, err)
   303  	defer body.Close()
   304  	assert.Equal(c, resp.StatusCode, http.StatusOK)
   305  	err = json.NewDecoder(body).Decode(out)
   306  	assert.NilError(c, err)
   307  }