Browse code

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 authored on 2017/02/06 14:22:57
Showing 10 changed files
... ...
@@ -1900,6 +1900,9 @@ definitions:
1900 1900
             type: "array"
1901 1901
             items:
1902 1902
               $ref: "#/definitions/Mount"
1903
+          StopSignal:
1904
+            description: "Signal to stop the container."
1905
+            type: "string"
1903 1906
           StopGracePeriod:
1904 1907
             description: "Amount of time to wait for the container to terminate before forcefully killing it."
1905 1908
             type: "integer"
... ...
@@ -32,6 +32,7 @@ type ContainerSpec struct {
32 32
 	Dir             string                  `json:",omitempty"`
33 33
 	User            string                  `json:",omitempty"`
34 34
 	Groups          []string                `json:",omitempty"`
35
+	StopSignal      string                  `json:",omitempty"`
35 36
 	TTY             bool                    `json:",omitempty"`
36 37
 	OpenStdin       bool                    `json:",omitempty"`
37 38
 	ReadOnly        bool                    `json:",omitempty"`
... ...
@@ -269,6 +269,7 @@ type serviceOptions struct {
269 269
 	workdir         string
270 270
 	user            string
271 271
 	groups          opts.ListOpts
272
+	stopSignal      string
272 273
 	tty             bool
273 274
 	readOnly        bool
274 275
 	mounts          opts.MountOpt
... ...
@@ -372,17 +373,18 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
372 372
 		},
373 373
 		TaskTemplate: swarm.TaskSpec{
374 374
 			ContainerSpec: swarm.ContainerSpec{
375
-				Image:    opts.image,
376
-				Args:     opts.args,
377
-				Env:      currentEnv,
378
-				Hostname: opts.hostname,
379
-				Labels:   runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
380
-				Dir:      opts.workdir,
381
-				User:     opts.user,
382
-				Groups:   opts.groups.GetAll(),
383
-				TTY:      opts.tty,
384
-				ReadOnly: opts.readOnly,
385
-				Mounts:   opts.mounts.Value(),
375
+				Image:      opts.image,
376
+				Args:       opts.args,
377
+				Env:        currentEnv,
378
+				Hostname:   opts.hostname,
379
+				Labels:     runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
380
+				Dir:        opts.workdir,
381
+				User:       opts.user,
382
+				Groups:     opts.groups.GetAll(),
383
+				StopSignal: opts.stopSignal,
384
+				TTY:        opts.tty,
385
+				ReadOnly:   opts.readOnly,
386
+				Mounts:     opts.mounts.Value(),
386 387
 				DNSConfig: &swarm.DNSConfig{
387 388
 					Nameservers: opts.dns.GetAll(),
388 389
 					Search:      opts.dnsSearch.GetAll(),
... ...
@@ -470,6 +472,9 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
470 470
 
471 471
 	flags.BoolVar(&opts.readOnly, flagReadOnly, false, "Mount the container's root filesystem as read only")
472 472
 	flags.SetAnnotation(flagReadOnly, "version", []string{"1.27"})
473
+
474
+	flags.StringVar(&opts.stopSignal, flagStopSignal, "", "Signal to stop the container")
475
+	flags.SetAnnotation(flagStopSignal, "version", []string{"1.27"})
473 476
 }
474 477
 
475 478
 const (
... ...
@@ -523,6 +528,7 @@ const (
523 523
 	flagRestartMaxAttempts    = "restart-max-attempts"
524 524
 	flagRestartWindow         = "restart-window"
525 525
 	flagStopGracePeriod       = "stop-grace-period"
526
+	flagStopSignal            = "stop-signal"
526 527
 	flagTTY                   = "tty"
527 528
 	flagUpdateDelay           = "update-delay"
528 529
 	flagUpdateFailureAction   = "update-failure-action"
... ...
@@ -349,6 +349,8 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
349 349
 		cspec.ReadOnly = readOnly
350 350
 	}
351 351
 
352
+	updateString(flagStopSignal, &cspec.StopSignal)
353
+
352 354
 	return nil
353 355
 }
354 356
 
... ...
@@ -441,3 +441,25 @@ func TestUpdateReadOnly(t *testing.T) {
441 441
 	updateService(flags, spec)
442 442
 	assert.Equal(t, cspec.ReadOnly, false)
443 443
 }
444
+
445
+func TestUpdateStopSignal(t *testing.T) {
446
+	spec := &swarm.ServiceSpec{}
447
+	cspec := &spec.TaskTemplate.ContainerSpec
448
+
449
+	// Update with --stop-signal=SIGUSR1
450
+	flags := newUpdateCommand(nil).Flags()
451
+	flags.Set("stop-signal", "SIGUSR1")
452
+	updateService(flags, spec)
453
+	assert.Equal(t, cspec.StopSignal, "SIGUSR1")
454
+
455
+	// Update without --stop-signal, no change
456
+	flags = newUpdateCommand(nil).Flags()
457
+	updateService(flags, spec)
458
+	assert.Equal(t, cspec.StopSignal, "SIGUSR1")
459
+
460
+	// Update with --stop-signal=SIGWINCH
461
+	flags = newUpdateCommand(nil).Flags()
462
+	flags.Set("stop-signal", "SIGWINCH")
463
+	updateService(flags, spec)
464
+	assert.Equal(t, cspec.StopSignal, "SIGWINCH")
465
+}
... ...
@@ -14,20 +14,21 @@ import (
14 14
 
15 15
 func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
16 16
 	containerSpec := types.ContainerSpec{
17
-		Image:     c.Image,
18
-		Labels:    c.Labels,
19
-		Command:   c.Command,
20
-		Args:      c.Args,
21
-		Hostname:  c.Hostname,
22
-		Env:       c.Env,
23
-		Dir:       c.Dir,
24
-		User:      c.User,
25
-		Groups:    c.Groups,
26
-		TTY:       c.TTY,
27
-		OpenStdin: c.OpenStdin,
28
-		ReadOnly:  c.ReadOnly,
29
-		Hosts:     c.Hosts,
30
-		Secrets:   secretReferencesFromGRPC(c.Secrets),
17
+		Image:      c.Image,
18
+		Labels:     c.Labels,
19
+		Command:    c.Command,
20
+		Args:       c.Args,
21
+		Hostname:   c.Hostname,
22
+		Env:        c.Env,
23
+		Dir:        c.Dir,
24
+		User:       c.User,
25
+		Groups:     c.Groups,
26
+		StopSignal: c.StopSignal,
27
+		TTY:        c.TTY,
28
+		OpenStdin:  c.OpenStdin,
29
+		ReadOnly:   c.ReadOnly,
30
+		Hosts:      c.Hosts,
31
+		Secrets:    secretReferencesFromGRPC(c.Secrets),
31 32
 	}
32 33
 
33 34
 	if c.DNSConfig != nil {
... ...
@@ -136,20 +137,21 @@ func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretRef
136 136
 
137 137
 func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
138 138
 	containerSpec := &swarmapi.ContainerSpec{
139
-		Image:     c.Image,
140
-		Labels:    c.Labels,
141
-		Command:   c.Command,
142
-		Args:      c.Args,
143
-		Hostname:  c.Hostname,
144
-		Env:       c.Env,
145
-		Dir:       c.Dir,
146
-		User:      c.User,
147
-		Groups:    c.Groups,
148
-		TTY:       c.TTY,
149
-		OpenStdin: c.OpenStdin,
150
-		ReadOnly:  c.ReadOnly,
151
-		Hosts:     c.Hosts,
152
-		Secrets:   secretReferencesToGRPC(c.Secrets),
139
+		Image:      c.Image,
140
+		Labels:     c.Labels,
141
+		Command:    c.Command,
142
+		Args:       c.Args,
143
+		Hostname:   c.Hostname,
144
+		Env:        c.Env,
145
+		Dir:        c.Dir,
146
+		User:       c.User,
147
+		Groups:     c.Groups,
148
+		StopSignal: c.StopSignal,
149
+		TTY:        c.TTY,
150
+		OpenStdin:  c.OpenStdin,
151
+		ReadOnly:   c.ReadOnly,
152
+		Hosts:      c.Hosts,
153
+		Secrets:    secretReferencesToGRPC(c.Secrets),
153 154
 	}
154 155
 
155 156
 	if c.DNSConfig != nil {
... ...
@@ -185,6 +185,7 @@ func (c *containerConfig) exposedPorts() map[nat.Port]struct{} {
185 185
 func (c *containerConfig) config() *enginecontainer.Config {
186 186
 	config := &enginecontainer.Config{
187 187
 		Labels:       c.labels(),
188
+		StopSignal:   c.spec().StopSignal,
188 189
 		Tty:          c.spec().TTY,
189 190
 		OpenStdin:    c.spec().OpenStdin,
190 191
 		User:         c.spec().User,
... ...
@@ -58,6 +58,7 @@ Options:
58 58
       --restart-window duration          Window used to evaluate the restart policy (ns|us|ms|s|m|h)
59 59
       --secret secret                    Specify secrets to expose to the service
60 60
       --stop-grace-period duration       Time to wait before force killing a container (ns|us|ms|s|m|h)
61
+      --stop-signal string               Signal to stop the container
61 62
   -t, --tty                              Allocate a pseudo-TTY
62 63
       --update-delay duration            Delay between updates (ns|us|ms|s|m|h) (default 0s)
63 64
       --update-failure-action string     Action on update failure ("pause"|"continue") (default "pause")
... ...
@@ -70,6 +70,7 @@ Options:
70 70
       --secret-add secret                Add or update a secret on a service
71 71
       --secret-rm list                   Remove a secret (default [])
72 72
       --stop-grace-period duration       Time to wait before force killing a container (ns|us|ms|s|m|h)
73
+      --stop-signal string               Signal to stop the container
73 74
   -t, --tty                              Allocate a pseudo-TTY
74 75
       --update-delay duration            Delay between updates (ns|us|ms|s|m|h) (default 0s)
75 76
       --update-failure-action string     Action on update failure ("pause"|"continue") (default "pause")
... ...
@@ -1764,3 +1764,31 @@ func (s *DockerSwarmSuite) TestNetworkInspectWithDuplicateNames(c *check.C) {
1764 1764
 	c.Assert(err, checker.NotNil, check.Commentf(out))
1765 1765
 	c.Assert(out, checker.Contains, "network foo is ambiguous (2 matches found based on name)")
1766 1766
 }
1767
+
1768
+func (s *DockerSwarmSuite) TestSwarmStopSignal(c *check.C) {
1769
+	testRequires(c, DaemonIsLinux, UserNamespaceROMount)
1770
+
1771
+	d := s.AddDaemon(c, true, true)
1772
+
1773
+	out, err := d.Cmd("service", "create", "--name", "top", "--stop-signal=SIGHUP", "busybox", "top")
1774
+	c.Assert(err, checker.IsNil, check.Commentf(out))
1775
+
1776
+	// make sure task has been deployed.
1777
+	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
1778
+
1779
+	out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.StopSignal }}", "top")
1780
+	c.Assert(err, checker.IsNil, check.Commentf(out))
1781
+	c.Assert(strings.TrimSpace(out), checker.Equals, "SIGHUP")
1782
+
1783
+	containers := d.ActiveContainers()
1784
+	out, err = d.Cmd("inspect", "--type", "container", "--format", "{{.Config.StopSignal}}", containers[0])
1785
+	c.Assert(err, checker.IsNil, check.Commentf(out))
1786
+	c.Assert(strings.TrimSpace(out), checker.Equals, "SIGHUP")
1787
+
1788
+	out, err = d.Cmd("service", "update", "--stop-signal=SIGUSR1", "top")
1789
+	c.Assert(err, checker.IsNil, check.Commentf(out))
1790
+
1791
+	out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.StopSignal }}", "top")
1792
+	c.Assert(err, checker.IsNil, check.Commentf(out))
1793
+	c.Assert(strings.TrimSpace(out), checker.Equals, "SIGUSR1")
1794
+}