github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/devicestate/firstboot20_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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  	"os"
    24  	"path/filepath"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/boot"
    31  	"github.com/snapcore/snapd/bootloader"
    32  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/overlord/devicestate"
    36  	"github.com/snapcore/snapd/overlord/ifacestate"
    37  	"github.com/snapcore/snapd/overlord/snapstate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  	"github.com/snapcore/snapd/seed/seedtest"
    40  	"github.com/snapcore/snapd/snap"
    41  	"github.com/snapcore/snapd/systemd"
    42  	"github.com/snapcore/snapd/testutil"
    43  )
    44  
    45  type firstBoot20Suite struct {
    46  	firstBootBaseTest
    47  
    48  	snapYaml map[string]string
    49  
    50  	// TestingSeed20 helps populating seeds (it provides
    51  	// MakeAssertedSnap, MakeSeed) for tests.
    52  	*seedtest.TestingSeed20
    53  }
    54  
    55  var _ = Suite(&firstBoot20Suite{})
    56  
    57  func (s *firstBoot20Suite) SetUpTest(c *C) {
    58  	s.snapYaml = seedtest.SampleSnapYaml
    59  
    60  	s.TestingSeed20 = &seedtest.TestingSeed20{}
    61  
    62  	s.setupBaseTest(c, &s.TestingSeed20.SeedSnaps)
    63  
    64  	// don't start the overlord here so that we can mock different modeenvs
    65  	// later, which is needed by devicestart manager startup with uc20 booting
    66  
    67  	s.SeedDir = dirs.SnapSeedDir
    68  
    69  	// mock the snap mapper as snapd here
    70  	s.AddCleanup(ifacestate.MockSnapMapper(&ifacestate.CoreSnapdSystemMapper{}))
    71  }
    72  
    73  func (s *firstBoot20Suite) setupCore20Seed(c *C, sysLabel string) *asserts.Model {
    74  	gadgetYaml := `
    75  volumes:
    76      volume-id:
    77          bootloader: grub
    78          structure:
    79          - name: ubuntu-seed
    80            role: system-seed
    81            type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
    82            size: 1G
    83          - name: ubuntu-data
    84            role: system-data
    85            type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
    86            size: 2G
    87  `
    88  
    89  	makeSnap := func(yamlKey string) {
    90  		var files [][]string
    91  		if yamlKey == "pc=20" {
    92  			files = append(files, []string{"meta/gadget.yaml", gadgetYaml})
    93  		}
    94  		s.MakeAssertedSnap(c, s.snapYaml[yamlKey], files, snap.R(1), "canonical", s.StoreSigning.Database)
    95  	}
    96  
    97  	makeSnap("snapd")
    98  	makeSnap("pc-kernel=20")
    99  	makeSnap("core20")
   100  	makeSnap("pc=20")
   101  
   102  	return s.MakeSeed(c, sysLabel, "my-brand", "my-model", map[string]interface{}{
   103  		"display-name": "my model",
   104  		"architecture": "amd64",
   105  		"base":         "core20",
   106  		"snaps": []interface{}{
   107  			map[string]interface{}{
   108  				"name":            "pc-kernel",
   109  				"id":              s.AssertedSnapID("pc-kernel"),
   110  				"type":            "kernel",
   111  				"default-channel": "20",
   112  			},
   113  			map[string]interface{}{
   114  				"name":            "pc",
   115  				"id":              s.AssertedSnapID("pc"),
   116  				"type":            "gadget",
   117  				"default-channel": "20",
   118  			}},
   119  	}, nil)
   120  }
   121  
   122  func (s *firstBoot20Suite) testPopulateFromSeedCore20Happy(c *C, m *boot.Modeenv) {
   123  	c.Assert(m, NotNil, Commentf("missing modeenv test data"))
   124  	err := m.WriteTo("")
   125  	c.Assert(err, IsNil)
   126  
   127  	// restart overlord to pick up the modeenv
   128  	s.startOverlord(c)
   129  
   130  	// XXX some things are not yet completely final/realistic
   131  	var sysdLog [][]string
   132  	systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
   133  		sysdLog = append(sysdLog, cmd)
   134  		return []byte("ActiveState=inactive\n"), nil
   135  	})
   136  	defer systemctlRestorer()
   137  
   138  	sysLabel := m.RecoverySystem
   139  	model := s.setupCore20Seed(c, sysLabel)
   140  
   141  	bloader := bootloadertest.Mock("mock", c.MkDir()).WithExtractedRunKernelImage()
   142  	bootloader.Force(bloader)
   143  	defer bootloader.Force(nil)
   144  
   145  	// since we are in runmode, MakeBootable will already have run from install
   146  	// mode, and extracted the kernel assets for the kernel snap into the
   147  	// bootloader, so set the current kernel there
   148  	kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
   149  	c.Assert(err, IsNil)
   150  	r := bloader.SetEnabledKernel(kernel)
   151  	defer r()
   152  
   153  	opts := devicestate.PopulateStateFromSeedOptions{
   154  		Label: sysLabel,
   155  		Mode:  m.Mode,
   156  	}
   157  
   158  	// run the firstboot stuff
   159  	st := s.overlord.State()
   160  	st.Lock()
   161  	defer st.Unlock()
   162  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, &opts, s.perfTimings)
   163  	c.Assert(err, IsNil)
   164  
   165  	checkOrder(c, tsAll, "snapd", "pc-kernel", "core20", "pc")
   166  
   167  	// now run the change and check the result
   168  	// use the expected kind otherwise settle with start another one
   169  	chg := st.NewChange("seed", "run the populate from seed changes")
   170  	for _, ts := range tsAll {
   171  		chg.AddAll(ts)
   172  	}
   173  	c.Assert(st.Changes(), HasLen, 1)
   174  
   175  	c.Assert(chg.Err(), IsNil)
   176  
   177  	// avoid device reg
   178  	chg1 := st.NewChange("become-operational", "init device")
   179  	chg1.SetStatus(state.DoingStatus)
   180  
   181  	// run change until it wants to restart
   182  	st.Unlock()
   183  	err = s.overlord.Settle(settleTimeout)
   184  	st.Lock()
   185  	c.Assert(err, IsNil)
   186  
   187  	// at this point the system is "restarting", pretend the restart has
   188  	// happened
   189  	c.Assert(chg.Status(), Equals, state.DoingStatus)
   190  	state.MockRestarting(st, state.RestartUnset)
   191  	st.Unlock()
   192  	err = s.overlord.Settle(settleTimeout)
   193  	st.Lock()
   194  	c.Assert(err, IsNil)
   195  	c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%s", chg.Err()))
   196  
   197  	// verify
   198  	f, err := os.Open(dirs.SnapStateFile)
   199  	c.Assert(err, IsNil)
   200  	state, err := state.ReadState(nil, f)
   201  	c.Assert(err, IsNil)
   202  
   203  	state.Lock()
   204  	defer state.Unlock()
   205  	// check snapd, core20, kernel, gadget
   206  	_, err = snapstate.CurrentInfo(state, "snapd")
   207  	c.Check(err, IsNil)
   208  	_, err = snapstate.CurrentInfo(state, "core20")
   209  	c.Check(err, IsNil)
   210  	_, err = snapstate.CurrentInfo(state, "pc-kernel")
   211  	c.Check(err, IsNil)
   212  	_, err = snapstate.CurrentInfo(state, "pc")
   213  	c.Check(err, IsNil)
   214  
   215  	// ensure required flag is set on all essential snaps
   216  	var snapst snapstate.SnapState
   217  	for _, reqName := range []string{"snapd", "core20", "pc-kernel", "pc"} {
   218  		err = snapstate.Get(state, reqName, &snapst)
   219  		c.Assert(err, IsNil)
   220  		c.Assert(snapst.Required, Equals, true, Commentf("required not set for %v", reqName))
   221  
   222  		if m.Mode == "run" {
   223  			// also ensure that in run mode none of the snaps are installed as
   224  			// symlinks, they must be copied onto ubuntu-data
   225  			files, err := filepath.Glob(filepath.Join(dirs.SnapBlobDir, reqName+"_*.snap"))
   226  			c.Assert(err, IsNil)
   227  			c.Assert(files, HasLen, 1)
   228  			c.Assert(osutil.IsSymlink(files[0]), Equals, false)
   229  		}
   230  	}
   231  
   232  	// the right systemd commands were run
   233  	c.Check(sysdLog, testutil.DeepContains, []string{"start", "usr-lib-snapd.mount"})
   234  
   235  	// and ensure state is now considered seeded
   236  	var seeded bool
   237  	err = state.Get("seeded", &seeded)
   238  	c.Assert(err, IsNil)
   239  	c.Check(seeded, Equals, true)
   240  
   241  	// check we set seed-time
   242  	var seedTime time.Time
   243  	err = state.Get("seed-time", &seedTime)
   244  	c.Assert(err, IsNil)
   245  	c.Check(seedTime.IsZero(), Equals, false)
   246  
   247  	// check that we removed recovery_system from modeenv
   248  	m2, err := boot.ReadModeenv("")
   249  	c.Assert(err, IsNil)
   250  	if m.Mode == "run" {
   251  		// recovery system is cleared in run mode
   252  		c.Assert(m2.RecoverySystem, Equals, "")
   253  	} else {
   254  		// but kept intact in other modes
   255  		c.Assert(m2.RecoverySystem, Equals, m.RecoverySystem)
   256  	}
   257  	c.Assert(m2.Base, Equals, m.Base)
   258  	c.Assert(m2.Mode, Equals, m.Mode)
   259  	// Note that we don't check CurrentKernels in the modeenv, even though in a
   260  	// real first boot that would also be set here, because setting that is done
   261  	// in the snapstate manager, not the devicestate manager
   262  
   263  	// check that the default device ctx has a Modeenv
   264  	dev, err := devicestate.DeviceCtx(s.overlord.State(), nil, nil)
   265  	c.Assert(err, IsNil)
   266  	c.Assert(dev.HasModeenv(), Equals, true)
   267  
   268  	// check that we marked the boot successful with bootstate20 methods, namely
   269  	// that we called SetNext, which since it was called on the kernel we
   270  	// already booted from, we should only have checked what the current kernel
   271  	// is
   272  
   273  	if m.Mode == "run" {
   274  		// only relevant in run mode
   275  
   276  		// the 3 calls here are :
   277  		// * 1 from MarkBootSuccessful() from ensureBootOk() before we restart
   278  		// * 1 from boot.SetNextBoot() from LinkSnap() from doInstall() from InstallPath() from
   279  		//     installSeedSnap() after restart
   280  		// * 1 from boot.GetCurrentBoot() from WaitRestart after restart
   281  		_, numKernelCalls := bloader.GetRunKernelImageFunctionSnapCalls("Kernel")
   282  		c.Assert(numKernelCalls, Equals, 3)
   283  	}
   284  	actual, _ := bloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
   285  	c.Assert(actual, HasLen, 0)
   286  	actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
   287  	c.Assert(actual, HasLen, 0)
   288  	actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
   289  	c.Assert(actual, HasLen, 0)
   290  
   291  	var whatseeded []devicestate.SeededSystem
   292  	err = state.Get("seeded-systems", &whatseeded)
   293  	if m.Mode == "run" {
   294  		c.Assert(err, IsNil)
   295  		c.Assert(whatseeded, DeepEquals, []devicestate.SeededSystem{{
   296  			System:    m.RecoverySystem,
   297  			Model:     "my-model",
   298  			BrandID:   "my-brand",
   299  			Revision:  model.Revision(),
   300  			Timestamp: model.Timestamp(),
   301  			SeedTime:  seedTime,
   302  		}})
   303  	} else {
   304  		c.Assert(err, NotNil)
   305  	}
   306  }
   307  
   308  func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunMode(c *C) {
   309  	m := boot.Modeenv{
   310  		Mode:           "run",
   311  		RecoverySystem: "20191018",
   312  		Base:           "core20_1.snap",
   313  	}
   314  	s.testPopulateFromSeedCore20Happy(c, &m)
   315  }
   316  
   317  func (s *firstBoot20Suite) TestPopulateFromSeedCore20InstallMode(c *C) {
   318  	m := boot.Modeenv{
   319  		Mode:           "install",
   320  		RecoverySystem: "20191019",
   321  		Base:           "core20_1.snap",
   322  	}
   323  	s.testPopulateFromSeedCore20Happy(c, &m)
   324  }
   325  
   326  func (s *firstBoot20Suite) TestPopulateFromSeedCore20RecoverMode(c *C) {
   327  	m := boot.Modeenv{
   328  		Mode:           "recover",
   329  		RecoverySystem: "20191020",
   330  		Base:           "core20_1.snap",
   331  	}
   332  	s.testPopulateFromSeedCore20Happy(c, &m)
   333  }