diff --git a/daemon/daemon.go b/daemon/daemon.go index 7092ddaf44..bdfffd729c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -683,6 +683,8 @@ func (daemon *Daemon) Shutdown() error { } } + pluginShutdown() + if err := daemon.cleanupMounts(); err != nil { return err } diff --git a/daemon/daemon_experimental.go b/daemon/daemon_experimental.go index 3fd0e765da..5244f1df2f 100644 --- a/daemon/daemon_experimental.go +++ b/daemon/daemon_experimental.go @@ -2,8 +2,15 @@ package daemon -import "github.com/docker/engine-api/types/container" +import ( + "github.com/docker/docker/plugin" + "github.com/docker/engine-api/types/container" +) func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) { return nil, nil } + +func pluginShutdown() { + plugin.GetManager().Shutdown() +} diff --git a/daemon/daemon_stub.go b/daemon/daemon_stub.go index 40e8ddc881..dd1fb09f1f 100644 --- a/daemon/daemon_stub.go +++ b/daemon/daemon_stub.go @@ -7,3 +7,6 @@ import "github.com/docker/engine-api/types/container" func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) { return nil, nil } + +func pluginShutdown() { +} diff --git a/integration-cli/docker_cli_daemon_experimental_test.go b/integration-cli/docker_cli_daemon_experimental_test.go index 7230dbb67d..74b20e9580 100644 --- a/integration-cli/docker_cli_daemon_experimental_test.go +++ b/integration-cli/docker_cli_daemon_experimental_test.go @@ -5,6 +5,9 @@ package main import ( "github.com/docker/docker/pkg/integration/checker" "github.com/go-check/check" + "os" + "os/exec" + "time" ) var pluginName = "tiborvass/no-remove" @@ -67,3 +70,66 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginDisabled(c *check.C) { c.Assert(out, checker.Contains, pluginName) c.Assert(out, checker.Contains, "false") } + +// TestDaemonShutdownLiveRestoreWithPlugins leaves plugin running. +func (s *DockerDaemonSuite) TestDaemonShutdownLiveRestoreWithPlugins(c *check.C) { + if err := s.d.Start("--live-restore"); err != nil { + c.Fatalf("Could not start daemon: %v", err) + } + if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pluginName); err != nil { + c.Fatalf("Could not install plugin: %v %s", err, out) + } + defer func() { + if err := s.d.Restart("--live-restore"); err != nil { + c.Fatalf("Could not restart daemon: %v", err) + } + if out, err := s.d.Cmd("plugin", "disable", pluginName); err != nil { + c.Fatalf("Could not disable plugin: %v %s", err, out) + } + if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil { + c.Fatalf("Could not remove plugin: %v %s", err, out) + } + }() + + if err := s.d.Kill(); err != nil { + c.Fatalf("Could not kill daemon: %v", err) + } + + cmd := exec.Command("pgrep", "-f", "plugin-no-remove") + if out, ec, err := runCommandWithOutput(cmd); ec != 0 { + c.Fatalf("Expected exit code '0', got %d err: %v output: %s ", ec, err, out) + } +} + +// TestDaemonShutdownWithPlugins shuts down running plugins. +func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) { + if err := s.d.Start(); err != nil { + c.Fatalf("Could not start daemon: %v", err) + } + if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pluginName); err != nil { + c.Fatalf("Could not install plugin: %v %s", err, out) + } + + defer func() { + if err := s.d.Restart(); err != nil { + c.Fatalf("Could not restart daemon: %v", err) + } + if out, err := s.d.Cmd("plugin", "disable", pluginName); err != nil { + c.Fatalf("Could not disable plugin: %v %s", err, out) + } + if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil { + c.Fatalf("Could not remove plugin: %v %s", err, out) + } + }() + + if err := s.d.cmd.Process.Signal(os.Interrupt); err != nil { + c.Fatalf("Could not kill daemon: %v", err) + } + + time.Sleep(5 * time.Second) + + cmd := exec.Command("pgrep", "-f", "plugin-no-remove") + if out, ec, err := runCommandWithOutput(cmd); ec != 1 { + c.Fatalf("Expected exit code '1', got %d err: %v output: %s ", ec, err, out) + } +} diff --git a/plugin/manager.go b/plugin/manager.go index be536191f2..5ee7de64c7 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -47,6 +47,7 @@ type plugin struct { client *plugins.Client restartManager restartmanager.RestartManager runtimeSourcePath string + exitChan chan bool } func (p *plugin) Client() *plugins.Client { @@ -98,6 +99,7 @@ type Manager struct { registryService registry.Service handleLegacy bool liveRestore bool + shutdown bool } // GetManager returns the singleton plugin Manager @@ -250,10 +252,23 @@ func LookupWithCapability(name, capability string) (Plugin, error) { return nil, ErrInadequateCapability{name, capability} } -// StateChanged updates daemon inter... +// StateChanged updates plugin internals using from libcontainerd events. func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error { logrus.Debugf("plugin state changed %s %#v", id, e) + switch e.State { + case libcontainerd.StateExit: + pm.RLock() + p, idOk := pm.plugins[id] + pm.RUnlock() + if !idOk { + return ErrNotFound(id) + } + if pm.shutdown == true { + p.exitChan <- true + } + } + return nil } diff --git a/plugin/manager_linux.go b/plugin/manager_linux.go index 277fa3c48e..6b9b570f72 100644 --- a/plugin/manager_linux.go +++ b/plugin/manager_linux.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "syscall" + "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/libcontainerd" @@ -128,3 +129,39 @@ func (pm *Manager) disable(p *plugin) error { pm.save() return nil } + +// Shutdown stops all plugins and called during daemon shutdown. +func (pm *Manager) Shutdown() { + pm.RLock() + defer pm.RUnlock() + + pm.shutdown = true + for _, p := range pm.plugins { + if p.restartManager != nil { + if err := p.restartManager.Cancel(); err != nil { + logrus.Error(err) + } + } + if pm.containerdClient != nil { + p.exitChan = make(chan bool) + err := pm.containerdClient.Signal(p.P.ID, int(syscall.SIGTERM)) + if err != nil { + logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) + } else { + select { + case <-p.exitChan: + logrus.Debug("Clean shutdown of plugin") + case <-time.After(time.Second * 10): + logrus.Debug("Force shutdown plugin") + if err := pm.containerdClient.Signal(p.P.ID, int(syscall.SIGKILL)); err != nil { + logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err) + } + } + } + close(p.exitChan) + } + if err := os.RemoveAll(p.runtimeSourcePath); err != nil { + logrus.Errorf("Remove plugin runtime failed with error: %v", err) + } + } +} diff --git a/plugin/manager_windows.go b/plugin/manager_windows.go index 84d7a4c1c9..cbdb78b682 100644 --- a/plugin/manager_windows.go +++ b/plugin/manager_windows.go @@ -23,3 +23,7 @@ func (pm *Manager) disable(p *plugin) error { func (pm *Manager) restore(p *plugin) error { return fmt.Errorf("Not implemented") } + +// Shutdown plugins +func (pm *Manager) Shutdown() { +}