diff options
| author | Michael Vogt <mvo@ubuntu.com> | 2021-08-27 12:58:09 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-08-27 12:58:09 +0200 |
| commit | 266dec06f617b70359ada078addbdcd97b08a73b (patch) | |
| tree | 45012d372bf009c7befa8e91992aff18c4c89c8f | |
| parent | 0593f9244df93fc2299473bb0b2db1b34b1f8305 (diff) | |
| parent | fe438d1870a2d2680485ee88a27acfc72df49bdd (diff) | |
Merge pull request #10626 from stolowski/refresh-control/pending-from-snap
o/hookstate: support snapctl refresh --pending from snap
| -rw-r--r-- | overlord/hookstate/ctlcmd/is_connected_test.go | 3 | ||||
| -rw-r--r-- | overlord/hookstate/ctlcmd/refresh.go | 41 | ||||
| -rw-r--r-- | overlord/hookstate/ctlcmd/refresh_test.go | 104 | ||||
| -rw-r--r-- | overlord/hookstate/hooks.go | 22 | ||||
| -rw-r--r-- | overlord/hookstate/hooks_test.go | 80 | ||||
| -rw-r--r-- | overlord/snapstate/autorefresh_gating.go | 34 | ||||
| -rw-r--r-- | overlord/snapstate/autorefresh_gating_test.go | 109 | ||||
| -rw-r--r-- | overlord/snapstate/snapstate.go | 9 |
8 files changed, 226 insertions, 176 deletions
diff --git a/overlord/hookstate/ctlcmd/is_connected_test.go b/overlord/hookstate/ctlcmd/is_connected_test.go index be9156a743..ba25f5d520 100644 --- a/overlord/hookstate/ctlcmd/is_connected_test.go +++ b/overlord/hookstate/ctlcmd/is_connected_test.go @@ -145,7 +145,8 @@ func mockInstalledSnap(c *C, st *state.State, snapYaml string) { SnapID: info.InstanceName() + "-id", }, }, - Current: info.Revision, + Current: info.Revision, + TrackingChannel: "stable", }) } diff --git a/overlord/hookstate/ctlcmd/refresh.go b/overlord/hookstate/ctlcmd/refresh.go index 23eaaea247..de8fc6aa6f 100644 --- a/overlord/hookstate/ctlcmd/refresh.go +++ b/overlord/hookstate/ctlcmd/refresh.go @@ -30,6 +30,7 @@ import ( "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" ) @@ -143,19 +144,17 @@ func getUpdateDetails(context *hookstate.Context) (*updateDetails, error) { context.Lock() defer context.Unlock() - if context.IsEphemeral() { - // TODO: support ephemeral context - return nil, nil + st := context.State() + + affected, err := snapstate.AffectedByRefreshCandidates(st) + if err != nil { + return nil, err } var base, restart bool - context.Get("base", &base) - context.Get("restart", &restart) - - var candidates map[string]*refreshCandidate - st := context.State() - if err := st.Get("refresh-candidates", &candidates); err != nil { - return nil, err + if affectedInfo, ok := affected[context.InstanceName()]; ok { + base = affectedInfo.Base + restart = affectedInfo.Restart } var snapst snapstate.SnapState @@ -163,6 +162,11 @@ func getUpdateDetails(context *hookstate.Context) (*updateDetails, error) { return nil, fmt.Errorf("internal error: cannot get snap state for %q: %v", context.InstanceName(), err) } + var candidates map[string]*refreshCandidate + if err := st.Get("refresh-candidates", &candidates); err != nil && err != state.ErrNoState { + return nil, err + } + var pending string switch { case snapst.RefreshInhibitedTime != nil: @@ -200,10 +204,6 @@ func (c *refreshCommand) printPendingInfo() error { if err != nil { return err } - // XXX: remove when ephemeral context is supported. - if details == nil { - return nil - } out, err := yaml.Marshal(details) if err != nil { return err @@ -224,9 +224,16 @@ func (c *refreshCommand) hold() error { // cache the action so that hook handler can implement default behavior ctx.Cache("action", snapstate.GateAutoRefreshHold) - var affecting []string - if err := ctx.Get("affecting-snaps", &affecting); err != nil { - return fmt.Errorf("internal error: cannot get affecting-snaps") + affecting, err := snapstate.AffectingSnapsForAffectedByRefreshCandidates(st, ctx.InstanceName()) + if err != nil { + return err + } + if len(affecting) == 0 { + // this shouldn't happen because the hook is executed during auto-refresh + // change which conflicts with other changes (if it happens that means + // something changed in the meantime and we didn't handle conflicts + // correctly). + return fmt.Errorf("internal error: snap %q is not affected by any snaps", ctx.InstanceName()) } // no duration specified, use maximum allowed for this gating snap. diff --git a/overlord/hookstate/ctlcmd/refresh_test.go b/overlord/hookstate/ctlcmd/refresh_test.go index d43430b2bd..babf4f7e2a 100644 --- a/overlord/hookstate/ctlcmd/refresh_test.go +++ b/overlord/hookstate/ctlcmd/refresh_test.go @@ -26,10 +26,12 @@ import ( . "gopkg.in/check.v1" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" "github.com/snapcore/snapd/overlord/hookstate/hooktest" + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" @@ -62,6 +64,13 @@ func (s *refreshSuite) SetUpTest(c *C) { s.AddCleanup(func() { dirs.SetRootDir("/") }) s.st = state.New(nil) s.mockHandler = hooktest.NewMockHandler() + + // snapstate.AffectedByRefreshCandidates needs a cached iface repo + repo := interfaces.NewRepository() + // no interfaces needed for this test suite + s.st.Lock() + defer s.st.Unlock() + ifacerepo.Replace(s.st, repo) } var refreshFromHookTests = []struct { @@ -75,21 +84,33 @@ var refreshFromHookTests = []struct { args: []string{"refresh", "--proceed", "--hold"}, err: "cannot use --proceed and --hold together", }, { - args: []string{"refresh", "--pending"}, - refreshCandidates: map[string]interface{}{"snap1": mockRefreshCandidate("snap1", "", "edge", "v1", snap.Revision{N: 3})}, - stdout: "pending: ready\nchannel: edge\nversion: v1\nrevision: 3\nbase: false\nrestart: false\n", + args: []string{"refresh", "--pending"}, + refreshCandidates: map[string]interface{}{ + "snap1": mockRefreshCandidate("snap1", "", "edge", "v1", snap.Revision{N: 3}), + }, + stdout: "pending: ready\nchannel: edge\nversion: v1\nrevision: 3\nbase: false\nrestart: false\n", }, { args: []string{"refresh", "--pending"}, stdout: "pending: none\nchannel: stable\nbase: false\nrestart: false\n", }, { - args: []string{"refresh", "--pending"}, - base: true, - restart: true, - stdout: "pending: none\nchannel: stable\nbase: true\nrestart: true\n", + args: []string{"refresh", "--pending"}, + refreshCandidates: map[string]interface{}{ + "snap1-base": mockRefreshCandidate("snap1-base", "", "edge", "v1", snap.Revision{N: 3}), + }, + stdout: "pending: none\nchannel: stable\nbase: true\nrestart: false\n", +}, { + args: []string{"refresh", "--pending"}, + refreshCandidates: map[string]interface{}{ + "kernel": mockRefreshCandidate("kernel", "", "edge", "v1", snap.Revision{N: 3}), + }, + stdout: "pending: none\nchannel: stable\nbase: false\nrestart: true\n", }, { args: []string{"refresh", "--pending"}, inhibited: true, stdout: "pending: inhibited\nchannel: stable\nbase: false\nrestart: false\n", +}, { + args: []string{"refresh", "--hold"}, + err: `internal error: snap "snap1" is not affected by any snaps`, }} func (s *refreshSuite) TestRefreshFromHook(c *C) { @@ -98,24 +119,32 @@ func (s *refreshSuite) TestRefreshFromHook(c *C) { setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1), Hook: "gate-auto-refresh"} mockContext, err := hookstate.NewContext(task, s.st, setup, s.mockHandler, "") c.Check(err, IsNil) + mockInstalledSnap(c, s.st, `name: snap1 +base: snap1-base +version: 1 +hooks: + gate-auto-refresh: +`) + mockInstalledSnap(c, s.st, `name: snap1-base +type: base +version: 1 +`) + mockInstalledSnap(c, s.st, `name: kernel +type: kernel +version: 1 +`) s.st.Unlock() for _, test := range refreshFromHookTests { - mockContext.Lock() - mockContext.Set("base", test.base) - mockContext.Set("restart", test.restart) + s.st.Lock() s.st.Set("refresh-candidates", test.refreshCandidates) - snapst := &snapstate.SnapState{ - Active: true, - Sequence: []*snap.SideInfo{{RealName: "snap1", Revision: snap.R(1)}}, - Current: snap.R(2), - TrackingChannel: "stable", - } if test.inhibited { + var snapst snapstate.SnapState + c.Assert(snapstate.Get(s.st, "snap1", &snapst), IsNil) snapst.RefreshInhibitedTime = &time.Time{} + snapstate.Set(s.st, "snap1", &snapst) } - snapstate.Set(s.st, "snap1", snapst) - mockContext.Unlock() + s.st.Unlock() stdout, stderr, err := ctlcmd.Run(mockContext, test.args, 0) comment := Commentf("%s", test.args) @@ -141,15 +170,23 @@ func (s *refreshSuite) TestRefreshHold(c *C) { mockContext, err := hookstate.NewContext(task, s.st, setup, s.mockHandler, "") c.Check(err, IsNil) - mockInstalledSnap(c, s.st, `name: foo + mockInstalledSnap(c, s.st, `name: snap1 +base: snap1-base +version: 1 +hooks: + gate-auto-refresh: +`) + mockInstalledSnap(c, s.st, `name: snap1-base +type: base version: 1 `) - s.st.Unlock() + candidates := map[string]interface{}{ + "snap1-base": mockRefreshCandidate("snap1-base", "", "edge", "v1", snap.Revision{N: 3}), + } + s.st.Set("refresh-candidates", candidates) - mockContext.Lock() - mockContext.Set("affecting-snaps", []string{"foo"}) - mockContext.Unlock() + s.st.Unlock() stdout, stderr, err := ctlcmd.Run(mockContext, []string{"refresh", "--hold"}, 0) c.Assert(err, IsNil) @@ -164,7 +201,7 @@ version: 1 var gating map[string]map[string]interface{} c.Assert(s.st.Get("snaps-hold", &gating), IsNil) - c.Check(gating["foo"]["snap1"], NotNil) + c.Check(gating["snap1-base"]["snap1"], NotNil) } func (s *refreshSuite) TestRefreshProceed(c *C) { @@ -273,6 +310,25 @@ version: 1 c.Check(called, Equals, true) } +func (s *refreshSuite) TestPendingFromSnapNoRefreshCandidates(c *C) { + s.st.Lock() + defer s.st.Unlock() + + mockInstalledSnap(c, s.st, `name: foo +version: 1 +`) + + setup := &hookstate.HookSetup{Snap: "foo", Revision: snap.R(1)} + mockContext, err := hookstate.NewContext(nil, s.st, setup, nil, "") + c.Check(err, IsNil) + s.st.Unlock() + defer s.st.Lock() + + stdout, _, err := ctlcmd.Run(mockContext, []string{"refresh", "--pending"}, 0) + c.Assert(err, IsNil) + c.Check(string(stdout), Equals, "pending: none\nchannel: stable\nbase: false\nrestart: false\n") +} + func (s *refreshSuite) TestRefreshProceedFromSnapError(c *C) { restore := ctlcmd.MockAutoRefreshForGatingSnap(func(st *state.State, gatingSnap string) error { c.Check(gatingSnap, Equals, "foo") diff --git a/overlord/hookstate/hooks.go b/overlord/hookstate/hooks.go index 5074c74de9..9f994d3d74 100644 --- a/overlord/hookstate/hooks.go +++ b/overlord/hookstate/hooks.go @@ -20,7 +20,6 @@ package hookstate import ( "fmt" "regexp" - "sort" "time" "github.com/snapcore/snapd/cmd/snaplock" @@ -234,9 +233,10 @@ func (h *gateAutoRefreshHookHandler) Error(hookErr error) (ignoreHookErr bool, e // the hook didn't request --hold, or it was --proceed. since the hook // errored out, assume hold. - var affecting []string - if err := ctx.Get("affecting-snaps", &affecting); err != nil { - return false, fmt.Errorf("internal error: cannot get affecting-snaps") + affecting, err := snapstate.AffectingSnapsForAffectedByRefreshCandidates(st, snapName) + if err != nil { + // becomes error of the handler + return false, err } // no duration specified, use maximum allowed for this gating snap. @@ -266,24 +266,14 @@ func NewGateAutoRefreshHookHandler(context *Context) *gateAutoRefreshHookHandler } } -func SetupGateAutoRefreshHook(st *state.State, snapName string, base, restart bool, affectingSnaps map[string]bool) *state.Task { +func SetupGateAutoRefreshHook(st *state.State, snapName string) *state.Task { hookSup := &HookSetup{ Snap: snapName, Hook: "gate-auto-refresh", Optional: true, } - affecting := make([]string, 0, len(affectingSnaps)) - for sn := range affectingSnaps { - affecting = append(affecting, sn) - } - sort.Strings(affecting) summary := fmt.Sprintf(i18n.G("Run hook %s of snap %q"), hookSup.Hook, hookSup.Snap) - hookCtx := map[string]interface{}{ - "base": base, - "restart": restart, - "affecting-snaps": affecting, - } - task := HookTask(st, summary, hookSup, hookCtx) + task := HookTask(st, summary, hookSup, nil) return task } diff --git a/overlord/hookstate/hooks_test.go b/overlord/hookstate/hooks_test.go index b9e02f7494..d4efdb29b6 100644 --- a/overlord/hookstate/hooks_test.go +++ b/overlord/hookstate/hooks_test.go @@ -28,8 +28,10 @@ import ( "gopkg.in/tomb.v2" "github.com/snapcore/snapd/cmd/snaplock/runinhibit" + "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/hookstate" + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" @@ -39,10 +41,16 @@ import ( const snapaYaml = `name: snap-a version: 1 +base: base-snap-a hooks: gate-auto-refresh: ` +const snapaBaseYaml = `name: base-snap-a +version: 1 +type: base +` + const snapbYaml = `name: snap-b version: 1 ` @@ -74,12 +82,36 @@ func (s *gateAutoRefreshHookSuite) SetUpTest(c *C) { Sequence: []*snap.SideInfo{si2}, Current: snap.R(1), }) + + si3 := &snap.SideInfo{RealName: "base-snap-a", SnapID: "base-snap-a-id1", Revision: snap.R(1)} + snaptest.MockSnap(c, snapaBaseYaml, si3) + snapstate.Set(s.state, "base-snap-a", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si3}, + Current: snap.R(1), + }) + + repo := interfaces.NewRepository() + // no interfaces needed for this test suite + ifacerepo.Replace(s.state, repo) } func (s *gateAutoRefreshHookSuite) TearDownTest(c *C) { s.commonTearDownTest(c) } +func mockRefreshCandidate(snapName, instanceKey, channel, version string, revision snap.Revision) interface{} { + sup := &snapstate.SnapSetup{ + Channel: channel, + InstanceKey: instanceKey, + SideInfo: &snap.SideInfo{ + Revision: revision, + RealName: snapName, + }, + } + return snapstate.MockRefreshCandidate(sup, version) +} + func (s *gateAutoRefreshHookSuite) settle(c *C) { err := s.o.Settle(5 * time.Second) c.Assert(err, IsNil) @@ -126,7 +158,7 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookProceedRuninhibitLock( tr.Set("core", "experimental.refresh-app-awareness", true) tr.Commit() - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-b": true}) + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) @@ -171,7 +203,7 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookHoldUnlocksRuninhibit( tr.Set("core", "experimental.refresh-app-awareness", true) tr.Commit() - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-b": true}) + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) @@ -219,7 +251,7 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshDefaultProceedUnlocksRunin tr.Set("core", "experimental.refresh-app-awareness", true) tr.Commit() - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-a": true}) + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) @@ -264,7 +296,7 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshDefaultProceed(c *C) { // sanity checkIsHeld(c, st, "snap-b", "snap-a") - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-b": true}) + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) @@ -304,7 +336,10 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookError(c *C) { st.Lock() defer st.Unlock() - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-b": true}) + candidates := map[string]interface{}{"snap-a": mockRefreshCandidate("snap-a", "", "edge", "v1", snap.Revision{N: 3})} + st.Set("refresh-candidates", candidates) + + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) @@ -315,8 +350,8 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookError(c *C) { c.Assert(strings.Join(task.Log(), ""), testutil.Contains, "ignoring hook error: fail") c.Assert(change.Status(), Equals, state.DoneStatus) - // and snap-b is now held. - checkIsHeld(c, st, "snap-b", "snap-a") + // and snap-a is now held. + checkIsHeld(c, st, "snap-a", "snap-a") // no runinhibit because the refresh-app-awareness feature is disabled. hint, err := runinhibit.IsLocked("snap-a") @@ -351,7 +386,10 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookErrorAfterProceed(c *C st.Lock() defer st.Unlock() - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-b": true}) + candidates := map[string]interface{}{"snap-a": mockRefreshCandidate("snap-a", "", "edge", "v1", snap.Revision{N: 3})} + st.Set("refresh-candidates", candidates) + + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) @@ -362,8 +400,8 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookErrorAfterProceed(c *C c.Assert(strings.Join(task.Log(), ""), testutil.Contains, "ignoring hook error: fail") c.Assert(change.Status(), Equals, state.DoneStatus) - // and snap-b is now held. - checkIsHeld(c, st, "snap-b", "snap-a") + // and snap-a is now held. + checkIsHeld(c, st, "snap-a", "snap-a") // no runinhibit because the refresh-app-awareness feature is disabled. hint, err := runinhibit.IsLocked("snap-a") @@ -397,7 +435,10 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookErrorRuninhibitUnlock( tr.Set("core", "experimental.refresh-app-awareness", true) tr.Commit() - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-b": true}) + candidates := map[string]interface{}{"snap-a": mockRefreshCandidate("snap-a", "", "edge", "v1", snap.Revision{N: 3})} + st.Set("refresh-candidates", candidates) + + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) @@ -408,8 +449,8 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookErrorRuninhibitUnlock( c.Assert(strings.Join(task.Log(), ""), testutil.Contains, "ignoring hook error: fail") c.Assert(change.Status(), Equals, state.DoneStatus) - // and snap-b is now held. - checkIsHeld(c, st, "snap-b", "snap-a") + // and snap-a is now held. + checkIsHeld(c, st, "snap-a", "snap-a") // inhibit lock is unlocked hint, err := runinhibit.IsLocked("snap-a") @@ -438,23 +479,26 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshHookErrorHoldErrorLogged(c st.Lock() defer st.Unlock() - task := hookstate.SetupGateAutoRefreshHook(st, "snap-a", false, false, map[string]bool{"snap-b": true}) + candidates := map[string]interface{}{"snap-a": mockRefreshCandidate("snap-a", "", "edge", "v1", snap.Revision{N: 3})} + st.Set("refresh-candidates", candidates) + + task := hookstate.SetupGateAutoRefreshHook(st, "snap-a") change := st.NewChange("kind", "summary") change.AddTask(task) - // pretend snap-b wasn't updated for a very long time. + // pretend snap-a wasn't updated for a very long time. var snapst snapstate.SnapState - c.Assert(snapstate.Get(st, "snap-b", &snapst), IsNil) + c.Assert(snapstate.Get(st, "snap-a", &snapst), IsNil) t := time.Now().Add(-365 * 24 * time.Hour) snapst.LastRefreshTime = &t - snapstate.Set(st, "snap-b", &snapst) + snapstate.Set(st, "snap-a", &snapst) st.Unlock() s.settle(c) st.Lock() c.Assert(strings.Join(task.Log(), ""), Matches, `.*error: cannot hold some snaps: - - snap "snap-a" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded \(while handling previous hook error: fail\)`) + - snap "snap-a" cannot hold snap "snap-a" anymore, maximum refresh postponement exceeded \(while handling previous hook error: fail\)`) c.Assert(change.Status(), Equals, state.DoneStatus) // and snap-b is not held (due to hold error). diff --git a/overlord/snapstate/autorefresh_gating.go b/overlord/snapstate/autorefresh_gating.go index d70a64b2bd..2651afcc29 100644 --- a/overlord/snapstate/autorefresh_gating.go +++ b/overlord/snapstate/autorefresh_gating.go @@ -408,6 +408,26 @@ func AffectedByRefreshCandidates(st *state.State) (map[string]*AffectedSnapInfo, return affected, err } +// AffectingSnapsForAffectedByRefreshCandidates returns the list of all snaps +// affecting affectedSnap (i.e. a gating snap), based on upcoming updates +// from refresh-candidates. +func AffectingSnapsForAffectedByRefreshCandidates(st *state.State, affectedSnap string) ([]string, error) { + affected, err := AffectedByRefreshCandidates(st) + if err != nil { + return nil, err + } + affectedInfo := affected[affectedSnap] + if affectedInfo == nil || len(affectedInfo.AffectingSnaps) == 0 { + return nil, nil + } + affecting := make([]string, 0, len(affectedInfo.AffectingSnaps)) + for sn := range affectedInfo.AffectingSnaps { + affecting = append(affecting, sn) + } + sort.Strings(affecting) + return affecting, nil +} + func affectedByRefresh(st *state.State, updates []string) (map[string]*AffectedSnapInfo, error) { allSnaps, err := All(st) if err != nil { @@ -609,20 +629,12 @@ func usesMountBackend(iface interfaces.Interface) bool { } // createGateAutoRefreshHooks creates gate-auto-refresh hooks for all affectedSnaps. -// The hooks will have their context data set from affectedSnapInfo flags (base, restart). // Hook tasks will be chained to run sequentially. -func createGateAutoRefreshHooks(st *state.State, affectedSnaps map[string]*AffectedSnapInfo) *state.TaskSet { +func createGateAutoRefreshHooks(st *state.State, affectedSnaps []string) *state.TaskSet { ts := state.NewTaskSet() var prev *state.Task - // sort names for easy testing - names := make([]string, 0, len(affectedSnaps)) - for snapName := range affectedSnaps { - names = append(names, snapName) - } - sort.Strings(names) - for _, snapName := range names { - affected := affectedSnaps[snapName] - hookTask := SetupGateAutoRefreshHook(st, snapName, affected.Base, affected.Restart, affected.AffectingSnaps) + for _, snapName := range affectedSnaps { + hookTask := SetupGateAutoRefreshHook(st, snapName) // XXX: it should be fine to run the hooks in parallel if prev != nil { hookTask.WaitFor(prev) diff --git a/overlord/snapstate/autorefresh_gating_test.go b/overlord/snapstate/autorefresh_gating_test.go index eb9be08d6c..99f08a87e0 100644 --- a/overlord/snapstate/autorefresh_gating_test.go +++ b/overlord/snapstate/autorefresh_gating_test.go @@ -1114,23 +1114,7 @@ func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) { st.Lock() defer st.Unlock() - affected := map[string]*snapstate.AffectedSnapInfo{ - "snap-a": { - Base: true, - Restart: true, - AffectingSnaps: map[string]bool{ - "snap-c": true, - "snap-d": true, - }, - }, - "snap-b": { - AffectingSnaps: map[string]bool{ - "snap-e": true, - "snap-f": true, - }, - }, - } - + affected := []string{"snap-a", "snap-b"} seenSnaps := make(map[string]bool) ts := snapstate.CreateGateAutoRefreshHooks(st, affected) @@ -1143,23 +1127,6 @@ func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) { c.Check(hs.Hook, Equals, "gate-auto-refresh") c.Check(hs.Optional, Equals, true) seenSnaps[hs.Snap] = true - - var data interface{} - c.Assert(t.Get("hook-context", &data), IsNil) - - // the order of hook tasks is not deterministic - if hs.Snap == "snap-a" { - c.Check(data, DeepEquals, map[string]interface{}{ - "base": true, - "restart": true, - "affecting-snaps": []interface{}{"snap-c", "snap-d"}}) - } else { - c.Assert(hs.Snap, Equals, "snap-b") - c.Check(data, DeepEquals, map[string]interface{}{ - "base": false, - "restart": false, - "affecting-snaps": []interface{}{"snap-e", "snap-f"}}) - } } checkHook(ts.Tasks()[0]) @@ -1194,6 +1161,27 @@ func (s *autorefreshGatingSuite) TestAffectedByRefreshCandidates(c *C) { }}}) } +func (s *autorefreshGatingSuite) TestAffectingSnapsForAffectedByRefreshCandidates(c *C) { + st := s.state + st.Lock() + defer st.Unlock() + + mockInstalledSnap(c, st, snapAyaml, useHook) + mockInstalledSnap(c, st, snapByaml, useHook) + mockInstalledSnap(c, st, baseSnapByaml, useHook) + + candidates := map[string]*snapstate.RefreshCandidate{ + "snap-a": {}, + "snap-b": {}, + "base-snap-b": {}, + } + st.Set("refresh-candidates", &candidates) + + affecting, err := snapstate.AffectingSnapsForAffectedByRefreshCandidates(st, "snap-b") + c.Assert(err, IsNil) + c.Check(affecting, DeepEquals, []string{"base-snap-b", "snap-b"}) +} + func (s *autorefreshGatingSuite) TestAutorefreshPhase1FeatureFlag(c *C) { st := s.state st.Lock() @@ -1347,8 +1335,6 @@ func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) { c.Assert(tss[1].Tasks(), HasLen, 2) - var snapAhookData, snapBhookData map[string]interface{} - // check hooks for affected snaps seenSnaps := make(map[string]bool) var hs hookstate.HookSetup @@ -1356,30 +1342,11 @@ func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) { c.Assert(task.Get("hook-setup", &hs), IsNil) c.Check(hs.Hook, Equals, "gate-auto-refresh") seenSnaps[hs.Snap] = true - switch hs.Snap { - case "snap-a": - task.Get("hook-context", &snapAhookData) - case "snap-b": - task.Get("hook-context", &snapBhookData) - default: - c.Fatalf("unexpected snap %q", hs.Snap) - } task = tss[1].Tasks()[1] c.Assert(task.Get("hook-setup", &hs), IsNil) c.Check(hs.Hook, Equals, "gate-auto-refresh") seenSnaps[hs.Snap] = true - switch hs.Snap { - case "snap-a": - task.Get("hook-context", &snapAhookData) - case "snap-b": - task.Get("hook-context", &snapBhookData) - default: - c.Fatalf("unexpected snap %q", hs.Snap) - } - - c.Check(snapAhookData["affecting-snaps"], DeepEquals, []interface{}{"snap-a"}) - c.Check(snapBhookData["affecting-snaps"], DeepEquals, []interface{}{"base-snap-b"}) // hook for snap-a because it gets refreshed, for snap-b because its base // gets refreshed. snap-c is refreshed but doesn't have the hook. @@ -1478,12 +1445,6 @@ func (s *autorefreshGatingSuite) TestAffectedByRefreshUsesCurrentSnapInfo(c *C) c.Assert(task.Get("hook-setup", &hs), IsNil) c.Check(hs.Hook, Equals, "gate-auto-refresh") c.Check(hs.Snap, Equals, "snap-b") - var data interface{} - c.Assert(task.Get("hook-context", &data), IsNil) - c.Check(data, DeepEquals, map[string]interface{}{ - "base": true, - "restart": false, - "affecting-snaps": []interface{}{"base-snap-b", "snap-b"}}) // check that refresh-candidates in the state were updated var candidates map[string]*snapstate.RefreshCandidate @@ -2381,14 +2342,6 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnap(c *C) { c.Check(hs.Snap, Equals, "snap-b") c.Check(hs.Optional, Equals, true) - var data interface{} - c.Assert(tasks[1].Get("hook-context", &data), IsNil) - c.Check(data, DeepEquals, map[string]interface{}{ - "base": true, - "restart": false, - "affecting-snaps": []interface{}{"base-snap-b", "snap-b"}, - }) - // last-refresh wasn't modified var lr time.Time st.Get("last-refresh", &lr) @@ -2500,24 +2453,6 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapMoreAffectedSnaps(c c.Check(hs.Hook, Equals, "gate-auto-refresh") c.Check(hs.Optional, Equals, true) seenSnaps[hs.Snap] = true - var data interface{} - c.Assert(tasks[i].Get("hook-context", &data), IsNil) - switch hs.Snap { - case "snap-b": - c.Check(data, DeepEquals, map[string]interface{}{ - "base": true, - "restart": false, - "affecting-snaps": []interface{}{"base-snap-b", "snap-b"}, - }) - case "snap-bb": - c.Check(data, DeepEquals, map[string]interface{}{ - "base": true, - "restart": false, - "affecting-snaps": []interface{}{"base-snap-b"}, - }) - default: - c.Fatalf("unexpected snap %q", hs.Snap) - } } c.Check(seenSnaps, DeepEquals, map[string]bool{ "snap-b": true, diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 70dc3c73f0..8649c76feb 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -579,7 +579,7 @@ var CheckHealthHook = func(st *state.State, snapName string, rev snap.Revision) panic("internal error: snapstate.CheckHealthHook is unset") } -var SetupGateAutoRefreshHook = func(st *state.State, snapName string, base, restart bool, affectingSnaps map[string]bool) *state.Task { +var SetupGateAutoRefreshHook = func(st *state.State, snapName string) *state.Task { panic("internal error: snapstate.SetupAutoRefreshGatingHook is unset") } @@ -2033,7 +2033,12 @@ func autoRefreshPhase1(ctx context.Context, st *state.State, forGatingSnap strin var hooks *state.TaskSet if len(affectedSnaps) > 0 { - hooks = createGateAutoRefreshHooks(st, affectedSnaps) + affected := make([]string, 0, len(affectedSnaps)) + for snapName := range affectedSnaps { + affected = append(affected, snapName) + } + sort.Strings(affected) + hooks = createGateAutoRefreshHooks(st, affected) } // gate-auto-refresh hooks, followed by conditional-auto-refresh task waiting |
