github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  		snapsup, err := snapstate.TaskSnapSetup(task0)
   183  		c.Assert(err, IsNil, Commentf("%#v", task0))
   184  		c.Check(snapsup.InstanceName(), Equals, snaps[matched])
   185  		matched++
   186  		if i == 0 {
   187  			c.Check(waitTasks, HasLen, 0)
   188  		} else {
   189  			c.Assert(waitTasks, HasLen, 1)
   190  			c.Assert(waitTasks[0].Kind(), Equals, prevTask.Kind())
   191  			c.Check(waitTasks[0], Equals, prevTask)
   192  		}
   193  
   194  		// find setup-aliases task in current taskset; its position
   195  		// is not fixed due to e.g. optional update-gadget-assets task.
   196  		var aliasesTask *state.Task
   197  		for _, t := range ts.Tasks() {
   198  			if t.Kind() == "setup-aliases" {
   199  				aliasesTask = t
   200  				break
   201  			}
   202  		}
   203  		c.Assert(aliasesTask, NotNil)
   204  		prevTask = aliasesTask
   205  	}
   206  
   207  	c.Check(matched, Equals, len(snaps))
   208  }
   209  
   210  func (s *firstbootPreseed16Suite) SetUpTest(c *C) {
   211  	s.TestingSeed16 = &seedtest.TestingSeed16{}
   212  	s.setup16BaseTest(c, &s.firstBootBaseTest)
   213  
   214  	s.SeedDir = dirs.SnapSeedDir
   215  
   216  	err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "assertions"), 0755)
   217  	c.Assert(err, IsNil)
   218  
   219  	s.AddCleanup(interfaces.MockSystemKey(`{"core": "123"}`))
   220  	c.Assert(interfaces.WriteSystemKey(), IsNil)
   221  }
   222  
   223  func (s *firstbootPreseed16Suite) TestPreseedHappy(c *C) {
   224  	restore := snapdenv.MockPreseeding(true)
   225  	defer restore()
   226  
   227  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   228  	defer mockMountCmd.Restore()
   229  
   230  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   231  	defer mockUmountCmd.Restore()
   232  
   233  	bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
   234  	bootloader.Force(bloader)
   235  	defer bootloader.Force(nil)
   236  	bloader.SetBootKernel("pc-kernel_1.snap")
   237  	bloader.SetBootBase("core_1.snap")
   238  
   239  	s.startOverlord(c)
   240  	st := s.overlord.State()
   241  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   242  	chg, _ := s.makeSeedChange(c, st, opts, checkPreseedTasks, checkPreseedOrder)
   243  	err := s.overlord.Settle(settleTimeout)
   244  
   245  	st.Lock()
   246  	defer st.Unlock()
   247  
   248  	c.Assert(err, IsNil)
   249  	c.Assert(chg.Err(), IsNil)
   250  
   251  	checkPreseedTaskStates(c, st)
   252  }
   253  
   254  func (s *firstbootPreseed16Suite) TestPreseedOnClassicHappy(c *C) {
   255  	restore := snapdenv.MockPreseeding(true)
   256  	defer restore()
   257  
   258  	restoreRelease := release.MockOnClassic(true)
   259  	defer restoreRelease()
   260  
   261  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   262  	defer mockMountCmd.Restore()
   263  
   264  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   265  	defer mockUmountCmd.Restore()
   266  
   267  	coreFname, _, _ := s.makeCoreSnaps(c, "")
   268  
   269  	// put a firstboot snap into the SnapBlobDir
   270  	snapYaml := `name: foo
   271  version: 1.0
   272  `
   273  	fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
   274  	s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
   275  
   276  	// add a model assertion and its chain
   277  	assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
   278  	s.WriteAssertions("model.asserts", assertsChain...)
   279  
   280  	// create a seed.yaml
   281  	content := []byte(fmt.Sprintf(`
   282  snaps:
   283   - name: foo
   284     file: %s
   285   - name: core
   286     file: %s
   287  `, fooFname, coreFname))
   288  	err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
   289  	c.Assert(err, IsNil)
   290  
   291  	// run the firstboot stuff
   292  	s.startOverlord(c)
   293  	st := s.overlord.State()
   294  	st.Lock()
   295  	defer st.Unlock()
   296  
   297  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   298  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
   299  	c.Assert(err, IsNil)
   300  
   301  	chg := st.NewChange("seed", "run the populate from seed changes")
   302  	for _, ts := range tsAll {
   303  		chg.AddAll(ts)
   304  	}
   305  	c.Assert(st.Changes(), HasLen, 1)
   306  
   307  	checkPreseedOrder(c, tsAll, "core", "foo")
   308  
   309  	st.Unlock()
   310  	err = s.overlord.Settle(settleTimeout)
   311  	st.Lock()
   312  
   313  	c.Assert(err, IsNil)
   314  	c.Assert(chg.Err(), IsNil)
   315  
   316  	checkPreseedTaskStates(c, st)
   317  	c.Check(chg.Status(), Equals, state.DoingStatus)
   318  
   319  	// verify
   320  	r, err := os.Open(dirs.SnapStateFile)
   321  	c.Assert(err, IsNil)
   322  	diskState, err := state.ReadState(nil, r)
   323  	c.Assert(err, IsNil)
   324  
   325  	diskState.Lock()
   326  	defer diskState.Unlock()
   327  
   328  	// seeded snaps are installed
   329  	_, err = snapstate.CurrentInfo(diskState, "core")
   330  	c.Check(err, IsNil)
   331  	_, err = snapstate.CurrentInfo(diskState, "foo")
   332  	c.Check(err, IsNil)
   333  
   334  	// but we're not considered seeded
   335  	var seeded bool
   336  	err = diskState.Get("seeded", &seeded)
   337  	c.Assert(err, Equals, state.ErrNoState)
   338  }
   339  
   340  func (s *firstbootPreseed16Suite) TestPreseedClassicWithSnapdOnlyHappy(c *C) {
   341  	restorePreseedMode := snapdenv.MockPreseeding(true)
   342  	defer restorePreseedMode()
   343  
   344  	restore := release.MockOnClassic(true)
   345  	defer restore()
   346  
   347  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   348  	defer mockMountCmd.Restore()
   349  
   350  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   351  	defer mockUmountCmd.Restore()
   352  
   353  	core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{
   354  		classic: true,
   355  	})
   356  
   357  	// put a firstboot snap into the SnapBlobDir
   358  	snapYaml := `name: foo
   359  version: 1.0
   360  base: core18
   361  `
   362  	fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
   363  	s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
   364  
   365  	// add a model assertion and its chain
   366  	assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
   367  	s.WriteAssertions("model.asserts", assertsChain...)
   368  
   369  	// create a seed.yaml
   370  	content := []byte(fmt.Sprintf(`
   371  snaps:
   372   - name: snapd
   373     file: %s
   374   - name: foo
   375     file: %s
   376   - name: core18
   377     file: %s
   378  `, snapdFname, fooFname, core18Fname))
   379  	err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
   380  	c.Assert(err, IsNil)
   381  
   382  	// run the firstboot stuff
   383  	s.startOverlord(c)
   384  	st := s.overlord.State()
   385  	st.Lock()
   386  	defer st.Unlock()
   387  
   388  	opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true}
   389  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings)
   390  	c.Assert(err, IsNil)
   391  
   392  	checkPreseedOrder(c, tsAll, "snapd", "core18", "foo")
   393  
   394  	// now run the change and check the result
   395  	chg := st.NewChange("seed", "run the populate from seed changes")
   396  	for _, ts := range tsAll {
   397  		chg.AddAll(ts)
   398  	}
   399  	c.Assert(st.Changes(), HasLen, 1)
   400  	c.Assert(chg.Err(), IsNil)
   401  
   402  	st.Unlock()
   403  	err = s.overlord.Settle(settleTimeout)
   404  	st.Lock()
   405  	c.Assert(err, IsNil)
   406  
   407  	checkPreseedTaskStates(c, st)
   408  	c.Check(chg.Status(), Equals, state.DoingStatus)
   409  
   410  	// verify
   411  	r, err := os.Open(dirs.SnapStateFile)
   412  	c.Assert(err, IsNil)
   413  	diskState, err := state.ReadState(nil, r)
   414  	c.Assert(err, IsNil)
   415  
   416  	diskState.Lock()
   417  	defer diskState.Unlock()
   418  
   419  	// seeded snaps are installed
   420  	_, err = snapstate.CurrentInfo(diskState, "snapd")
   421  	c.Check(err, IsNil)
   422  	_, err = snapstate.CurrentInfo(diskState, "core18")
   423  	c.Check(err, IsNil)
   424  	_, err = snapstate.CurrentInfo(diskState, "foo")
   425  	c.Check(err, IsNil)
   426  
   427  	// but we're not considered seeded
   428  	var seeded bool
   429  	err = diskState.Get("seeded", &seeded)
   430  	c.Assert(err, Equals, state.ErrNoState)
   431  }