Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

snap-preseed: support for preseeding of snapd and core18 #8170

Merged
merged 13 commits into from Feb 28, 2020
Merged
20 changes: 16 additions & 4 deletions cmd/snap-preseed/main_test.go
Expand Up @@ -214,6 +214,22 @@ func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) {
c.Check(path, Equals, "/some/path/core")
}

func (s *startPreseedSuite) TestSystemSnapFromSnapdSeed(c *C) {
tmpDir := c.MkDir()

restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
return &Fake16Seed{
Essential: []*seed.Snap{{Path: "/some/path/snapd.snap", SideInfo: &snap.SideInfo{RealName: "snapd"}}},
UsesSnapd: true,
}, nil
})
defer restore()

path, err := main.SystemSnapFromSeed(tmpDir)
c.Assert(err, IsNil)
c.Check(path, Equals, "/some/path/snapd.snap")
}

func (s *startPreseedSuite) TestSystemSnapFromSeedOpenError(c *C) {
tmpDir := c.MkDir()

Expand All @@ -240,10 +256,6 @@ func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) {
_, err = main.SystemSnapFromSeed(tmpDir)
c.Assert(err, ErrorMatches, "core snap not found")

fakeSeed.UsesSnapd = true
_, err = main.SystemSnapFromSeed(tmpDir)
c.Assert(err, ErrorMatches, "preseeding with snapd snap is not supported yet")

fakeSeed.LoadMetaErr = fmt.Errorf("load meta failed")
_, err = main.SystemSnapFromSeed(tmpDir)
c.Assert(err, ErrorMatches, "load meta failed")
Expand Down
19 changes: 11 additions & 8 deletions cmd/snap-preseed/preseed_linux.go
Expand Up @@ -83,24 +83,27 @@ var systemSnapFromSeed = func(rootDir string) (string, error) {
return "", err
}

// TODO: handle core18, snapd snap.
var required string
if seed.UsesSnapdSnap() {
return "", fmt.Errorf("preseeding with snapd snap is not supported yet")
required = "snapd"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably still fail here if the model is not classic ? that needs its own tests and need to deal with wrappers/core18.go, no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm you're right, and I missed wrappers for core18, definately needs a close look, probably needs extra checks in the spread test too. Thanks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be clear the code in wrappers/core18.go is a nop for classic afair

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it's a noop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably still fail here if the model is not classic ? that needs its own tests and need to deal with wrappers/core18.go, no ?

Added error on non-classic + test.

} else {
required = "core"
}

var coreSnapPath string
var snapPath string
ess := seed.EssentialSnaps()
if len(ess) > 0 {
if ess[0].SnapName() == "core" {
coreSnapPath = ess[0].Path
// core / snapd snap is the first essential snap.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if ess[0].SnapName() == required {
snapPath = ess[0].Path
}
}

if coreSnapPath == "" {
return "", fmt.Errorf("core snap not found")
if snapPath == "" {
return "", fmt.Errorf("%s snap not found", required)
}

return coreSnapPath, nil
return snapPath, nil
}

func prepareChroot(preseedChroot string) (func(), error) {
Expand Down
116 changes: 116 additions & 0 deletions overlord/devicestate/firstboot_preseed_test.go
Expand Up @@ -102,6 +102,7 @@ func checkPreseedTaskStates(c *C, st *state.State) {
c.Fatalf("unhandled task kind %s", t.Kind())
}
}

// sanity: check that doneTasks is not declaring more tasks than
// actually expected.
c.Check(doneTasks, DeepEquals, seenDone)
Expand Down Expand Up @@ -183,6 +184,7 @@ func checkPreseedOrder(c *C, tsAll []*state.TaskSet, snaps ...string) {
c.Check(waitTasks, HasLen, 0)
} else {
c.Assert(waitTasks, HasLen, 1)
c.Assert(waitTasks[0].Kind(), Equals, prevTask.Kind())
c.Check(waitTasks[0], Equals, prevTask)
}

Expand Down Expand Up @@ -306,4 +308,118 @@ snaps:
c.Assert(chg.Err(), IsNil)

checkPreseedTaskStates(c, st)
c.Check(chg.Status(), Equals, state.DoingStatus)

// verify
r, err := os.Open(dirs.SnapStateFile)
c.Assert(err, IsNil)
diskState, err := state.ReadState(nil, r)
c.Assert(err, IsNil)

diskState.Lock()
defer diskState.Unlock()

// seeded snaps are installed
_, err = snapstate.CurrentInfo(diskState, "core")
c.Check(err, IsNil)
_, err = snapstate.CurrentInfo(diskState, "foo")
c.Check(err, IsNil)

// but we're not considered seeded
var seeded bool
err = diskState.Get("seeded", &seeded)
c.Assert(err, Equals, state.ErrNoState)
}

func (s *firstbootPreseed16Suite) TestPreseedClassicWithSnapdOnlyHappy(c *C) {
restorePreseedMode := release.MockPreseedMode(func() bool { return true })
defer restorePreseedMode()

restore := release.MockOnClassic(true)
defer restore()

mockMountCmd := testutil.MockCommand(c, "mount", "")
defer mockMountCmd.Restore()

mockUmountCmd := testutil.MockCommand(c, "umount", "")
defer mockUmountCmd.Restore()

core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{
classic: true,
})

