github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/integration-cli/docker_api_exec_test.go (about)

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