From d1fbf4b8a4bc06fb87fe257aa1fbbcb987866c06 Mon Sep 17 00:00:00 2001 From: ernestl Date: Tue, 9 Jul 2024 17:57:48 +0200 Subject: [PATCH 1/2] overlord, overlord/snapstate: auto-install snapd on seeded classic systems --- overlord/managers_test.go | 27 +++- overlord/snapstate/aliasesv2_test.go | 1 + overlord/snapstate/backend_test.go | 2 +- overlord/snapstate/handlers.go | 15 ++- overlord/snapstate/handlers_prereq_test.go | 122 ++++++++++++++----- overlord/snapstate/snapstate_install_test.go | 12 ++ overlord/snapstate/snapstate_test.go | 46 ++++++- 7 files changed, 183 insertions(+), 42 deletions(-) diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 3ec083a0ec5..98de8657421 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -465,6 +465,19 @@ func (s *baseMgrsSuite) SetUpTest(c *C) { }, }) + // add snapd itself + snapstate.Set(st, "snapd", &snapstate.SnapState{ + Active: true, + Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ + {RealName: "snapd", SnapID: fakeSnapID("snapd"), Revision: snap.R(1)}, + }), + Current: snap.R(1), + SnapType: "snapd", + Flags: snapstate.Flags{ + Required: true, + }, + }) + // commonly used core and snapd revisions in tests defaultInfoFile := ` VERSION=2.54.3+git1.g479e745-dirty @@ -575,6 +588,13 @@ func (s *mgrsSuiteCore) SetUpTest(c *C) { // it panicking. restore := release.MockOnClassic(false) s.baseMgrsSuite.SetUpTest(c) + + // remove snapd snap added for baseMgrsSuite + st := s.o.State() + st.Lock() + snapstate.Set(st, "snapd", nil) + st.Unlock() + s.AddCleanup(restore) } @@ -712,9 +732,11 @@ hooks: // nothing in snaps all, err := snapstate.All(st) c.Assert(err, IsNil) - c.Check(all, HasLen, 1) + c.Check(all, HasLen, 2) _, ok := all["core"] c.Check(ok, Equals, true) + _, ok = all["snapd"] + c.Check(ok, Equals, true) // nothing in config var config map[string]*json.RawMessage @@ -6061,6 +6083,9 @@ func (s *kernelSuite) SetUpTest(c *C) { st.Lock() defer st.Unlock() + // remove snapd snap added for baseMgrsSuite + snapstate.Set(st, "snapd", nil) + // create/set custom model assertion model := s.brands.Model("can0nical", "my-model", modelDefaults) devicestatetest.SetDevice(st, &auth.DeviceState{ diff --git a/overlord/snapstate/aliasesv2_test.go b/overlord/snapstate/aliasesv2_test.go index c62ab3a9a44..6a81dcea859 100644 --- a/overlord/snapstate/aliasesv2_test.go +++ b/overlord/snapstate/aliasesv2_test.go @@ -248,6 +248,7 @@ func (s *snapmgrTestSuite) TestAutoAliasesDeltaAll(c *C) { c.Check(seen, DeepEquals, map[string]bool{ "core": true, + "snapd": true, "alias-snap": true, "other-snap": true, "alias-snap-apps-go-away": true, diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go index 930ab0a0dbd..fdfce8c1bef 100644 --- a/overlord/snapstate/backend_test.go +++ b/overlord/snapstate/backend_test.go @@ -261,7 +261,7 @@ func (f *fakeStore) snap(spec snapSpec) (*snap.Info, error) { switch spec.Name { case "core", "core16", "ubuntu-core", "some-core": typ = snap.TypeOS - case "some-base", "other-base", "some-other-base", "yet-another-base", "core18", "core22": + case "some-base", "other-base", "some-other-base", "yet-another-base", "core18", "core20", "core22", "core24", "bare": typ = snap.TypeBase case "some-kernel": typ = snap.TypeKernel diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go index 5827a0bad99..aa1d1628baa 100644 --- a/overlord/snapstate/handlers.go +++ b/overlord/snapstate/handlers.go @@ -560,18 +560,23 @@ func (m *SnapManager) installPrereqs(t *state.Task, base string, prereq map[stri } } - // on systems without core or snapd need to install snapd to - // make interfaces work - LP: 1819318 + // On classic systems that are already seeded, automatically + // install snapd snap (covers LP: 1819318). Not allowed for + // Ubuntu Core systems - requires remodeling. var tsSnapd *state.TaskSet snapdSnapInstalled, err := isInstalled(st, "snapd") if err != nil { return err } - coreSnapInstalled, err := isInstalled(st, "core") - if err != nil { + + // consider the state of seeding to avoid seed conflict error + var seeded bool + err = st.Get("seeded", &seeded) + if err != nil && !errors.Is(err, state.ErrNoState) { return err } - if base != "core" && !snapdSnapInstalled && !coreSnapInstalled { + + if release.OnClassic && seeded && !snapdSnapInstalled { timings.Run(tm, "install-prereq", "install snapd", func(timings.Measurer) { noTypeBaseCheck := false tsSnapd, err = m.installOneBaseOrRequired(t, "snapd", nil, noTypeBaseCheck, defaultSnapdSnapsChannel(), onInFlightErr, userID, Flags{ diff --git a/overlord/snapstate/handlers_prereq_test.go b/overlord/snapstate/handlers_prereq_test.go index a6e52e1e951..df0859d2db0 100644 --- a/overlord/snapstate/handlers_prereq_test.go +++ b/overlord/snapstate/handlers_prereq_test.go @@ -91,13 +91,45 @@ func (s *prereqSuite) SetUpTest(c *C) { func (s *prereqSuite) TestDoPrereqNothingToDo(c *C) { s.state.Lock() - si1 := &snap.SideInfo{ - RealName: "core", - Revision: snap.R(1), - } + // install snapd so that prerequisites handler won't try to install it + snapstate.Set(s.state, "snapd", &snapstate.SnapState{ + Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ + {RealName: "snapd", Revision: snap.R(1)}, + }), + Current: snap.R(1), + }) + + t := s.state.NewTask("prerequisites", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{ + RealName: "foo", + Revision: snap.R(33), + }, + Base: "none", + }) + s.state.NewChange("sample", "...").AddTask(t) + s.state.Unlock() + + s.se.Ensure() + s.se.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) TestDoPrereqNothingToDoOnCore(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + s.state.Lock() + snapstate.Set(s.state, "core", &snapstate.SnapState{ - Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{si1}), - Current: si1.Revision, + Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ + {RealName: "core", Revision: snap.R(1)}, + }), + Current: snap.R(1), }) t := s.state.NewTask("prerequisites", "test") @@ -106,6 +138,7 @@ func (s *prereqSuite) TestDoPrereqNothingToDo(c *C) { RealName: "foo", Revision: snap.R(33), }, + Base: "core", }) s.state.NewChange("sample", "...").AddTask(t) s.state.Unlock() @@ -302,11 +335,23 @@ func (s *prereqSuite) TestDoPrereqTalksToStoreAndQueues(c *C) { }, revno: snap.R(11), }, + { + op: "storesvc-snap-action", + }, + { + op: "storesvc-snap-action:action", + action: store.SnapAction{ + Action: "install", + InstanceName: "snapd", + Channel: "stable", + }, + revno: snap.R(11), + }, }) c.Check(t.Status(), Equals, state.DoneStatus) // check that the do-prereq task added all needed prereqs - expectedLinkedSnaps := []string{"prereq1", "prereq2", "some-base"} + expectedLinkedSnaps := []string{"prereq1", "prereq2", "some-base", "snapd"} linkedSnaps := make([]string, 0, len(expectedLinkedSnaps)) for _, t := range chg.Tasks() { if t.Kind() == "link-snap" { @@ -363,6 +408,14 @@ func (s *prereqSuite) TestDoPrereqRetryWhenBaseInFlight(c *C) { }, }) + // install snapd so that prerequisites handler won't try to install it + snapstate.Set(s.state, "snapd", &snapstate.SnapState{ + Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ + {RealName: "snapd", Revision: snap.R(1)}, + }), + Current: snap.R(1), + }) + // pretend foo gets installed and needs core (which is in progress) prereqTask = s.state.NewTask("prerequisites", "foo") prereqTask.Set("snap-setup", &snapstate.SnapSetup{ @@ -480,10 +533,11 @@ func (s *prereqSuite) TestDoPrereqChannelEnvvars(c *C) { defer os.Unsetenv("SNAPD_PREREQS_CHANNEL") s.state.Lock() - snapstate.Set(s.state, "core", &snapstate.SnapState{ + // install snapd so that prerequisites handler won't try to install it + snapstate.Set(s.state, "snapd", &snapstate.SnapState{ Active: true, Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ - {RealName: "core", Revision: snap.R(1)}, + {RealName: "snapd", Revision: snap.R(1)}, }), Current: snap.R(1), SnapType: "os", @@ -602,7 +656,10 @@ func (s *prereqSuite) TestDoPrereqNothingToDoForSnapdSnap(c *C) { s.state.Unlock() } -func (s *prereqSuite) TestDoPrereqCore16wCoreNothingToDo(c *C) { +func (s *prereqSuite) TestDoPrereqCore16WithCoreNothingToDoOnCore(c *C) { + restore := release.MockOnClassic(false) + defer restore() + s.state.Lock() si1 := &snap.SideInfo{ @@ -634,12 +691,8 @@ func (s *prereqSuite) TestDoPrereqCore16wCoreNothingToDo(c *C) { c.Check(t.Status(), Equals, state.DoneStatus) } -func (s *prereqSuite) testDoPrereqNoCorePullsInSnaps(c *C, base string) { - restore := release.MockOnClassic(true) - defer restore() - +func (s *prereqSuite) testDoPrereqBasePullsInSnapd(c *C, base string) { s.state.Lock() - t := s.state.NewTask("prerequisites", "test") t.Set("snap-setup", &snapstate.SnapSetup{ SideInfo: &snap.SideInfo{ @@ -687,16 +740,32 @@ func (s *prereqSuite) testDoPrereqNoCorePullsInSnaps(c *C, base string) { c.Check(t.Status(), Equals, state.DoneStatus) } -func (s *prereqSuite) TestDoPrereqCore16noCore(c *C) { - s.testDoPrereqNoCorePullsInSnaps(c, "core16") +func (s *prereqSuite) TestDoPrereqCorePullsInSnapd(c *C) { + s.testDoPrereqBasePullsInSnapd(c, "core") +} + +func (s *prereqSuite) TestDoPrereqCore16PullsInSnapd(c *C) { + s.testDoPrereqBasePullsInSnapd(c, "core16") } -func (s *prereqSuite) TestDoPrereqCore18NoCorePullsInSnapd(c *C) { - s.testDoPrereqNoCorePullsInSnaps(c, "core18") +func (s *prereqSuite) TestDoPrereqCore18PullsInSnapd(c *C) { + s.testDoPrereqBasePullsInSnapd(c, "core18") } -func (s *prereqSuite) TestDoPrereqOtherBaseNoCorePullsInSnapd(c *C) { - s.testDoPrereqNoCorePullsInSnaps(c, "some-base") +func (s *prereqSuite) TestDoPrereqCore20PullsInSnapd(c *C) { + s.testDoPrereqBasePullsInSnapd(c, "core20") +} + +func (s *prereqSuite) TestDoPrereqCore22PullsInSnapd(c *C) { + s.testDoPrereqBasePullsInSnapd(c, "core22") +} + +func (s *prereqSuite) TestDoPrereqCore24PullsInSnapd(c *C) { + s.testDoPrereqBasePullsInSnapd(c, "core24") +} + +func (s *prereqSuite) TestDoPrereqOtherBasePullsInSnapd(c *C) { + s.testDoPrereqBasePullsInSnapd(c, "other-base") } func (s *prereqSuite) TestDoPrereqBaseIsNotBase(c *C) { @@ -914,17 +983,6 @@ func (s *prereqSuite) TestDoPrereqSkipDuringRemodel(c *C) { // ChangeConflictError, which is then ignored, making this test invalid snapstate.ReplaceStore(s.state, storetest.Store{}) - // install snapd so that prerequisites handler won't try to install it - snapstate.Set(s.state, "snapd", &snapstate.SnapState{ - Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ - { - RealName: "snapd", - Revision: snap.R(1), - }, - }), - Current: snap.R(1), - }) - prereqTask := s.state.NewTask("prerequisites", "test") prereqTask.Set("snap-setup", &snapstate.SnapSetup{ SideInfo: &snap.SideInfo{ diff --git a/overlord/snapstate/snapstate_install_test.go b/overlord/snapstate/snapstate_install_test.go index 223254ca40f..00285f08b78 100644 --- a/overlord/snapstate/snapstate_install_test.go +++ b/overlord/snapstate/snapstate_install_test.go @@ -386,6 +386,9 @@ func (s *snapmgrTestSuite) TestInstallSnapdSnapTypeOnClassic(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + // setup a classic model so the device context says we are on classic defer snapstatetest.MockDeviceModel(ClassicModel())() @@ -407,6 +410,9 @@ func (s *snapmgrTestSuite) TestInstallSnapdSnapTypeOnCore(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + opts := &snapstate.RevisionOptions{Channel: "some-channel"} ts, err := snapstate.Install(context.Background(), s.state, "snapd", opts, 0, snapstate.Flags{}) c.Assert(err, IsNil) @@ -530,6 +536,9 @@ func (s *snapmgrTestSuite) TestInstallWithDeviceContextNoRemodelConflict(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + // unset the global store, it will need to come via the device context snapstate.ReplaceStore(s.state, nil) @@ -926,6 +935,9 @@ func (s *snapmgrTestSuite) TestInstallSnapdConflict(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + tugc := s.state.NewTask("update-gadget-cmdline", "update gadget cmdline") chg := s.state.NewChange("optional-kernel-cmdline", "optional kernel cmdline") chg.AddTask(tugc) diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index 175b3734cf1..52fd78964d3 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -313,6 +313,14 @@ func (s *snapmgrBaseTest) SetUpTest(c *C) { Current: snap.R(1), SnapType: "os", }) + snapstate.Set(s.state, "snapd", &snapstate.SnapState{ + Active: true, + Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ + {RealName: "snapd", Revision: snap.R(1)}, + }), + Current: snap.R(1), + SnapType: "snapd", + }) // commonly used revisions in tests defaultInfoFile := ` @@ -5926,6 +5934,9 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapDoesRunWithAnySnap(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + tr := config.NewTransaction(s.state) tr.Set("core", "experimental.snapd-snap", true) tr.Commit() @@ -5963,6 +5974,9 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapStartsAutomaticallyWhenEnabled s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + snapstate.Set(s.state, "core", &snapstate.SnapState{ Active: true, Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "beta"}}), @@ -5975,13 +5989,13 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapStartsAutomaticallyWhenEnabled s.settle(c) - c.Check(s.state.Changes(), HasLen, 1) + c.Assert(s.state.Changes(), HasLen, 1) chg := s.state.Changes()[0] c.Check(chg.Kind(), Equals, "transition-to-snapd-snap") c.Assert(chg.Err(), IsNil) c.Assert(chg.IsReady(), Equals, true) - // snapd snap is instaleld from the default channel + // snapd snap is installed from the default channel var snapst snapstate.SnapState snapstate.Get(s.state, "snapd", &snapst) c.Assert(snapst.TrackingChannel, Equals, "latest/stable") @@ -5991,6 +6005,9 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapWithCoreRunthrough(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + // setup a classic model so the device context says we are on classic defer snapstatetest.MockDeviceModel(ClassicModel())() @@ -6029,6 +6046,9 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapTimeLimitWorks(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + tr := config.NewTransaction(s.state) tr.Set("core", "experimental.snapd-snap", true) tr.Commit() @@ -6068,6 +6088,9 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapError(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + snapstate.ReplaceStore(s.state, unhappyStore{fakeStore: s.fakeStore}) tr := config.NewTransaction(s.state) @@ -6238,6 +6261,7 @@ func (s *snapmgrTestSuite) TestEnsureAliasesV2(c *C) { } snapstate.Set(s.state, "core", nil) + snapstate.Set(s.state, "snapd", nil) snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{ Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ {RealName: "alias-snap", Revision: snap.R(11)}, @@ -6306,6 +6330,7 @@ func (s *snapmgrTestSuite) TestEnsureAliasesV2SnapDisabled(c *C) { } snapstate.Set(s.state, "core", nil) + snapstate.Set(s.state, "snapd", nil) snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{ Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{ {RealName: "alias-snap", Revision: snap.R(11)}, @@ -6833,6 +6858,9 @@ func (s *snapmgrTestSuite) TestSnapdSnapOnCoreWithoutBase(c *C) { r := release.MockOnClassic(false) defer r() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + // it is now possible to install snapd snap on a system with core _, err := snapstate.Install(context.Background(), s.state, "snapd", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) c.Assert(err, IsNil) @@ -6844,6 +6872,9 @@ func (s *snapmgrTestSuite) TestSnapdSnapOnSystemsWithoutBaseOnUbuntuCore(c *C) { r := release.MockOnClassic(false) defer r() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + // it is not possible to opt-into the snapd snap on core yet tr := config.NewTransaction(s.state) tr.Set("core", "experimental.snapd-snap", true) @@ -6858,6 +6889,9 @@ func (s *snapmgrTestSuite) TestNoSnapdSnapOnSystemsWithoutBaseButOption(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + tr := config.NewTransaction(s.state) tr.Set("core", "experimental.snapd-snap", true) tr.Commit() @@ -6870,6 +6904,9 @@ func (s *snapmgrTestSuite) TestNoConfigureForSnapdSnap(c *C) { s.state.Lock() defer s.state.Unlock() + // remove snapd snap added for snapmgrBaseTest + snapstate.Set(s.state, "snapd", nil) + // snapd cannot be installed unless the model uses a base snap r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) defer r() @@ -7783,8 +7820,10 @@ func (s *snapmgrTestSuite) TestInstallModeDisableFreshInstallEnabledByHook(c *C) var snapst snapstate.SnapState st.Lock() err := snapstate.Get(st, "services-snap", &snapst) + // Use Check here, Assert causes deadlock + c.Check(err, IsNil) st.Unlock() - c.Assert(err, IsNil) + snapst.ServicesEnabledByHooks = []string{"svcInstallModeDisable"} st.Lock() snapstate.Set(st, "services-snap", &snapst) @@ -8769,6 +8808,7 @@ func (s *snapmgrTestSuite) TestResolveValidationSetsEnforcementError(c *C) { c.Check(pinned, DeepEquals, pinnedSeqs) c.Check(snaps, testutil.DeepUnsortedMatches, []*snapasserts.InstalledSnap{ {SnapRef: naming.NewSnapRef("core", ""), Revision: snap.R(1)}, + {SnapRef: naming.NewSnapRef("snapd", ""), Revision: snap.R(1)}, {SnapRef: naming.NewSnapRef("some-other-snap", "some-other-snap-id"), Revision: snap.R(2)}, {SnapRef: naming.NewSnapRef("some-snap", "some-snap-id"), Revision: snap.R(1)}}) c.Check(snapsToIgnore, HasLen, 0) From b13af6b0573635dd578d7252d9472f0354f66a2e Mon Sep 17 00:00:00 2001 From: ernestl Date: Thu, 12 Sep 2024 00:51:33 +0200 Subject: [PATCH 2/2] tests: add auto-install transition test --- tests/lib/prepare.sh | 2 +- .../connections-reference.txt | 43 ++++++ .../task.yaml | 132 ++++++++++++++++++ .../task.yaml | 22 ++- 4 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 tests/main/snapd-snap-transition-auto-install/connections-reference.txt create mode 100644 tests/main/snapd-snap-transition-auto-install/task.yaml rename tests/main/{snapd-snap-transition => snapd-snap-transition-experimental-flag}/task.yaml (53%) diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh index 096e8fd318d..10605402580 100755 --- a/tests/lib/prepare.sh +++ b/tests/lib/prepare.sh @@ -401,7 +401,7 @@ prepare_classic() { # This also prevents snapd from automatically installing snapd snap as # prerequisite for installing any non-base snap introduced in PR#14173. if snap list snapd ; then - snap snap info snapd + snap info snapd echo "Error: not expecting snapd snap to be installed" exit 1 else diff --git a/tests/main/snapd-snap-transition-auto-install/connections-reference.txt b/tests/main/snapd-snap-transition-auto-install/connections-reference.txt new file mode 100644 index 00000000000..8d8eb4270b3 --- /dev/null +++ b/tests/main/snapd-snap-transition-auto-install/connections-reference.txt @@ -0,0 +1,43 @@ +Interface Plug Slot Notes +audio-playback firefox:audio-playback :audio-playback - +audio-record firefox:audio-record :audio-record - +avahi-observe firefox:avahi-observe :avahi-observe - +bluez fwupd:bluez :bluez - +browser-support firefox:browser-sandbox :browser-support - +camera firefox:camera :camera - +content[gnome-42-2204] firefox:gnome-42-2204 gnome-42-2204:gnome-42-2204 - +content[gtk-3-themes] firefox:gtk-3-themes gtk-common-themes:gtk-3-themes - +content[icon-themes] firefox:icon-themes gtk-common-themes:icon-themes - +content[sound-themes] firefox:sound-themes gtk-common-themes:sound-themes - +cups-control firefox:cups-control :cups-control - +desktop firefox:desktop :desktop - +desktop-legacy firefox:desktop-legacy :desktop-legacy - +gsettings firefox:gsettings :gsettings - +hardware-observe firefox:hardware-observe :hardware-observe - +hardware-observe fwupd:hardware-observe :hardware-observe - +home firefox:home :home - +home fwupd:home :home - +joystick firefox:joystick :joystick - +login-session-observe firefox:login-session-observe :login-session-observe - +mount-control firefox:host-hunspell :mount-control - +network firefox:network :network - +network fwupd:network :network - +network-bind firefox:network-bind :network-bind - +opengl firefox:opengl :opengl - +opengl fwupd:opengl :opengl - +personal-files firefox:dot-mozilla-firefox :personal-files - +polkit fwupd:polkit :polkit - +raw-usb fwupd:raw-usb :raw-usb - +removable-media firefox:removable-media :removable-media - +screen-inhibit-control firefox:screen-inhibit-control :screen-inhibit-control - +shutdown fwupd:shutdown :shutdown - +system-files firefox:etc-firefox :system-files - +system-files firefox:host-usr-share-hunspell :system-files - +system-packages-doc firefox:system-packages-doc :system-packages-doc - +u2f-devices firefox:u2f-devices :u2f-devices - +udisks2 fwupd:udisks2 :udisks2 - +unity7 firefox:unity7 :unity7 - +upower-observe firefox:upower-observe :upower-observe - +upower-observe fwupd:upower-observe :upower-observe - +wayland firefox:wayland :wayland - +x11 firefox:x11 :x11 - diff --git a/tests/main/snapd-snap-transition-auto-install/task.yaml b/tests/main/snapd-snap-transition-auto-install/task.yaml new file mode 100644 index 00000000000..26fbb6bb49e --- /dev/null +++ b/tests/main/snapd-snap-transition-auto-install/task.yaml @@ -0,0 +1,132 @@ +summary: Ensure auto-install based transition to snapd snap works. + +details: | + Ensure that we can transition classic systems to snapd snap + with auto-install of snapd snap from snapd deb or snapd in core + triggered by any snap install that is not of type os, base, + kernel, gadget or snapd. This change was introduced in PR14173. + +# Exclude Ubuntu Core systems - require proper remodeling +# Simplify test by limiting system to ubuntu 22.04 amd64 +systems: [ubuntu-22.04-64] + +environment: + SNAPD_SRC/deb: "deb" + SNAPD_SRC/core: "core" + +prepare: | + # Expect system preparation stage installed built snapd and core snaps. + # Check this and remove all snaps to remove snapd. + snap list core + snap list snapd + snap remove core + snap remove snapd + # at this point we expect to be running from the deb + systemctl restart snapd.service + + # Install deb + core before introduction of snapd auto-install + # on classic systems. + # deb: snapd | Aug 2024 | 2.63+22.04ubuntu0.1 | http://archive.ubuntu.com/ubuntu/pool/main/s/snapd/2.63+22.04ubuntu0.1_amd64.deb + # deb: snapd | Apr 2022 | 2.55.3+22.04 | http://archive.ubuntu.com/ubuntu/pool/main/s/snapd/2.55.3+22.04_amd64.deb + # snap: core | Jun 2024 | 16-2.61.4-20240607 (rev 17200) | https://snapcraft.io/core/releases + + # 'deb' : transition snapd deb to re-exec from snapd snap + # 'core': transition re-exec from snapd in core snap to re-exec from snapd snap + if [ "$SNAPD_SRC" = "deb" ]; then + # core < deb, exec snapd deb + apt install -y --allow-downgrades snapd=2.63+22.04ubuntu0.1 + else + # core => deb, re-exec snapd from core snap + apt install -y --allow-downgrades snapd=2.55.3+22.04 + fi + snap install core --revision=17200 # 16-2.61.4-20240607 + + # installation of core should not trigger prerequisites, but let's double check snapd is not installed + not snap list snapd + +execute: | + # check re-exec disabled, exec from snapd deb or re-exec from core snap + snap_mount_dir="$(os.paths snap-mount-dir)" + expect="" + if [ "${SNAP_REEXEC:-}" = "0" ]; then + echo "Ensure re-exec disabled" + expect="DEBUG: re-exec disabled by user" + elif [ "$SNAPD_SRC" = "deb" ]; then + echo "Ensure exec from snapd deb" + expect="DEBUG: snap \(at \"$snap_mount_dir/core/current\"\) is older \(\"2\.61\.4\"\) than distribution package \(\"2\.63\+22\.04ubuntu0\.1\"\)" + else + echo "Ensure re-exec from core snap" + expect="DEBUG: restarting into \"$snap_mount_dir/core/current/usr/bin/snap\"" + fi + SNAPD_DEBUG=1 snap list 2>&1 | MATCH "$expect" + + # Install snaps with snaps with complex interface requirements. + # Use specific revisions to pin expected behaviour. + install_snaps() { + snap install firefox --revision=4848 # 130.0-2 + snap install modem-manager --revision=541 # 1.20.0-2 + snap install fwupd --revision=6368 # 1.9.23-12-g92df65c6a + } + + # Trigger transition to snapd by installing either deb or core snap that supports snapd auto-install. + # For both the deb and the core snap, installation of snapd snap is expected to follow after + # installation of any snap. + change_marker=$(snap changes | wc -l) + expected_changes_after_mark="" + expected_snap_with_snapd_prereq="" + if [ "$SNAPD_SRC" = "deb" ]; then + echo "Update snapd deb and install snaps to trigger auto-install of snapd" + # install snapd deb built during image preparation + sudo dpkg -i "${GOHOME}"/snapd_*.deb + + # Install snaps after updating snapd deb to exercise the post PR14173 logic for installing snapd snap. + # The first snap install will trigger the snapd auto-install. + install_snaps + expected_changes_after_mark="3" + expected_snap_with_snapd_prereq="firefox" + else + echo "Install snaps, update core snap and trigger auto-install of snapd with install of hello-world snap" + # install snaps before updating core snap to exercise transition of system slots and plugs + # from provided by core snap to provided by snapd snap + install_snaps + + # check connections + snap connections > /tmp/connections-actual.txt + diff -u ./connections-reference.txt /tmp/connections-actual.txt + + # install core snap built during image preparation + snap install --dangerous "$TESTSTMP"/core_snap/core_*.snap + + # snapd auto-install should happen as prerequisite for hello-world + snap install hello-world + expected_changes_after_mark="5" + expected_snap_with_snapd_prereq="hello-world" + fi + + echo "Ensure no change errors during installation" + snap changes | tail -n +"$change_marker" | NOMATCH "Error" + test "$(snap changes | tail -n +"$change_marker" | grep -c "Done")" = "$expected_changes_after_mark" + + echo "Ensure snapd snap was successfully automatically installed" + snap list snapd + change=$(snap changes | tail -n +"$change_marker" | grep "Install \"$expected_snap_with_snapd_prereq\"" | awk '{print $1}') + snap change "$change" | NOMATCH "Error" + snap change "$change" | MATCH "INFO Requested daemon restart \(snapd snap\)." + snap change "$change" | MATCH "Automatically connect eligible plugs and slots of snap \"snapd\"" + + # check re-exec disabled, exec from snapd deb or re-exec from snapd snap + if [ "${SNAP_REEXEC:-}" = "0" ]; then + echo "Ensure re-exec disabled" + expect="DEBUG: re-exec disabled by user" + elif [ "$SNAPD_SRC" = "deb" ]; then + echo "Ensure exec from snapd deb" + expect="DEBUG: snap \(at \"$snap_mount_dir/snapd/current\"\) is older \(\"2\.[0-9.]*\"\) than distribution package \(\"1337.[0-9.]*\"\)" + else + echo "Ensure re-exec from snapd snap" + expect="DEBUG: restarting into \"$snap_mount_dir/snapd/current/usr/bin/snap\"" + fi + SNAPD_DEBUG=1 snap list 2>&1 | MATCH "$expect" + + echo "Ensure expected interface connections" + snap connections > /tmp/connections-actual.txt + diff -u ./connections-reference.txt /tmp/connections-actual.txt diff --git a/tests/main/snapd-snap-transition/task.yaml b/tests/main/snapd-snap-transition-experimental-flag/task.yaml similarity index 53% rename from tests/main/snapd-snap-transition/task.yaml rename to tests/main/snapd-snap-transition-experimental-flag/task.yaml index bd02f569c02..dc2153fdee2 100644 --- a/tests/main/snapd-snap-transition/task.yaml +++ b/tests/main/snapd-snap-transition-experimental-flag/task.yaml @@ -1,25 +1,24 @@ -summary: Ensure the snapd snap transition works +summary: Ensure snapd-snap experimental flag based transition to snapd snap works. details: | - Ensure the snapd snap transition feature works + Ensure that we can transition classic systems from core snap + to snapd snap using the snapd-snap experimental flag. -# ubuntu-core-18+ already has the snapd snap -# FIXME: ubuntu-core-16 needs special code for the transition -systems: [-ubuntu-core-18-*, -ubuntu-core-2*, -ubuntu-core-16-*] +# Exclude Ubuntu Core systems - require proper remodeling +systems: [-ubuntu-core-*] -execute: | - echo "Ensure no snapd snap is installed" +prepare: | # remove all snaps to remove snapd snap remove core # this is only possible when snapd is the only installed snap snap remove snapd - # we should now be running from the distro package + # at this point we expect to be running from the distro package systemctl restart snapd.service - # TODO the test should install the snapd snap we built + # transition from core snap to snapd snap snap install --dangerous "$TESTSTMP"/core_snap/core_*.snap - - echo "Enable the snapd snap" +execute: | + echo "Enable the snapd snap experimental feature" snap set core experimental.snapd-snap=true for _ in $(seq 30); do @@ -31,4 +30,3 @@ execute: | done snap list snapd snap changes | MATCH "Transition to the snapd snap" -