github.com/wozhu6104/docker@v20.10.10+incompatible/integration-cli/docker_api_exec_test.go (about)

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