// put a firstboot snap into the SnapBlobDir
snapYaml := `name: foo
version: 1.0
base: core18
`
fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)

// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
s.WriteAssertions("model.asserts", assertsChain...)

// create a seed.yaml
content := []byte(fmt.Sprintf(`
snaps:
- name: snapd
file: %s
- name: foo
file: %s
- name: core18
file: %s
`, snapdFname, fooFname, core18Fname))
err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)

// run the firstboot stuff
s.startOverlord(c)
st := s.overlord.State()
st.Lock()
defer st.Unlock()

opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
c.Assert(err, IsNil)

checkPreseedOrder(c, tsAll, "snapd", "core18", "foo")

// now run the change and check the result
chg := st.NewChange("seed", "run the populate from seed changes")
for _, ts := range tsAll {
chg.AddAll(ts)
}
c.Assert(st.Changes(), HasLen, 1)
c.Assert(chg.Err(), IsNil)

st.Unlock()
err = s.overlord.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)

checkPreseedTaskStates(c, st)
c.Check(chg.Status(), Equals, state.DoingStatus)

// verify
r, err := os.Open(dirs.SnapStateFile)
c.Assert(err, IsNil)
diskState, err := state.ReadState(nil, r)
c.Assert(err, IsNil)

diskState.Lock()
defer diskState.Unlock()

// seeded snaps are installed
_, err = snapstate.CurrentInfo(diskState, "snapd")
c.Check(err, IsNil)
_, err = snapstate.CurrentInfo(diskState, "core18")
c.Check(err, IsNil)
_, err = snapstate.CurrentInfo(diskState, "foo")
c.Check(err, IsNil)

// but we're not considered seeded
var seeded bool
err = diskState.Get("seeded", &seeded)
c.Assert(err, Equals, state.ErrNoState)
stolowski marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion overlord/devicestate/firstboot_test.go
Expand Up @@ -1160,7 +1160,7 @@ type core18SnapsOpts struct {
gadget bool
}

