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 ( import (
"context" "context"
"net/url" "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. // ContainerRestart stops and starts a container again.
// It makes the daemon wait for the container to be up again for // It makes the daemon wait for the container to be up again for
// a specific amount of time, given the timeout. // 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{} query := url.Values{}
if timeout != nil { if options.Timeout != nil {
query.Set("t", timetypes.DurationToSecondsString(*timeout)) 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) resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)

View File

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

View File

@ -3,9 +3,10 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"net/url" "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 // 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, // 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, // otherwise the engine default. A negative timeout value can be specified,
// meaning no timeout, i.e. no forceful termination is performed. // 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{} query := url.Values{}
if timeout != nil { if options.Timeout != nil {
query.Set("t", timetypes.DurationToSecondsString(*timeout)) 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) resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)

View File

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

View File

@ -5,7 +5,6 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
@ -65,12 +64,12 @@ type ContainerAPIClient interface {
ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error
ContainerRename(ctx context.Context, container, newContainerName string) error ContainerRename(ctx context.Context, container, newContainerName string) error
ContainerResize(ctx context.Context, container string, options types.ResizeOptions) 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) ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error) ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error)
ContainerStatsOneShot(ctx context.Context, container string) (types.ContainerStats, error) ContainerStatsOneShot(ctx context.Context, container string) (types.ContainerStats, error)
ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) 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) ContainerTop(ctx context.Context, container string, arguments []string) (container.ContainerTopOKBody, error)
ContainerUnpause(ctx context.Context, container string) error ContainerUnpause(ctx context.Context, container string) error
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, 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) assert.NilError(c, err)
defer cli.Close() defer cli.Close()
timeout := 1 * time.Second timeout := 1
err = cli.ContainerRestart(context.Background(), name, &timeout) err = cli.ContainerRestart(context.Background(), name, container.StopOptions{Timeout: &timeout})
assert.NilError(c, err) assert.NilError(c, err)
assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil) 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) assert.NilError(c, err)
defer cli.Close() defer cli.Close()
err = cli.ContainerRestart(context.Background(), name, nil) err = cli.ContainerRestart(context.Background(), name, container.StopOptions{})
assert.NilError(c, err) assert.NilError(c, err)
assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil) 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) { func (s *DockerSuite) TestContainerAPIStop(c *testing.T) {
name := "test-api-stop" name := "test-api-stop"
runSleepingContainer(c, "-i", "--name", name) runSleepingContainer(c, "-i", "--name", name)
timeout := 30 * time.Second timeout := 30
cli, err := client.NewClientWithOpts(client.FromEnv) cli, err := client.NewClientWithOpts(client.FromEnv)
assert.NilError(c, err) assert.NilError(c, err)
defer cli.Close() 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.NilError(c, err)
assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil) assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil)
// second call to start should give 304 // second call to start should give 304
// maybe add ContainerStartWithRaw to test it // 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) assert.NilError(c, err)
} }
@ -1255,7 +1259,7 @@ func (s *DockerSuite) TestContainerAPIPostContainerStop(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer cli.Close() defer cli.Close()
err = cli.ContainerStop(context.Background(), containerID, nil) err = cli.ContainerStop(context.Background(), containerID, container.StopOptions{})
assert.NilError(c, err) assert.NilError(c, err)
assert.Assert(c, waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second) == nil) 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" containerderrdefs "github.com/containerd/containerd/errdefs"
"github.com/docker/docker/api/types" "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/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
@ -80,7 +81,7 @@ func TestPauseStopPausedContainer(t *testing.T) {
err := client.ContainerPause(ctx, cID) err := client.ContainerPause(ctx, cID)
assert.NilError(t, err) assert.NilError(t, err)
err = client.ContainerStop(ctx, cID, nil) err = client.ContainerStop(ctx, cID, containertypes.StopOptions{})
assert.NilError(t, err) assert.NilError(t, err)
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) 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) assert.NilError(t, err)
// Stop/Start the container to get registered // Stop/Start the container to get registered
// FIXME(vdemeester) this is a really weird behavior as it fails otherwise // 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) assert.NilError(t, err)
err = client.ContainerStart(ctx, container1Name, types.ContainerStartOptions{}) err = client.ContainerStart(ctx, container1Name, types.ContainerStartOptions{})
assert.NilError(t, err) assert.NilError(t, err)

View File

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

View File

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

View File

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

View File

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