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