func (s *firstBoot16Suite) makeCore18Snaps(c *C, opts *core18SnapsOpts) (core18Fn, snapdFn, kernelFn, gadgetFn string) {
func (s *firstBoot16BaseTest) makeCore18Snaps(c *C, opts *core18SnapsOpts) (core18Fn, snapdFn, kernelFn, gadgetFn string) {
if opts == nil {
opts = &core18SnapsOpts{}
}
Expand Down
7 changes: 7 additions & 0 deletions tests/main/preseed-snapd-snap/seed.yaml.in
@@ -0,0 +1,7 @@
snaps:
- name: snapd
file: @SNAPD@
unasserted: true
- name: core18
file: @CORE@
unasserted: true
108 changes: 108 additions & 0 deletions tests/main/preseed-snapd-snap/task.yaml
@@ -0,0 +1,108 @@
summary: Check that preseeding of current ubuntu cloud image works with snapd
and core18 works.
stolowski marked this conversation as resolved.
Show resolved Hide resolved
description: |
This test checks that preseeding of Ubuntu cloud images with snap-preseed
command works, up to the point where the image is ready to be booted.
The test assumes cloud image with a core and lxd snaps in its seeds/.

systems: [ubuntu-20.04-*]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgive my ignorance, why is this limited to Ubuntu 20.04?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because no ubuntu cloud image seeds core18+snapd yet, and 20.04 is the only possible candidate (or maybe this is even too optimistic assumption).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add this as a comment please.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I think they might consider that switch for bionic too, because otherwise they will end up with core + core18 on those at some point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I can enable it for more systems; if they start seeding snapd on 20.04 but not on others I can make setup_preseeding_core18 conditional and modify the image only where it is still needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabled for 19.10 as well, but not for 18.04 since it doesn't do seeding at all at the moment. I will enable once it actually introduces seeds.


environment:
IMAGE_MOUNTPOINT: /mnt/cloudimg

prepare: |
# the get_image_url_for_nested_vm is a convenient helper that returns
# a cloud image url matching current $SPREAD_SYSTEM.
#shellcheck source=tests/lib/nested.sh
. "$TESTSLIB/nested.sh"
wget "$(get_image_url_for_nested_vm)" -O cloudimg.img
mkdir -p "$IMAGE_MOUNTPOINT"

restore: |
#shellcheck source=tests/lib/preseed.sh
. "$TESTSLIB/preseed.sh"

# any of the restore commands can fail depending on where execute part stopped,
zyga marked this conversation as resolved.
Show resolved Hide resolved
# account for that with ||true.
umount_ubuntu_image "$IMAGE_MOUNTPOINT" || true

execute: |
setup_preseeding_core18() {
local IMAGE_MOUNTPOINT=$1
local SEEDS_DIR
SEEDS_DIR="$IMAGE_MOUNTPOINT/var/lib/snapd/seed/"
rm -f "$SEEDS_DIR"/snaps/*.snap
rm -f "$SEEDS_DIR"/seed.yaml

snap download --edge snapd
snap download --edge core18

CORE=$(ls core18*.snap)
SNAPD=$(ls snapd*.snap)
sed seed.yaml.in -E -e "s/@CORE@/$CORE/" -e "s/@SNAPD@/$SNAPD/" > "$SEEDS_DIR"/seed.yaml

cp "$CORE" "$SNAPD" "$SEEDS_DIR"/snaps/
}

#shellcheck source=tests/lib/preseed.sh
. "$TESTSLIB/preseed.sh"
mount_ubuntu_image cloudimg.img "$IMAGE_MOUNTPOINT"
setup_preseeding_core18 "$IMAGE_MOUNTPOINT"

echo "Running pre-seeeding"
/usr/lib/snapd/snap-preseed "$IMAGE_MOUNTPOINT"

# sanity, core snap mounted by snap-preseed got unmounted
mount | not MATCH "snap-preseed"

snap debug state "$IMAGE_MOUNTPOINT"/var/lib/snapd/state.json --change=1 > tasks.log

echo "Check that the tasks of preseeded snapd have expected statuses"
# Note, these checks match statuses, but not the order
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an interesting way to handle a blob of text that has variable elements.

MATCH "Done .+ prerequisites +Ensure prerequisites for \"snapd\" are available" < tasks.log
MATCH "Done .+ prepare-snap +Prepare snap \"/var/lib/snapd/seed/snaps/snapd_[0-9]+.snap" < tasks.log
MATCH "Done .+ mount-snap +Mount snap \"snapd\"" < tasks.log
MATCH "Done .+ copy-snap-data +Copy snap \"snapd\" data" < tasks.log
MATCH "Done .+ setup-profiles +Setup snap \"snapd\" \(unset\) security profiles" < tasks.log
MATCH "Done .+ link-snap +Make snap \"snapd\" \(unset\) available to the system" < tasks.log
MATCH "Done .+ auto-connect +Automatically connect eligible plugs and slots of snap \"snapd\"" < tasks.log
MATCH "Done .+ set-auto-aliases +Set automatic aliases for snap \"snapd\"" < tasks.log
MATCH "Done .+ setup-aliases +Setup snap \"snapd\" aliases" < tasks.log
MATCH "Done .+ prerequisites +Ensure prerequisites for \"core18\" are available" < tasks.log
MATCH "Done .+ prepare-snap +Prepare snap \"/var/lib/snapd/seed/snaps/core18_[0-9]+.snap\"" < tasks.log
MATCH "Done .+ mount-snap +Mount snap \"core18\" \(unset\)" < tasks.log
MATCH "Done .+ copy-snap-data +Copy snap \"core18\" data" < tasks.log
MATCH "Done .+ setup-profiles +Setup snap \"core18\" \(unset\) security profiles" < tasks.log
MATCH "Done .+ link-snap +Make snap \"core18\" \(unset\) available to the system" < tasks.log
MATCH "Done .+ auto-connect +Automatically connect eligible plugs and slots of snap \"core18\"" < tasks.log
MATCH "Done .+ setup-profiles +Setup snap \"core18\" \(unset\) security profiles" < tasks.log
MATCH "Done .+ set-auto-aliases +Set automatic aliases for snap \"core18\"" < tasks.log
MATCH "Done .+ setup-aliases +Setup snap \"core18\" aliases" < tasks.log

echo "Checking that there were no other 'Done' tasks when preseeding"
[ "$(grep -c ' Done ' tasks.log)" = "18" ]
Comment on lines +84 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's really cool, good thinking.


# mark-preseeded task is where snap-preseed stopped, therefore it's in Doing.
MATCH "Doing .+ mark-preseeded +Mark system pre-seeded" < tasks.log

# everything below is pending execution on first boot
MATCH "Do .+ run-hook +Run install hook of \"snapd\" snap if present" < tasks.log
MATCH "Do .+ start-snap-services +Start snap \"snapd\" \(unset\) services" < tasks.log
MATCH "Do .+ run-hook +Run configure hook of \"core\" snap if present" < tasks.log
MATCH "Do .+ run-hook +Run install hook of \"core18\" snap if present" < tasks.log
MATCH "Do .+ start-snap-services +Start snap \"core18\" \(unset\) services" < tasks.log
MATCH "Do .+ run-hook +Run health check of \"core18\" snap" < tasks.log
MATCH "Do .+ mark-seeded +Mark system seeded" < tasks.log

echo "Checking that mount units have been created on the target image"
SYSTEMD_UNITS="$IMAGE_MOUNTPOINT"/etc/systemd
test -f "$SYSTEMD_UNITS"/system/snap-snapd-*.mount
test -f "$SYSTEMD_UNITS"/system/snap-core18-*.mount

echo "Checking enabled systemd mount units"
test -L "$SYSTEMD_UNITS"/system/multi-user.target.wants/snap-snapd-*.mount
test -L "$SYSTEMD_UNITS"/system/multi-user.target.wants/snap-core18-*.mount

#shellcheck source=tests/lib/preseed.sh
. "$TESTSLIB/preseed.sh"
umount_ubuntu_image "$IMAGE_MOUNTPOINT"