diff --git a/snap/validate.go b/snap/validate.go index 25da7a69bb87..9f1fb37a996b 100644 --- a/snap/validate.go +++ b/snap/validate.go @@ -422,7 +422,7 @@ func ValidateApp(app *AppInfo) error { // validate refresh-mode switch app.RefreshMode { - case "", "keep", "restart": + case "", "keep", "restart", "sigterm", "sigterm-all", "sighup", "sighup-all", "sigusr1", "sigusr1-all": // valid default: return fmt.Errorf(`"refresh-mode" field contains invalid value %q`, app.RefreshMode) diff --git a/snap/validate_test.go b/snap/validate_test.go index f1e1278100ac..8204d797ce86 100644 --- a/snap/validate_test.go +++ b/snap/validate_test.go @@ -367,6 +367,12 @@ func (s *ValidateSuite) TestAppRefreshMode(c *C) { {"", true}, {"keep", true}, {"restart", true}, + {"sigterm", true}, + {"sigterm-all", true}, + {"sighup", true}, + {"sighup-all", true}, + {"sigusr1", true}, + {"sigusr1-all", true}, // bad {"invalid-thing", false}, } { diff --git a/systemd/systemd.go b/systemd/systemd.go index 68ce9eb826e8..3ed5459e7aad 100644 --- a/systemd/systemd.go +++ b/systemd/systemd.go @@ -105,7 +105,7 @@ type Systemd interface { Disable(service string) error Start(service ...string) error Stop(service string, timeout time.Duration) error - Kill(service, signal string) error + Kill(service, signal, who string) error Restart(service string, timeout time.Duration) error Status(services ...string) ([]*ServiceStatus, error) LogReader(services []string, n string, follow bool) (io.ReadCloser, error) @@ -311,8 +311,11 @@ loop: } // Kill all processes of the unit with the given signal -func (s *systemd) Kill(serviceName, signal string) error { - _, err := systemctlCmd("kill", serviceName, "-s", signal) +func (s *systemd) Kill(serviceName, signal, who string) error { + if who == "" { + who = "all" + } + _, err := systemctlCmd("kill", serviceName, "-s", signal, "--kill-who="+who) return err } diff --git a/systemd/systemd_test.go b/systemd/systemd_test.go index 656c08eae451..f011365dfbf9 100644 --- a/systemd/systemd_test.go +++ b/systemd/systemd_test.go @@ -387,8 +387,8 @@ func (s *SystemdTestSuite) TestRestart(c *C) { } func (s *SystemdTestSuite) TestKill(c *C) { - c.Assert(New("", s.rep).Kill("foo", "HUP"), IsNil) - c.Check(s.argses, DeepEquals, [][]string{{"kill", "foo", "-s", "HUP"}}) + c.Assert(New("", s.rep).Kill("foo", "HUP", ""), IsNil) + c.Check(s.argses, DeepEquals, [][]string{{"kill", "foo", "-s", "HUP", "--kill-who=all"}}) } func (s *SystemdTestSuite) TestIsTimeout(c *C) { diff --git a/wrappers/services.go b/wrappers/services.go index 2c39d3866903..f3aa36251c82 100644 --- a/wrappers/services.go +++ b/wrappers/services.go @@ -76,9 +76,9 @@ func stopService(sysd systemd.Systemd, app *snap.AppInfo, inter interacter) erro } inter.Notify(fmt.Sprintf("%s refused to stop, killing.", serviceName)) // ignore errors for kill; nothing we'd do differently at this point - sysd.Kill(serviceName, "TERM") + sysd.Kill(serviceName, "TERM", "") time.Sleep(killWait) - sysd.Kill(serviceName, "KILL") + sysd.Kill(serviceName, "KILL", "") } @@ -225,8 +225,32 @@ func StopServices(apps []*snap.AppInfo, reason snap.ServiceStopReason, inter int continue } // Skip stop on refresh when refresh mode is "keep" - if app.RefreshMode == "keep" && reason == snap.StopReasonRefresh { - continue + if reason == snap.StopReasonRefresh { + switch app.RefreshMode { + case "keep": + // skip this service + continue + case "sigterm": + sysd.Kill(app.ServiceName(), "TERM", "main") + continue + case "sigterm-all": + sysd.Kill(app.ServiceName(), "TERM", "all") + continue + case "sighup": + sysd.Kill(app.ServiceName(), "HUP", "main") + continue + case "sighup-all": + sysd.Kill(app.ServiceName(), "HUP", "all") + continue + case "sigusr1": + sysd.Kill(app.ServiceName(), "USR1", "main") + continue + case "sigusr1-all": + sysd.Kill(app.ServiceName(), "USR1", "all") + continue + case "", "restart": + // do nothing here, the default below to stop + } } if err := stopService(sysd, app, inter); err != nil { return err diff --git a/wrappers/services_test.go b/wrappers/services_test.go index 883368c3ded5..06edb7e603cd 100644 --- a/wrappers/services_test.go +++ b/wrappers/services_test.go @@ -173,8 +173,8 @@ apps: c.Check(sysdLog, DeepEquals, [][]string{ {"stop", svcFName}, // check kill invocations - {"kill", svcFName, "-s", "TERM"}, - {"kill", svcFName, "-s", "KILL"}, + {"kill", svcFName, "-s", "TERM", "--kill-who=all"}, + {"kill", svcFName, "-s", "KILL", "--kill-who=all"}, }) } @@ -609,7 +609,7 @@ func (s *servicesTestSuite) TestServiceAfterBefore(c *C) { } -func (s *servicesTestSuite) TestStopServiceSurvive(c *C) { +func (s *servicesTestSuite) TestStopServiceKeep(c *C) { var sysdLog [][]string r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { sysdLog = append(sysdLog, cmd) @@ -649,3 +649,60 @@ apps: }) } + +func (s *servicesTestSuite) TestStopServiceSigs(c *C) { + var sysdLog [][]string + r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { + sysdLog = append(sysdLog, cmd) + return []byte("ActiveState=inactive\n"), nil + }) + defer r() + + survivorFile := filepath.Join(s.tempdir, "/etc/systemd/system/snap.survive-snap.srv.service") + for _, t := range []struct { + mode string + expectedSig string + expectedWho string + }{ + {mode: "sigterm", expectedSig: "TERM", expectedWho: "main"}, + {mode: "sigterm-all", expectedSig: "TERM", expectedWho: "all"}, + {mode: "sighup", expectedSig: "HUP", expectedWho: "main"}, + {mode: "sighup-all", expectedSig: "HUP", expectedWho: "all"}, + {mode: "sigusr1", expectedSig: "USR1", expectedWho: "main"}, + {mode: "sigusr1-all", expectedSig: "USR1", expectedWho: "all"}, + } { + surviveYaml := fmt.Sprintf(`name: survive-snap +version: 1.0 +apps: + srv: + command: bin/survivor + refresh-mode: %s + daemon: simple +`, t.mode) + info := snaptest.MockSnap(c, surviveYaml, &snap.SideInfo{Revision: snap.R(1)}) + + sysdLog = nil + err := wrappers.AddSnapServices(info, nil) + c.Assert(err, IsNil) + c.Check(sysdLog, DeepEquals, [][]string{ + {"--root", dirs.GlobalRootDir, "enable", filepath.Base(survivorFile)}, + {"daemon-reload"}, + }) + + sysdLog = nil + err = wrappers.StopServices(info.Services(), snap.StopReasonRefresh, progress.Null) + c.Assert(err, IsNil) + c.Check(sysdLog, DeepEquals, [][]string{ + {"kill", filepath.Base(survivorFile), "-s", t.expectedSig, "--kill-who=" + t.expectedWho}, + }, Commentf("failure in %s", t.mode)) + + sysdLog = nil + err = wrappers.StopServices(info.Services(), snap.StopReasonRemove, progress.Null) + c.Assert(err, IsNil) + c.Check(sysdLog, DeepEquals, [][]string{ + {"stop", filepath.Base(survivorFile)}, + {"show", "--property=ActiveState", "snap.survive-snap.srv.service"}, + }) + } + +}