| package main | |
| import ( | |
| "bytes" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "net/http" | |
| "strings" | |
| "sync" | |
| "testing" | |
| "github.com/docker/docker/integration-cli/cli" | |
| "github.com/docker/docker/testutil" | |
| "github.com/docker/docker/testutil/request" | |
| "github.com/pkg/errors" | |
| "gotest.tools/v3/assert" | |
| ) | |
| func (s *DockerAPISuite) TestExecResizeAPIHeightWidthNoInt(c *testing.T) { | |
| testRequires(c, DaemonIsLinux) | |
| out := cli.DockerCmd(c, "run", "-d", "busybox", "top").Stdout() | |
| cleanedContainerID := strings.TrimSpace(out) | |
| endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" | |
| res, _, err := request.Post(testutil.GetContext(c), endpoint) | |
| assert.NilError(c, err) | |
| assert.Equal(c, res.StatusCode, http.StatusBadRequest) | |
| } | |
| // Part of #14845 | |
| func (s *DockerAPISuite) TestExecResizeImmediatelyAfterExecStart(c *testing.T) { | |
| name := "exec_resize_test" | |
| cli.DockerCmd(c, "run", "-d", "-i", "-t", "--name", name, "--restart", "always", "busybox", "/bin/sh") | |
| testExecResize := func() error { | |
| data := map[string]interface{}{ | |
| "AttachStdin": true, | |
| "Cmd": []string{"/bin/sh"}, | |
| } | |
| uri := fmt.Sprintf("/containers/%s/exec", name) | |
| res, body, err := request.Post(testutil.GetContext(c), uri, request.JSONBody(data)) | |
| if err != nil { | |
| return err | |
| } | |
| if res.StatusCode != http.StatusCreated { | |
| return errors.Errorf("POST %s is expected to return %d, got %d", uri, http.StatusCreated, res.StatusCode) | |
| } | |
| buf, err := request.ReadBody(body) | |
| assert.NilError(c, err) | |
| out := map[string]string{} | |
| err = json.Unmarshal(buf, &out) | |
| if err != nil { | |
| return errors.Wrap(err, "ExecCreate returned invalid json") | |
| } | |
| execID := out["Id"] | |
| if len(execID) < 1 { | |
| return errors.New("ExecCreate got invalid execID") | |
| } | |
| payload := bytes.NewBufferString(`{"Tty":true}`) | |
| wc, _, err := requestHijack(http.MethodPost, fmt.Sprintf("/exec/%s/start", execID), payload, "application/json", request.DaemonHost()) | |
| if err != nil { | |
| return errors.Wrap(err, "failed to start the exec") | |
| } | |
| defer wc.Close() | |
| _, rc, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/exec/%s/resize?h=24&w=80", execID), request.ContentType("text/plain")) | |
| if err != nil { | |
| // It's probably a panic of the daemon if io.ErrUnexpectedEOF is returned. | |
| if err == io.ErrUnexpectedEOF { | |
| return errors.New("the daemon might have crashed") | |
| } | |
| // Other error happened, should be reported. | |
| return errors.Wrap(err, "failed to exec resize immediately after start") | |
| } | |
| rc.Close() | |
| return nil | |
| } | |
| // The panic happens when daemon.ContainerExecStart is called but the | |
| // container.Exec is not called. | |
| // Because the panic is not 100% reproducible, we send the requests concurrently | |
| // to increase the probability that the problem is triggered. | |
| var ( | |
| n = 10 | |
| ch = make(chan error, n) | |
| wg sync.WaitGroup | |
| ) | |
| for i := 0; i < n; i++ { | |
| wg.Add(1) | |
| go func() { | |
| defer wg.Done() | |
| if err := testExecResize(); err != nil { | |
| ch <- err | |
| } | |
| }() | |
| } | |
| wg.Wait() | |
| select { | |
| case err := <-ch: | |
| c.Fatal(err.Error()) | |
| default: | |
| } | |
| } |