Skip to content

Commit

Permalink
o/snapstate: handle refreshing a snap that only has component revisio…
Browse files Browse the repository at this point in the history
…n changes
  • Loading branch information
andrewphelpsj committed Sep 11, 2024
1 parent e21ad34 commit 5befa9a
Show file tree
Hide file tree
Showing 9 changed files with 565 additions and 61 deletions.
8 changes: 7 additions & 1 deletion overlord/snapstate/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ func (f *fakeStore) lookupRefresh(cand refreshCand) (*snap.Info, error) {
confinement = snap.ClassicConfinement
case "channel-for-devmode/stable":
confinement = snap.DevModeConfinement
case "channel-for-components":
case "channel-for-components", "channel-for-components-only-component-refresh":
components = map[string]*snap.Component{
"test-component": {
Type: snap.TestComponent,
Expand Down Expand Up @@ -628,6 +628,12 @@ func (f *fakeStore) lookupRefresh(cand refreshCand) (*snap.Info, error) {
return info, nil
}

// this is a special case for testing the case where we only refresh
// component revisions
if cand.channel == "channel-for-components-only-component-refresh" {
return info, nil
}

return nil, store.ErrNoUpdateAvailable
}

Expand Down
18 changes: 9 additions & 9 deletions overlord/snapstate/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,18 +256,18 @@ type componentInstallFlags struct {
}

type componentInstallTaskSet struct {
compSetupTaskID string
beforeLink []*state.Task
linkTask *state.Task
postOpHookAndAfter []*state.Task
discardTask *state.Task
compSetupTaskID string
beforeLink []*state.Task
linkTask *state.Task
postOpHookToDiscard []*state.Task
discardTask *state.Task
}

func (c *componentInstallTaskSet) taskSet() *state.TaskSet {
tasks := make([]*state.Task, 0, len(c.beforeLink)+1+len(c.postOpHookAndAfter)+1)
tasks := make([]*state.Task, 0, len(c.beforeLink)+1+len(c.postOpHookToDiscard)+1)
tasks = append(tasks, c.beforeLink...)
tasks = append(tasks, c.linkTask)
tasks = append(tasks, c.postOpHookAndAfter...)
tasks = append(tasks, c.postOpHookToDiscard...)
if c.discardTask != nil {
tasks = append(tasks, c.discardTask)
}
Expand Down Expand Up @@ -428,7 +428,7 @@ func doInstallComponent(st *state.State, snapst *SnapState, compSetup ComponentS
} else {
postOpHook = SetupPostRefreshComponentHook(st, snapsup.InstanceName(), compSi.Component.ComponentName)
}
componentTS.postOpHookAndAfter = append(componentTS.postOpHookAndAfter, postOpHook)
componentTS.postOpHookToDiscard = append(componentTS.postOpHookToDiscard, postOpHook)
addTask(postOpHook)

if !compSetup.MultiComponentInstall && kmodSetup == nil && compSetup.CompType == snap.KernelModulesComponent {
Expand All @@ -437,7 +437,7 @@ func doInstallComponent(st *state.State, snapst *SnapState, compSetup ComponentS
compSi.Component, revisionStr))
kmodSetup.Set("component-setup-task", prepare.ID())
kmodSetup.Set("snap-setup-task", snapSetupTaskID)
componentTS.postOpHookAndAfter = append(componentTS.postOpHookAndAfter, kmodSetup)
componentTS.postOpHookToDiscard = append(componentTS.postOpHookToDiscard, kmodSetup)
}
if kmodSetup != nil {
// note that we don't use addTask here because this task is shared and
Expand Down
2 changes: 1 addition & 1 deletion overlord/snapstate/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2714,7 +2714,7 @@ func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error {
return err
}

if len(snapst.Sequence.Revisions) == 1 {
if oldCurrent.Unset() {
// XXX: shouldn't these two just log and carry on? this is an undo handler...
timings.Run(perfTimings, "discard-snap-namespace", fmt.Sprintf("discard the namespace of snap %q", snapsup.InstanceName()), func(tm timings.Measurer) {
err = m.backend.DiscardSnapNamespace(snapsup.InstanceName())
Expand Down
12 changes: 5 additions & 7 deletions overlord/snapstate/handlers_components.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,12 +523,16 @@ func (m *SnapManager) doUnlinkCurrentComponent(t *state.Task, _ *tomb.Tomb) (err
defer st.Unlock()

// snapSt is a copy of the current state
compSetup, _, snapSt, err := compSetupAndState(t)
compSetup, snapsup, snapSt, err := compSetupAndState(t)
if err != nil {
return err
}
cref := compSetup.CompSideInfo.Component

if err := saveCurrentKernelModuleComponents(t, snapsup, snapSt); err != nil {
return err
}

// Expected to be installed
snapInfo, err := snapSt.CurrentInfo()
if err != nil {
Expand Down Expand Up @@ -561,12 +565,6 @@ func (m *SnapManager) doUnlinkComponent(t *state.Task, _ *tomb.Tomb) (err error)
return err
}

// TODO:COMPS: test taking this branch when unlinking components during a
// refresh where we lose components
if err := saveCurrentKernelModuleComponents(t, snapSup, snapSt); err != nil {
return err
}

cref := compSetup.CompSideInfo.Component
// Remove component for the specified revision
if err := m.unlinkComponent(
Expand Down
14 changes: 4 additions & 10 deletions overlord/snapstate/handlers_components_link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func (s *linkCompSnapSuite) TestDoUnlinkCurrentComponent(c *C) {
setStateWithOneComponent(s.state, snapName, snapRev, compName, compRev)
s.state.Unlock()

s.testDoUnlinkComponent(c, snapName, snapRev, compName, compRev, "unlink-current-component", nil)
s.testDoUnlinkComponent(c, snapName, snapRev, compName, compRev, "unlink-current-component", []*snap.ComponentSideInfo{})
}

func (s *linkCompSnapSuite) TestDoUnlinkCurrentComponentOtherCompPresent(c *C) {
Expand All @@ -303,11 +303,11 @@ func (s *linkCompSnapSuite) TestDoUnlinkCurrentComponentOtherCompPresent(c *C) {
csi1 := snap.NewComponentSideInfo(naming.NewComponentRef(snapName, compName), compRev)
csi2 := snap.NewComponentSideInfo(naming.NewComponentRef(snapName, "other-comp"), snap.R(33))
cs1 := sequence.NewComponentState(csi1, snap.TestComponent)
cs2 := sequence.NewComponentState(csi2, snap.TestComponent)
cs2 := sequence.NewComponentState(csi2, snap.KernelModulesComponent)
setStateWithComponents(s.state, snapName, snapRev, []*sequence.ComponentState{cs1, cs2})
s.state.Unlock()

s.testDoUnlinkComponent(c, snapName, snapRev, compName, compRev, "unlink-current-component", nil)
s.testDoUnlinkComponent(c, snapName, snapRev, compName, compRev, "unlink-current-component", []*snap.ComponentSideInfo{cs2.SideInfo})
}

func (s *linkCompSnapSuite) TestDoUnlinkCurrentComponentTwoTasks(c *C) {
Expand Down Expand Up @@ -471,19 +471,13 @@ func (s *linkCompSnapSuite) TestDoUnlinkComponent(c *C) {

s.state.Lock()

kmodCsi := &snap.ComponentSideInfo{
Component: naming.NewComponentRef(snapName, "kmod-comp"),
Revision: snap.R(10),
}

// State must contain the component. Note that in this case
// the snap does not need to be active.
ssi := &snap.SideInfo{RealName: snapName, Revision: snapRev,
SnapID: "some-snap-id"}
csi := snap.NewComponentSideInfo(naming.NewComponentRef(snapName, compName), compRev)
comps := []*sequence.ComponentState{
sequence.NewComponentState(csi, snap.TestComponent),
sequence.NewComponentState(kmodCsi, snap.KernelModulesComponent),
}
snapstate.Set(s.state, snapName, &snapstate.SnapState{
Active: false,
Expand All @@ -495,7 +489,7 @@ func (s *linkCompSnapSuite) TestDoUnlinkComponent(c *C) {

s.state.Unlock()

s.testDoUnlinkComponent(c, snapName, snapRev, compName, compRev, "unlink-component", []*snap.ComponentSideInfo{kmodCsi})
s.testDoUnlinkComponent(c, snapName, snapRev, compName, compRev, "unlink-component", nil)
}

func (s *linkCompSnapSuite) TestDoUnlinkThenUndoUnlinkComponent(c *C) {
Expand Down
54 changes: 44 additions & 10 deletions overlord/snapstate/snapstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,14 @@ func doInstall(st *state.State, snapst *SnapState, snapsup SnapSetup, compsups [
addTask(t)
}

// run refresh hooks when updating existing snap, otherwise run install hook further down.
runRefreshHooks := snapst.IsInstalled() && !snapsup.Flags.Revert
// if the snap is already installed, and the revision we are refreshing to
// is the same as the current revision, and we're not forcing an update,
// then we know that we're really modifying the state of components.
componentOnlyUpdate := snapst.IsInstalled() && snapsup.Revision() == snapst.Current && !snapsup.AlwaysUpdate

// run refresh hooks when updating existing snap, otherwise run install hook
// further down.
runRefreshHooks := snapst.IsInstalled() && !componentOnlyUpdate && !snapsup.Flags.Revert
if runRefreshHooks {
preRefreshHook := SetupPreRefreshHook(st, snapsup.InstanceName())
addTask(preRefreshHook)
Expand Down Expand Up @@ -620,8 +626,6 @@ func doInstall(st *state.State, snapst *SnapState, snapsup SnapSetup, compsups [
startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q%s services"), snapsup.InstanceName(), revisionStr))
addTask(startSnapServices)

// TODO:COMPS: test discarding components during a snap refresh (coming
// soon!)
for _, t := range tasksBeforeDiscard {
addTask(t)
}
Expand Down Expand Up @@ -776,7 +780,7 @@ func splitComponentTasksForInstall(

tasksBeforePreRefreshHook = append(tasksBeforePreRefreshHook, componentTS.beforeLink...)
tasksAfterLinkSnap = append(tasksAfterLinkSnap, componentTS.linkTask)
tasksAfterPostOpHook = append(tasksAfterPostOpHook, componentTS.postOpHookAndAfter...)
tasksAfterPostOpHook = append(tasksAfterPostOpHook, componentTS.postOpHookToDiscard...)
if componentTS.discardTask != nil {
tasksBeforeDiscard = append(tasksBeforeDiscard, componentTS.discardTask)
}
Expand Down Expand Up @@ -1881,12 +1885,32 @@ type update struct {
// revision of the snap.
//
// TODO:COMPS: check if we need to change the state of components
func (u *update) revisionSatisfied() bool {
func (u *update) revisionSatisfied() (bool, error) {
if u.Setup.AlwaysUpdate || !u.SnapState.IsInstalled() {
return false
return false, nil
}

if u.SnapState.Current != u.Setup.Revision() {
return false, nil
}

comps, err := u.SnapState.CurrentComponentInfos()
if err != nil {
return false, err
}

return u.SnapState.Current == u.Setup.Revision()
currentCompRevs := make(map[string]snap.Revision, len(comps))
for _, comp := range comps {
currentCompRevs[comp.Component.ComponentName] = comp.Revision
}

for _, comp := range u.Components {
if currentCompRevs[comp.CompSideInfo.Component.ComponentName] != comp.Revision() {
return false, nil
}
}

return true, nil
}

func doPotentiallySplitUpdate(st *state.State, requested []string, updates []update, opts Options) ([]string, *UpdateTaskSets, error) {
Expand Down Expand Up @@ -1991,7 +2015,12 @@ func doUpdate(st *state.State, requested []string, updates []update, opts Option
// and bases and then other snaps
for _, up := range updates {
// if the update is already satisfied, then we can skip it
if up.revisionSatisfied() {
ok, err := up.revisionSatisfied()
if err != nil {
return nil, false, nil, err
}

if ok {
alreadySatisfied = append(alreadySatisfied, up)
continue
}
Expand Down Expand Up @@ -2267,7 +2296,12 @@ func autoAliasesUpdate(st *state.State, requested []string, updates []update) (c
// snaps with updates
updating := make(map[string]bool, len(updates))
for _, up := range updates {
updating[up.Setup.InstanceName()] = !up.revisionSatisfied()
ok, err := up.revisionSatisfied()
if err != nil {
return nil, nil, nil, err
}

updating[up.Setup.InstanceName()] = !ok
}

// add explicitly auto-aliases only for snaps that are not updated
Expand Down
40 changes: 28 additions & 12 deletions overlord/snapstate/snapstate_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6430,8 +6430,22 @@ func undoOps(instanceName string, newSequence, prevSequence *sequence.RevisionSi
for i := len(newComponents) - 1; i >= 0; i-- {
csi := newComponents[i].SideInfo

containerName := fmt.Sprintf("%s+%s", instanceName, csi.Component.ComponentName)
filename := fmt.Sprintf("%s_%v.comp", containerName, csi.Revision)
compName := csi.Component.ComponentName
compRev := csi.Revision

containerName := fmt.Sprintf("%s+%s", instanceName, compName)
filename := fmt.Sprintf("%s_%v.comp", containerName, compRev)

// if the snap revision isn't changing, then we need to re-link the old
// component
if snapRevision == prevRevision {
oldCS := prevSequence.FindComponent(csi.Component)

ops = append(ops, []fakeOp{{
op: "link-component",
path: snap.ComponentMountDir(compName, oldCS.SideInfo.Revision, instanceName),
}}...)
}

ops = append(ops, []fakeOp{{
op: "undo-setup-component",
Expand All @@ -6444,16 +6458,18 @@ func undoOps(instanceName string, newSequence, prevSequence *sequence.RevisionSi
}}...)
}

ops = append(ops, []fakeOp{{
op: "undo-setup-snap",
name: instanceName,
stype: "app",
path: snapMount,
}, {
op: "remove-snap-dir",
name: instanceName,
path: filepath.Join(dirs.SnapMountDir, instanceName),
}}...)
if snapRevision != prevRevision {
ops = append(ops, []fakeOp{{
op: "undo-setup-snap",
name: instanceName,
stype: "app",
path: snapMount,
}, {
op: "remove-snap-dir",
name: instanceName,
path: filepath.Join(dirs.SnapMountDir, instanceName),
}}...)
}

return ops
}
Expand Down
Loading

0 comments on commit 5befa9a

Please sign in to comment.