Browse Source

Add `--stop-signal` for `service create` and `service update`

This fix tries to address the issue raised in 25696 where
it was not possible to specify `--stop-signal` for `docker service create`
and `docker service update`, in order to use special signal to stop
the container.

This fix adds `--stop-signal` and update the `StopSignal` in `Config`
through `service create` and `service update`.

Related docs has been updated.

Integration test has been added.

This fix fixes 25696.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Yong Tang 8 years ago
parent
commit
c2d49ec214

+ 3 - 0
api/swagger.yaml

@@ -1900,6 +1900,9 @@ definitions:
             type: "array"
             items:
               $ref: "#/definitions/Mount"
+          StopSignal:
+            description: "Signal to stop the container."
+            type: "string"
           StopGracePeriod:
             description: "Amount of time to wait for the container to terminate before forcefully killing it."
             type: "integer"

+ 1 - 0
api/types/swarm/container.go

@@ -32,6 +32,7 @@ type ContainerSpec struct {
 	Dir             string                  `json:",omitempty"`
 	User            string                  `json:",omitempty"`
 	Groups          []string                `json:",omitempty"`
+	StopSignal      string                  `json:",omitempty"`
 	TTY             bool                    `json:",omitempty"`
 	OpenStdin       bool                    `json:",omitempty"`
 	ReadOnly        bool                    `json:",omitempty"`

+ 17 - 11
cli/command/service/opts.go

@@ -269,6 +269,7 @@ type serviceOptions struct {
 	workdir         string
 	user            string
 	groups          opts.ListOpts
+	stopSignal      string
 	tty             bool
 	readOnly        bool
 	mounts          opts.MountOpt
@@ -372,17 +373,18 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
 		},
 		TaskTemplate: swarm.TaskSpec{
 			ContainerSpec: swarm.ContainerSpec{
-				Image:    opts.image,
-				Args:     opts.args,
-				Env:      currentEnv,
-				Hostname: opts.hostname,
-				Labels:   runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
-				Dir:      opts.workdir,
-				User:     opts.user,
-				Groups:   opts.groups.GetAll(),
-				TTY:      opts.tty,
-				ReadOnly: opts.readOnly,
-				Mounts:   opts.mounts.Value(),
+				Image:      opts.image,
+				Args:       opts.args,
+				Env:        currentEnv,
+				Hostname:   opts.hostname,
+				Labels:     runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
+				Dir:        opts.workdir,
+				User:       opts.user,
+				Groups:     opts.groups.GetAll(),
+				StopSignal: opts.stopSignal,
+				TTY:        opts.tty,
+				ReadOnly:   opts.readOnly,
+				Mounts:     opts.mounts.Value(),
 				DNSConfig: &swarm.DNSConfig{
 					Nameservers: opts.dns.GetAll(),
 					Search:      opts.dnsSearch.GetAll(),
@@ -470,6 +472,9 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
 
 	flags.BoolVar(&opts.readOnly, flagReadOnly, false, "Mount the container's root filesystem as read only")
 	flags.SetAnnotation(flagReadOnly, "version", []string{"1.27"})
+
+	flags.StringVar(&opts.stopSignal, flagStopSignal, "", "Signal to stop the container")
+	flags.SetAnnotation(flagStopSignal, "version", []string{"1.27"})
 }
 
 const (
@@ -523,6 +528,7 @@ const (
 	flagRestartMaxAttempts    = "restart-max-attempts"
 	flagRestartWindow         = "restart-window"
 	flagStopGracePeriod       = "stop-grace-period"
+	flagStopSignal            = "stop-signal"
 	flagTTY                   = "tty"
 	flagUpdateDelay           = "update-delay"
 	flagUpdateFailureAction   = "update-failure-action"

+ 2 - 0
cli/command/service/update.go

@@ -349,6 +349,8 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
 		cspec.ReadOnly = readOnly
 	}
 
+	updateString(flagStopSignal, &cspec.StopSignal)
+
 	return nil
 }
 

+ 22 - 0
cli/command/service/update_test.go

@@ -441,3 +441,25 @@ func TestUpdateReadOnly(t *testing.T) {
 	updateService(flags, spec)
 	assert.Equal(t, cspec.ReadOnly, false)
 }
+
+func TestUpdateStopSignal(t *testing.T) {
+	spec := &swarm.ServiceSpec{}
+	cspec := &spec.TaskTemplate.ContainerSpec
+
+	// Update with --stop-signal=SIGUSR1
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("stop-signal", "SIGUSR1")
+	updateService(flags, spec)
+	assert.Equal(t, cspec.StopSignal, "SIGUSR1")
+
+	// Update without --stop-signal, no change
+	flags = newUpdateCommand(nil).Flags()
+	updateService(flags, spec)
+	assert.Equal(t, cspec.StopSignal, "SIGUSR1")
+
+	// Update with --stop-signal=SIGWINCH
+	flags = newUpdateCommand(nil).Flags()
+	flags.Set("stop-signal", "SIGWINCH")
+	updateService(flags, spec)
+	assert.Equal(t, cspec.StopSignal, "SIGWINCH")
+}

+ 30 - 28
daemon/cluster/convert/container.go

@@ -14,20 +14,21 @@ import (
 
 func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
 	containerSpec := types.ContainerSpec{
-		Image:     c.Image,
-		Labels:    c.Labels,
-		Command:   c.Command,
-		Args:      c.Args,
-		Hostname:  c.Hostname,
-		Env:       c.Env,
-		Dir:       c.Dir,
-		User:      c.User,
-		Groups:    c.Groups,
-		TTY:       c.TTY,
-		OpenStdin: c.OpenStdin,
-		ReadOnly:  c.ReadOnly,
-		Hosts:     c.Hosts,
-		Secrets:   secretReferencesFromGRPC(c.Secrets),
+		Image:      c.Image,
+		Labels:     c.Labels,
+		Command:    c.Command,
+		Args:       c.Args,
+		Hostname:   c.Hostname,
+		Env:        c.Env,
+		Dir:        c.Dir,
+		User:       c.User,
+		Groups:     c.Groups,
+		StopSignal: c.StopSignal,
+		TTY:        c.TTY,
+		OpenStdin:  c.OpenStdin,
+		ReadOnly:   c.ReadOnly,
+		Hosts:      c.Hosts,
+		Secrets:    secretReferencesFromGRPC(c.Secrets),
 	}
 
 	if c.DNSConfig != nil {
@@ -136,20 +137,21 @@ func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretRef
 
 func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
 	containerSpec := &swarmapi.ContainerSpec{
-		Image:     c.Image,
-		Labels:    c.Labels,
-		Command:   c.Command,
-		Args:      c.Args,
-		Hostname:  c.Hostname,
-		Env:       c.Env,
-		Dir:       c.Dir,
-		User:      c.User,
-		Groups:    c.Groups,
-		TTY:       c.TTY,
-		OpenStdin: c.OpenStdin,
-		ReadOnly:  c.ReadOnly,
-		Hosts:     c.Hosts,
-		Secrets:   secretReferencesToGRPC(c.Secrets),
+		Image:      c.Image,
+		Labels:     c.Labels,
+		Command:    c.Command,
+		Args:       c.Args,
+		Hostname:   c.Hostname,
+		Env:        c.Env,
+		Dir:        c.Dir,
+		User:       c.User,
+		Groups:     c.Groups,
+		StopSignal: c.StopSignal,
+		TTY:        c.TTY,
+		OpenStdin:  c.OpenStdin,
+		ReadOnly:   c.ReadOnly,
+		Hosts:      c.Hosts,
+		Secrets:    secretReferencesToGRPC(c.Secrets),
 	}
 
 	if c.DNSConfig != nil {

+ 1 - 0
daemon/cluster/executor/container/container.go

@@ -185,6 +185,7 @@ func (c *containerConfig) exposedPorts() map[nat.Port]struct{} {
 func (c *containerConfig) config() *enginecontainer.Config {
 	config := &enginecontainer.Config{
 		Labels:       c.labels(),
+		StopSignal:   c.spec().StopSignal,
 		Tty:          c.spec().TTY,
 		OpenStdin:    c.spec().OpenStdin,
 		User:         c.spec().User,

+ 1 - 0
docs/reference/commandline/service_create.md

@@ -58,6 +58,7 @@ Options:
       --restart-window duration          Window used to evaluate the restart policy (ns|us|ms|s|m|h)
       --secret secret                    Specify secrets to expose to the service
       --stop-grace-period duration       Time to wait before force killing a container (ns|us|ms|s|m|h)
+      --stop-signal string               Signal to stop the container
   -t, --tty                              Allocate a pseudo-TTY
       --update-delay duration            Delay between updates (ns|us|ms|s|m|h) (default 0s)
       --update-failure-action string     Action on update failure ("pause"|"continue") (default "pause")

+ 1 - 0
docs/reference/commandline/service_update.md

@@ -70,6 +70,7 @@ Options:
       --secret-add secret                Add or update a secret on a service
       --secret-rm list                   Remove a secret (default [])
       --stop-grace-period duration       Time to wait before force killing a container (ns|us|ms|s|m|h)
+      --stop-signal string               Signal to stop the container
   -t, --tty                              Allocate a pseudo-TTY
       --update-delay duration            Delay between updates (ns|us|ms|s|m|h) (default 0s)
       --update-failure-action string     Action on update failure ("pause"|"continue") (default "pause")

+ 28 - 0
integration-cli/docker_cli_swarm_test.go

@@ -1764,3 +1764,31 @@ func (s *DockerSwarmSuite) TestNetworkInspectWithDuplicateNames(c *check.C) {
 	c.Assert(err, checker.NotNil, check.Commentf(out))
 	c.Assert(out, checker.Contains, "network foo is ambiguous (2 matches found based on name)")
 }
+
+func (s *DockerSwarmSuite) TestSwarmStopSignal(c *check.C) {
+	testRequires(c, DaemonIsLinux, UserNamespaceROMount)
+
+	d := s.AddDaemon(c, true, true)
+
+	out, err := d.Cmd("service", "create", "--name", "top", "--stop-signal=SIGHUP", "busybox", "top")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+
+	// make sure task has been deployed.
+	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
+
+	out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.StopSignal }}", "top")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(strings.TrimSpace(out), checker.Equals, "SIGHUP")
+
+	containers := d.ActiveContainers()
+	out, err = d.Cmd("inspect", "--type", "container", "--format", "{{.Config.StopSignal}}", containers[0])
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(strings.TrimSpace(out), checker.Equals, "SIGHUP")
+
+	out, err = d.Cmd("service", "update", "--stop-signal=SIGUSR1", "top")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+
+	out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.StopSignal }}", "top")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(strings.TrimSpace(out), checker.Equals, "SIGUSR1")
+}