diff options
| author | Philip Meulengracht <philip.meulengracht@canonical.com> | 2023-09-06 22:19:14 +0200 |
|---|---|---|
| committer | Michael Vogt <michael.vogt@gmail.com> | 2023-09-19 14:53:12 +0200 |
| commit | 619489d6ea9d4bb9a864b6bfbde5ba2fa77e7af8 (patch) | |
| tree | 8b362397ea8d02945bd9c70f91c5ac15540bb08a | |
| parent | 1a58d996f994d82209946e2a3e118227d95638cf (diff) | |
wrappers: support activated services in QueryDisabledServices/ServicesEnableState
| -rw-r--r-- | overlord/configstate/configcore/corecfg_test.go | 9 | ||||
| -rw-r--r-- | overlord/configstate/configcore/vitality_test.go | 4 | ||||
| -rw-r--r-- | overlord/managers_test.go | 20 | ||||
| -rw-r--r-- | overlord/servicestate/servicemgr_test.go | 63 | ||||
| -rw-r--r-- | overlord/snapstate/backend/link.go | 1 | ||||
| -rw-r--r-- | tests/main/snap-service-install-mode/svc.v1/meta/snap.yaml | 6 | ||||
| -rw-r--r-- | tests/main/snap-service-install-mode/task.yaml | 12 | ||||
| -rw-r--r-- | wrappers/services.go | 174 | ||||
| -rw-r--r-- | wrappers/services_test.go | 243 |
9 files changed, 428 insertions, 104 deletions
diff --git a/overlord/configstate/configcore/corecfg_test.go b/overlord/configstate/configcore/corecfg_test.go index 9f2fbee937..59466ed7b7 100644 --- a/overlord/configstate/configcore/corecfg_test.go +++ b/overlord/configstate/configcore/corecfg_test.go @@ -149,6 +149,15 @@ func (s *configcoreSuite) SetUpTest(c *C) { s.AddCleanup(osutil.MockMountInfo("")) s.systemctlOutput = func(args ...string) []byte { + if args[0] == "show" { + return []byte(fmt.Sprintf(`Type=notify +Id=%[1]s +Names=%[1]s +ActiveState=inactive +UnitFileState=enabled +NeedDaemonReload=no +`, args[len(args)-1])) + } return []byte("ActiveState=inactive") } diff --git a/overlord/configstate/configcore/vitality_test.go b/overlord/configstate/configcore/vitality_test.go index b1ce9a95f9..d532b838a1 100644 --- a/overlord/configstate/configcore/vitality_test.go +++ b/overlord/configstate/configcore/vitality_test.go @@ -151,7 +151,7 @@ func (s *vitalitySuite) testConfigureVitalityWithValidSnap(c *C, uc18 bool) { svcName := "snap.test-snap.foo.service" c.Check(s.systemctlArgs, DeepEquals, [][]string{ {"daemon-reload"}, - {"is-enabled", "snap.test-snap.foo.service"}, + {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.foo.service"}, {"--no-reload", "enable", "snap.test-snap.foo.service"}, {"daemon-reload"}, {"start", "snap.test-snap.foo.service"}, @@ -211,7 +211,7 @@ func (s *vitalitySuite) TestConfigureVitalityWithQuotaGroup(c *C) { svcName := "snap.test-snap.foo.service" c.Check(s.systemctlArgs, DeepEquals, [][]string{ {"daemon-reload"}, - {"is-enabled", "snap.test-snap.foo.service"}, + {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.foo.service"}, {"--no-reload", "enable", "snap.test-snap.foo.service"}, {"daemon-reload"}, {"start", "snap.test-snap.foo.service"}, diff --git a/overlord/managers_test.go b/overlord/managers_test.go index e8ca7a04bc..eada1bb338 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -9032,8 +9032,14 @@ WantedBy=multi-user.target c.Check(cmd, DeepEquals, []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}) return []byte("InactiveEnterTimestamp=" + t1.Format("Mon 2006-01-02 15:04:05 MST")), nil case 12: - c.Check(cmd, DeepEquals, []string{"is-enabled", "snap.test-snap.svc1.service"}) - return []byte("enabled"), nil + c.Check(cmd, DeepEquals, []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}) + return []byte(`Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=enabled +NeedDaemonReload=no +`), nil case 13: c.Check(cmd, DeepEquals, []string{"start", "snap.test-snap.svc1.service"}) return nil, nil @@ -9262,8 +9268,14 @@ WantedBy=multi-user.target c.Check(cmd, DeepEquals, []string{"show", "--property", "InactiveEnterTimestamp", "snap.test-snap.svc1.service"}) return []byte("InactiveEnterTimestamp=" + t1.Format("Mon 2006-01-02 15:04:05 MST")), nil case 12: - c.Check(cmd, DeepEquals, []string{"is-enabled", "snap.test-snap.svc1.service"}) - return []byte("enabled"), nil + c.Check(cmd, DeepEquals, []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}) + return []byte(`Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=enabled +NeedDaemonReload=no +`), nil case 13: // starting the snap fails c.Check(cmd, DeepEquals, []string{"start", "snap.test-snap.svc1.service"}) diff --git a/overlord/servicestate/servicemgr_test.go b/overlord/servicestate/servicemgr_test.go index d119cc9e6b..098baeba33 100644 --- a/overlord/servicestate/servicemgr_test.go +++ b/overlord/servicestate/servicemgr_test.go @@ -654,8 +654,14 @@ func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndRes output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), }, { - expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, - output: "enabled", + expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}, + output: `Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=enabled +NeedDaemonReload=no +`, }, { expArgs: []string{"start", "snap.test-snap.svc1.service"}, @@ -735,9 +741,14 @@ func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesButDoe }, // the service is disabled { - expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, - output: "disabled", - err: systemctlDisabledServiceError{}, + expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}, + output: `Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=disabled +NeedDaemonReload=no +`, }, // then we don't restart the service even though it was killed }) @@ -953,8 +964,14 @@ func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFil output: fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str), }, { - expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, - output: "enabled", + expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}, + output: `Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=enabled +NeedDaemonReload=no +`, }, { expArgs: []string{"start", "snap.test-snap.svc1.service"}, @@ -1040,8 +1057,14 @@ func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFil output: fmt.Sprintf("InactiveEnterTimestamp=%s", t1Str), }, { - expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, - output: "enabled", + expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}, + output: `Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=enabled +NeedDaemonReload=no +`, }, { expArgs: []string{"start", "snap.test-snap.svc1.service"}, @@ -1113,8 +1136,14 @@ func (s *ensureSnapServiceSuite) TestEnsureSnapServicesSimpleRewritesServicesFil output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), }, { - expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, - output: "enabled", + expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}, + output: `Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=enabled +NeedDaemonReload=no +`, }, { expArgs: []string{"start", "snap.test-snap.svc1.service"}, @@ -1287,8 +1316,14 @@ func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndRes output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), }, { - expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, - output: "enabled", + expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}, + output: `Type=notify +Id=snap.test-snap.svc1.service +Names=snap.test-snap.svc1.service +ActiveState=active +UnitFileState=enabled +NeedDaemonReload=no +`, }, { expArgs: []string{"start", "snap.test-snap.svc1.service"}, @@ -1369,7 +1404,7 @@ func (s *ensureSnapServiceSuite) TestEnsureSnapServicesWritesServicesFilesAndTri output: fmt.Sprintf("InactiveEnterTimestamp=%s", slightFuture), }, { - expArgs: []string{"is-enabled", "snap.test-snap.svc1.service"}, + expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", "snap.test-snap.svc1.service"}, err: fmt.Errorf("systemd is having a bad day"), }, }) diff --git a/overlord/snapstate/backend/link.go b/overlord/snapstate/backend/link.go index f0ebfd504e..f615462bd1 100644 --- a/overlord/snapstate/backend/link.go +++ b/overlord/snapstate/backend/link.go @@ -319,6 +319,7 @@ func (b Backend) UnlinkSnap(info *snap.Info, linkCtx LinkContext, meter progress // ServicesEnableState returns the current enabled/disabled states of a snap's // services, primarily for committing before snap removal/disable/revert. +// XXX: Not able to find where this is actually used? func (b Backend) ServicesEnableState(info *snap.Info, meter progress.Meter) (map[string]bool, error) { return wrappers.ServicesEnableState(info, meter) } diff --git a/tests/main/snap-service-install-mode/svc.v1/meta/snap.yaml b/tests/main/snap-service-install-mode/svc.v1/meta/snap.yaml index 561ee57e4e..f9b4dfb9d2 100644 --- a/tests/main/snap-service-install-mode/svc.v1/meta/snap.yaml +++ b/tests/main/snap-service-install-mode/svc.v1/meta/snap.yaml @@ -8,8 +8,12 @@ apps: command: sleep infinity daemon: simple install-mode: disable - timer: 0:00~24:00/96 # every 15m svc-enabled-by-hook: command: sleep infinity daemon: simple install-mode: disable + svc-enabled-by-timer: + command: sleep infinity + daemon: simple + install-mode: disable + timer: 0:00~24:00/96 # every 15m diff --git a/tests/main/snap-service-install-mode/task.yaml b/tests/main/snap-service-install-mode/task.yaml index ab6e37924f..b80cd0ba66 100644 --- a/tests/main/snap-service-install-mode/task.yaml +++ b/tests/main/snap-service-install-mode/task.yaml @@ -14,9 +14,10 @@ execute: | snap services | MATCH 'svc.svc1\s+enabled\s+active' snap services | MATCH 'svc.svc2\s+disabled\s+inactive' snap services | MATCH 'svc.svc-enabled-by-hook\s+enabled\s+active' + snap services | MATCH 'svc.svc-enabled-by-timer\s+disabled\s+inactive' # ensure that the timer service unit is also disabled by poking systemd - systemctl show --property=UnitFileState snap.svc.svc2.timer | grep "disabled" + systemctl show --property=UnitFileState snap.svc.svc-enabled-by-timer.timer | grep "disabled" echo "And after a refresh nothing changes" "$TESTSTOOLS"/snaps-state install-local ./svc.v1 @@ -25,15 +26,20 @@ execute: | snap services | MATCH 'svc.svc-enabled-by-hook\s+enabled\s+active' # ensure again that the timer service unit is still disabled by poking systemd - systemctl show --property=UnitFileState snap.svc.svc2.timer | grep "disabled" + systemctl show --property=UnitFileState snap.svc.svc-enabled-by-timer.timer | grep "disabled" echo "But install-mode: disable services can be enabled" snap start --enable svc.svc2 snap services | MATCH 'svc.svc2\s+enabled\s+active' - echo "And after a refresh the service stays enabled" + echo "And install-mode: disable activated services can be enabled" + snap start --enable svc.svc-enabled-by-timer + snap services | MATCH 'svc.svc-enabled-by-timer\s+enabled\s+inactive' + + echo "And after a refresh the services stays enabled" "$TESTSTOOLS"/snaps-state install-local ./svc.v1 snap services | MATCH 'svc.svc2\s+enabled\s+active' + snap services | MATCH 'svc.svc-enabled-by-timer\s+enabled\s+inactive' # Now test with a refresh from svc.v1 to svc.v2 # svc.v2 has: diff --git a/wrappers/services.go b/wrappers/services.go index db59e10f87..0af94114f3 100644 --- a/wrappers/services.go +++ b/wrappers/services.go @@ -1016,31 +1016,6 @@ func StopServices(apps []*snap.AppInfo, flags *StopServicesFlags, reason snap.Se return nil } -// ServicesEnableState returns a map of service names from the given snap, -// together with their enable/disable status. -func ServicesEnableState(s *snap.Info, inter Interacter) (map[string]bool, error) { - sysd := systemd.New(systemd.SystemMode, inter) - - // loop over all services in the snap, querying systemd for the current - // systemd state of the snaps - snapSvcsState := make(map[string]bool, len(s.Apps)) - for name, app := range s.Apps { - if !app.IsService() { - continue - } - // FIXME: handle user daemons - if app.DaemonScope != snap.SystemDaemon { - continue - } - state, err := sysd.IsEnabled(app.ServiceName()) - if err != nil { - return nil, err - } - snapSvcsState[name] = state - } - return snapSvcsState, nil -} - // RemoveQuotaGroup ensures that the slice file for a quota group is removed. It // assumes that the slice corresponding to the group is not in use anymore by // any services or sub-groups of the group when it is invoked. To remove a group @@ -1813,6 +1788,91 @@ func generateOnCalendarSchedules(schedule []*timeutil.Schedule) []string { return calendarEvents } +// serviceStatus represents the status of a service, and any of its activation +// service units. It also provides a method isEnabled which can determine the true +// enable status for services that are activated. +type serviceStatus struct { + name string + service *systemd.UnitStatus + activators []*systemd.UnitStatus + slotEnabled bool +} + +func (s *serviceStatus) isEnabled() bool { + // If the service is slot activated, it cannot be disabled and thus always + // is enabled. + if s.slotEnabled { + return true + } + + // If there are no activator units, then return status of the + // primary service. + if len(s.activators) == 0 { + return s.service.Enabled + } + + // Just a single of those activators need to be enabled for us + // to report the service as enabled. + for _, a := range s.activators { + if a.Enabled { + return true + } + } + return false +} + +func appServiceUnitsMany(apps []*snap.AppInfo) []string { + var allUnits []string + for _, app := range apps { + if !app.IsService() { + continue + } + // TODO: handle user daemons + if app.DaemonScope != snap.SystemDaemon { + continue + } + svc, activators := serviceUnits(app) + allUnits = append(allUnits, svc) + allUnits = append(allUnits, activators...) + } + return allUnits +} + +func queryServiceStatusMany(sysd systemd.Systemd, apps []*snap.AppInfo) ([]*serviceStatus, error) { + allUnits := appServiceUnitsMany(apps) + unitStatuses, err := sysd.Status(allUnits) + if err != nil { + return nil, err + } + + var appStatuses []*serviceStatus + var statusIndex int + for _, app := range apps { + if !app.IsService() { + continue + } + // TODO: handle user daemons + if app.DaemonScope != snap.SystemDaemon { + continue + } + + // This builds on the principle that sysd.Status returns service unit statuses + // in the exact same order we requested them in. + _, activators := serviceUnits(app) + svcSt := &serviceStatus{ + name: app.Name, + service: unitStatuses[statusIndex], + slotEnabled: serviceIsSlotActivated(app), + } + if len(activators) > 0 { + svcSt.activators = unitStatuses[statusIndex+1 : statusIndex+1+len(activators)] + } + appStatuses = append(appStatuses, svcSt) + statusIndex += 1 + len(activators) + } + return appStatuses, nil +} + type RestartServicesFlags struct { // Reload set if we might need to reload the service definitions. Reload bool @@ -1831,49 +1891,45 @@ type RestartServicesFlags struct { // The list of explicitServices needs to use systemd unit names. // TODO: change explicitServices format to be less unusual, more consistent // (introduce AppRef?) -func RestartServices(svcs []*snap.AppInfo, explicitServices []string, +func RestartServices(apps []*snap.AppInfo, explicitServices []string, flags *RestartServicesFlags, inter Interacter, tm timings.Measurer) error { if flags == nil { flags = &RestartServicesFlags{} } sysd := systemd.New(systemd.SystemMode, inter) - unitNames := make([]string, 0, len(svcs)) - for _, srv := range svcs { - // they're *supposed* to be all services, but checking doesn't hurt - if !srv.IsService() { - continue - } - unitNames = append(unitNames, srv.ServiceName()) - } - - unitStatuses, err := sysd.Status(unitNames) + // Get service statuses for each of the apps + sts, err := queryServiceStatusMany(sysd, apps) if err != nil { return err } - for _, unit := range unitStatuses { + for _, st := range sts { + unitName := st.service.Name + unitActive := st.service.Active + unitEnabled := st.isEnabled() + // If the unit was explicitly mentioned in the command line, restart it // even if it is disabled; otherwise, we only restart units which are // currently enabled or running. Reference: // https://forum.snapcraft.io/t/command-line-interface-to-manipulate-services/262/47 - if !unit.Active && !strutil.ListContains(explicitServices, unit.Name) { + if !unitActive && !strutil.ListContains(explicitServices, unitName) { if !flags.AlsoEnabledNonActive { - logger.Noticef("not restarting inactive unit %s", unit.Name) + logger.Noticef("not restarting inactive unit %s", unitName) continue - } else if !unit.Enabled { - logger.Noticef("not restarting disabled and inactive unit %s", unit.Name) + } else if !unitEnabled { + logger.Noticef("not restarting disabled and inactive unit %s", unitName) continue } } var err error - timings.Run(tm, "restart-service", fmt.Sprintf("restart service %s", unit.Name), func(nested timings.Measurer) { + timings.Run(tm, "restart-service", fmt.Sprintf("restart service %s", unitName), func(nested timings.Measurer) { if flags.Reload { - err = sysd.ReloadOrRestart([]string{unit.Name}) + err = sysd.ReloadOrRestart([]string{unitName}) } else { // note: stop followed by start, not just 'restart' - err = sysd.Restart([]string{unit.Name}) + err = sysd.Restart([]string{unitName}) } }) if err != nil { @@ -1884,21 +1940,37 @@ func RestartServices(svcs []*snap.AppInfo, explicitServices []string, return nil } +// ServicesEnableState returns a map of service names from the given snap, +// together with their enable/disable status. +func ServicesEnableState(s *snap.Info, inter Interacter) (map[string]bool, error) { + sysd := systemd.New(systemd.SystemMode, inter) + sts, err := queryServiceStatusMany(sysd, s.Services()) + if err != nil { + return nil, err + } + + // loop over all services in the snap, storing the current enable status + snapSvcsState := make(map[string]bool, len(sts)) + for _, st := range sts { + snapSvcsState[st.name] = st.isEnabled() + } + return snapSvcsState, nil +} + // QueryDisabledServices returns a list of all currently disabled snap services // in the snap. func QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) { - // save the list of services that are in the disabled state before unlinking - // and thus removing the snap services - snapSvcStates, err := ServicesEnableState(info, pb) + sysd := systemd.New(systemd.SystemMode, pb) + sts, err := queryServiceStatusMany(sysd, info.Services()) if err != nil { return nil, err } - disabledSnapSvcs := []string{} // add all disabled services to the list - for svc, isEnabled := range snapSvcStates { - if !isEnabled { - disabledSnapSvcs = append(disabledSnapSvcs, svc) + disabledSnapSvcs := []string{} + for _, st := range sts { + if !st.isEnabled() { + disabledSnapSvcs = append(disabledSnapSvcs, st.name) } } diff --git a/wrappers/services_test.go b/wrappers/services_test.go index 6858698b03..16afe95875 100644 --- a/wrappers/services_test.go +++ b/wrappers/services_test.go @@ -2865,13 +2865,12 @@ apps: func (s *servicesTestSuite) TestServicesEnableState(c *C) { info := snaptest.MockSnap(c, packageHello+` - svc2: + svc1: command: bin/hello daemon: forking - svc3: + svc2: command: bin/hello daemon: simple - daemon-scope: user `, &snap.SideInfo{Revision: snap.R(12)}) svc1File := "snap.hello-snap.svc1.service" svc2File := "snap.hello-snap.svc2.service" @@ -2884,18 +2883,31 @@ func (s *servicesTestSuite) TestServicesEnableState(c *C) { fi case "$1" in - is-enabled) - case "$2" in - "snap.hello-snap.svc1.service") - echo "disabled" - exit 1 - ;; - "snap.hello-snap.svc2.service") - echo "enabled" + show) + case "$3" in + "snap.hello-snap.svc1.service"|snap.hello-snap.svc2.service) + for SVC in $3 $4 + do + echo "Type=notify" + echo "Id=$SVC" + echo "Names=$SVC" + echo "NeedDaemonReload=no" + if [ "$SVC" = "snap.hello-snap.svc1.service" ]; then + echo "ActiveState=active" + echo "UnitFileState=enabled" + elif [ "$SVC" = "snap.hello-snap.svc2.service" ]; then + echo "ActiveState=inactive" + echo "UnitFileState=disabled" + fi + if [ "$SVC" != "$4" ]; then + echo "" + fi + done exit 0 ;; *) - echo "unexpected is-enabled of service $2" + shift 2 + echo "unexpected show of service $*" exit 2 ;; esac @@ -2911,17 +2923,17 @@ func (s *servicesTestSuite) TestServicesEnableState(c *C) { c.Assert(err, IsNil) c.Assert(states, DeepEquals, map[string]bool{ - "svc1": false, - "svc2": true, + "svc1": true, + "svc2": false, }) // the calls could be out of order in the list, since iterating over a map // is non-deterministic, so manually check each call - c.Assert(r.Calls(), HasLen, 2) + c.Assert(r.Calls(), HasLen, 1) for _, call := range r.Calls() { - c.Assert(call, HasLen, 3) - c.Assert(call[:2], DeepEquals, []string{"systemctl", "is-enabled"}) - switch call[2] { + c.Assert(call, HasLen, 5) + c.Assert(call[:2], DeepEquals, []string{"systemctl", "show"}) + switch call[3] { case svc1File, svc2File: default: c.Errorf("unknown service for systemctl call: %s", call[2]) @@ -2941,14 +2953,14 @@ func (s *servicesTestSuite) TestServicesEnableStateFail(c *C) { fi case "$1" in - is-enabled) - case "$2" in + show) + case "$3" in "snap.hello-snap.svc1.service") echo "whoops" exit 1 ;; *) - echo "unexpected is-enabled of service $2" + echo "unexpected is-enabled of service $3" exit 2 ;; esac @@ -2961,13 +2973,189 @@ func (s *servicesTestSuite) TestServicesEnableStateFail(c *C) { defer r.Restore() _, err := wrappers.ServicesEnableState(info, progress.Null) - c.Assert(err, ErrorMatches, ".*is-enabled snap.hello-snap.svc1.service\\] failed with exit status 1: whoops\n.*") + c.Assert(err, ErrorMatches, ".*show --property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload snap.hello-snap.svc1.service\\] failed with exit status 1: whoops\n.*") c.Assert(r.Calls(), DeepEquals, [][]string{ - {"systemctl", "is-enabled", svc1File}, + {"systemctl", "show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", svc1File}, }) } +func (s *servicesTestSuite) TestQueryDisabledServices(c *C) { + info := snaptest.MockSnap(c, packageHelloNoSrv+` + svc1: + daemon: simple + command: bin/hello + svc2: + daemon: simple + command: bin/hello +`, &snap.SideInfo{Revision: snap.R(12)}) + s.systemctlRestorer() + // This will mock the following: + // svc 1 will be reported as disabled + // svc 2 will be reported as enabled + r := testutil.MockCommand(c, "systemctl", `#!/bin/sh + if [ "$1" = "--root" ]; then + # shifting by 2 also drops the temp dir arg to --root + shift 2 + fi + + case "$1" in + show) + case "$3" in + "snap.hello-snap.svc1.service"|"snap.hello-snap.svc2.service") + for SVC in $3 $4 + do + echo "Type=notify" + echo "Id=$SVC" + echo "Names=$SVC" + echo "NeedDaemonReload=no" + if [ "$SVC" = "snap.hello-snap.svc1.service" ]; then + echo "ActiveState=inactive" + echo "UnitFileState=disabled" + elif [ "$SVC" = "snap.hello-snap.svc2.service" ]; then + echo "ActiveState=inactive" + echo "UnitFileState=enabled" + fi + if [ "$SVC" != "$4" ]; then + echo "" + fi + done + exit 0 + ;; + *) + shift 2 + echo "unexpected show of service $*" + exit 2 + ;; + esac + ;; + *) + echo "unexpected op $*" + exit 2 + esac + `) + defer r.Restore() + + disabledSvcs, err := wrappers.QueryDisabledServices(info, progress.Null) + c.Assert(err, IsNil) + + // ensure svc1 was reported as disabled + c.Assert(disabledSvcs, DeepEquals, []string{"svc1"}) + + // the calls could be out of order in the list, since iterating over a map + // is non-deterministic, so manually check each call + c.Assert(r.Calls(), HasLen, 1) + for _, call := range r.Calls() { + c.Assert(call, HasLen, 5) + c.Assert(call[:2], DeepEquals, []string{"systemctl", "show"}) + switch call[3] { + case "snap.hello-snap.svc1.service", "snap.hello-snap.svc2.service": + default: + c.Errorf("unknown service for systemctl call: %s", call[2]) + } + } +} + +func (s *servicesTestSuite) TestQueryDisabledServicesActivatedServices(c *C) { + info := snaptest.MockSnap(c, packageHelloNoSrv+` + svc1: + daemon: simple + plugs: [network-bind] + sockets: + sock1: + listen-stream: $SNAP_COMMON/sock1.socket + socket-mode: 0666 + sock2: + listen-stream: $SNAP_DATA/sock2.socket + svc2: + daemon: simple + command: bin/hello +`, &snap.SideInfo{Revision: snap.R(12)}) + s.systemctlRestorer() + // This will mock the following: + // svc 1 will be reported as static + // svc 2 will be reported as enabled + // svc 1 has two socket activations that both will be reported as disabled + r := testutil.MockCommand(c, "systemctl", `#!/bin/sh + if [ "$1" = "--root" ]; then + # shifting by 2 also drops the temp dir arg to --root + shift 2 + fi + + case "$1" in + show) + case "$3" in + "snap.hello-snap.svc1.service"|"snap.hello-snap.svc2.service") + for SVC in $3 $4 + do + echo "Type=notify" + echo "Id=$SVC" + echo "Names=$SVC" + echo "NeedDaemonReload=no" + if [ "$SVC" = "snap.hello-snap.svc1.service" ]; then + echo "ActiveState=inactive" + echo "UnitFileState=static" + elif [ "$SVC" = "snap.hello-snap.svc2.service" ]; then + echo "ActiveState=inactive" + echo "UnitFileState=enabled" + fi + if [ "$SVC" != "$4" ]; then + echo "" + fi + done + exit 0 + ;; + "snap.hello-snap.svc1.sock1.socket"|"snap.hello-snap.svc1.sock2.socket") + echo "Type=notify" + echo "Id=snap.hello-snap.svc1.sock1.socket" + echo "Names=snap.hello-snap.svc1.sock1.socket" + echo "ActiveState=inactive" + echo "UnitFileState=disabled" + echo "NeedDaemonReload=no" + echo "" + echo "Type=notify" + echo "Id=snap.hello-snap.svc1.sock2.socket" + echo "Names=snap.hello-snap.svc1.sock2.socket" + echo "ActiveState=inactive" + echo "UnitFileState=disabled" + echo "NeedDaemonReload=no" + exit 0 + ;; + *) + shift 2 + echo "unexpected show of service $*" + exit 2 + ;; + esac + ;; + *) + echo "unexpected op $*" + exit 2 + esac + `) + defer r.Restore() + + disabledSvcs, err := wrappers.QueryDisabledServices(info, progress.Null) + c.Assert(err, IsNil) + + // ensure svc1 were reported as disabled + c.Assert(disabledSvcs, DeepEquals, []string{"svc1"}) + + // the calls could be out of order in the list, since iterating over a map + // is non-deterministic, so manually check each call + c.Assert(r.Calls(), HasLen, 2) + for _, call := range r.Calls() { + c.Assert(call, HasLen, 5) + c.Assert(call[:2], DeepEquals, []string{"systemctl", "show"}) + switch call[3] { + case "snap.hello-snap.svc1.service", "snap.hello-snap.svc2.service", "snap.hello-snap.svc3.service": + case "snap.hello-snap.svc1.sock1.socket", "snap.hello-snap.svc1.sock2.socket": + default: + c.Errorf("unknown service for systemctl call: %s", call[2]) + } + } +} + func (s *servicesTestSuite) TestAddSnapServicesWithDisabledServices(c *C) { info := snaptest.MockSnap(c, packageHello+` svc2: @@ -4667,8 +4855,7 @@ apps: sort.Sort(snap.AppInfoBySnapApp(services)) c.Assert(wrappers.RestartServices(services, nil, &wrappers.RestartServicesFlags{AlsoEnabledNonActive: true}, progress.Null, s.perfTimings), IsNil) c.Check(s.sysdLog, DeepEquals, [][]string{ - {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", - srvFile1, srvFile2, srvFile3, srvFile4}, + {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", srvFile1, srvFile2, srvFile3, srvFile4}, {"stop", srvFile1}, {"show", "--property=ActiveState", srvFile1}, {"start", srvFile1}, @@ -4685,8 +4872,7 @@ apps: s.sysdLog = nil c.Assert(wrappers.RestartServices(services, []string{srvFile4}, &wrappers.RestartServicesFlags{AlsoEnabledNonActive: true}, progress.Null, s.perfTimings), IsNil) c.Check(s.sysdLog, DeepEquals, [][]string{ - {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", - srvFile1, srvFile2, srvFile3, srvFile4}, + {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", srvFile1, srvFile2, srvFile3, srvFile4}, {"stop", srvFile1}, {"show", "--property=ActiveState", srvFile1}, {"start", srvFile1}, @@ -4705,8 +4891,7 @@ apps: s.sysdLog = nil c.Assert(wrappers.RestartServices(services, nil, &wrappers.RestartServicesFlags{AlsoEnabledNonActive: false}, progress.Null, s.perfTimings), IsNil) c.Check(s.sysdLog, DeepEquals, [][]string{ - {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", - srvFile1, srvFile2, srvFile3, srvFile4}, + {"show", "--property=Id,ActiveState,UnitFileState,Type,Names,NeedDaemonReload", srvFile1, srvFile2, srvFile3, srvFile4}, {"stop", srvFile1}, {"show", "--property=ActiveState", srvFile1}, {"start", srvFile1}, |
