github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/devicestate/firstboot_preseed_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package devicestate_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/boot/boottest"
    31  	"github.com/snapcore/snapd/bootloader"
    32  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/interfaces"
    35  	"github.com/snapcore/snapd/overlord/devicestate"
    36  	"github.com/snapcore/snapd/overlord/hookstate"
    37  	"github.com/snapcore/snapd/overlord/snapstate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  	"github.com/snapcore/snapd/release"
    40  	"github.com/snapcore/snapd/seed/seedtest"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snapdenv"
    43  	"github.com/snapcore/snapd/testutil"
    44  )
    45  
    46  type firstbootPreseed16Suite struct {
    47  	firstBootBaseTest
    48  	firstBoot16BaseTest
    49  }
    50  
    51  var _ = Suite(&firstbootPreseed16Suite{})
    52  
    53  func checkPreseedTasks(c *C, tsAll []*state.TaskSet) {
    54  	// the tasks of the last taskset must be mark-preseeded, mark-seeded, in that order
    55  	lastTasks := tsAll[len(tsAll)-1].Tasks()
    56  	c.Check(lastTasks, HasLen, 2)
    57  	preseedTask := lastTasks[0]
    58  	markSeededTask := lastTasks[1]
    59  	c.Assert(preseedTask.Kind(), Equals, "mark-preseeded")
    60  	c.Check(markSeededTask.Kind(), Equals, "mark-seeded")
    61  
    62  	// mark-seeded waits for mark-preseeded
    63  	var waitsForPreseeded bool
    64  	for _, wt := range markSeededTask.WaitTasks() {
    65  		if wt.Kind() == "mark-preseeded" {
    66  			waitsForPreseeded = true
    67  		}
    68  	}
    69  	c.Check(waitsForPreseeded, Equals, true)
    70  }
    71  
    72  func checkPreseedTaskStates(c *C, st *state.State) {
    73  	doneTasks := map[string]bool{
    74  		"prerequisites":    true,
    75  		"prepare-snap":     true,
    76  		"link-snap":        true,
    77  		"mount-snap":       true,
    78  		"setup-profiles":   true,
    79  		"copy-snap-data":   true,
    80  		"set-auto-aliases": true,
    81  		"setup-aliases":    true,
    82  		"auto-connect":     true,
    83  	}
    84  	if !release.OnClassic {
    85  		doneTasks["update-gadget-assets"] = true
    86  	}
    87  	doTasks := map[string]bool{
    88  		"run-hook":            true,
    89  		"mark-seeded":         true,
    90  		"start-snap-services": true,
    91  	}
    92  	seenDone := make(map[string]bool)
    93  	for _, t := range st.Tasks() {
    94  		if t.Status() == state.DoneStatus {
    95  			seenDone[t.Kind()] = true
    96  		}
    97  		switch {
    98  		case doneTasks[t.Kind()]:
    99  			c.Check(t.Status(), Equals, state.DoneStatus, Commentf("task: %s", t.Kind()))
   100  		case t.Kind() == "mark-preseeded":
   101  			c.Check(t.Status(), Equals, state.DoingStatus, Commentf("task: %s", t.Kind()))
   102  		case doTasks[t.Kind()]:
   103  			c.Check(t.Status(), Equals, state.DoStatus, Commentf("task: %s", t.Kind()))
   104  		default:
   105  			c.Fatalf("unhandled task kind %s", t.Kind())
   106  		}
   107  	}
   108  
   109  	// sanity: check that doneTasks is not declaring more tasks than
   110  	// actually expected.
   111  	c.Check(doneTasks, DeepEquals, seenDone)
   112  }
   113  
   114  func markPreseededInWaitChain(t *state.Task) bool {
   115  	for _, wt := range t.WaitTasks() {
   116  		if wt.Kind() == "mark-preseeded" {
   117  			return true
   118  		}
   119  		if markPreseededInWaitChain(wt) {
   120  			return true
   121  		}
   122  	}
   123  	return false
   124  }
   125  
   126  func checkPreseedOrder(c *C, tsAll []*state.TaskSet, snaps ...string) {
   127  	matched := 0
   128  	markSeeded := 0
   129  	markPreseeded := 0
   130  	markPreseededWaitingForAliases := 0
   131  
   132  	for _, ts := range tsAll {
   133  		for _, t := range ts.Tasks() {
   134  			switch t.Kind() {
   135  			case "run-hook":
   136  				// ensure that hooks are run after mark-preseeded
   137  				c.Check(markPreseededInWaitChain(t), Equals, true)
   138  			case "mark-seeded":
   139  				// nothing waits for mark-seeded
   140  				c.Check(t.HaltTasks(), HasLen, 0)
   141  				markSeeded++
   142  				c.Check(markPreseededInWaitChain(t), Equals, true)
   143  			case "mark-preseeded":
   144  				for _, wt := range t.WaitTasks() {
   145  					if wt.Kind() == "setup-aliases" {
   146  						markPreseededWaitingForAliases++
   147  					}
   148  				}
   149  				markPreseeded++
   150  			}
   151  		}
   152  	}
   153  
   154  	c.Check(markSeeded, Equals, 1)
   155  	c.Check(markPreseeded, Equals, 1)
   156  	c.Check(markPreseededWaitingForAliases, Equals, len(snaps))
   157  
   158  	// check that prerequisites tasks for all snaps are present and
   159  	// are chained properly.
   160  	var prevTask *state.Task
   161  	for i, ts := range tsAll {
   162  		task0 := ts.Tasks()[0]
   163  		waitTasks := task0.WaitTasks()
   164  		// all tasksets start with prerequisites task, except for
   165  		// tasksets with just the configure hook of special snaps,
   166  		// or last taskset.
   167  		if task0.Kind() != "prerequisites" {
   168  			if i == len(tsAll)-1 {
   169  				c.Check(task0.Kind(), Equals, "mark-preseeded")
   170  				c.Check(ts.Tasks()[1].Kind(), Equals, "mark-seeded")
   171  				c.Check(ts.Tasks(), HasLen, 2)
   172  			} else {
   173  				c.Check(task0.Kind(), Equals, "run-hook")
   174  				var hsup hookstate.HookSetup
   175  				c.Assert(task0.Get("hook-setup", &hsup), IsNil)
   176  				c.Check(hsup.Hook, Equals, "configure")
   177  				c.Check(ts.Tasks(), HasLen, 1)
   178  			}
   179  			continue
   180  		}
   181  
   182  		if i == 0 {
   183  			c.Check(waitTasks, HasLen, 0)
   184  		} else {
   185  			c.Assert(waitTasks, HasLen, 1)
   186  			c.Assert(waitTasks[0].Kind(), Equals, prevTask.Kind())
   187  			c.Check(waitTasks[0], Equals, prevTask)
   188  		}
   189  
   190  		// make sure that install-hooks wait for the previous snap, and for
   191  		// mark-preseeded.
   192  		hookEdgeTask, err := ts.Edge(snapstate.HooksEdge)
   193  		c.Assert(err, IsNil)
   194  		c.Assert(hookEdgeTask.Kind(), Equals, "run-hook")
   195  		var hsup hookstate.HookSetup
   196  		c.Assert(hookEdgeTask.Get("hook-setup", &hsup), IsNil)
   197  		c.Check(hsup.Hook, Equals, "install")
   198  		switch hsup.Snap {
   199  		case "core", "core18", "snapd":
   200  			// ignore
   201  		default:
   202  			// snaps other than core/core18/snapd
   203  			var waitsForMarkPreseeded, waitsForPreviousSnapHook, waitsForPreviousSnap bool
   204  			for _, wt := range hookEdgeTask.WaitTasks() {
   205  				switch wt.Kind() {
   206  				case "setup-aliases":
   207  					continue
   208  				case "run-hook":
   209  					var wtsup hookstate.HookSetup
   210  					c.Assert(wt.Get("hook-setup", &wtsup), IsNil)
   211  					c.Check(wtsup.Snap, Equals, snaps[matched-1])
   212  					waitsForPreviousSnapHook = true
   213  				case "mark-preseeded":
   214  					waitsForMarkPreseeded = true
   215  				case "prerequisites":
   216  				default:
   217  					snapsup, err := snapstate.TaskSnapSetup(wt)
   218  					c.Assert(err, IsNil, Commentf("%#v", wt))
   219  					c.Check(snapsup.SnapName(), Equals, snaps[matched-1], Commentf("%s: %#v", hsup.Snap, wt))
   220  					waitsForPreviousSnap = true
   221  				}
   222  			}
   223  			c.Assert(waitsForMarkPreseeded, Equals, true)
   224  			c.Assert(waitsForPreviousSnapHook, Equals, true)
   225  			if snaps[matched-1] != "core" && snaps[matched-1] != "core18" && snaps[matched-1] != "pc" {
   226  				c.Check(waitsForPreviousSnap, Equals, true, Commentf("%s", snaps[matched-1]))
   227  			}
   228  		}
   229  
   230  		snapsup, err := snapstate.TaskSnapSetup(task0)
   231  		c.Assert(err, IsNil, Commentf("%#v", task0))
   232  		c.Check(snapsup.InstanceName(), Equals, snaps[matched])
   233  		matched++
   234  
   235  		// find setup-aliases task in current taskset; its position
   236  		// is not fixed due to e.g. optional update-gadget-assets task.
   237  		var aliasesTask *state.Task
   238  		for _, t := range ts.Tasks() {
   239  			if t.Kind() == "setup-aliases" {
   240  				aliasesTask = t
   241  				break
   242  			}
   243  		}
   244  		c.Assert(aliasesTask, NotNil)
   245  		prevTask = aliasesTask
   246  	}
   247  
   248  	c.Check(matched, Equals, len(snaps))
   249  }
   250  
   251  func (s *firstbootPreseed16Suite) SetUpTest(c *C) {
   252  	s.TestingSeed16 = &seedtest.TestingSeed16{}
   253  	s.setup16BaseTest(c, &s.firstBootBaseTest)
   254  
   255  	s.SeedDir = dirs.SnapSeedDir
   256  
   257  	err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "assertions"), 0755)
   258  	c.Assert(err, IsNil)
   259  
   260  	s.AddCleanup(interfaces.MockSystemKey(`{"core": "123"}`))
   261  	c.Assert(interfaces.WriteSystemKey(), IsNil)
   262  }
   263  
   264  func (s *firstbootPreseed16Suite) TestPreseedHappy(c *C) {
   265  	restore := snapdenv.MockPreseeding(true)
   266  	defer restore()
   267  
   268  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   269  	defer mockMountCmd.Restore()
   270  
   271  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   272  	defer mockUmountCmd.Restore()
   273  
   274  	bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
   275  	bootloader.Force(bloader)
   276  	defer bootloader.Force(nil)
   277  	bloader.SetBootKernel("pc-kernel_1.snap")
   278  	bloader.SetBootBase("core_1.snap")
   279  
   280  	s.startOverlord(c)
   281  	st := s.overlord.State()
   282  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   283  	chg, _ := s.makeSeedChange(c, st, opts, checkPreseedTasks, checkPreseedOrder)
   284  	err := s.overlord.Settle(settleTimeout)
   285  
   286  	st.Lock()
   287  	defer st.Unlock()
   288  
   289  	c.Assert(err, IsNil)
   290  	c.Assert(chg.Err(), IsNil)
   291  
   292  	checkPreseedTaskStates(c, st)
   293  }
   294  
   295  func (s *firstbootPreseed16Suite) TestPreseedOnClassicHappy(c *C) {
   296  	restore := snapdenv.MockPreseeding(true)
   297  	defer restore()
   298  
   299  	restoreRelease := release.MockOnClassic(true)
   300  	defer restoreRelease()
   301  
   302  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   303  	defer mockMountCmd.Restore()
   304  
   305  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   306  	defer mockUmountCmd.Restore()
   307  
   308  	coreFname, _, _ := s.makeCoreSnaps(c, "")
   309  
   310  	// put a firstboot snap into the SnapBlobDir
   311  	snapYaml := `name: foo
   312  version: 1.0
   313  `
   314  	fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
   315  	s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
   316  
   317  	// put a firstboot snap into the SnapBlobDir
   318  	snapYaml2 := `name: bar
   319  version: 1.0
   320  `
   321  	barFname, barDecl, barRev := s.MakeAssertedSnap(c, snapYaml2, nil, snap.R(33), "developerid")
   322  	s.WriteAssertions("bar.asserts", s.devAcct, barRev, barDecl)
   323  
   324  	// add a model assertion and its chain
   325  	assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
   326  	s.WriteAssertions("model.asserts", assertsChain...)
   327  
   328  	// create a seed.yaml
   329  	content := []byte(fmt.Sprintf(`
   330  snaps:
   331   - name: foo
   332     file: %s
   333   - name: bar
   334     file: %s
   335   - name: core
   336     file: %s
   337  `, fooFname, barFname, coreFname))
   338  	err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
   339  	c.Assert(err, IsNil)
   340  
   341  	// run the firstboot stuff
   342  	s.startOverlord(c)
   343  	st := s.overlord.State()
   344  	st.Lock()
   345  	defer st.Unlock()
   346  
   347  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   348  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
   349  	c.Assert(err, IsNil)
   350  
   351  	chg := st.NewChange("seed", "run the populate from seed changes")
   352  	for _, ts := range tsAll {
   353  		chg.AddAll(ts)
   354  	}
   355  	c.Assert(st.Changes(), HasLen, 1)
   356  
   357  	checkPreseedOrder(c, tsAll, "core", "foo", "bar")
   358  
   359  	st.Unlock()
   360  	err = s.overlord.Settle(settleTimeout)
   361  	st.Lock()
   362  
   363  	c.Assert(err, IsNil)
   364  	c.Assert(chg.Err(), IsNil)
   365  
   366  	checkPreseedTaskStates(c, st)
   367  	c.Check(chg.Status(), Equals, state.DoingStatus)
   368  
   369  	// verify
   370  	r, err := os.Open(dirs.SnapStateFile)
   371  	c.Assert(err, IsNil)
   372  	diskState, err := state.ReadState(nil, r)
   373  	c.Assert(err, IsNil)
   374  
   375  	diskState.Lock()
   376  	defer diskState.Unlock()
   377  
   378  	// seeded snaps are installed
   379  	_, err = snapstate.CurrentInfo(diskState, "core")
   380  	c.Check(err, IsNil)
   381  	_, err = snapstate.CurrentInfo(diskState, "foo")
   382  	c.Check(err, IsNil)
   383  	_, err = snapstate.CurrentInfo(diskState, "bar")
   384  	c.Check(err, IsNil)
   385  
   386  	// but we're not considered seeded
   387  	var seeded bool
   388  	err = diskState.Get("seeded", &seeded)
   389  	c.Assert(err, Equals, state.ErrNoState)
   390  }
   391  
   392  func (s *firstbootPreseed16Suite) TestPreseedClassicWithSnapdOnlyHappy(c *C) {
   393  	restorePreseedMode := snapdenv.MockPreseeding(true)
   394  	defer restorePreseedMode()
   395  
   396  	restore := release.MockOnClassic(true)
   397  	defer restore()
   398  
   399  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   400  	defer mockMountCmd.Restore()
   401  
   402  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   403  	defer mockUmountCmd.Restore()
   404  
   405  	core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{
   406  		classic: true,
   407  	})
   408  
   409  	// put a firstboot snap into the SnapBlobDir
   410  	snapYaml := `name: foo
   411  version: 1.0
   412  base: core18
   413  `
   414  	fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
   415  	s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
   416  
   417  	// add a model assertion and its chain
   418  	assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
   419  	s.WriteAssertions("model.asserts", assertsChain...)
   420  
   421  	// create a seed.yaml
   422  	content := []byte(fmt.Sprintf(`
   423  snaps:
   424   - name: snapd
   425     file: %s
   426   - name: foo
   427     file: %s
   428   - name: core18
   429     file: %s
   430  `, snapdFname, fooFname, core18Fname))
   431  	err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
   432  	c.Assert(err, IsNil)
   433  
   434  	// run the firstboot stuff
   435  	s.startOverlord(c)
   436  	st := s.overlord.State()
   437  	st.Lock()
   438  	defer st.Unlock()
   439  
   440  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   441  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
   442  	c.Assert(err, IsNil)
   443  
   444  	// now run the change and check the result
   445  	chg := st.NewChange("seed", "run the populate from seed changes")
   446  	for _, ts := range tsAll {
   447  		chg.AddAll(ts)
   448  	}
   449  	c.Assert(st.Changes(), HasLen, 1)
   450  	c.Assert(chg.Err(), IsNil)
   451  
   452  	checkPreseedOrder(c, tsAll, "snapd", "core18", "foo")
   453  
   454  	st.Unlock()
   455  	err = s.overlord.Settle(settleTimeout)
   456  	st.Lock()
   457  	c.Assert(err, IsNil)
   458  
   459  	checkPreseedTaskStates(c, st)
   460  	c.Check(chg.Status(), Equals, state.DoingStatus)
   461  
   462  	// verify
   463  	r, err := os.Open(dirs.SnapStateFile)
   464  	c.Assert(err, IsNil)
   465  	diskState, err := state.ReadState(nil, r)
   466  	c.Assert(err, IsNil)
   467  
   468  	diskState.Lock()
   469  	defer diskState.Unlock()
   470  
   471  	// seeded snaps are installed
   472  	_, err = snapstate.CurrentInfo(diskState, "snapd")
   473  	c.Check(err, IsNil)
   474  	_, err = snapstate.CurrentInfo(diskState, "core18")
   475  	c.Check(err, IsNil)
   476  	_, err = snapstate.CurrentInfo(diskState, "foo")
   477  	c.Check(err, IsNil)
   478  
   479  	// but we're not considered seeded
   480  	var seeded bool
   481  	err = diskState.Get("seeded", &seeded)
   482  	c.Assert(err, Equals, state.ErrNoState)
   483  }