Permalink
...
Checking mergeability…
Don’t worry, you can still create the pull request.
Comparing changes
Open a pull request
- 18 commits
- 18 files changed
- 0 commit comments
- 1 contributor
Unified
Split
Showing
with
520 additions
and 55 deletions.
- +78 −39 overlord/snapstate/handlers.go
- +137 −0 overlord/snapstate/handlers_prereq_test.go
- +5 −0 overlord/snapstate/snapmgr.go
- +44 −0 overlord/snapstate/snapstate.go
- +186 −0 overlord/snapstate/snapstate_test.go
- +1 −0 store/details.go
- +18 −0 store/store.go
- +20 −1 store/store_test.go
- 0 ...snapd-content-plug-empty-content-attr → test-snapd-content-plug-no-content-attr}/bin/content-plug
- 0 ...pd-content-plug-empty-content-attr → test-snapd-content-plug-no-content-attr}/import/.placeholder
- +2 −2 ...t-snapd-content-plug-empty-content-attr → test-snapd-content-plug-no-content-attr}/meta/snap.yaml
- +1 −1 tests/lib/snaps/test-snapd-content-plug/meta/snap.yaml
- +1 −1 ...t-snapd-content-slot-empty-content-attr → test-snapd-content-slot-no-content-attr}/meta/snap.yaml
- 0 ...t-snapd-content-slot-empty-content-attr → test-snapd-content-slot-no-content-attr}/shared-content
- +8 −0 tests/lib/snaps/test-snapd-content-slot2/meta/snap.yaml
- +3 −0 tests/lib/snaps/test-snapd-content-slot2/shared-content
- +7 −7 tests/main/interfaces-content-empty-content-attr/task.yaml
- +9 −4 tests/main/interfaces-content/task.yaml
View
117
overlord/snapstate/handlers.go
| @@ -156,55 +156,94 @@ func (m *SnapManager) doPrerequisites(t *state.Task, _ *tomb.Tomb) error { | ||
| return nil | ||
| } | ||
| - // check prereqs | ||
| - prereqName := defaultCoreSnapName | ||
| + // we need to make sure we install all prereqs together in one | ||
| + // operation | ||
| + wanted := make([]string, len(snapsup.Prereq), len(snapsup.Prereq)+1) | ||
| + copy(wanted, snapsup.Prereq) | ||
| + // append base | ||
| + wanted = append(wanted, defaultCoreSnapName) | ||
| if snapsup.Base != "" { | ||
| - prereqName = snapsup.Base | ||
| + wanted[len(wanted)-1] = snapsup.Base | ||
| } | ||
| - | ||
| - var prereqState SnapState | ||
| - err = Get(st, prereqName, &prereqState) | ||
| - // we have the prereq already | ||
| - if err == nil { | ||
| - return nil | ||
| - } | ||
| - // if it is a real error, report | ||
| - if err != state.ErrNoState { | ||
| + if err := m.installPrereqs(t, wanted, snapsup.UserID); err != nil { | ||
| return err | ||
| } | ||
| - // check that there is no task that installs the prereq already | ||
| - prereqPending, err := changeInFlight(st, prereqName) | ||
| - if err != nil { | ||
| - return err | ||
| - } | ||
| - if prereqPending { | ||
| - // if something else installs core already we need to | ||
| - // wait for that to either finish successfully or fail | ||
| - return &state.Retry{After: prerequisitesRetryTimeout} | ||
| - } | ||
| + return nil | ||
| +} | ||
| - // not installed, nor queued for install -> install it | ||
| - ts, err := Install(st, prereqName, defaultBaseSnapsChannel, snap.R(0), snapsup.UserID, Flags{}) | ||
| - // something might have triggered an explicit install of core while | ||
| - // the state was unlocked -> deal with that here | ||
| - if _, ok := err.(changeDuringInstallError); ok { | ||
| - return &state.Retry{After: prerequisitesRetryTimeout} | ||
| - } | ||
| - if _, ok := err.(changeConflictError); ok { | ||
| - return &state.Retry{After: prerequisitesRetryTimeout} | ||
| - } | ||
| - if err != nil { | ||
| - return err | ||
| +func (m *SnapManager) installPrereqs(t *state.Task, wanted []string, userID int) error { | ||
| + st := t.State() | ||
| + | ||
| + // We try to install all wanted snaps. If one snap cannot be installed | ||
| + // becasue of change conflicts or similar we retry. Only if all snaps | ||
| + // can be installed together we add the tasks to the change. | ||
| + // | ||
| + // FIXME: we need to test for circular wants here. | ||
| + var tss []*state.TaskSet | ||
| + for _, prereqName := range wanted { | ||
| + var prereqState SnapState | ||
| + err := Get(st, prereqName, &prereqState) | ||
| + // we have the prereq already | ||
| + if err == nil { | ||
| + continue | ||
| + } | ||
| + // if it is a real error, report | ||
| + if err != state.ErrNoState { | ||
| + return err | ||
| + } | ||
| + | ||
| + for _, tc := range t.Change().Tasks() { | ||
| + // check if we have already queued it for install | ||
| + if tc.Kind() == "link-snap" { | ||
| + snapsup, err := TaskSnapSetup(tc) | ||
| + if err != nil { | ||
| + return err | ||
| + } | ||
| + if snapsup.Name() == prereqName { | ||
| + continue | ||
| + } | ||
| + } | ||
| + // FIXME: what about discard-snap? | ||
| + } | ||
| + | ||
| + // check that there is no task that installs the prereq already | ||
| + prereqPending, err := changeInFlight(st, prereqName) | ||
| + if err != nil { | ||
| + return err | ||
| + } | ||
| + if prereqPending { | ||
| + // if something else installs core already we need to | ||
| + // wait for that to either finish successfully or fail | ||
| + return &state.Retry{After: prerequisitesRetryTimeout} | ||
| + } | ||
| + | ||
| + // not installed, nor queued for install -> install it | ||
| + ts, err := Install(st, prereqName, defaultBaseSnapsChannel, snap.R(0), userID, Flags{}) | ||
| + // something might have triggered an explicit install of core while | ||
| + // the state was unlocked -> deal with that here | ||
| + if _, ok := err.(changeDuringInstallError); ok { | ||
| + return &state.Retry{After: prerequisitesRetryTimeout} | ||
| + } | ||
| + if _, ok := err.(changeConflictError); ok { | ||
| + return &state.Retry{After: prerequisitesRetryTimeout} | ||
| + } | ||
| + if err != nil { | ||
| + return err | ||
| + } | ||
| + tss = append(tss, ts) | ||
| } | ||
| - ts.JoinLane(st.NewLane()) | ||
| - // inject install for core into this change | ||
| + // inject install for prereq into this change | ||
| chg := t.Change() | ||
| - for _, t := range chg.Tasks() { | ||
| - t.WaitAll(ts) | ||
| + for _, ts := range tss { | ||
| + ts.JoinLane(st.NewLane()) | ||
| + for _, t := range chg.Tasks() { | ||
| + t.WaitAll(ts) | ||
| + } | ||
| + chg.AddAll(ts) | ||
| } | ||
| - chg.AddAll(ts) | ||
| + | ||
| // make sure that the new change is committed to the state | ||
| // together with marking this task done | ||
| t.SetStatus(state.DoneStatus) | ||
View
137
overlord/snapstate/handlers_prereq_test.go
| @@ -0,0 +1,137 @@ | ||
| +// -*- Mode: Go; indent-tabs-mode: t -*- | ||
| + | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +package snapstate_test | ||
| + | ||
| +import ( | ||
| + . "gopkg.in/check.v1" | ||
| + | ||
| + "github.com/snapcore/snapd/overlord/snapstate" | ||
| + "github.com/snapcore/snapd/overlord/state" | ||
| + "github.com/snapcore/snapd/snap" | ||
| +) | ||
| + | ||
| +type prereqSuite struct { | ||
| + state *state.State | ||
| + snapmgr *snapstate.SnapManager | ||
| + fakeStore *fakeStore | ||
| + | ||
| + fakeBackend *fakeSnappyBackend | ||
| + | ||
| + reset func() | ||
| +} | ||
| + | ||
| +var _ = Suite(&prereqSuite{}) | ||
| + | ||
| +func (s *prereqSuite) SetUpTest(c *C) { | ||
| + s.fakeBackend = &fakeSnappyBackend{} | ||
| + s.state = state.New(nil) | ||
| + s.state.Lock() | ||
| + defer s.state.Unlock() | ||
| + | ||
| + s.fakeStore = &fakeStore{ | ||
| + state: s.state, | ||
| + fakeBackend: s.fakeBackend, | ||
| + } | ||
| + snapstate.ReplaceStore(s.state, s.fakeStore) | ||
| + | ||
| + var err error | ||
| + s.snapmgr, err = snapstate.Manager(s.state) | ||
| + c.Assert(err, IsNil) | ||
| + s.snapmgr.AddForeignTaskHandlers(s.fakeBackend) | ||
| + | ||
| + snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend) | ||
| + | ||
| + s.reset = snapstate.MockReadInfo(s.fakeBackend.ReadInfo) | ||
| +} | ||
| + | ||
| +func (s *prereqSuite) TearDownTest(c *C) { | ||
| + s.reset() | ||
| +} | ||
| + | ||
| +func (s *prereqSuite) TestDoPrereqNothingToDo(c *C) { | ||
| + s.state.Lock() | ||
| + | ||
| + si1 := &snap.SideInfo{ | ||
| + RealName: "core", | ||
| + Revision: snap.R(1), | ||
| + } | ||
| + snapstate.Set(s.state, "core", &snapstate.SnapState{ | ||
| + Sequence: []*snap.SideInfo{si1}, | ||
| + Current: si1.Revision, | ||
| + }) | ||
| + | ||
| + t := s.state.NewTask("prerequisites", "test") | ||
| + t.Set("snap-setup", &snapstate.SnapSetup{ | ||
| + SideInfo: &snap.SideInfo{ | ||
| + RealName: "foo", | ||
| + Revision: snap.R(33), | ||
| + }, | ||
| + }) | ||
| + s.state.NewChange("dummy", "...").AddTask(t) | ||
| + s.state.Unlock() | ||
| + | ||
| + s.snapmgr.Ensure() | ||
| + s.snapmgr.Wait() | ||
| + | ||
| + s.state.Lock() | ||
| + defer s.state.Unlock() | ||
| + c.Assert(s.fakeBackend.ops, HasLen, 0) | ||
| + c.Check(t.Status(), Equals, state.DoneStatus) | ||
| +} | ||
| + | ||
| +func (s *prereqSuite) TestDoPrereqTalksToStore(c *C) { | ||
| + s.state.Lock() | ||
| + t := s.state.NewTask("prerequisites", "test") | ||
| + t.Set("snap-setup", &snapstate.SnapSetup{ | ||
| + SideInfo: &snap.SideInfo{ | ||
| + RealName: "foo", | ||
| + Revision: snap.R(33), | ||
| + }, | ||
| + Channel: "beta", | ||
| + Base: "some-base", | ||
| + Prereq: []string{"prereq1", "prereq2"}, | ||
| + }) | ||
| + s.state.NewChange("dummy", "...").AddTask(t) | ||
| + s.state.Unlock() | ||
| + | ||
| + s.snapmgr.Ensure() | ||
| + s.snapmgr.Wait() | ||
| + | ||
| + s.state.Lock() | ||
| + defer s.state.Unlock() | ||
| + c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{ | ||
| + { | ||
| + op: "storesvc-snap", | ||
| + name: "prereq1", | ||
| + revno: snap.R(11), | ||
| + }, | ||
| + { | ||
| + op: "storesvc-snap", | ||
| + name: "prereq2", | ||
| + revno: snap.R(11), | ||
| + }, | ||
| + { | ||
| + op: "storesvc-snap", | ||
| + name: "some-base", | ||
| + revno: snap.R(11), | ||
| + }, | ||
| + }) | ||
| + c.Check(t.Status(), Equals, state.DoneStatus) | ||
| +} |
View
5
overlord/snapstate/snapmgr.go
| @@ -61,6 +61,11 @@ type SnapSetup struct { | ||
| UserID int `json:"user-id,omitempty"` | ||
| Base string `json:"base,omitempty"` | ||
| + // Prereq is a list of snap-names that need to get installed | ||
| + // together with this snap. Typically used when installing | ||
| + // content-snaps with default-providers. | ||
| + Prereq []string `json:"prereq,omitempty"` | ||
| + | ||
| Flags | ||
| SnapPath string `json:"snap-path,omitempty"` | ||
View
44
overlord/snapstate/snapstate.go
| @@ -32,6 +32,7 @@ import ( | ||
| "github.com/snapcore/snapd/interfaces" | ||
| "github.com/snapcore/snapd/logger" | ||
| "github.com/snapcore/snapd/overlord/auth" | ||
| + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" | ||
| "github.com/snapcore/snapd/overlord/snapstate/backend" | ||
| "github.com/snapcore/snapd/overlord/state" | ||
| "github.com/snapcore/snapd/release" | ||
| @@ -440,6 +441,47 @@ func CheckChangeConflict(st *state.State, snapName string, checkConflictPredicat | ||
| return nil | ||
| } | ||
| +func contentAttr(attrs map[string]interface{}) string { | ||
| + // we always have a "content" attr in plug/slots of interface | ||
| + // type "content" | ||
| + s, _ := attrs["content"].(string) | ||
| + return s | ||
| +} | ||
| + | ||
| +func contentIfaceAvailable(st *state.State, contentTag string) bool { | ||
| + repo := ifacerepo.Get(st) | ||
| + for _, slot := range repo.AllSlots("content") { | ||
| + if contentAttr(slot.Attrs) == "" { | ||
| + continue | ||
| + } | ||
| + if contentAttr(slot.Attrs) == contentTag { | ||
| + return true | ||
| + } | ||
| + } | ||
| + return false | ||
| +} | ||
| + | ||
| +// defaultContentPlugProviders takes a snap.Info and returns what | ||
| +// default providers there are. | ||
| +func defaultContentPlugProviders(st *state.State, info *snap.Info) []string { | ||
| + out := []string{} | ||
| + for _, plug := range info.Plugs { | ||
| + if plug.Interface == "content" { | ||
| + if contentAttr(plug.Attrs) == "" { | ||
| + continue | ||
| + } | ||
| + if !contentIfaceAvailable(st, contentAttr(plug.Attrs)) { | ||
| + dprovider, ok := plug.Attrs["default-provider"].(string) | ||
| + if !ok || dprovider == "" { | ||
| + continue | ||
| + } | ||
| + out = append(out, dprovider) | ||
| + } | ||
| + } | ||
| + } | ||
| + return out | ||
| +} | ||
| + | ||
| // InstallPath returns a set of tasks for installing snap from a file path. | ||
| // Note that the state must be locked by the caller. | ||
| // The provided SideInfo can contain just a name which results in a | ||
| @@ -479,6 +521,7 @@ func InstallPath(st *state.State, si *snap.SideInfo, path, channel string, flags | ||
| snapsup := &SnapSetup{ | ||
| Base: info.Base, | ||
| + Prereq: defaultContentPlugProviders(st, info), | ||
| SideInfo: si, | ||
| SnapPath: path, | ||
| Channel: channel, | ||
| @@ -524,6 +567,7 @@ func Install(st *state.State, name, channel string, revision snap.Revision, user | ||
| snapsup := &SnapSetup{ | ||
| Channel: channel, | ||
| Base: info.Base, | ||
| + Prereq: defaultContentPlugProviders(st, info), | ||
| UserID: userID, | ||
| Flags: flags.ForSnapSetup(), | ||
| DownloadInfo: &info.DownloadInfo, | ||
Oops, something went wrong.