summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2021-08-27 12:58:09 +0200
committerGitHub <noreply@github.com>2021-08-27 12:58:09 +0200
commit266dec06f617b70359ada078addbdcd97b08a73b (patch)
tree45012d372bf009c7befa8e91992aff18c4c89c8f
parent0593f9244df93fc2299473bb0b2db1b34b1f8305 (diff)
parentfe438d1870a2d2680485ee88a27acfc72df49bdd (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.go3
-rw-r--r--overlord/hookstate/ctlcmd/refresh.go41
-rw-r--r--overlord/hookstate/ctlcmd/refresh_test.go104
-rw-r--r--overlord/hookstate/hooks.go22
-rw-r--r--overlord/hookstate/hooks_test.go80
-rw-r--r--overlord/snapstate/autorefresh_gating.go34
-rw-r--r--overlord/snapstate/autorefresh_gating_test.go109
-rw-r--r--overlord/snapstate/snapstate.go9
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