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>
This commit is contained in:
Yong Tang 2017-02-05 21:22:57 -08:00
parent 19795b934c
commit c2d49ec214
10 changed files with 106 additions and 39 deletions

View file

@ -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"

View file

@ -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"`

View file

@ -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"

View file

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

View file

@ -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")
}

View file

@ -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 {

View file

@ -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,

View file

@ -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")

View file

@ -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")

View file

@ -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")
}