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>
... | ... |
@@ -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" |
... | ... |
@@ -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 |
+} |