Merge pull request #83 from Random-Liu/send-specified-stop-signal

Send stop signal specified in image config.
This commit is contained in:
Lantao Liu 2017-06-20 15:56:48 -07:00 committed by GitHub
commit 41ded18484
13 changed files with 476 additions and 5 deletions

5
Godeps/Godeps.json generated
View File

@ -210,6 +210,11 @@
"Comment": "v1.13.1", "Comment": "v1.13.1",
"Rev": "092cba3727bb9b4a2f0e922cd6c0f93ea270e363" "Rev": "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
}, },
{
"ImportPath": "github.com/docker/docker/pkg/signal",
"Comment": "v1.13.1",
"Rev": "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
},
{ {
"ImportPath": "github.com/docker/docker/pkg/stringid", "ImportPath": "github.com/docker/docker/pkg/stringid",
"Comment": "v1.13.1", "Comment": "v1.13.1",

View File

@ -20,12 +20,11 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/containerd/containerd/api/services/execution"
"github.com/docker/docker/pkg/signal"
"github.com/golang/glog" "github.com/golang/glog"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/containerd/containerd/api/services/execution"
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1" runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1"
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata" "github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
@ -76,10 +75,24 @@ func (c *criContainerdService) stopContainer(ctx context.Context, meta *metadata
} }
if timeout > 0 { if timeout > 0 {
// TODO(random-liu): [P1] Get stop signal from image config.
stopSignal := unix.SIGTERM stopSignal := unix.SIGTERM
imageMeta, err := c.imageMetadataStore.Get(meta.ImageRef)
if err != nil {
// NOTE(random-liu): It's possible that the container is stopped,
// deleted and image is garbage collected before this point. However,
// the chance is really slim, even it happens, it's still fine to return
// an error here.
return fmt.Errorf("failed to get image metadata %q: %v", meta.ImageRef, err)
}
if imageMeta.Config.StopSignal != "" {
stopSignal, err = signal.ParseSignal(imageMeta.Config.StopSignal)
if err != nil {
return fmt.Errorf("failed to parse stop signal %q: %v",
imageMeta.Config.StopSignal, err)
}
}
glog.V(2).Infof("Stop container %q with signal %v", id, stopSignal) glog.V(2).Infof("Stop container %q with signal %v", id, stopSignal)
_, err := c.taskService.Kill(ctx, &execution.KillRequest{ _, err = c.taskService.Kill(ctx, &execution.KillRequest{
ContainerID: id, ContainerID: id,
Signal: uint32(stopSignal), Signal: uint32(stopSignal),
PidOrAll: &execution.KillRequest_All{All: true}, PidOrAll: &execution.KillRequest_All{All: true},

View File

@ -23,6 +23,7 @@ import (
"github.com/containerd/containerd/api/services/execution" "github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/task" "github.com/containerd/containerd/api/types/task"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -96,9 +97,14 @@ func TestStopContainer(t *testing.T) {
testMetadata := metadata.ContainerMetadata{ testMetadata := metadata.ContainerMetadata{
ID: testID, ID: testID,
Pid: testPid, Pid: testPid,
ImageRef: "test-image-id",
CreatedAt: time.Now().UnixNano(), CreatedAt: time.Now().UnixNano(),
StartedAt: time.Now().UnixNano(), StartedAt: time.Now().UnixNano(),
} }
testImageMetadata := metadata.ImageMetadata{
ID: "test-image-id",
Config: &imagespec.ImageConfig{},
}
testContainer := task.Task{ testContainer := task.Task{
ID: testID, ID: testID,
Pid: testPid, Pid: testPid,
@ -107,6 +113,7 @@ func TestStopContainer(t *testing.T) {
for desc, test := range map[string]struct { for desc, test := range map[string]struct {
metadata *metadata.ContainerMetadata metadata *metadata.ContainerMetadata
containerdContainer *task.Task containerdContainer *task.Task
stopSignal string
stopErr error stopErr error
noTimeout bool noTimeout bool
expectErr bool expectErr bool
@ -222,6 +229,27 @@ func TestStopContainer(t *testing.T) {
}, },
}, },
}, },
"should use stop signal specified in image config if not empty": {
metadata: &testMetadata,
containerdContainer: &testContainer,
stopSignal: "SIGHUP",
expectErr: false,
// deleted by the event monitor.
expectCalls: []servertesting.CalledDetail{
{
Name: "kill",
Argument: &execution.KillRequest{
ContainerID: testID,
Signal: uint32(unix.SIGHUP),
PidOrAll: &execution.KillRequest_All{All: true},
},
},
{
Name: "delete",
Argument: &execution.DeleteRequest{ContainerID: testID},
},
},
},
"should directly kill container if timeout is 0": { "should directly kill container if timeout is 0": {
metadata: &testMetadata, metadata: &testMetadata,
containerdContainer: &testContainer, containerdContainer: &testContainer,
@ -259,6 +287,8 @@ func TestStopContainer(t *testing.T) {
if test.containerdContainer != nil { if test.containerdContainer != nil {
fake.SetFakeTasks([]task.Task{*test.containerdContainer}) fake.SetFakeTasks([]task.Task{*test.containerdContainer})
} }
testImageMetadata.Config.StopSignal = test.stopSignal
assert.NoError(t, c.imageMetadataStore.Create(testImageMetadata))
if test.stopErr != nil { if test.stopErr != nil {
fake.InjectError("kill", test.stopErr) fake.InjectError("kill", test.stopErr)
} }

1
vendor/github.com/docker/docker/pkg/signal/README.md generated vendored Normal file
View File

@ -0,0 +1 @@
This package provides helper functions for dealing with signals across various operating systems

54
vendor/github.com/docker/docker/pkg/signal/signal.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
// Package signal provides helper functions for dealing with signals across
// various operating systems.
package signal
import (
"fmt"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
)
// CatchAll catches all signals and relays them to the specified channel.
func CatchAll(sigc chan os.Signal) {
handledSigs := []os.Signal{}
for _, s := range SignalMap {
handledSigs = append(handledSigs, s)
}
signal.Notify(sigc, handledSigs...)
}
// StopCatch stops catching the signals and closes the specified channel.
func StopCatch(sigc chan os.Signal) {
signal.Stop(sigc)
close(sigc)
}
// ParseSignal translates a string to a valid syscall signal.
// It returns an error if the signal map doesn't include the given signal.
func ParseSignal(rawSignal string) (syscall.Signal, error) {
s, err := strconv.Atoi(rawSignal)
if err == nil {
if s == 0 {
return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
}
return syscall.Signal(s), nil
}
signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
if !ok {
return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
}
return signal, nil
}
// ValidSignalForPlatform returns true if a signal is valid on the platform
func ValidSignalForPlatform(sig syscall.Signal) bool {
for _, v := range SignalMap {
if v == sig {
return true
}
}
return false
}

View File

@ -0,0 +1,41 @@
package signal
import (
"syscall"
)
// SignalMap is a map of Darwin signals.
var SignalMap = map[string]syscall.Signal{
"ABRT": syscall.SIGABRT,
"ALRM": syscall.SIGALRM,
"BUG": syscall.SIGBUS,
"CHLD": syscall.SIGCHLD,
"CONT": syscall.SIGCONT,
"EMT": syscall.SIGEMT,
"FPE": syscall.SIGFPE,
"HUP": syscall.SIGHUP,
"ILL": syscall.SIGILL,
"INFO": syscall.SIGINFO,
"INT": syscall.SIGINT,
"IO": syscall.SIGIO,
"IOT": syscall.SIGIOT,
"KILL": syscall.SIGKILL,
"PIPE": syscall.SIGPIPE,
"PROF": syscall.SIGPROF,
"QUIT": syscall.SIGQUIT,
"SEGV": syscall.SIGSEGV,
"STOP": syscall.SIGSTOP,
"SYS": syscall.SIGSYS,
"TERM": syscall.SIGTERM,
"TRAP": syscall.SIGTRAP,
"TSTP": syscall.SIGTSTP,
"TTIN": syscall.SIGTTIN,
"TTOU": syscall.SIGTTOU,
"URG": syscall.SIGURG,
"USR1": syscall.SIGUSR1,
"USR2": syscall.SIGUSR2,
"VTALRM": syscall.SIGVTALRM,
"WINCH": syscall.SIGWINCH,
"XCPU": syscall.SIGXCPU,
"XFSZ": syscall.SIGXFSZ,
}

View File

@ -0,0 +1,43 @@
package signal
import (
"syscall"
)
// SignalMap is a map of FreeBSD signals.
var SignalMap = map[string]syscall.Signal{
"ABRT": syscall.SIGABRT,
"ALRM": syscall.SIGALRM,
"BUF": syscall.SIGBUS,
"CHLD": syscall.SIGCHLD,
"CONT": syscall.SIGCONT,
"EMT": syscall.SIGEMT,
"FPE": syscall.SIGFPE,
"HUP": syscall.SIGHUP,
"ILL": syscall.SIGILL,
"INFO": syscall.SIGINFO,
"INT": syscall.SIGINT,
"IO": syscall.SIGIO,
"IOT": syscall.SIGIOT,
"KILL": syscall.SIGKILL,
"LWP": syscall.SIGLWP,
"PIPE": syscall.SIGPIPE,
"PROF": syscall.SIGPROF,
"QUIT": syscall.SIGQUIT,
"SEGV": syscall.SIGSEGV,
"STOP": syscall.SIGSTOP,
"SYS": syscall.SIGSYS,
"TERM": syscall.SIGTERM,
"THR": syscall.SIGTHR,
"TRAP": syscall.SIGTRAP,
"TSTP": syscall.SIGTSTP,
"TTIN": syscall.SIGTTIN,
"TTOU": syscall.SIGTTOU,
"URG": syscall.SIGURG,
"USR1": syscall.SIGUSR1,
"USR2": syscall.SIGUSR2,
"VTALRM": syscall.SIGVTALRM,
"WINCH": syscall.SIGWINCH,
"XCPU": syscall.SIGXCPU,
"XFSZ": syscall.SIGXFSZ,
}

View File

@ -0,0 +1,80 @@
package signal
import (
"syscall"
)
const (
sigrtmin = 34
sigrtmax = 64
)
// SignalMap is a map of Linux signals.
var SignalMap = map[string]syscall.Signal{
"ABRT": syscall.SIGABRT,
"ALRM": syscall.SIGALRM,
"BUS": syscall.SIGBUS,
"CHLD": syscall.SIGCHLD,
"CLD": syscall.SIGCLD,
"CONT": syscall.SIGCONT,
"FPE": syscall.SIGFPE,
"HUP": syscall.SIGHUP,
"ILL": syscall.SIGILL,
"INT": syscall.SIGINT,
"IO": syscall.SIGIO,
"IOT": syscall.SIGIOT,
"KILL": syscall.SIGKILL,
"PIPE": syscall.SIGPIPE,
"POLL": syscall.SIGPOLL,
"PROF": syscall.SIGPROF,
"PWR": syscall.SIGPWR,
"QUIT": syscall.SIGQUIT,
"SEGV": syscall.SIGSEGV,
"STKFLT": syscall.SIGSTKFLT,
"STOP": syscall.SIGSTOP,
"SYS": syscall.SIGSYS,
"TERM": syscall.SIGTERM,
"TRAP": syscall.SIGTRAP,
"TSTP": syscall.SIGTSTP,
"TTIN": syscall.SIGTTIN,
"TTOU": syscall.SIGTTOU,
"UNUSED": syscall.SIGUNUSED,
"URG": syscall.SIGURG,
"USR1": syscall.SIGUSR1,
"USR2": syscall.SIGUSR2,
"VTALRM": syscall.SIGVTALRM,
"WINCH": syscall.SIGWINCH,
"XCPU": syscall.SIGXCPU,
"XFSZ": syscall.SIGXFSZ,
"RTMIN": sigrtmin,
"RTMIN+1": sigrtmin + 1,
"RTMIN+2": sigrtmin + 2,
"RTMIN+3": sigrtmin + 3,
"RTMIN+4": sigrtmin + 4,
"RTMIN+5": sigrtmin + 5,
"RTMIN+6": sigrtmin + 6,
"RTMIN+7": sigrtmin + 7,
"RTMIN+8": sigrtmin + 8,
"RTMIN+9": sigrtmin + 9,
"RTMIN+10": sigrtmin + 10,
"RTMIN+11": sigrtmin + 11,
"RTMIN+12": sigrtmin + 12,
"RTMIN+13": sigrtmin + 13,
"RTMIN+14": sigrtmin + 14,
"RTMIN+15": sigrtmin + 15,
"RTMAX-14": sigrtmax - 14,
"RTMAX-13": sigrtmax - 13,
"RTMAX-12": sigrtmax - 12,
"RTMAX-11": sigrtmax - 11,
"RTMAX-10": sigrtmax - 10,
"RTMAX-9": sigrtmax - 9,
"RTMAX-8": sigrtmax - 8,
"RTMAX-7": sigrtmax - 7,
"RTMAX-6": sigrtmax - 6,
"RTMAX-5": sigrtmax - 5,
"RTMAX-4": sigrtmax - 4,
"RTMAX-3": sigrtmax - 3,
"RTMAX-2": sigrtmax - 2,
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}

View File

@ -0,0 +1,42 @@
package signal
import (
"syscall"
)
// SignalMap is a map of Solaris signals.
// SIGINFO and SIGTHR not defined for Solaris
var SignalMap = map[string]syscall.Signal{
"ABRT": syscall.SIGABRT,
"ALRM": syscall.SIGALRM,
"BUF": syscall.SIGBUS,
"CHLD": syscall.SIGCHLD,
"CONT": syscall.SIGCONT,
"EMT": syscall.SIGEMT,
"FPE": syscall.SIGFPE,
"HUP": syscall.SIGHUP,
"ILL": syscall.SIGILL,
"INT": syscall.SIGINT,
"IO": syscall.SIGIO,
"IOT": syscall.SIGIOT,
"KILL": syscall.SIGKILL,
"LWP": syscall.SIGLWP,
"PIPE": syscall.SIGPIPE,
"PROF": syscall.SIGPROF,
"QUIT": syscall.SIGQUIT,
"SEGV": syscall.SIGSEGV,
"STOP": syscall.SIGSTOP,
"SYS": syscall.SIGSYS,
"TERM": syscall.SIGTERM,
"TRAP": syscall.SIGTRAP,
"TSTP": syscall.SIGTSTP,
"TTIN": syscall.SIGTTIN,
"TTOU": syscall.SIGTTOU,
"URG": syscall.SIGURG,
"USR1": syscall.SIGUSR1,
"USR2": syscall.SIGUSR2,
"VTALRM": syscall.SIGVTALRM,
"WINCH": syscall.SIGWINCH,
"XCPU": syscall.SIGXCPU,
"XFSZ": syscall.SIGXFSZ,
}

View File

@ -0,0 +1,21 @@
// +build !windows
package signal
import (
"syscall"
)
// Signals used in cli/command (no windows equivalent, use
// invalid signals so they don't get handled)
const (
// SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted.
SIGCHLD = syscall.SIGCHLD
// SIGWINCH is a signal sent to a process when its controlling terminal changes its size
SIGWINCH = syscall.SIGWINCH
// SIGPIPE is a signal sent to a process when a pipe is written to before the other end is open for reading
SIGPIPE = syscall.SIGPIPE
// DefaultStopSignal is the syscall signal used to stop a container in unix systems.
DefaultStopSignal = "SIGTERM"
)

View File

@ -0,0 +1,10 @@
// +build !linux,!darwin,!freebsd,!windows,!solaris
package signal
import (
"syscall"
)
// SignalMap is an empty map of signals for unsupported platform.
var SignalMap = map[string]syscall.Signal{}

View File

@ -0,0 +1,28 @@
// +build windows
package signal
import (
"syscall"
)
// Signals used in cli/command (no windows equivalent, use
// invalid signals so they don't get handled)
const (
SIGCHLD = syscall.Signal(0xff)
SIGWINCH = syscall.Signal(0xff)
SIGPIPE = syscall.Signal(0xff)
// DefaultStopSignal is the syscall signal used to stop a container in windows systems.
DefaultStopSignal = "15"
)
// SignalMap is a map of "supported" signals. As per the comment in GOLang's
// ztypes_windows.go: "More invented values for signals". Windows doesn't
// really support signals in any way, shape or form that Unix does.
//
// We have these so that docker kill can be used to gracefully (TERM) and
// forcibly (KILL) terminate a container on Windows.
var SignalMap = map[string]syscall.Signal{
"KILL": syscall.SIGKILL,
"TERM": syscall.SIGTERM,
}

103
vendor/github.com/docker/docker/pkg/signal/trap.go generated vendored Normal file
View File

@ -0,0 +1,103 @@
package signal
import (
"fmt"
"os"
gosignal "os/signal"
"path/filepath"
"runtime"
"strings"
"sync/atomic"
"syscall"
"time"
"github.com/Sirupsen/logrus"
"github.com/pkg/errors"
)
// Trap sets up a simplified signal "trap", appropriate for common
// behavior expected from a vanilla unix command-line tool in general
// (and the Docker engine in particular).
//
// * If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated.
// * If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is
// skipped and the process is terminated immediately (allows force quit of stuck daemon)
// * A SIGQUIT always causes an exit without cleanup, with a goroutine dump preceding exit.
// * Ignore SIGPIPE events. These are generated by systemd when journald is restarted while
// the docker daemon is not restarted and also running under systemd.
// Fixes https://github.com/docker/docker/issues/19728
//
func Trap(cleanup func()) {
c := make(chan os.Signal, 1)
// we will handle INT, TERM, QUIT, SIGPIPE here
signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGPIPE}
gosignal.Notify(c, signals...)
go func() {
interruptCount := uint32(0)
for sig := range c {
if sig == syscall.SIGPIPE {
continue
}
go func(sig os.Signal) {
logrus.Infof("Processing signal '%v'", sig)
switch sig {
case os.Interrupt, syscall.SIGTERM:
if atomic.LoadUint32(&interruptCount) < 3 {
// Initiate the cleanup only once
if atomic.AddUint32(&interruptCount, 1) == 1 {
// Call the provided cleanup handler
cleanup()
os.Exit(0)
} else {
return
}
} else {
// 3 SIGTERM/INT signals received; force exit without cleanup
logrus.Info("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
}
case syscall.SIGQUIT:
DumpStacks("")
logrus.Info("Forcing docker daemon shutdown without cleanup on SIGQUIT")
}
//for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal #
os.Exit(128 + int(sig.(syscall.Signal)))
}(sig)
}
}()
}
const stacksLogNameTemplate = "goroutine-stacks-%s.log"
// DumpStacks appends the runtime stack into file in dir and returns full path
// to that file.
func DumpStacks(dir string) (string, error) {
var (
buf []byte
stackSize int
)
bufferLen := 16384
for stackSize == len(buf) {
buf = make([]byte, bufferLen)
stackSize = runtime.Stack(buf, true)
bufferLen *= 2
}
buf = buf[:stackSize]
var f *os.File
if dir != "" {
path := filepath.Join(dir, fmt.Sprintf(stacksLogNameTemplate, strings.Replace(time.Now().Format(time.RFC3339), ":", "", -1)))
var err error
f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return "", errors.Wrap(err, "failed to open file to write the goroutine stacks")
}
defer f.Close()
defer f.Sync()
} else {
f = os.Stderr
}
if _, err := f.Write(buf); err != nil {
return "", errors.Wrap(err, "failed to write goroutine stacks")
}
return f.Name(), nil
}