Skip to content

Commit

Permalink
o/snapstate: support user-services when refreshing snaps
Browse files Browse the repository at this point in the history
  • Loading branch information
Meulengracht committed Apr 29, 2024
1 parent 7a5924f commit 1762c26
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 32 deletions.
1 change: 1 addition & 0 deletions overlord/snapstate/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ var (
)

type AuxStoreInfo = auxStoreInfo
type DisabledServices = disabledServices

// link, misc handlers
var (
Expand Down
98 changes: 70 additions & 28 deletions overlord/snapstate/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1954,33 +1954,47 @@ func writeSeqFile(name string, snapst *SnapState) error {
return osutil.AtomicWriteFile(p, b, 0644, 0)
}

type disabledServices struct {
MissingSystemServices []string
FoundSystemServices []string
MissingUserServices map[int][]string
FoundUserServices map[int][]string
}

// missingDisabledServices returns a list of services that are present in
// this snap info and should be disabled as well as a list of disabled
// services that are currently missing (i.e. they were renamed).
// present in this snap info.
// the first arg is the disabled services when the snap was last active
func missingDisabledServices(svcs []string, info *snap.Info) ([]string, []string, error) {
// make a copy of all the previously disabled services that we will remove
// from, as well as an empty list to add to for the found services
missingSvcs := []string{}
foundSvcs := []string{}

// for all the previously disabled services, check if they are in the
// current snap info revision as services or not
for _, disabledSvcName := range svcs {
// check if the service is an app _and_ is a service
if app, ok := info.Apps[disabledSvcName]; ok && app.IsService() {
foundSvcs = append(foundSvcs, disabledSvcName)
} else {
missingSvcs = append(missingSvcs, disabledSvcName)
func missingDisabledServices(sysSvcs []string, userSvcs map[int][]string, info *snap.Info) (*disabledServices, error) {
overview := &disabledServices{
MissingUserServices: make(map[int][]string),
FoundUserServices: make(map[int][]string),
}

categorize := func(names []string) ([]string, []string) {
foundSvcs := []string{}
missingSvcs := []string{}
for _, name := range names {
// check if the service is an app _and_ is a service
if app, ok := info.Apps[name]; ok && app.IsService() {
foundSvcs = append(foundSvcs, name)
} else {
missingSvcs = append(missingSvcs, name)
}
}
sort.Strings(missingSvcs)
sort.Strings(foundSvcs)
return foundSvcs, missingSvcs
}

// sort the lists for easier testing
sort.Strings(missingSvcs)
sort.Strings(foundSvcs)

return foundSvcs, missingSvcs, nil
overview.FoundSystemServices, overview.MissingSystemServices = categorize(sysSvcs)
for uid, svcs := range userSvcs {
found, missing := categorize(svcs)
overview.FoundUserServices[uid] = found
overview.MissingUserServices[uid] = missing
}
return overview, nil
}

// LinkSnapParticipant is an interface for interacting with snap link/unlink
Expand Down Expand Up @@ -3013,29 +3027,39 @@ func (m *SnapManager) startSnapServices(t *state.Task, _ *tomb.Tomb) error {
// as well as the services which are not present in this revision, but were
// present and disabled in a previous one and as such should be kept inside
// snapst for persistent storage
svcsToDisable, svcsToSave, err := missingDisabledServices(snapst.LastActiveDisabledServices, currentInfo)
missingSvcsOverview, err := missingDisabledServices(
snapst.LastActiveDisabledServices,
snapst.LastActiveDisabledUserServices,
currentInfo)
if err != nil {
return err
}

// check what services with "InstallMode: disable" need to be disabled
svcsToDisableFromInstallMode, err := installModeDisabledServices(st, snapst, currentInfo)
svcsToDisable, err := installModeDisabledServices(st, snapst, currentInfo)
if err != nil {
return err
}
svcsToDisable = append(svcsToDisable, svcsToDisableFromInstallMode...)

// append services that were disabled by hooks (they should not get re-enabled)
svcsToDisable = append(svcsToDisable, snapst.ServicesDisabledByHooks...)

// Insert it into the overview, into the 'found' list. Re-use the entire
// list for all users (-1) as there won't be any difference between users
// in this specific case (and to allow us to keep a singular list of services
// that need to be disabled).
missingSvcsOverview.FoundSystemServices = append(missingSvcsOverview.FoundSystemServices, svcsToDisable...)
missingSvcsOverview.FoundUserServices[-1] = svcsToDisable

// save the current last-active-disabled-services before we re-write it in case we
// need to undo this
t.Set("old-last-active-disabled-services", snapst.LastActiveDisabledServices)
t.Set("old-last-active-disabled-user-services", snapst.LastActiveDisabledUserServices)

// commit the missing services to state so when we unlink this revision and
// go to a different revision with potentially different service names, the
// currently missing service names will be re-disabled if they exist later
snapst.LastActiveDisabledServices = svcsToSave
snapst.LastActiveDisabledServices = missingSvcsOverview.MissingSystemServices
snapst.LastActiveDisabledUserServices = missingSvcsOverview.MissingUserServices

// reset services tracked by operations from hooks
snapst.ServicesDisabledByHooks = nil
Expand All @@ -3056,7 +3080,8 @@ func (m *SnapManager) startSnapServices(t *state.Task, _ *tomb.Tomb) error {

st.Unlock()
err = m.backend.StartServices(startupOrdered, &wrappers.DisabledServices{
SystemServices: svcsToDisable,
SystemServices: missingSvcsOverview.FoundSystemServices,
UserServices: missingSvcsOverview.FoundUserServices,
}, pb, perfTimings)
st.Lock()

Expand All @@ -3082,10 +3107,16 @@ func (m *SnapManager) undoStartSnapServices(t *state.Task, _ *tomb.Tomb) error {
}

var oldLastActiveDisabledServices []string
var oldLastActiveDisabledUserServices map[int][]string
if err := t.Get("old-last-active-disabled-services", &oldLastActiveDisabledServices); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
if err := t.Get("old-last-active-disabled-user-services", &oldLastActiveDisabledUserServices); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
snapst.LastActiveDisabledServices = oldLastActiveDisabledServices
snapst.LastActiveDisabledUserServices = oldLastActiveDisabledUserServices

Set(st, snapsup.InstanceName(), snapst)

svcs := currentInfo.Services()
Expand Down Expand Up @@ -3160,6 +3191,7 @@ func (m *SnapManager) stopSnapServices(t *state.Task, _ *tomb.Tomb) error {

// for undo
t.Set("old-last-active-disabled-services", snapst.LastActiveDisabledServices)
t.Set("old-last-active-disabled-user-services", snapst.LastActiveDisabledUserServices)
// undo could queryDisabledServices, but this avoids it
t.Set("disabled-services", disabledServices)

Expand All @@ -3173,6 +3205,11 @@ func (m *SnapManager) stopSnapServices(t *state.Task, _ *tomb.Tomb) error {
snapst.LastActiveDisabledServices,
disabledServices.SystemServices...,
)
// Merge the two user-services maps, ideally we would have one struct but we must
// preserve the backwards-compat with the json layout
for uid, svcs := range disabledServices.UserServices {
snapst.LastActiveDisabledUserServices[uid] = append(snapst.LastActiveDisabledUserServices[uid], svcs...)
}

// reset services tracked by operations from hooks
snapst.ServicesDisabledByHooks = nil
Expand Down Expand Up @@ -3210,11 +3247,16 @@ func (m *SnapManager) undoStopSnapServices(t *state.Task, _ *tomb.Tomb) error {
return err
}

var lastActiveDisabled []string
if err := t.Get("old-last-active-disabled-services", &lastActiveDisabled); err != nil && !errors.Is(err, state.ErrNoState) {
var oldLastActiveDisabledServices []string
var oldLastActiveDisabledUserServices map[int][]string
if err := t.Get("old-last-active-disabled-services", &oldLastActiveDisabledServices); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
if err := t.Get("old-last-active-disabled-user-services", &oldLastActiveDisabledUserServices); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
snapst.LastActiveDisabledServices = lastActiveDisabled
snapst.LastActiveDisabledServices = oldLastActiveDisabledServices
snapst.LastActiveDisabledUserServices = oldLastActiveDisabledUserServices
Set(st, snapsup.InstanceName(), snapst)

var disabledServices wrappers.DisabledServices
Expand Down
6 changes: 3 additions & 3 deletions overlord/snapstate/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ func (s *handlersSuite) TestComputeMissingDisabledServices(c *C) {
} {
info := &snap.Info{Apps: tt.apps}

found, missing, err := snapstate.MissingDisabledServices(tt.stDisabledSvcsList, info)
c.Assert(missing, DeepEquals, tt.missing, Commentf(tt.comment))
c.Assert(found, DeepEquals, tt.found, Commentf(tt.comment))
overview, err := snapstate.MissingDisabledServices(tt.stDisabledSvcsList, nil, info)
c.Assert(overview.MissingSystemServices, DeepEquals, tt.missing, Commentf(tt.comment))
c.Assert(overview.FoundSystemServices, DeepEquals, tt.found, Commentf(tt.comment))
c.Assert(err, Equals, tt.err, Commentf(tt.comment))
}
}
Expand Down
3 changes: 2 additions & 1 deletion overlord/snapstate/snapmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ type SnapState struct {
// in the snap are removed from this list on link-snap, so that we can
// remember services that were disabled in another revision and then renamed
// or otherwise removed from the snap in a future refresh.
LastActiveDisabledServices []string `json:"last-active-disabled-services,omitempty"`
LastActiveDisabledServices []string `json:"last-active-disabled-services,omitempty"`
LastActiveDisabledUserServices map[int][]string `json:"last-active-disabled-user-services,omitempty"`

// tracking services enabled and disabled by hooks
ServicesEnabledByHooks []string `json:"services-enabled-by-hooks,omitempty"`
Expand Down

0 comments on commit 1762c26

Please sign in to comment.