gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/devicestate/devicestate_install_mode_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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  	"bytes"
    24  	"compress/gzip"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  
    30  	. "gopkg.in/check.v1"
    31  	"gopkg.in/tomb.v2"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/boot"
    35  	"github.com/snapcore/snapd/bootloader"
    36  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    37  	"github.com/snapcore/snapd/dirs"
    38  	"github.com/snapcore/snapd/gadget"
    39  	"github.com/snapcore/snapd/gadget/install"
    40  	"github.com/snapcore/snapd/logger"
    41  	"github.com/snapcore/snapd/overlord/auth"
    42  	"github.com/snapcore/snapd/overlord/devicestate"
    43  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    44  	"github.com/snapcore/snapd/overlord/hookstate"
    45  	"github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
    46  	"github.com/snapcore/snapd/overlord/snapstate"
    47  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    48  	"github.com/snapcore/snapd/overlord/state"
    49  	"github.com/snapcore/snapd/release"
    50  	"github.com/snapcore/snapd/secboot"
    51  	"github.com/snapcore/snapd/snap"
    52  	"github.com/snapcore/snapd/snap/snaptest"
    53  	"github.com/snapcore/snapd/sysconfig"
    54  	"github.com/snapcore/snapd/testutil"
    55  	"github.com/snapcore/snapd/timings"
    56  )
    57  
    58  type deviceMgrInstallModeSuite struct {
    59  	deviceMgrBaseSuite
    60  
    61  	ConfigureTargetSystemOptsPassed []*sysconfig.Options
    62  	ConfigureTargetSystemErr        error
    63  }
    64  
    65  var _ = Suite(&deviceMgrInstallModeSuite{})
    66  
    67  func (s *deviceMgrInstallModeSuite) findInstallSystem() *state.Change {
    68  	for _, chg := range s.state.Changes() {
    69  		if chg.Kind() == "install-system" {
    70  			return chg
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  func (s *deviceMgrInstallModeSuite) SetUpTest(c *C) {
    77  	s.deviceMgrBaseSuite.SetUpTest(c)
    78  
    79  	s.ConfigureTargetSystemOptsPassed = nil
    80  	s.ConfigureTargetSystemErr = nil
    81  	restore := devicestate.MockSysconfigConfigureTargetSystem(func(mod *asserts.Model, opts *sysconfig.Options) error {
    82  		c.Check(mod, NotNil)
    83  		s.ConfigureTargetSystemOptsPassed = append(s.ConfigureTargetSystemOptsPassed, opts)
    84  		return s.ConfigureTargetSystemErr
    85  	})
    86  	s.AddCleanup(restore)
    87  
    88  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error {
    89  		return fmt.Errorf("TPM not available")
    90  	})
    91  	s.AddCleanup(restore)
    92  
    93  	s.state.Lock()
    94  	defer s.state.Unlock()
    95  	s.state.Set("seeded", true)
    96  
    97  	fakeJournalctl := testutil.MockCommand(c, "journalctl", "")
    98  	s.AddCleanup(fakeJournalctl.Restore)
    99  }
   100  
   101  const (
   102  	pcSnapID       = "pcididididididididididididididid"
   103  	pcKernelSnapID = "pckernelidididididididididididid"
   104  	core20SnapID   = "core20ididididididididididididid"
   105  )
   106  
   107  func (s *deviceMgrInstallModeSuite) makeMockInstalledPcGadget(c *C, grade, installDeviceHook string, gadgetDefaultsYaml string) *asserts.Model {
   108  	si := &snap.SideInfo{
   109  		RealName: "pc-kernel",
   110  		Revision: snap.R(1),
   111  		SnapID:   pcKernelSnapID,
   112  	}
   113  	snapstate.Set(s.state, "pc-kernel", &snapstate.SnapState{
   114  		SnapType: "kernel",
   115  		Sequence: []*snap.SideInfo{si},
   116  		Current:  si.Revision,
   117  		Active:   true,
   118  	})
   119  	kernelInfo := snaptest.MockSnapWithFiles(c, "name: pc-kernel\ntype: kernel", si, nil)
   120  	kernelFn := snaptest.MakeTestSnapWithFiles(c, "name: pc-kernel\ntype: kernel\nversion: 1.0", nil)
   121  	err := os.Rename(kernelFn, kernelInfo.MountFile())
   122  	c.Assert(err, IsNil)
   123  
   124  	si = &snap.SideInfo{
   125  		RealName: "pc",
   126  		Revision: snap.R(1),
   127  		SnapID:   pcSnapID,
   128  	}
   129  	snapstate.Set(s.state, "pc", &snapstate.SnapState{
   130  		SnapType: "gadget",
   131  		Sequence: []*snap.SideInfo{si},
   132  		Current:  si.Revision,
   133  		Active:   true,
   134  	})
   135  
   136  	files := [][]string{
   137  		{"meta/gadget.yaml", uc20gadgetYamlWithSave + gadgetDefaultsYaml},
   138  	}
   139  	if installDeviceHook != "" {
   140  		files = append(files, []string{"meta/hooks/install-device", installDeviceHook})
   141  	}
   142  	snaptest.MockSnapWithFiles(c, "name: pc\ntype: gadget", si, files)
   143  
   144  	si = &snap.SideInfo{
   145  		RealName: "core20",
   146  		Revision: snap.R(2),
   147  		SnapID:   core20SnapID,
   148  	}
   149  	snapstate.Set(s.state, "core20", &snapstate.SnapState{
   150  		SnapType: "base",
   151  		Sequence: []*snap.SideInfo{si},
   152  		Current:  si.Revision,
   153  		Active:   true,
   154  	})
   155  	snaptest.MockSnapWithFiles(c, "name: core20\ntype: base", si, nil)
   156  
   157  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
   158  		"display-name": "my model",
   159  		"architecture": "amd64",
   160  		"base":         "core20",
   161  		"grade":        grade,
   162  		"snaps": []interface{}{
   163  			map[string]interface{}{
   164  				"name":            "pc-kernel",
   165  				"id":              pcKernelSnapID,
   166  				"type":            "kernel",
   167  				"default-channel": "20",
   168  			},
   169  			map[string]interface{}{
   170  				"name":            "pc",
   171  				"id":              pcSnapID,
   172  				"type":            "gadget",
   173  				"default-channel": "20",
   174  			}},
   175  	})
   176  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   177  		Brand: "my-brand",
   178  		Model: "my-model",
   179  		// no serial in install mode
   180  	})
   181  
   182  	return mockModel
   183  }
   184  
   185  type encTestCase struct {
   186  	tpm               bool
   187  	bypass            bool
   188  	encrypt           bool
   189  	trustedBootloader bool
   190  }
   191  
   192  var (
   193  	dataEncryptionKey = secboot.EncryptionKey{'d', 'a', 't', 'a', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
   194  	dataRecoveryKey   = secboot.RecoveryKey{'r', 'e', 'c', 'o', 'v', 'e', 'r', 'y', 10, 11, 12, 13, 14, 15, 16, 17}
   195  
   196  	saveKey      = secboot.EncryptionKey{'s', 'a', 'v', 'e', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
   197  	reinstallKey = secboot.RecoveryKey{'r', 'e', 'i', 'n', 's', 't', 'a', 'l', 'l', 11, 12, 13, 14, 15, 16, 17}
   198  )
   199  
   200  func (s *deviceMgrInstallModeSuite) doRunChangeTestWithEncryption(c *C, grade string, tc encTestCase) error {
   201  	restore := release.MockOnClassic(false)
   202  	defer restore()
   203  	bootloaderRootdir := c.MkDir()
   204  
   205  	var brGadgetRoot, brDevice string
   206  	var brOpts install.Options
   207  	var installRunCalled int
   208  	var installSealingObserver gadget.ContentObserver
   209  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) {
   210  		// ensure we can grab the lock here, i.e. that it's not taken
   211  		s.state.Lock()
   212  		s.state.Unlock()
   213  
   214  		c.Check(mod.Grade(), Equals, asserts.ModelGrade(grade))
   215  
   216  		brGadgetRoot = gadgetRoot
   217  		brDevice = device
   218  		brOpts = options
   219  		installSealingObserver = obs
   220  		installRunCalled++
   221  		var keysForRoles map[string]*install.EncryptionKeySet
   222  		if tc.encrypt {
   223  			keysForRoles = map[string]*install.EncryptionKeySet{
   224  				gadget.SystemData: {
   225  					Key:         dataEncryptionKey,
   226  					RecoveryKey: dataRecoveryKey,
   227  				},
   228  				gadget.SystemSave: {
   229  					Key:         saveKey,
   230  					RecoveryKey: reinstallKey,
   231  				},
   232  			}
   233  		}
   234  		return &install.InstalledSystemSideData{
   235  			KeysForRoles: keysForRoles,
   236  		}, nil
   237  	})
   238  	defer restore()
   239  
   240  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error {
   241  		if tc.tpm {
   242  			return nil
   243  		} else {
   244  			return fmt.Errorf("TPM not available")
   245  		}
   246  	})
   247  	defer restore()
   248  
   249  	if tc.trustedBootloader {
   250  		tab := bootloadertest.Mock("trusted", bootloaderRootdir).WithTrustedAssets()
   251  		tab.TrustedAssetsList = []string{"trusted-asset"}
   252  		bootloader.Force(tab)
   253  		s.AddCleanup(func() { bootloader.Force(nil) })
   254  
   255  		err := os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755)
   256  		c.Assert(err, IsNil)
   257  		err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "trusted-asset"), nil, 0644)
   258  		c.Assert(err, IsNil)
   259  	}
   260  
   261  	s.state.Lock()
   262  	mockModel := s.makeMockInstalledPcGadget(c, grade, "", "")
   263  	s.state.Unlock()
   264  
   265  	bypassEncryptionPath := filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")
   266  	if tc.bypass {
   267  		err := os.MkdirAll(filepath.Dir(bypassEncryptionPath), 0755)
   268  		c.Assert(err, IsNil)
   269  		f, err := os.Create(bypassEncryptionPath)
   270  		c.Assert(err, IsNil)
   271  		f.Close()
   272  	} else {
   273  		os.RemoveAll(bypassEncryptionPath)
   274  	}
   275  
   276  	bootMakeBootableCalled := 0
   277  	restore = devicestate.MockBootMakeSystemRunnable(func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error {
   278  		c.Check(model, DeepEquals, mockModel)
   279  		c.Check(bootWith.KernelPath, Matches, ".*/var/lib/snapd/snaps/pc-kernel_1.snap")
   280  		c.Check(bootWith.BasePath, Matches, ".*/var/lib/snapd/snaps/core20_2.snap")
   281  		c.Check(bootWith.RecoverySystemDir, Matches, "/systems/20191218")
   282  		c.Check(bootWith.UnpackedGadgetDir, Equals, filepath.Join(dirs.SnapMountDir, "pc/1"))
   283  		if tc.encrypt {
   284  			c.Check(seal, NotNil)
   285  		} else {
   286  			c.Check(seal, IsNil)
   287  		}
   288  		bootMakeBootableCalled++
   289  		return nil
   290  	})
   291  	defer restore()
   292  
   293  	modeenv := boot.Modeenv{
   294  		Mode:           "install",
   295  		RecoverySystem: "20191218",
   296  	}
   297  	c.Assert(modeenv.WriteTo(""), IsNil)
   298  	devicestate.SetSystemMode(s.mgr, "install")
   299  
   300  	// normally done by snap-bootstrap
   301  	err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755)
   302  	c.Assert(err, IsNil)
   303  
   304  	s.settle(c)
   305  
   306  	// the install-system change is created
   307  	s.state.Lock()
   308  	defer s.state.Unlock()
   309  	installSystem := s.findInstallSystem()
   310  	c.Assert(installSystem, NotNil)
   311  
   312  	// and was run successfully
   313  	if err := installSystem.Err(); err != nil {
   314  		// we failed, no further checks needed
   315  		return err
   316  	}
   317  
   318  	c.Assert(installSystem.Status(), Equals, state.DoneStatus)
   319  
   320  	// in the right way
   321  	c.Assert(brGadgetRoot, Equals, filepath.Join(dirs.SnapMountDir, "/pc/1"))
   322  	c.Assert(brDevice, Equals, "")
   323  	if tc.encrypt {
   324  		c.Assert(brOpts, DeepEquals, install.Options{
   325  			Mount:   true,
   326  			Encrypt: true,
   327  		})
   328  	} else {
   329  		c.Assert(brOpts, DeepEquals, install.Options{
   330  			Mount: true,
   331  		})
   332  	}
   333  	if tc.encrypt {
   334  		// inteface is not nil
   335  		c.Assert(installSealingObserver, NotNil)
   336  		// we expect a very specific type
   337  		trustedInstallObserver, ok := installSealingObserver.(*boot.TrustedAssetsInstallObserver)
   338  		c.Assert(ok, Equals, true, Commentf("unexpected type: %T", installSealingObserver))
   339  		c.Assert(trustedInstallObserver, NotNil)
   340  	} else {
   341  		c.Assert(installSealingObserver, IsNil)
   342  	}
   343  
   344  	c.Assert(installRunCalled, Equals, 1)
   345  	c.Assert(bootMakeBootableCalled, Equals, 1)
   346  	c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   347  
   348  	return nil
   349  }
   350  
   351  func (s *deviceMgrInstallModeSuite) TestInstallTaskErrors(c *C) {
   352  	restore := release.MockOnClassic(false)
   353  	defer restore()
   354  
   355  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   356  		return nil, fmt.Errorf("The horror, The horror")
   357  	})
   358  	defer restore()
   359  
   360  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   361  		[]byte("mode=install\n"), 0644)
   362  	c.Assert(err, IsNil)
   363  
   364  	s.state.Lock()
   365  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   366  	devicestate.SetSystemMode(s.mgr, "install")
   367  	s.state.Unlock()
   368  
   369  	s.settle(c)
   370  
   371  	s.state.Lock()
   372  	defer s.state.Unlock()
   373  
   374  	installSystem := s.findInstallSystem()
   375  	c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks:
   376  - Setup system for run mode \(cannot install system: The horror, The horror\)`)
   377  	// no restart request on failure
   378  	c.Check(s.restartRequests, HasLen, 0)
   379  }
   380  
   381  func (s *deviceMgrInstallModeSuite) TestInstallExpTasks(c *C) {
   382  	restore := release.MockOnClassic(false)
   383  	defer restore()
   384  
   385  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   386  		return nil, nil
   387  	})
   388  	defer restore()
   389  
   390  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   391  		[]byte("mode=install\n"), 0644)
   392  	c.Assert(err, IsNil)
   393  
   394  	s.state.Lock()
   395  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   396  	devicestate.SetSystemMode(s.mgr, "install")
   397  	s.state.Unlock()
   398  
   399  	s.settle(c)
   400  
   401  	s.state.Lock()
   402  	defer s.state.Unlock()
   403  
   404  	installSystem := s.findInstallSystem()
   405  	c.Check(installSystem.Err(), IsNil)
   406  
   407  	tasks := installSystem.Tasks()
   408  	c.Assert(tasks, HasLen, 2)
   409  	setupRunSystemTask := tasks[0]
   410  	restartSystemToRunModeTask := tasks[1]
   411  
   412  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   413  	c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode")
   414  
   415  	// setup-run-system has no pre-reqs
   416  	c.Assert(setupRunSystemTask.WaitTasks(), HasLen, 0)
   417  
   418  	// restart-system-to-run-mode has a pre-req of setup-run-system
   419  	waitTasks := restartSystemToRunModeTask.WaitTasks()
   420  	c.Assert(waitTasks, HasLen, 1)
   421  	c.Assert(waitTasks[0].ID(), Equals, setupRunSystemTask.ID())
   422  
   423  	// we did request a restart through restartSystemToRunModeTask
   424  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   425  }
   426  
   427  func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookExpTasks(c *C) {
   428  	restore := release.MockOnClassic(false)
   429  	defer restore()
   430  
   431  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   432  		return nil, nil
   433  	})
   434  	defer restore()
   435  
   436  	hooksCalled := []*hookstate.Context{}
   437  	restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
   438  		ctx.Lock()
   439  		defer ctx.Unlock()
   440  
   441  		hooksCalled = append(hooksCalled, ctx)
   442  		return nil, nil
   443  	})
   444  	defer restore()
   445  
   446  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   447  		[]byte("mode=install\n"), 0644)
   448  	c.Assert(err, IsNil)
   449  
   450  	s.state.Lock()
   451  	s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "")
   452  	devicestate.SetSystemMode(s.mgr, "install")
   453  	s.state.Unlock()
   454  
   455  	s.settle(c)
   456  
   457  	s.state.Lock()
   458  	defer s.state.Unlock()
   459  
   460  	installSystem := s.findInstallSystem()
   461  	c.Check(installSystem.Err(), IsNil)
   462  
   463  	tasks := installSystem.Tasks()
   464  	c.Assert(tasks, HasLen, 3)
   465  	setupRunSystemTask := tasks[0]
   466  	installDevice := tasks[1]
   467  	restartSystemToRunModeTask := tasks[2]
   468  
   469  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   470  	c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode")
   471  	c.Assert(installDevice.Kind(), Equals, "run-hook")
   472  
   473  	// setup-run-system has no pre-reqs
   474  	c.Assert(setupRunSystemTask.WaitTasks(), HasLen, 0)
   475  
   476  	// install-device has a pre-req of setup-run-system
   477  	waitTasks := installDevice.WaitTasks()
   478  	c.Assert(waitTasks, HasLen, 1)
   479  	c.Assert(waitTasks[0].ID(), Equals, setupRunSystemTask.ID())
   480  
   481  	// install-device restart-task references to restart-system-to-run-mode
   482  	var restartTask string
   483  	err = installDevice.Get("restart-task", &restartTask)
   484  	c.Assert(err, IsNil)
   485  	c.Check(restartTask, Equals, restartSystemToRunModeTask.ID())
   486  
   487  	// restart-system-to-run-mode has a pre-req of install-device
   488  	waitTasks = restartSystemToRunModeTask.WaitTasks()
   489  	c.Assert(waitTasks, HasLen, 1)
   490  	c.Assert(waitTasks[0].ID(), Equals, installDevice.ID())
   491  
   492  	// we did request a restart through restartSystemToRunModeTask
   493  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   494  
   495  	c.Assert(hooksCalled, HasLen, 1)
   496  	c.Assert(hooksCalled[0].HookName(), Equals, "install-device")
   497  }
   498  
   499  func (s *deviceMgrInstallModeSuite) testInstallWithInstallDeviceHookSnapctlReboot(c *C, arg string, rst state.RestartType) {
   500  	restore := release.MockOnClassic(false)
   501  	defer restore()
   502  
   503  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   504  		return nil, nil
   505  	})
   506  	defer restore()
   507  
   508  	restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
   509  		c.Assert(ctx.HookName(), Equals, "install-device")
   510  
   511  		// snapctl reboot --halt
   512  		_, _, err := ctlcmd.Run(ctx, []string{"reboot", arg}, 0)
   513  		return nil, err
   514  	})
   515  	defer restore()
   516  
   517  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   518  		[]byte("mode=install\n"), 0644)
   519  	c.Assert(err, IsNil)
   520  
   521  	s.state.Lock()
   522  	s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "")
   523  	devicestate.SetSystemMode(s.mgr, "install")
   524  	s.state.Unlock()
   525  
   526  	s.settle(c)
   527  
   528  	s.state.Lock()
   529  	defer s.state.Unlock()
   530  
   531  	installSystem := s.findInstallSystem()
   532  	c.Check(installSystem.Err(), IsNil)
   533  
   534  	// we did end up requesting the right shutdown
   535  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{rst})
   536  }
   537  
   538  func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookSnapctlRebootHalt(c *C) {
   539  	s.testInstallWithInstallDeviceHookSnapctlReboot(c, "--halt", state.RestartSystemHaltNow)
   540  }
   541  
   542  func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookSnapctlRebootPoweroff(c *C) {
   543  	s.testInstallWithInstallDeviceHookSnapctlReboot(c, "--poweroff", state.RestartSystemPoweroffNow)
   544  }
   545  
   546  func (s *deviceMgrInstallModeSuite) TestInstallWithBrokenInstallDeviceHookUnhappy(c *C) {
   547  	restore := release.MockOnClassic(false)
   548  	defer restore()
   549  
   550  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   551  		return nil, nil
   552  	})
   553  	defer restore()
   554  
   555  	hooksCalled := []*hookstate.Context{}
   556  	restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
   557  		ctx.Lock()
   558  		defer ctx.Unlock()
   559  
   560  		hooksCalled = append(hooksCalled, ctx)
   561  		return []byte("hook exited broken"), fmt.Errorf("hook broken")
   562  	})
   563  	defer restore()
   564  
   565  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   566  		[]byte("mode=install\n"), 0644)
   567  	c.Assert(err, IsNil)
   568  
   569  	s.state.Lock()
   570  	s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "")
   571  	devicestate.SetSystemMode(s.mgr, "install")
   572  	s.state.Unlock()
   573  
   574  	s.settle(c)
   575  
   576  	s.state.Lock()
   577  	defer s.state.Unlock()
   578  
   579  	installSystem := s.findInstallSystem()
   580  	c.Check(installSystem.Err(), ErrorMatches, `cannot perform the following tasks:
   581  - Run install-device hook \(run hook \"install-device\": hook exited broken\)`)
   582  
   583  	tasks := installSystem.Tasks()
   584  	c.Assert(tasks, HasLen, 3)
   585  	setupRunSystemTask := tasks[0]
   586  	installDevice := tasks[1]
   587  	restartSystemToRunModeTask := tasks[2]
   588  
   589  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   590  	c.Assert(installDevice.Kind(), Equals, "run-hook")
   591  	c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode")
   592  
   593  	// install-device is in Error state
   594  	c.Assert(installDevice.Status(), Equals, state.ErrorStatus)
   595  
   596  	// setup-run-system is in Done (it has no undo handler)
   597  	c.Assert(setupRunSystemTask.Status(), Equals, state.DoneStatus)
   598  
   599  	// restart-system-to-run-mode is in Hold
   600  	c.Assert(restartSystemToRunModeTask.Status(), Equals, state.HoldStatus)
   601  
   602  	// we didn't request a restart since restartsystemToRunMode didn't run
   603  	c.Check(s.restartRequests, HasLen, 0)
   604  
   605  	c.Assert(hooksCalled, HasLen, 1)
   606  	c.Assert(hooksCalled[0].HookName(), Equals, "install-device")
   607  }
   608  
   609  func (s *deviceMgrInstallModeSuite) TestInstallSetupRunSystemTaskNoRestarts(c *C) {
   610  	restore := release.MockOnClassic(false)
   611  	defer restore()
   612  
   613  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   614  		return nil, nil
   615  	})
   616  	defer restore()
   617  
   618  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   619  		[]byte("mode=install\n"), 0644)
   620  	c.Assert(err, IsNil)
   621  
   622  	s.state.Lock()
   623  	defer s.state.Unlock()
   624  
   625  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   626  	devicestate.SetSystemMode(s.mgr, "install")
   627  
   628  	// also set the system as installed so that the install-system change
   629  	// doesn't get automatically added and we can craft our own change with just
   630  	// the setup-run-system task and not with the restart-system-to-run-mode
   631  	// task
   632  	devicestate.SetInstalledRan(s.mgr, true)
   633  
   634  	s.state.Unlock()
   635  	defer s.state.Lock()
   636  
   637  	s.settle(c)
   638  
   639  	s.state.Lock()
   640  	defer s.state.Unlock()
   641  
   642  	// make sure there is no install-system change that snuck in underneath us
   643  	installSystem := s.findInstallSystem()
   644  	c.Check(installSystem, IsNil)
   645  
   646  	t := s.state.NewTask("setup-run-system", "setup run system")
   647  	chg := s.state.NewChange("install-system", "install the system")
   648  	chg.AddTask(t)
   649  
   650  	// now let the change run
   651  	s.state.Unlock()
   652  	defer s.state.Lock()
   653  
   654  	s.settle(c)
   655  
   656  	s.state.Lock()
   657  	defer s.state.Unlock()
   658  
   659  	// now we should have the install-system change
   660  	installSystem = s.findInstallSystem()
   661  	c.Check(installSystem, Not(IsNil))
   662  	c.Check(installSystem.Err(), IsNil)
   663  
   664  	tasks := installSystem.Tasks()
   665  	c.Assert(tasks, HasLen, 1)
   666  	setupRunSystemTask := tasks[0]
   667  
   668  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   669  
   670  	// we did not request a restart (since that is done in restart-system-to-run-mode)
   671  	c.Check(s.restartRequests, HasLen, 0)
   672  }
   673  
   674  func (s *deviceMgrInstallModeSuite) TestInstallModeNotInstallmodeNoChg(c *C) {
   675  	restore := release.MockOnClassic(false)
   676  	defer restore()
   677  
   678  	s.state.Lock()
   679  	devicestate.SetSystemMode(s.mgr, "")
   680  	s.state.Unlock()
   681  
   682  	s.settle(c)
   683  
   684  	s.state.Lock()
   685  	defer s.state.Unlock()
   686  
   687  	// the install-system change is *not* created (not in install mode)
   688  	installSystem := s.findInstallSystem()
   689  	c.Assert(installSystem, IsNil)
   690  }
   691  
   692  func (s *deviceMgrInstallModeSuite) TestInstallModeNotClassic(c *C) {
   693  	restore := release.MockOnClassic(true)
   694  	defer restore()
   695  
   696  	s.state.Lock()
   697  	devicestate.SetSystemMode(s.mgr, "install")
   698  	s.state.Unlock()
   699  
   700  	s.settle(c)
   701  
   702  	s.state.Lock()
   703  	defer s.state.Unlock()
   704  
   705  	// the install-system change is *not* created (we're on classic)
   706  	installSystem := s.findInstallSystem()
   707  	c.Assert(installSystem, IsNil)
   708  }
   709  
   710  func (s *deviceMgrInstallModeSuite) TestInstallDangerous(c *C) {
   711  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: false, encrypt: false})
   712  	c.Assert(err, IsNil)
   713  }
   714  
   715  func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPM(c *C) {
   716  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{
   717  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   718  	})
   719  	c.Assert(err, IsNil)
   720  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   721  }
   722  
   723  func (s *deviceMgrInstallModeSuite) TestInstallDangerousBypassEncryption(c *C) {
   724  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: true, encrypt: false})
   725  	c.Assert(err, IsNil)
   726  }
   727  
   728  func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPMBypassEncryption(c *C) {
   729  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: true, bypass: true, encrypt: false})
   730  	c.Assert(err, IsNil)
   731  }
   732  
   733  func (s *deviceMgrInstallModeSuite) TestInstallSigned(c *C) {
   734  	err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: false, encrypt: false})
   735  	c.Assert(err, IsNil)
   736  }
   737  
   738  func (s *deviceMgrInstallModeSuite) TestInstallSignedWithTPM(c *C) {
   739  	err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{
   740  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   741  	})
   742  	c.Assert(err, IsNil)
   743  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   744  }
   745  
   746  func (s *deviceMgrInstallModeSuite) TestInstallSignedBypassEncryption(c *C) {
   747  	err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: true, encrypt: false})
   748  	c.Assert(err, IsNil)
   749  }
   750  
   751  func (s *deviceMgrInstallModeSuite) TestInstallSecured(c *C) {
   752  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: false, encrypt: false})
   753  	c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*")
   754  }
   755  
   756  func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPM(c *C) {
   757  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
   758  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   759  	})
   760  	c.Assert(err, IsNil)
   761  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   762  }
   763  
   764  func (s *deviceMgrInstallModeSuite) TestInstallDangerousEncryptionWithTPMNoTrustedAssets(c *C) {
   765  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{
   766  		tpm: true, bypass: false, encrypt: true, trustedBootloader: false,
   767  	})
   768  	c.Assert(err, IsNil)
   769  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   770  }
   771  
   772  func (s *deviceMgrInstallModeSuite) TestInstallDangerousNoEncryptionWithTrustedAssets(c *C) {
   773  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{
   774  		tpm: false, bypass: false, encrypt: false, trustedBootloader: true,
   775  	})
   776  	c.Assert(err, IsNil)
   777  }
   778  
   779  func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPMAndSave(c *C) {
   780  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
   781  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   782  	})
   783  	c.Assert(err, IsNil)
   784  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   785  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey))
   786  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key"), testutil.FileEquals, reinstallKey[:])
   787  	marker, err := ioutil.ReadFile(filepath.Join(boot.InstallHostFDEDataDir, "marker"))
   788  	c.Assert(err, IsNil)
   789  	c.Check(marker, HasLen, 32)
   790  	c.Check(filepath.Join(boot.InstallHostFDESaveDir, "marker"), testutil.FileEquals, marker)
   791  }
   792  
   793  func (s *deviceMgrInstallModeSuite) TestInstallSecuredBypassEncryption(c *C) {
   794  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: true, encrypt: false})
   795  	c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*")
   796  }
   797  
   798  func (s *deviceMgrInstallModeSuite) TestInstallBootloaderVarSetFails(c *C) {
   799  	restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   800  		c.Check(options.Encrypt, Equals, false)
   801  		// no keys set
   802  		return &install.InstalledSystemSideData{}, nil
   803  	})
   804  	defer restore()
   805  
   806  	restore = devicestate.MockBootEnsureNextBootToRunMode(func(systemLabel string) error {
   807  		c.Check(systemLabel, Equals, "1234")
   808  		// no keys set
   809  		return fmt.Errorf("bootloader goes boom")
   810  	})
   811  	defer restore()
   812  
   813  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("no encrypted soup for you") })
   814  	defer restore()
   815  
   816  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   817  		[]byte("mode=install\nrecovery_system=1234"), 0644)
   818  	c.Assert(err, IsNil)
   819  
   820  	s.state.Lock()
   821  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   822  	devicestate.SetSystemMode(s.mgr, "install")
   823  	s.state.Unlock()
   824  
   825  	s.settle(c)
   826  
   827  	s.state.Lock()
   828  	defer s.state.Unlock()
   829  
   830  	installSystem := s.findInstallSystem()
   831  	c.Check(installSystem.Err(), ErrorMatches, `cannot perform the following tasks:
   832  - Ensure next boot to run mode \(bootloader goes boom\)`)
   833  	// no restart request on failure
   834  	c.Check(s.restartRequests, HasLen, 0)
   835  }
   836  
   837  func (s *deviceMgrInstallModeSuite) testInstallEncryptionSanityChecks(c *C, errMatch string) {
   838  	restore := release.MockOnClassic(false)
   839  	defer restore()
   840  
   841  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil })
   842  	defer restore()
   843  
   844  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   845  		[]byte("mode=install\n"), 0644)
   846  	c.Assert(err, IsNil)
   847  
   848  	s.state.Lock()
   849  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   850  	devicestate.SetSystemMode(s.mgr, "install")
   851  	s.state.Unlock()
   852  
   853  	s.settle(c)
   854  
   855  	s.state.Lock()
   856  	defer s.state.Unlock()
   857  
   858  	installSystem := s.findInstallSystem()
   859  	c.Check(installSystem.Err(), ErrorMatches, errMatch)
   860  	// no restart request on failure
   861  	c.Check(s.restartRequests, HasLen, 0)
   862  }
   863  
   864  func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoKeys(c *C) {
   865  	restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   866  		c.Check(options.Encrypt, Equals, true)
   867  		// no keys set
   868  		return &install.InstalledSystemSideData{}, nil
   869  	})
   870  	defer restore()
   871  	s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks:
   872  - Setup system for run mode \(internal error: system encryption keys are unset\)`)
   873  }
   874  
   875  func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoSystemDataKey(c *C) {
   876  	restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   877  		c.Check(options.Encrypt, Equals, true)
   878  		// no keys set
   879  		return &install.InstalledSystemSideData{
   880  			// empty map
   881  			KeysForRoles: map[string]*install.EncryptionKeySet{},
   882  		}, nil
   883  	})
   884  	defer restore()
   885  	s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks:
   886  - Setup system for run mode \(internal error: system encryption keys are unset\)`)
   887  }
   888  
   889  func (s *deviceMgrInstallModeSuite) mockInstallModeChange(c *C, modelGrade, gadgetDefaultsYaml string) *asserts.Model {
   890  	restore := release.MockOnClassic(false)
   891  	defer restore()
   892  
   893  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
   894  		return nil, nil
   895  	})
   896  	defer restore()
   897  
   898  	s.state.Lock()
   899  	mockModel := s.makeMockInstalledPcGadget(c, modelGrade, "", gadgetDefaultsYaml)
   900  	s.state.Unlock()
   901  	c.Check(mockModel.Grade(), Equals, asserts.ModelGrade(modelGrade))
   902  
   903  	restore = devicestate.MockBootMakeSystemRunnable(func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error {
   904  		return nil
   905  	})
   906  	defer restore()
   907  
   908  	modeenv := boot.Modeenv{
   909  		Mode:           "install",
   910  		RecoverySystem: "20191218",
   911  	}
   912  	c.Assert(modeenv.WriteTo(""), IsNil)
   913  	devicestate.SetSystemMode(s.mgr, "install")
   914  
   915  	// normally done by snap-bootstrap
   916  	err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755)
   917  	c.Assert(err, IsNil)
   918  
   919  	s.settle(c)
   920  
   921  	return mockModel
   922  }
   923  
   924  func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfig(c *C) {
   925  	s.mockInstallModeChange(c, "dangerous", "")
   926  
   927  	s.state.Lock()
   928  	defer s.state.Unlock()
   929  
   930  	// the install-system change is created
   931  	installSystem := s.findInstallSystem()
   932  	c.Assert(installSystem, NotNil)
   933  
   934  	// and was run successfully
   935  	c.Check(installSystem.Err(), IsNil)
   936  	c.Check(installSystem.Status(), Equals, state.DoneStatus)
   937  
   938  	// and sysconfig.ConfigureTargetSystem was run exactly once
   939  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   940  		{
   941  			AllowCloudInit: true,
   942  			TargetRootDir:  boot.InstallHostWritableDir,
   943  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
   944  		},
   945  	})
   946  
   947  	// and the special dirs in _writable_defaults were created
   948  	for _, dir := range []string{"/etc/udev/rules.d/", "/etc/modules-load.d/", "/etc/modprobe.d/"} {
   949  		fullDir := filepath.Join(sysconfig.WritableDefaultsDir(boot.InstallHostWritableDir), dir)
   950  		c.Assert(fullDir, testutil.FilePresent)
   951  	}
   952  }
   953  
   954  func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfigErr(c *C) {
   955  	s.ConfigureTargetSystemErr = fmt.Errorf("error from sysconfig.ConfigureTargetSystem")
   956  	s.mockInstallModeChange(c, "dangerous", "")
   957  
   958  	s.state.Lock()
   959  	defer s.state.Unlock()
   960  
   961  	// the install-system was run but errorred as specified in the above mock
   962  	installSystem := s.findInstallSystem()
   963  	c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks:
   964  - Setup system for run mode \(error from sysconfig.ConfigureTargetSystem\)`)
   965  	// and sysconfig.ConfigureTargetSystem was run exactly once
   966  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   967  		{
   968  			AllowCloudInit: true,
   969  			TargetRootDir:  boot.InstallHostWritableDir,
   970  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
   971  		},
   972  	})
   973  }
   974  
   975  func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitInDangerous(c *C) {
   976  	// pretend we have a cloud-init config on the seed partition
   977  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
   978  	err := os.MkdirAll(cloudCfg, 0755)
   979  	c.Assert(err, IsNil)
   980  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
   981  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
   982  		c.Assert(err, IsNil)
   983  	}
   984  
   985  	s.mockInstallModeChange(c, "dangerous", "")
   986  
   987  	// and did tell sysconfig about the cloud-init files
   988  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   989  		{
   990  			AllowCloudInit:  true,
   991  			CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"),
   992  			TargetRootDir:   boot.InstallHostWritableDir,
   993  			GadgetDir:       filepath.Join(dirs.SnapMountDir, "pc/1/"),
   994  		},
   995  	})
   996  }
   997  
   998  func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitGadgetAndSeedConfigSigned(c *C) {
   999  	// pretend we have a cloud-init config on the seed partition
  1000  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
  1001  	err := os.MkdirAll(cloudCfg, 0755)
  1002  	c.Assert(err, IsNil)
  1003  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
  1004  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
  1005  		c.Assert(err, IsNil)
  1006  	}
  1007  
  1008  	// we also have gadget cloud init too
  1009  	gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/")
  1010  	err = os.MkdirAll(gadgetDir, 0755)
  1011  	c.Assert(err, IsNil)
  1012  	err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644)
  1013  	c.Assert(err, IsNil)
  1014  
  1015  	s.mockInstallModeChange(c, "signed", "")
  1016  
  1017  	// sysconfig is told about both configs
  1018  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1019  		{
  1020  			AllowCloudInit:  true,
  1021  			TargetRootDir:   boot.InstallHostWritableDir,
  1022  			GadgetDir:       filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1023  			CloudInitSrcDir: cloudCfg,
  1024  		},
  1025  	})
  1026  }
  1027  
  1028  func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitBothGadgetAndUbuntuSeedDangerous(c *C) {
  1029  	// pretend we have a cloud-init config on the seed partition
  1030  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
  1031  	err := os.MkdirAll(cloudCfg, 0755)
  1032  	c.Assert(err, IsNil)
  1033  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
  1034  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
  1035  		c.Assert(err, IsNil)
  1036  	}
  1037  
  1038  	// we also have gadget cloud init too
  1039  	gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/")
  1040  	err = os.MkdirAll(gadgetDir, 0755)
  1041  	c.Assert(err, IsNil)
  1042  	err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644)
  1043  	c.Assert(err, IsNil)
  1044  
  1045  	s.mockInstallModeChange(c, "dangerous", "")
  1046  
  1047  	// and did tell sysconfig about the cloud-init files
  1048  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1049  		{
  1050  			AllowCloudInit:  true,
  1051  			CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"),
  1052  			TargetRootDir:   boot.InstallHostWritableDir,
  1053  			GadgetDir:       filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1054  		},
  1055  	})
  1056  }
  1057  
  1058  func (s *deviceMgrInstallModeSuite) TestInstallModeSignedNoUbuntuSeedCloudInit(c *C) {
  1059  	// pretend we have no cloud-init config anywhere
  1060  	s.mockInstallModeChange(c, "signed", "")
  1061  
  1062  	// we didn't pass any cloud-init src dir but still left cloud-init enabled
  1063  	// if for example a CI-DATA USB drive was provided at runtime
  1064  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1065  		{
  1066  			AllowCloudInit: true,
  1067  			TargetRootDir:  boot.InstallHostWritableDir,
  1068  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1069  		},
  1070  	})
  1071  }
  1072  
  1073  func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredGadgetCloudConfCloudInit(c *C) {
  1074  	// pretend we have a cloud.conf from the gadget
  1075  	gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/")
  1076  	err := os.MkdirAll(gadgetDir, 0755)
  1077  	c.Assert(err, IsNil)
  1078  	err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644)
  1079  	c.Assert(err, IsNil)
  1080  
  1081  	err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
  1082  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
  1083  	})
  1084  	c.Assert(err, IsNil)
  1085  
  1086  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1087  		{
  1088  			AllowCloudInit: true,
  1089  			TargetRootDir:  boot.InstallHostWritableDir,
  1090  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1091  		},
  1092  	})
  1093  }
  1094  
  1095  func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredNoUbuntuSeedCloudInit(c *C) {
  1096  	// pretend we have a cloud-init config on the seed partition with some files
  1097  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
  1098  	err := os.MkdirAll(cloudCfg, 0755)
  1099  	c.Assert(err, IsNil)
  1100  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
  1101  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
  1102  		c.Assert(err, IsNil)
  1103  	}
  1104  
  1105  	err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
  1106  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
  1107  	})
  1108  	c.Assert(err, IsNil)
  1109  
  1110  	// we did tell sysconfig about the ubuntu-seed cloud config dir because it
  1111  	// exists, but it is up to sysconfig to use the model to determine to ignore
  1112  	// the files
  1113  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1114  		{
  1115  			AllowCloudInit:  false,
  1116  			TargetRootDir:   boot.InstallHostWritableDir,
  1117  			GadgetDir:       filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1118  			CloudInitSrcDir: cloudCfg,
  1119  		},
  1120  	})
  1121  }
  1122  
  1123  func (s *deviceMgrInstallModeSuite) TestInstallModeWritesModel(c *C) {
  1124  	// pretend we have a cloud-init config on the seed partition
  1125  	model := s.mockInstallModeChange(c, "dangerous", "")
  1126  
  1127  	var buf bytes.Buffer
  1128  	err := asserts.NewEncoder(&buf).Encode(model)
  1129  	c.Assert(err, IsNil)
  1130  
  1131  	s.state.Lock()
  1132  	defer s.state.Unlock()
  1133  
  1134  	installSystem := s.findInstallSystem()
  1135  	c.Assert(installSystem, NotNil)
  1136  
  1137  	// and was run successfully
  1138  	c.Check(installSystem.Err(), IsNil)
  1139  	c.Check(installSystem.Status(), Equals, state.DoneStatus)
  1140  
  1141  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileEquals, buf.String())
  1142  }
  1143  
  1144  func (s *deviceMgrInstallModeSuite) testInstallGadgetNoSave(c *C) {
  1145  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
  1146  		[]byte("mode=install\n"), 0644)
  1147  	c.Assert(err, IsNil)
  1148  
  1149  	s.state.Lock()
  1150  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
  1151  	info, err := snapstate.CurrentInfo(s.state, "pc")
  1152  	c.Assert(err, IsNil)
  1153  	// replace gadget yaml with one that has no ubuntu-save
  1154  	c.Assert(uc20gadgetYaml, Not(testutil.Contains), "ubuntu-save")
  1155  	err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(uc20gadgetYaml), 0644)
  1156  	c.Assert(err, IsNil)
  1157  	devicestate.SetSystemMode(s.mgr, "install")
  1158  	s.state.Unlock()
  1159  
  1160  	s.settle(c)
  1161  }
  1162  
  1163  func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetErr(c *C) {
  1164  	restore := release.MockOnClassic(false)
  1165  	defer restore()
  1166  
  1167  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
  1168  		return nil, fmt.Errorf("unexpected call")
  1169  	})
  1170  	defer restore()
  1171  
  1172  	// pretend we have a TPM
  1173  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil })
  1174  	defer restore()
  1175  
  1176  	s.testInstallGadgetNoSave(c)
  1177  
  1178  	s.state.Lock()
  1179  	defer s.state.Unlock()
  1180  
  1181  	installSystem := s.findInstallSystem()
  1182  	c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks:
  1183  - Setup system for run mode \(cannot use gadget: gadget does not support encrypted data: required partition with system-save role is missing\)`)
  1184  	// no restart request on failure
  1185  	c.Check(s.restartRequests, HasLen, 0)
  1186  }
  1187  
  1188  func (s *deviceMgrInstallModeSuite) TestInstallWithoutEncryptionValidatesGadgetWithoutSaveHappy(c *C) {
  1189  	restore := release.MockOnClassic(false)
  1190  	defer restore()
  1191  
  1192  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
  1193  		return nil, nil
  1194  	})
  1195  	defer restore()
  1196  
  1197  	// pretend we have a TPM
  1198  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("TPM2 not available") })
  1199  	defer restore()
  1200  
  1201  	s.testInstallGadgetNoSave(c)
  1202  
  1203  	s.state.Lock()
  1204  	defer s.state.Unlock()
  1205  
  1206  	installSystem := s.findInstallSystem()
  1207  	c.Check(installSystem.Err(), IsNil)
  1208  	c.Check(s.restartRequests, HasLen, 1)
  1209  }
  1210  
  1211  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncrypted(c *C) {
  1212  	st := s.state
  1213  	st.Lock()
  1214  	defer st.Unlock()
  1215  
  1216  	mockModel := s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{
  1217  		"architecture": "amd64",
  1218  		"kernel":       "pc-kernel",
  1219  		"gadget":       "pc",
  1220  	})
  1221  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1222  		Brand: "canonical",
  1223  		Model: "pc",
  1224  	})
  1225  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1226  
  1227  	for _, tc := range []struct {
  1228  		hasFDESetupHook bool
  1229  		hasTPM          bool
  1230  		encrypt         bool
  1231  	}{
  1232  		// unhappy: no tpm, no hook
  1233  		{false, false, false},
  1234  		// happy: either tpm or hook or both
  1235  		{false, true, true},
  1236  		{true, false, true},
  1237  		{true, true, true},
  1238  	} {
  1239  		hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1240  			ctx.Lock()
  1241  			defer ctx.Unlock()
  1242  			ctx.Set("fde-setup-result", []byte(`{"features":[]}`))
  1243  			return nil, nil
  1244  		}
  1245  		rhk := hookstate.MockRunHook(hookInvoke)
  1246  		defer rhk()
  1247  
  1248  		if tc.hasFDESetupHook {
  1249  			makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup)
  1250  		} else {
  1251  			makeInstalledMockKernelSnap(c, st, kernelYamlNoFdeSetup)
  1252  		}
  1253  		restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error {
  1254  			if tc.hasTPM {
  1255  				return nil
  1256  			}
  1257  			return fmt.Errorf("tpm says no")
  1258  		})
  1259  		defer restore()
  1260  
  1261  		encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, st, deviceCtx)
  1262  		c.Assert(err, IsNil)
  1263  		c.Check(encrypt, Equals, tc.encrypt, Commentf("%v", tc))
  1264  	}
  1265  }
  1266  
  1267  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedStorageSafety(c *C) {
  1268  	s.state.Lock()
  1269  	defer s.state.Unlock()
  1270  
  1271  	restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil })
  1272  	defer restore()
  1273  
  1274  	var testCases = []struct {
  1275  		grade, storageSafety string
  1276  
  1277  		expectedEncryption bool
  1278  	}{
  1279  		// we don't test unset here because the assertion assembly
  1280  		// will ensure it has a default
  1281  		{"dangerous", "prefer-unencrypted", false},
  1282  		{"dangerous", "prefer-encrypted", true},
  1283  		{"dangerous", "encrypted", true},
  1284  		{"signed", "prefer-unencrypted", false},
  1285  		{"signed", "prefer-encrypted", true},
  1286  		{"signed", "encrypted", true},
  1287  		// secured+prefer-{,un}encrypted is an error at the
  1288  		// assertion level already so cannot be tested here
  1289  		{"secured", "encrypted", true},
  1290  	}
  1291  	for _, tc := range testCases {
  1292  		mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
  1293  			"display-name":   "my model",
  1294  			"architecture":   "amd64",
  1295  			"base":           "core20",
  1296  			"grade":          tc.grade,
  1297  			"storage-safety": tc.storageSafety,
  1298  			"snaps": []interface{}{
  1299  				map[string]interface{}{
  1300  					"name":            "pc-kernel",
  1301  					"id":              pcKernelSnapID,
  1302  					"type":            "kernel",
  1303  					"default-channel": "20",
  1304  				},
  1305  				map[string]interface{}{
  1306  					"name":            "pc",
  1307  					"id":              pcSnapID,
  1308  					"type":            "gadget",
  1309  					"default-channel": "20",
  1310  				}},
  1311  		})
  1312  		deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1313  
  1314  		encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1315  		c.Assert(err, IsNil)
  1316  		c.Check(encrypt, Equals, tc.expectedEncryption)
  1317  	}
  1318  }
  1319  
  1320  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrors(c *C) {
  1321  	s.state.Lock()
  1322  	defer s.state.Unlock()
  1323  
  1324  	restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("tpm says no") })
  1325  	defer restore()
  1326  
  1327  	var testCases = []struct {
  1328  		grade, storageSafety string
  1329  
  1330  		expectedErr string
  1331  	}{
  1332  		// we don't test unset here because the assertion assembly
  1333  		// will ensure it has a default
  1334  		{
  1335  			"dangerous", "encrypted",
  1336  			"cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no",
  1337  		}, {
  1338  			"signed", "encrypted",
  1339  			"cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no",
  1340  		}, {
  1341  			"secured", "",
  1342  			"cannot encrypt device storage as mandated by model grade secured: tpm says no",
  1343  		}, {
  1344  			"secured", "encrypted",
  1345  			"cannot encrypt device storage as mandated by model grade secured: tpm says no",
  1346  		},
  1347  	}
  1348  	for _, tc := range testCases {
  1349  		mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
  1350  			"display-name":   "my model",
  1351  			"architecture":   "amd64",
  1352  			"base":           "core20",
  1353  			"grade":          tc.grade,
  1354  			"storage-safety": tc.storageSafety,
  1355  			"snaps": []interface{}{
  1356  				map[string]interface{}{
  1357  					"name":            "pc-kernel",
  1358  					"id":              pcKernelSnapID,
  1359  					"type":            "kernel",
  1360  					"default-channel": "20",
  1361  				},
  1362  				map[string]interface{}{
  1363  					"name":            "pc",
  1364  					"id":              pcSnapID,
  1365  					"type":            "gadget",
  1366  					"default-channel": "20",
  1367  				}},
  1368  		})
  1369  		deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1370  		_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1371  		c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%s %s", tc.grade, tc.storageSafety))
  1372  	}
  1373  }
  1374  
  1375  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedFDEHook(c *C) {
  1376  	st := s.state
  1377  	st.Lock()
  1378  	defer st.Unlock()
  1379  
  1380  	s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{
  1381  		"architecture": "amd64",
  1382  		"kernel":       "pc-kernel",
  1383  		"gadget":       "pc",
  1384  	})
  1385  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1386  		Brand: "canonical",
  1387  		Model: "pc",
  1388  	})
  1389  	makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup)
  1390  
  1391  	for _, tc := range []struct {
  1392  		hookOutput  string
  1393  		expectedErr string
  1394  	}{
  1395  		// invalid json
  1396  		{"xxx", `cannot parse hook output "xxx": invalid character 'x' looking for beginning of value`},
  1397  		// no output is invalid
  1398  		{"", `cannot parse hook output "": unexpected end of JSON input`},
  1399  		// specific error
  1400  		{`{"error":"failed"}`, `cannot use hook: it returned error: failed`},
  1401  		{`{}`, `cannot use hook: neither "features" nor "error" returned`},
  1402  		// valid
  1403  		{`{"features":[]}`, ""},
  1404  		{`{"features":["a"]}`, ""},
  1405  		{`{"features":["a","b"]}`, ""},
  1406  		// features must be list of strings
  1407  		{`{"features":[1]}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`},
  1408  		{`{"features":1}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`},
  1409  		{`{"features":"1"}`, `cannot parse hook output ".*": json: cannot unmarshal string into Go struct.*`},
  1410  	} {
  1411  		hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1412  			ctx.Lock()
  1413  			defer ctx.Unlock()
  1414  			ctx.Set("fde-setup-result", []byte(tc.hookOutput))
  1415  			return nil, nil
  1416  		}
  1417  		rhk := hookstate.MockRunHook(hookInvoke)
  1418  		defer rhk()
  1419  
  1420  		err := devicestate.DeviceManagerCheckFDEFeatures(s.mgr, st)
  1421  		if tc.expectedErr != "" {
  1422  			c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc))
  1423  		} else {
  1424  			c.Check(err, IsNil, Commentf("%v", tc))
  1425  		}
  1426  	}
  1427  }
  1428  
  1429  var checkEncryptionModelHeaders = map[string]interface{}{
  1430  	"display-name": "my model",
  1431  	"architecture": "amd64",
  1432  	"base":         "core20",
  1433  	"grade":        "dangerous",
  1434  	"snaps": []interface{}{
  1435  		map[string]interface{}{
  1436  			"name":            "pc-kernel",
  1437  			"id":              pcKernelSnapID,
  1438  			"type":            "kernel",
  1439  			"default-channel": "20",
  1440  		},
  1441  		map[string]interface{}{
  1442  			"name":            "pc",
  1443  			"id":              pcSnapID,
  1444  			"type":            "gadget",
  1445  			"default-channel": "20",
  1446  		}},
  1447  }
  1448  
  1449  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsTPM(c *C) {
  1450  	s.state.Lock()
  1451  	defer s.state.Unlock()
  1452  
  1453  	restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error {
  1454  		return fmt.Errorf("tpm says no")
  1455  	})
  1456  	defer restore()
  1457  
  1458  	logbuf, restore := logger.MockLogger()
  1459  	defer restore()
  1460  
  1461  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders)
  1462  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1463  	_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1464  	c.Check(err, IsNil)
  1465  	c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as checking TPM gave: tpm says no\n")
  1466  }
  1467  
  1468  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsHook(c *C) {
  1469  	s.state.Lock()
  1470  	defer s.state.Unlock()
  1471  
  1472  	logbuf, restore := logger.MockLogger()
  1473  	defer restore()
  1474  
  1475  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders)
  1476  	// mock kernel installed but no hook or handle so checkEncryption
  1477  	// will fail
  1478  	makeInstalledMockKernelSnap(c, s.state, kernelYamlWithFdeSetup)
  1479  
  1480  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1481  	_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1482  	c.Check(err, IsNil)
  1483  	c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as querying kernel fde-setup hook did not succeed:.*\n")
  1484  }
  1485  
  1486  func (s *deviceMgrInstallModeSuite) TestInstallHappyLogfiles(c *C) {
  1487  	restore := release.MockOnClassic(false)
  1488  	defer restore()
  1489  
  1490  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver, _ timings.Measurer) (*install.InstalledSystemSideData, error) {
  1491  		return nil, nil
  1492  	})
  1493  	defer restore()
  1494  
  1495  	mockedSnapCmd := testutil.MockCommand(c, "snap", `
  1496  echo "mock output of: $(basename "$0") $*"
  1497  `)
  1498  	defer mockedSnapCmd.Restore()
  1499  
  1500  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
  1501  		[]byte("mode=install\n"), 0644)
  1502  	c.Assert(err, IsNil)
  1503  
  1504  	s.state.Lock()
  1505  	// pretend we are seeding
  1506  	chg := s.state.NewChange("seed", "just for testing")
  1507  	chg.AddTask(s.state.NewTask("test-task", "the change needs a task"))
  1508  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
  1509  	devicestate.SetSystemMode(s.mgr, "install")
  1510  	s.state.Unlock()
  1511  
  1512  	s.settle(c)
  1513  
  1514  	s.state.Lock()
  1515  	defer s.state.Unlock()
  1516  
  1517  	installSystem := s.findInstallSystem()
  1518  	c.Check(installSystem.Err(), IsNil)
  1519  	c.Check(s.restartRequests, HasLen, 1)
  1520  
  1521  	// logs are created
  1522  	c.Check(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/log/install-mode.log.gz"), testutil.FilePresent)
  1523  	timingsPath := filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data/var/log/install-timings.txt.gz")
  1524  	c.Check(timingsPath, testutil.FilePresent)
  1525  
  1526  	f, err := os.Open(timingsPath)
  1527  	c.Assert(err, IsNil)
  1528  	defer f.Close()
  1529  	gz, err := gzip.NewReader(f)
  1530  	c.Assert(err, IsNil)
  1531  	content, err := ioutil.ReadAll(gz)
  1532  	c.Assert(err, IsNil)
  1533  	c.Check(string(content), Equals, `---- Output of: snap changes
  1534  mock output of: snap changes
  1535  
  1536  ---- Output of snap debug timings --ensure=seed
  1537  mock output of: snap debug timings --ensure=seed
  1538  
  1539  ---- Output of snap debug timings --ensure=install-system
  1540  mock output of: snap debug timings --ensure=install-system
  1541  `)
  1542  
  1543  	// and the right commands are run
  1544  	c.Check(mockedSnapCmd.Calls(), DeepEquals, [][]string{
  1545  		{"snap", "changes"},
  1546  		{"snap", "debug", "timings", "--ensure=seed"},
  1547  		{"snap", "debug", "timings", "--ensure=install-system"},
  1548  	})
  1549  }