github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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  		doneTasks["update-gadget-cmdline"] = true
    87  	}
    88  	doTasks := map[string]bool{
    89  		"run-hook":            true,
    90  		"mark-seeded":         true,
    91  		"start-snap-services": true,
    92  	}
    93  	seenDone := make(map[string]bool)
    94  	for _, t := range st.Tasks() {
    95  		if t.Status() == state.DoneStatus {
    96  			seenDone[t.Kind()] = true
    97  		}
    98  		switch {
    99  		case doneTasks[t.Kind()]:
   100  			c.Check(t.Status(), Equals, state.DoneStatus, Commentf("task: %s", t.Kind()))
   101  		case t.Kind() == "mark-preseeded":
   102  			c.Check(t.Status(), Equals, state.DoingStatus, Commentf("task: %s", t.Kind()))
   103  		case doTasks[t.Kind()]:
   104  			c.Check(t.Status(), Equals, state.DoStatus, Commentf("task: %s", t.Kind()))
   105  		default:
   106  			c.Fatalf("unhandled task kind %s", t.Kind())
   107  		}
   108  	}
   109  
   110  	// sanity: check that doneTasks is not declaring more tasks than
   111  	// actually expected.
   112  	c.Check(doneTasks, DeepEquals, seenDone)
   113  }
   114  
   115  func markPreseededInWaitChain(t *state.Task) bool {
   116  	for _, wt := range t.WaitTasks() {
   117  		if wt.Kind() == "mark-preseeded" {
   118  			return true
   119  		}
   120  		if markPreseededInWaitChain(wt) {
   121  			return true
   122  		}
   123  	}
   124  	return false
   125  }
   126  
   127  func checkPreseedOrder(c *C, tsAll []*state.TaskSet, snaps ...string) {
   128  	matched := 0
   129  	markSeeded := 0
   130  	markPreseeded := 0
   131  	markPreseededWaitingForAliases := 0
   132  
   133  	for _, ts := range tsAll {
   134  		for _, t := range ts.Tasks() {
   135  			switch t.Kind() {
   136  			case "run-hook":
   137  				// ensure that hooks are run after mark-preseeded
   138  				c.Check(markPreseededInWaitChain(t), Equals, true)
   139  			case "mark-seeded":
   140  				// nothing waits for mark-seeded
   141  				c.Check(t.HaltTasks(), HasLen, 0)
   142  				markSeeded++
   143  				c.Check(markPreseededInWaitChain(t), Equals, true)
   144  			case "mark-preseeded":
   145  				for _, wt := range t.WaitTasks() {
   146  					if wt.Kind() == "setup-aliases" {
   147  						markPreseededWaitingForAliases++
   148  					}
   149  				}
   150  				markPreseeded++
   151  			}
   152  		}
   153  	}
   154  
   155  	c.Check(markSeeded, Equals, 1)
   156  	c.Check(markPreseeded, Equals, 1)
   157  	c.Check(markPreseededWaitingForAliases, Equals, len(snaps))
   158  
   159  	// check that prerequisites tasks for all snaps are present and
   160  	// are chained properly.
   161  	var prevTask *state.Task
   162  	for i, ts := range tsAll {
   163  		task0 := ts.Tasks()[0]
   164  		waitTasks := task0.WaitTasks()
   165  		// all tasksets start with prerequisites task, except for
   166  		// tasksets with just the configure hook of special snaps,
   167  		// or last taskset.
   168  		if task0.Kind() != "prerequisites" {
   169  			if i == len(tsAll)-1 {
   170  				c.Check(task0.Kind(), Equals, "mark-preseeded")
   171  				c.Check(ts.Tasks()[1].Kind(), Equals, "mark-seeded")
   172  				c.Check(ts.Tasks(), HasLen, 2)
   173  			} else {
   174  				c.Check(task0.Kind(), Equals, "run-hook")
   175  				var hsup hookstate.HookSetup
   176  				c.Assert(task0.Get("hook-setup", &hsup), IsNil)
   177  				c.Check(hsup.Hook, Equals, "configure")
   178  				c.Check(ts.Tasks(), HasLen, 1)
   179  			}
   180  			continue
   181  		}
   182  
   183  		if i == 0 {
   184  			c.Check(waitTasks, HasLen, 0)
   185  		} else {
   186  			c.Assert(waitTasks, HasLen, 1)
   187  			c.Assert(waitTasks[0].Kind(), Equals, prevTask.Kind())
   188  			c.Check(waitTasks[0], Equals, prevTask)
   189  		}
   190  
   191  		// make sure that install-hooks wait for the previous snap, and for
   192  		// mark-preseeded.
   193  		hookEdgeTask, err := ts.Edge(snapstate.HooksEdge)
   194  		c.Assert(err, IsNil)
   195  		c.Assert(hookEdgeTask.Kind(), Equals, "run-hook")
   196  		var hsup hookstate.HookSetup
   197  		c.Assert(hookEdgeTask.Get("hook-setup", &hsup), IsNil)
   198  		c.Check(hsup.Hook, Equals, "install")
   199  		switch hsup.Snap {
   200  		case "core", "core18", "snapd":
   201  			// ignore
   202  		default:
   203  			// snaps other than core/core18/snapd
   204  			var waitsForMarkPreseeded, waitsForPreviousSnapHook, waitsForPreviousSnap bool
   205  			for _, wt := range hookEdgeTask.WaitTasks() {
   206  				switch wt.Kind() {
   207  				case "setup-aliases":
   208  					continue
   209  				case "run-hook":
   210  					var wtsup hookstate.HookSetup
   211  					c.Assert(wt.Get("hook-setup", &wtsup), IsNil)
   212  					c.Check(wtsup.Snap, Equals, snaps[matched-1])
   213  					waitsForPreviousSnapHook = true
   214  				case "mark-preseeded":
   215  					waitsForMarkPreseeded = true
   216  				case "prerequisites":
   217  				default:
   218  					snapsup, err := snapstate.TaskSnapSetup(wt)
   219  					c.Assert(err, IsNil, Commentf("%#v", wt))
   220  					c.Check(snapsup.SnapName(), Equals, snaps[matched-1], Commentf("%s: %#v", hsup.Snap, wt))
   221  					waitsForPreviousSnap = true
   222  				}
   223  			}
   224  			c.Assert(waitsForMarkPreseeded, Equals, true)
   225  			c.Assert(waitsForPreviousSnapHook, Equals, true)
   226  			if snaps[matched-1] != "core" && snaps[matched-1] != "core18" && snaps[matched-1] != "pc" {
   227  				c.Check(waitsForPreviousSnap, Equals, true, Commentf("%s", snaps[matched-1]))
   228  			}
   229  		}
   230  
   231  		snapsup, err := snapstate.TaskSnapSetup(task0)
   232  		c.Assert(err, IsNil, Commentf("%#v", task0))
   233  		c.Check(snapsup.InstanceName(), Equals, snaps[matched])
   234  		matched++
   235  
   236  		// find setup-aliases task in current taskset; its position
   237  		// is not fixed due to e.g. optional update-gadget-assets task.
   238  		var aliasesTask *state.Task
   239  		for _, t := range ts.Tasks() {
   240  			if t.Kind() == "setup-aliases" {
   241  				aliasesTask = t
   242  				break
   243  			}
   244  		}
   245  		c.Assert(aliasesTask, NotNil)
   246  		prevTask = aliasesTask
   247  	}
   248  
   249  	c.Check(matched, Equals, len(snaps))
   250  }
   251  
   252  func (s *firstbootPreseed16Suite) SetUpTest(c *C) {
   253  	s.TestingSeed16 = &seedtest.TestingSeed16{}
   254  	s.setup16BaseTest(c, &s.firstBootBaseTest)
   255  
   256  	s.SeedDir = dirs.SnapSeedDir
   257  
   258  	err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "assertions"), 0755)
   259  	c.Assert(err, IsNil)
   260  
   261  	s.AddCleanup(interfaces.MockSystemKey(`{"core": "123"}`))
   262  	c.Assert(interfaces.WriteSystemKey(), IsNil)
   263  }
   264  
   265  func (s *firstbootPreseed16Suite) TestPreseedHappy(c *C) {
   266  	restore := snapdenv.MockPreseeding(true)
   267  	defer restore()
   268  
   269  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   270  	defer mockMountCmd.Restore()
   271  
   272  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   273  	defer mockUmountCmd.Restore()
   274  
   275  	bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
   276  	bootloader.Force(bloader)
   277  	defer bootloader.Force(nil)
   278  	bloader.SetBootKernel("pc-kernel_1.snap")
   279  	bloader.SetBootBase("core_1.snap")
   280  
   281  	s.startOverlord(c)
   282  	st := s.overlord.State()
   283  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   284  	chg, _ := s.makeSeedChange(c, st, opts, checkPreseedTasks, checkPreseedOrder)
   285  	err := s.overlord.Settle(settleTimeout)
   286  
   287  	st.Lock()
   288  	defer st.Unlock()
   289  
   290  	c.Assert(err, IsNil)
   291  	c.Assert(chg.Err(), IsNil)
   292  
   293  	checkPreseedTaskStates(c, st)
   294  }
   295  
   296  func (s *firstbootPreseed16Suite) TestPreseedOnClassicHappy(c *C) {
   297  	restore := snapdenv.MockPreseeding(true)
   298  	defer restore()
   299  
   300  	restoreRelease := release.MockOnClassic(true)
   301  	defer restoreRelease()
   302  
   303  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   304  	defer mockMountCmd.Restore()
   305  
   306  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   307  	defer mockUmountCmd.Restore()
   308  
   309  	coreFname, _, _ := s.makeCoreSnaps(c, "")
   310  
   311  	// put a firstboot snap into the SnapBlobDir
   312  	snapYaml := `name: foo
   313  version: 1.0
   314  `
   315  	fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
   316  	s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
   317  
   318  	// put a firstboot snap into the SnapBlobDir
   319  	snapYaml2 := `name: bar
   320  version: 1.0
   321  `
   322  	barFname, barDecl, barRev := s.MakeAssertedSnap(c, snapYaml2, nil, snap.R(33), "developerid")
   323  	s.WriteAssertions("bar.asserts", s.devAcct, barRev, barDecl)
   324  
   325  	// add a model assertion and its chain
   326  	assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
   327  	s.WriteAssertions("model.asserts", assertsChain...)
   328  
   329  	// create a seed.yaml
   330  	content := []byte(fmt.Sprintf(`
   331  snaps:
   332   - name: foo
   333     file: %s
   334   - name: bar
   335     file: %s
   336   - name: core
   337     file: %s
   338  `, fooFname, barFname, coreFname))
   339  	err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
   340  	c.Assert(err, IsNil)
   341  
   342  	// run the firstboot stuff
   343  	s.startOverlord(c)
   344  	st := s.overlord.State()
   345  	st.Lock()
   346  	defer st.Unlock()
   347  
   348  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   349  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
   350  	c.Assert(err, IsNil)
   351  
   352  	chg := st.NewChange("seed", "run the populate from seed changes")
   353  	for _, ts := range tsAll {
   354  		chg.AddAll(ts)
   355  	}
   356  	c.Assert(st.Changes(), HasLen, 1)
   357  
   358  	checkPreseedOrder(c, tsAll, "core", "foo", "bar")
   359  
   360  	st.Unlock()
   361  	err = s.overlord.Settle(settleTimeout)
   362  	st.Lock()
   363  
   364  	c.Assert(err, IsNil)
   365  	c.Assert(chg.Err(), IsNil)
   366  
   367  	checkPreseedTaskStates(c, st)
   368  	c.Check(chg.Status(), Equals, state.DoingStatus)
   369  
   370  	// verify
   371  	r, err := os.Open(dirs.SnapStateFile)
   372  	c.Assert(err, IsNil)
   373  	diskState, err := state.ReadState(nil, r)
   374  	c.Assert(err, IsNil)
   375  
   376  	diskState.Lock()
   377  	defer diskState.Unlock()
   378  
   379  	// seeded snaps are installed
   380  	_, err = snapstate.CurrentInfo(diskState, "core")
   381  	c.Check(err, IsNil)
   382  	_, err = snapstate.CurrentInfo(diskState, "foo")
   383  	c.Check(err, IsNil)
   384  	_, err = snapstate.CurrentInfo(diskState, "bar")
   385  	c.Check(err, IsNil)
   386  
   387  	// but we're not considered seeded
   388  	var seeded bool
   389  	err = diskState.Get("seeded", &seeded)
   390  	c.Assert(err, Equals, state.ErrNoState)
   391  }
   392  
   393  func (s *firstbootPreseed16Suite) TestPreseedClassicWithSnapdOnlyHappy(c *C) {
   394  	restorePreseedMode := snapdenv.MockPreseeding(true)
   395  	defer restorePreseedMode()
   396  
   397  	restore := release.MockOnClassic(true)
   398  	defer restore()
   399  
   400  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   401  	defer mockMountCmd.Restore()
   402  
   403  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   404  	defer mockUmountCmd.Restore()
   405  
   406  	core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{
   407  		classic: true,
   408  	})
   409  
   410  	// put a firstboot snap into the SnapBlobDir
   411  	snapYaml := `name: foo
   412  version: 1.0
   413  base: core18
   414  `
   415  	fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
   416  	s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
   417  
   418  	// add a model assertion and its chain
   419  	assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
   420  	s.WriteAssertions("model.asserts", assertsChain...)
   421  
   422  	// create a seed.yaml
   423  	content := []byte(fmt.Sprintf(`
   424  snaps:
   425   - name: snapd
   426     file: %s
   427   - name: foo
   428     file: %s
   429   - name: core18
   430     file: %s
   431  `, snapdFname, fooFname, core18Fname))
   432  	err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
   433  	c.Assert(err, IsNil)
   434  
   435  	// run the firstboot stuff
   436  	s.startOverlord(c)
   437  	st := s.overlord.State()
   438  	st.Lock()
   439  	defer st.Unlock()
   440  
   441  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   442  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
   443  	c.Assert(err, IsNil)
   444  
   445  	// now run the change and check the result
   446  	chg := st.NewChange("seed", "run the populate from seed changes")
   447  	for _, ts := range tsAll {
   448  		chg.AddAll(ts)
   449  	}
   450  	c.Assert(st.Changes(), HasLen, 1)
   451  	c.Assert(chg.Err(), IsNil)
   452  
   453  	checkPreseedOrder(c, tsAll, "snapd", "core18", "foo")
   454  
   455  	st.Unlock()
   456  	err = s.overlord.Settle(settleTimeout)
   457  	st.Lock()
   458  	c.Assert(err, IsNil)
   459  
   460  	checkPreseedTaskStates(c, st)
   461  	c.Check(chg.Status(), Equals, state.DoingStatus)
   462  
   463  	// verify
   464  	r, err := os.Open(dirs.SnapStateFile)
   465  	c.Assert(err, IsNil)
   466  	diskState, err := state.ReadState(nil, r)
   467  	c.Assert(err, IsNil)
   468  
   469  	diskState.Lock()
   470  	defer diskState.Unlock()
   471  
   472  	// seeded snaps are installed
   473  	_, err = snapstate.CurrentInfo(diskState, "snapd")
   474  	c.Check(err, IsNil)
   475  	_, err = snapstate.CurrentInfo(diskState, "core18")
   476  	c.Check(err, IsNil)
   477  	_, err = snapstate.CurrentInfo(diskState, "foo")
   478  	c.Check(err, IsNil)
   479  
   480  	// but we're not considered seeded
   481  	var seeded bool
   482  	err = diskState.Get("seeded", &seeded)
   483  	c.Assert(err, Equals, state.ErrNoState)
   484  }