client: ContainerStop(), ContainerRestart(): support stop-signal

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2022-02-16 11:36:37 +01:00
parent 9060126639
commit e8fa708ae5
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
12 changed files with 68 additions and 40 deletions

View File

@ -3,18 +3,22 @@ package client // import "github.com/docker/docker/client"
import (
"context"
"net/url"
"time"
"strconv"
timetypes "github.com/docker/docker/api/types/time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/versions"
)
// ContainerRestart stops and starts a container again.
// It makes the daemon wait for the container to be up again for
// a specific amount of time, given the timeout.
func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout *time.Duration) error {
func (cli *Client) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
query := url.Values{}
if timeout != nil {
query.Set("t", timetypes.DurationToSecondsString(*timeout))
if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout))
}
if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
query.Set("signal", options.Signal)
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
ensureReaderClosed(resp)

View File

@ -8,8 +8,8 @@ import (
"net/http"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/errdefs"
)
@ -17,20 +17,23 @@ func TestContainerRestartError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
timeout := 0 * time.Second
err := client.ContainerRestart(context.Background(), "nothing", &timeout)
err := client.ContainerRestart(context.Background(), "nothing", container.StopOptions{})
if !errdefs.IsSystem(err) {
t.Fatalf("expected a Server Error, got %[1]T: %[1]v", err)
}
}
func TestContainerRestart(t *testing.T) {
expectedURL := "/containers/container_id/restart"
const expectedURL = "/v1.42/containers/container_id/restart"
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
s := req.URL.Query().Get("signal")
if s != "SIGKILL" {
return nil, fmt.Errorf("signal not set in URL query. Expected 'SIGKILL', got '%s'", s)
}
t := req.URL.Query().Get("t")
if t != "100" {
return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t)
@ -40,9 +43,13 @@ func TestContainerRestart(t *testing.T) {
Body: io.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
version: "1.42",
}
timeout := 100 * time.Second
err := client.ContainerRestart(context.Background(), "container_id", &timeout)
timeout := 100
err := client.ContainerRestart(context.Background(), "container_id", container.StopOptions{
Signal: "SIGKILL",
Timeout: &timeout,
})
if err != nil {
t.Fatal(err)
}

View File

@ -3,9 +3,10 @@ package client // import "github.com/docker/docker/client"
import (
"context"
"net/url"
"time"
"strconv"
timetypes "github.com/docker/docker/api/types/time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/versions"
)
// ContainerStop stops a container. In case the container fails to stop
@ -15,10 +16,13 @@ import (
// If the timeout is nil, the container's StopTimeout value is used, if set,
// otherwise the engine default. A negative timeout value can be specified,
// meaning no timeout, i.e. no forceful termination is performed.
func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error {
func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
query := url.Values{}
if timeout != nil {
query.Set("t", timetypes.DurationToSecondsString(*timeout))
if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout))
}
if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
query.Set("signal", options.Signal)
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
ensureReaderClosed(resp)

View File

@ -8,8 +8,8 @@ import (
"net/http"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/errdefs"
)
@ -17,20 +17,23 @@ func TestContainerStopError(t *testing.T) {
client := &Client{
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
timeout := 0 * time.Second
err := client.ContainerStop(context.Background(), "nothing", &timeout)
err := client.ContainerStop(context.Background(), "nothing", container.StopOptions{})
if !errdefs.IsSystem(err) {
t.Fatalf("expected a Server Error, got %[1]T: %[1]v", err)
}
}
func TestContainerStop(t *testing.T) {
expectedURL := "/containers/container_id/stop"
const expectedURL = "/v1.42/containers/container_id/stop"
client := &Client{
client: newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
s := req.URL.Query().Get("signal")
if s != "SIGKILL" {
return nil, fmt.Errorf("signal not set in URL query. Expected 'SIGKILL', got '%s'", s)
}
t := req.URL.Query().Get("t")
if t != "100" {
return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t)
@ -40,9 +43,13 @@ func TestContainerStop(t *testing.T) {
Body: io.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
version: "1.42",
}
timeout := 100 * time.Second
err := client.ContainerStop(context.Background(), "container_id", &timeout)
timeout := 100
err := client.ContainerStop(context.Background(), "container_id", container.StopOptions{
Signal: "SIGKILL",
Timeout: &timeout,
})
if err != nil {
t.Fatal(err)
}

View File

@ -5,7 +5,6 @@ import (
"io"
"net"
"net/http"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@ -65,12 +64,12 @@ type ContainerAPIClient interface {
ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error
ContainerRename(ctx context.Context, container, newContainerName string) error
ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error
ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
ContainerRestart(ctx context.Context, container string, options container.StopOptions) error
ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error)
ContainerStatsOneShot(ctx context.Context, container string) (types.ContainerStats, error)
ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
ContainerStop(ctx context.Context, container string, options container.StopOptions) error
ContainerTop(ctx context.Context, container string, arguments []string) (container.ContainerTopOKBody, error)
ContainerUnpause(ctx context.Context, container string) error
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error)

View File

@ -913,8 +913,8 @@ func (s *DockerSuite) TestContainerAPIRestart(c *testing.T) {
assert.NilError(c, err)
defer cli.Close()
timeout := 1 * time.Second
err = cli.ContainerRestart(context.Background(), name, &timeout)
timeout := 1
err = cli.ContainerRestart(context.Background(), name, container.StopOptions{Timeout: &timeout})
assert.NilError(c, err)
assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil)
@ -930,7 +930,7 @@ func (s *DockerSuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) {
assert.NilError(c, err)
defer cli.Close()
err = cli.ContainerRestart(context.Background(), name, nil)
err = cli.ContainerRestart(context.Background(), name, container.StopOptions{})
assert.NilError(c, err)
assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil)
@ -965,19 +965,23 @@ func (s *DockerSuite) TestContainerAPIStart(c *testing.T) {
func (s *DockerSuite) TestContainerAPIStop(c *testing.T) {
name := "test-api-stop"
runSleepingContainer(c, "-i", "--name", name)
timeout := 30 * time.Second
timeout := 30
cli, err := client.NewClientWithOpts(client.FromEnv)
assert.NilError(c, err)
defer cli.Close()
err = cli.ContainerStop(context.Background(), name, &timeout)
err = cli.ContainerStop(context.Background(), name, container.StopOptions{
Timeout: &timeout,
})
assert.NilError(c, err)
assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil)
// second call to start should give 304
// maybe add ContainerStartWithRaw to test it
err = cli.ContainerStop(context.Background(), name, &timeout)
err = cli.ContainerStop(context.Background(), name, container.StopOptions{
Timeout: &timeout,
})
assert.NilError(c, err)
}
@ -1255,7 +1259,7 @@ func (s *DockerSuite) TestContainerAPIPostContainerStop(c *testing.T) {
assert.NilError(c, err)
defer cli.Close()
err = cli.ContainerStop(context.Background(), containerID, nil)
err = cli.ContainerStop(context.Background(), containerID, container.StopOptions{})
assert.NilError(c, err)
assert.Assert(c, waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second) == nil)
}

View File

@ -8,6 +8,7 @@ import (
containerderrdefs "github.com/containerd/containerd/errdefs"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions"
@ -80,7 +81,7 @@ func TestPauseStopPausedContainer(t *testing.T) {
err := client.ContainerPause(ctx, cID)
assert.NilError(t, err)
err = client.ContainerStop(ctx, cID, nil)
err = client.ContainerStop(ctx, cID, containertypes.StopOptions{})
assert.NilError(t, err)
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))

View File

@ -143,7 +143,7 @@ func TestRenameAnonymousContainer(t *testing.T) {
assert.NilError(t, err)
// Stop/Start the container to get registered
// FIXME(vdemeester) this is a really weird behavior as it fails otherwise
err = client.ContainerStop(ctx, container1Name, nil)
err = client.ContainerStop(ctx, container1Name, containertypes.StopOptions{})
assert.NilError(t, err)
err = client.ContainerStart(ctx, container1Name, types.ContainerStartOptions{})
assert.NilError(t, err)

View File

@ -9,6 +9,7 @@ import (
"time"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
@ -56,8 +57,7 @@ func TestStopContainerWithTimeout(t *testing.T) {
t.Parallel()
id := container.Run(ctx, t, client, testCmd)
timeout := time.Duration(d.timeout) * time.Second
err := client.ContainerStop(ctx, id, &timeout)
err := client.ContainerStop(ctx, id, containertypes.StopOptions{Timeout: &d.timeout})
assert.NilError(t, err)
poll.WaitOn(t, container.IsStopped(ctx, client, id),

View File

@ -5,6 +5,7 @@ import (
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"gotest.tools/v3/assert"
"gotest.tools/v3/poll"
@ -29,7 +30,7 @@ func TestStopContainerWithRestartPolicyAlways(t *testing.T) {
}
for _, name := range names {
err := client.ContainerStop(ctx, name, nil)
err := client.ContainerStop(ctx, name, containertypes.StopOptions{})
assert.NilError(t, err)
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"gotest.tools/v3/assert"
"gotest.tools/v3/poll"
@ -53,8 +54,7 @@ func TestStopContainerWithTimeout(t *testing.T) {
t.Parallel()
id := container.Run(ctx, t, client, testCmd)
timeout := time.Duration(d.timeout) * time.Second
err := client.ContainerStop(ctx, id, &timeout)
err := client.ContainerStop(ctx, id, containertypes.StopOptions{Timeout: &d.timeout})
assert.NilError(t, err)
poll.WaitOn(t, container.IsStopped(ctx, client, id),

View File

@ -5,6 +5,7 @@ import (
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/testutil/request"
"gotest.tools/v3/assert"
@ -86,7 +87,7 @@ func TestWaitBlocked(t *testing.T) {
waitResC, errC := cli.ContainerWait(ctx, containerID, "")
err := cli.ContainerStop(ctx, containerID, nil)
err := cli.ContainerStop(ctx, containerID, containertypes.StopOptions{})
assert.NilError(t, err)
select {