Skip to content

Commit

Permalink
o/snapstate: account for remodeling when installing prereqs as well a…
Browse files Browse the repository at this point in the history
…s updating them (#14137)

* o/snapstate: account for remodeling when installing prereqs as well as updating them

* o/snapstate: test that we do not wait for already installed prereqs that might be getting updated during a remodel

* o/snapstate: flatten code and update comment to account for refresh

* o/snapstate: move check for remodeling to be earlier, since we do not want to update prereqs during a remodel anyways
  • Loading branch information
andrewphelpsj authored Jul 2, 2024
1 parent 981b979 commit 015a2e4
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 9 deletions.
19 changes: 10 additions & 9 deletions overlord/snapstate/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,15 @@ func (m *SnapManager) installOneBaseOrRequired(t *state.Task, snapName string, c
return true, nil
}

// if we are remodeling, then we should return early due to the way that
// tasks are ordered by the remodeling code. specifically, all snap
// downloads during a remodel happen prior to snap installation. thus,
// we cannot wait for snaps to be installed here. see remodelTasks for
// more information on how the tasks are ordered.
if deviceCtx.ForRemodeling() {
return nil, nil
}

if isInstalled {
if len(contentAttrs) > 0 {
// the default provider is already installed, update it if it's missing content attributes the snap needs
Expand All @@ -351,22 +360,14 @@ func (m *SnapManager) installOneBaseOrRequired(t *state.Task, snapName string, c
} else if ok {
return nil, onInFlight
}

return nil, nil
}

// not installed, wait for it if it is. If not, we'll install it
if ok, err := inProgress(snapName); err != nil {
return nil, err
} else if ok {
// if we are remodeling, then we should return early due to the way that
// tasks are ordered by the remodeling code. specifically, all snap
// downloads during a remodel happen prior to snap installation. thus,
// we cannot wait for snaps to be installed here. see remodelTasks for
// more information on how the tasks are ordered.
if deviceCtx.ForRemodeling() {
return nil, nil
}

return nil, onInFlight
}

Expand Down
70 changes: 70 additions & 0 deletions overlord/snapstate/handlers_prereq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,76 @@ func (s *prereqSuite) TestDoPrereqRetryWhenBaseInFlight(c *C) {
c.Check(chg.Status(), Equals, state.DoneStatus)
}

func (s *prereqSuite) TestDoPrereqNoRetryWhenBaseInFlightDuringRemodel(c *C) {
restore := snapstate.MockPrerequisitesRetryTimeout(1 * time.Millisecond)
defer restore()

s.runner.AddHandler("link-snap", func(task *state.Task, _ *tomb.Tomb) error {
st := task.State()
st.Lock()
defer st.Unlock()

snapsup, err := snapstate.TaskSnapSetup(task)
c.Assert(err, IsNil)
fmt.Println(snapsup.InstanceName())

return nil
}, nil)

s.state.Lock()
defer s.state.Unlock()

restore = snapstatetest.MockDeviceContext(&snapstatetest.TrivialDeviceContext{
Remodeling: true,
})
defer restore()

// make it look like core is already installed
snapstate.Set(s.state, "core", &snapstate.SnapState{
Active: true,
Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{
{RealName: "core", Revision: snap.R(1)},
}),
Current: snap.R(1),
SnapType: "os",
})

// pretend foo gets installed and needs core (which we will make it look
// like the install is in progress)
prereqTask := s.state.NewTask("prerequisites", "foo")
prereqTask.Set("snap-setup", &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
RealName: "foo",
},
})

tCore := s.state.NewTask("link-snap", "Pretend core gets installed")
tCore.Set("snap-setup", &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
RealName: "core",
Revision: snap.R(11),
},
})

// this makes sure that the prereq task runs first, but the link-snap task
// for core will be found by the prereq task handler
tCore.WaitFor(prereqTask)

chg := s.state.NewChange("sample", "...")
chg.AddTask(prereqTask)
chg.AddTask(tCore)

s.state.Unlock()
s.se.Ensure()
s.se.Wait()
s.state.Lock()

// prereq task is done, but the core task isn't done. this means that we
// didn't wait for the core task to finish, since we are remodeling
c.Check(prereqTask.Status(), Equals, state.DoneStatus)
c.Check(tCore.Status(), Equals, state.DoStatus)
}

func (s *prereqSuite) TestDoPrereqChannelEnvvars(c *C) {
os.Setenv("SNAPD_BASES_CHANNEL", "edge")
defer os.Unsetenv("SNAPD_BASES_CHANNEL")
Expand Down

0 comments on commit 015a2e4

Please sign in to comment.