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 |
+} |