github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/devicestate/devicestate_systems_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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  	"errors"
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  
    29  	. "gopkg.in/check.v1"
    30  	"gopkg.in/tomb.v2"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/asserts/assertstest"
    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/logger"
    39  	"github.com/snapcore/snapd/overlord/auth"
    40  	"github.com/snapcore/snapd/overlord/devicestate"
    41  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    42  	"github.com/snapcore/snapd/overlord/snapstate"
    43  	"github.com/snapcore/snapd/overlord/state"
    44  	"github.com/snapcore/snapd/seed"
    45  	"github.com/snapcore/snapd/seed/seedtest"
    46  	"github.com/snapcore/snapd/snap"
    47  	"github.com/snapcore/snapd/snap/snaptest"
    48  	"github.com/snapcore/snapd/strutil"
    49  	"github.com/snapcore/snapd/testutil"
    50  )
    51  
    52  type mockedSystemSeed struct {
    53  	label string
    54  	model *asserts.Model
    55  	brand *asserts.Account
    56  }
    57  
    58  type deviceMgrSystemsBaseSuite struct {
    59  	deviceMgrBaseSuite
    60  
    61  	logbuf            *bytes.Buffer
    62  	mockedSystemSeeds []mockedSystemSeed
    63  	ss                *seedtest.SeedSnaps
    64  	model             *asserts.Model
    65  }
    66  
    67  type deviceMgrSystemsSuite struct {
    68  	deviceMgrSystemsBaseSuite
    69  }
    70  
    71  var _ = Suite(&deviceMgrSystemsSuite{})
    72  var _ = Suite(&deviceMgrSystemsCreateSuite{})
    73  
    74  func (s *deviceMgrSystemsBaseSuite) SetUpTest(c *C) {
    75  	s.deviceMgrBaseSuite.SetUpTest(c)
    76  
    77  	s.brands.Register("other-brand", brandPrivKey3, map[string]interface{}{
    78  		"display-name": "other publisher",
    79  	})
    80  	s.state.Lock()
    81  	defer s.state.Unlock()
    82  	s.ss = &seedtest.SeedSnaps{
    83  		StoreSigning: s.storeSigning,
    84  		Brands:       s.brands,
    85  	}
    86  
    87  	s.model = s.makeModelAssertionInState(c, "canonical", "pc-20", map[string]interface{}{
    88  		"architecture": "amd64",
    89  		// UC20
    90  		"grade": "dangerous",
    91  		"base":  "core20",
    92  		"snaps": []interface{}{
    93  			map[string]interface{}{
    94  				"name":            "pc-kernel",
    95  				"id":              s.ss.AssertedSnapID("pc-kernel"),
    96  				"type":            "kernel",
    97  				"default-channel": "20",
    98  			},
    99  			map[string]interface{}{
   100  				"name":            "pc",
   101  				"id":              s.ss.AssertedSnapID("pc"),
   102  				"type":            "gadget",
   103  				"default-channel": "20",
   104  			},
   105  			map[string]interface{}{
   106  				"name": "core20",
   107  				"id":   s.ss.AssertedSnapID("core20"),
   108  				"type": "base",
   109  			},
   110  			map[string]interface{}{
   111  				"name": "snapd",
   112  				"id":   s.ss.AssertedSnapID("snapd"),
   113  				"type": "snapd",
   114  			},
   115  		},
   116  	})
   117  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   118  		Brand:  "canonical",
   119  		Model:  "pc-20",
   120  		Serial: "serialserialserial",
   121  	})
   122  	assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("my-brand")...)
   123  	assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("other-brand")...)
   124  
   125  	// all tests should be in run mode by default, if they need to be in
   126  	// different modes they should set that individually
   127  	devicestate.SetSystemMode(s.mgr, "run")
   128  
   129  	// state after mark-seeded ran
   130  	modeenv := boot.Modeenv{
   131  		Mode:           "run",
   132  		RecoverySystem: "",
   133  
   134  		Model:          s.model.Model(),
   135  		BrandID:        s.model.BrandID(),
   136  		Grade:          string(s.model.Grade()),
   137  		ModelSignKeyID: s.model.SignKeyID(),
   138  	}
   139  	err := modeenv.WriteTo("")
   140  	s.state.Set("seeded", true)
   141  
   142  	c.Assert(err, IsNil)
   143  
   144  	logbuf, restore := logger.MockLogger()
   145  	s.logbuf = logbuf
   146  	s.AddCleanup(restore)
   147  
   148  	nopHandler := func(task *state.Task, _ *tomb.Tomb) error { return nil }
   149  	s.o.TaskRunner().AddHandler("fake-download", nopHandler, nil)
   150  }
   151  
   152  func (s *deviceMgrSystemsSuite) SetUpTest(c *C) {
   153  	s.deviceMgrSystemsBaseSuite.SetUpTest(c)
   154  
   155  	// now create a minimal uc20 seed dir with snaps/assertions
   156  	seed20 := &seedtest.TestingSeed20{
   157  		SeedSnaps: *s.ss,
   158  		SeedDir:   dirs.SnapSeedDir,
   159  	}
   160  
   161  	restore := seed.MockTrusted(s.storeSigning.Trusted)
   162  	s.AddCleanup(restore)
   163  
   164  	myBrandAcc := s.brands.Account("my-brand")
   165  	otherBrandAcc := s.brands.Account("other-brand")
   166  
   167  	// add essential snaps
   168  	seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   169  	seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   170  	seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   171  	seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   172  
   173  	model1 := seed20.MakeSeed(c, "20191119", "my-brand", "my-model", map[string]interface{}{
   174  		"display-name": "my fancy model",
   175  		"architecture": "amd64",
   176  		"base":         "core20",
   177  		"snaps": []interface{}{
   178  			map[string]interface{}{
   179  				"name":            "pc-kernel",
   180  				"id":              seed20.AssertedSnapID("pc-kernel"),
   181  				"type":            "kernel",
   182  				"default-channel": "20",
   183  			},
   184  			map[string]interface{}{
   185  				"name":            "pc",
   186  				"id":              seed20.AssertedSnapID("pc"),
   187  				"type":            "gadget",
   188  				"default-channel": "20",
   189  			}},
   190  	}, nil)
   191  	model2 := seed20.MakeSeed(c, "20200318", "my-brand", "my-model-2", map[string]interface{}{
   192  		"display-name": "same brand different model",
   193  		"architecture": "amd64",
   194  		"base":         "core20",
   195  		"snaps": []interface{}{
   196  			map[string]interface{}{
   197  				"name":            "pc-kernel",
   198  				"id":              seed20.AssertedSnapID("pc-kernel"),
   199  				"type":            "kernel",
   200  				"default-channel": "20",
   201  			},
   202  			map[string]interface{}{
   203  				"name":            "pc",
   204  				"id":              seed20.AssertedSnapID("pc"),
   205  				"type":            "gadget",
   206  				"default-channel": "20",
   207  			}},
   208  	}, nil)
   209  	model3 := seed20.MakeSeed(c, "other-20200318", "other-brand", "other-model", map[string]interface{}{
   210  		"display-name": "different brand different model",
   211  		"architecture": "amd64",
   212  		"base":         "core20",
   213  		"snaps": []interface{}{
   214  			map[string]interface{}{
   215  				"name":            "pc-kernel",
   216  				"id":              seed20.AssertedSnapID("pc-kernel"),
   217  				"type":            "kernel",
   218  				"default-channel": "20",
   219  			},
   220  			map[string]interface{}{
   221  				"name":            "pc",
   222  				"id":              seed20.AssertedSnapID("pc"),
   223  				"type":            "gadget",
   224  				"default-channel": "20",
   225  			}},
   226  	}, nil)
   227  
   228  	s.mockedSystemSeeds = []mockedSystemSeed{{
   229  		label: "20191119",
   230  		model: model1,
   231  		brand: myBrandAcc,
   232  	}, {
   233  		label: "20200318",
   234  		model: model2,
   235  		brand: myBrandAcc,
   236  	}, {
   237  		label: "other-20200318",
   238  		model: model3,
   239  		brand: otherBrandAcc,
   240  	}}
   241  }
   242  
   243  func (s *deviceMgrSystemsSuite) TestListNoSystems(c *C) {
   244  	dirs.SetRootDir(c.MkDir())
   245  
   246  	systems, err := s.mgr.Systems()
   247  	c.Assert(err, Equals, devicestate.ErrNoSystems)
   248  	c.Assert(systems, HasLen, 0)
   249  
   250  	err = os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "systems"), 0755)
   251  	c.Assert(err, IsNil)
   252  
   253  	systems, err = s.mgr.Systems()
   254  	c.Assert(err, Equals, devicestate.ErrNoSystems)
   255  	c.Assert(systems, HasLen, 0)
   256  }
   257  
   258  func (s *deviceMgrSystemsSuite) TestListSystemsNotPossible(c *C) {
   259  	if os.Geteuid() == 0 {
   260  		c.Skip("this test cannot run as root")
   261  	}
   262  	err := os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0000)
   263  	c.Assert(err, IsNil)
   264  	defer os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0755)
   265  
   266  	// stdlib swallows up the errors when opening the target directory
   267  	systems, err := s.mgr.Systems()
   268  	c.Assert(err, Equals, devicestate.ErrNoSystems)
   269  	c.Assert(systems, HasLen, 0)
   270  }
   271  
   272  // TODO:UC20 update once we can list actions
   273  var defaultSystemActions []devicestate.SystemAction = []devicestate.SystemAction{
   274  	{Title: "Install", Mode: "install"},
   275  }
   276  var currentSystemActions []devicestate.SystemAction = []devicestate.SystemAction{
   277  	{Title: "Reinstall", Mode: "install"},
   278  	{Title: "Recover", Mode: "recover"},
   279  	{Title: "Run normally", Mode: "run"},
   280  }
   281  
   282  func (s *deviceMgrSystemsSuite) TestListSeedSystemsNoCurrent(c *C) {
   283  	systems, err := s.mgr.Systems()
   284  	c.Assert(err, IsNil)
   285  	c.Assert(systems, HasLen, 3)
   286  	c.Check(systems, DeepEquals, []*devicestate.System{{
   287  		Current: false,
   288  		Label:   s.mockedSystemSeeds[0].label,
   289  		Model:   s.mockedSystemSeeds[0].model,
   290  		Brand:   s.mockedSystemSeeds[0].brand,
   291  		Actions: defaultSystemActions,
   292  	}, {
   293  		Current: false,
   294  		Label:   s.mockedSystemSeeds[1].label,
   295  		Model:   s.mockedSystemSeeds[1].model,
   296  		Brand:   s.mockedSystemSeeds[1].brand,
   297  		Actions: defaultSystemActions,
   298  	}, {
   299  		Current: false,
   300  		Label:   s.mockedSystemSeeds[2].label,
   301  		Model:   s.mockedSystemSeeds[2].model,
   302  		Brand:   s.mockedSystemSeeds[2].brand,
   303  		Actions: defaultSystemActions,
   304  	}})
   305  }
   306  
   307  func (s *deviceMgrSystemsSuite) TestListSeedSystemsCurrent(c *C) {
   308  	s.state.Lock()
   309  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   310  		{
   311  			System:  s.mockedSystemSeeds[1].label,
   312  			Model:   s.mockedSystemSeeds[1].model.Model(),
   313  			BrandID: s.mockedSystemSeeds[1].brand.AccountID(),
   314  		},
   315  	})
   316  	s.state.Unlock()
   317  
   318  	systems, err := s.mgr.Systems()
   319  	c.Assert(err, IsNil)
   320  	c.Assert(systems, HasLen, 3)
   321  	c.Check(systems, DeepEquals, []*devicestate.System{{
   322  		Current: false,
   323  		Label:   s.mockedSystemSeeds[0].label,
   324  		Model:   s.mockedSystemSeeds[0].model,
   325  		Brand:   s.mockedSystemSeeds[0].brand,
   326  		Actions: defaultSystemActions,
   327  	}, {
   328  		// this seed was used for installing the running system
   329  		Current: true,
   330  		Label:   s.mockedSystemSeeds[1].label,
   331  		Model:   s.mockedSystemSeeds[1].model,
   332  		Brand:   s.mockedSystemSeeds[1].brand,
   333  		Actions: currentSystemActions,
   334  	}, {
   335  		Current: false,
   336  		Label:   s.mockedSystemSeeds[2].label,
   337  		Model:   s.mockedSystemSeeds[2].model,
   338  		Brand:   s.mockedSystemSeeds[2].brand,
   339  		Actions: defaultSystemActions,
   340  	}})
   341  }
   342  
   343  func (s *deviceMgrSystemsSuite) TestBrokenSeedSystems(c *C) {
   344  	// break the first seed
   345  	err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model"))
   346  	c.Assert(err, IsNil)
   347  
   348  	systems, err := s.mgr.Systems()
   349  	c.Assert(err, IsNil)
   350  	c.Assert(systems, HasLen, 2)
   351  	c.Check(systems, DeepEquals, []*devicestate.System{{
   352  		Current: false,
   353  		Label:   s.mockedSystemSeeds[1].label,
   354  		Model:   s.mockedSystemSeeds[1].model,
   355  		Brand:   s.mockedSystemSeeds[1].brand,
   356  		Actions: defaultSystemActions,
   357  	}, {
   358  		Current: false,
   359  		Label:   s.mockedSystemSeeds[2].label,
   360  		Model:   s.mockedSystemSeeds[2].model,
   361  		Brand:   s.mockedSystemSeeds[2].brand,
   362  		Actions: defaultSystemActions,
   363  	}})
   364  }
   365  
   366  func (s *deviceMgrSystemsSuite) TestRequestModeInstallHappyForAny(c *C) {
   367  	// no current system
   368  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install", Title: "Install"})
   369  	c.Assert(err, IsNil)
   370  
   371  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   372  	c.Assert(err, IsNil)
   373  	c.Check(m, DeepEquals, map[string]string{
   374  		"snapd_recovery_system": "20191119",
   375  		"snapd_recovery_mode":   "install",
   376  	})
   377  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   378  	c.Check(s.logbuf.String(), Matches, `.*: restarting into system "20191119" for action "Install"\n`)
   379  }
   380  
   381  func (s *deviceMgrSystemsSuite) TestRequestSameModeSameSystem(c *C) {
   382  	s.state.Lock()
   383  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   384  		{
   385  			System:  s.mockedSystemSeeds[0].label,
   386  			Model:   s.mockedSystemSeeds[0].model.Model(),
   387  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   388  		},
   389  	})
   390  	s.state.Unlock()
   391  
   392  	label := s.mockedSystemSeeds[0].label
   393  
   394  	happyModes := []string{"run"}
   395  	sadModes := []string{"install", "recover"}
   396  
   397  	for _, mode := range append(happyModes, sadModes...) {
   398  		s.logbuf.Reset()
   399  
   400  		c.Logf("checking mode: %q", mode)
   401  		// non run modes use modeenv
   402  		modeenv := boot.Modeenv{
   403  			Mode: mode,
   404  		}
   405  		if mode != "run" {
   406  			modeenv.RecoverySystem = s.mockedSystemSeeds[0].label
   407  		}
   408  		err := modeenv.WriteTo("")
   409  		c.Assert(err, IsNil)
   410  
   411  		devicestate.SetSystemMode(s.mgr, mode)
   412  		err = s.bootloader.SetBootVars(map[string]string{
   413  			"snapd_recovery_mode":   mode,
   414  			"snapd_recovery_system": label,
   415  		})
   416  		c.Assert(err, IsNil)
   417  		err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode})
   418  		if strutil.ListContains(sadModes, mode) {
   419  			c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   420  		} else {
   421  			c.Assert(err, IsNil)
   422  		}
   423  		// bootloader vars shouldn't change
   424  		m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   425  		c.Assert(err, IsNil)
   426  		c.Check(m, DeepEquals, map[string]string{
   427  			"snapd_recovery_mode":   mode,
   428  			"snapd_recovery_system": label,
   429  		})
   430  		// should never restart
   431  		c.Check(s.restartRequests, HasLen, 0)
   432  		// no log output
   433  		c.Check(s.logbuf.String(), Equals, "")
   434  	}
   435  }
   436  
   437  func (s *deviceMgrSystemsSuite) TestRequestSeedingSameConflict(c *C) {
   438  	label := s.mockedSystemSeeds[0].label
   439  
   440  	devicestate.SetSystemMode(s.mgr, "run")
   441  
   442  	s.state.Lock()
   443  	s.state.Set("seeded", nil)
   444  	s.state.Unlock()
   445  
   446  	for _, mode := range []string{"run", "install", "recover"} {
   447  		s.logbuf.Reset()
   448  
   449  		c.Logf("checking mode: %q", mode)
   450  		modeenv := boot.Modeenv{
   451  			Mode:           mode,
   452  			RecoverySystem: s.mockedSystemSeeds[0].label,
   453  		}
   454  		err := modeenv.WriteTo("")
   455  		c.Assert(err, IsNil)
   456  
   457  		err = s.bootloader.SetBootVars(map[string]string{
   458  			"snapd_recovery_mode":   "",
   459  			"snapd_recovery_system": label,
   460  		})
   461  		c.Assert(err, IsNil)
   462  		err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode})
   463  		c.Assert(err, ErrorMatches, "cannot request system action, system is seeding")
   464  		// no log output
   465  		c.Check(s.logbuf.String(), Equals, "")
   466  	}
   467  }
   468  
   469  func (s *deviceMgrSystemsSuite) TestRequestSeedingDifferentNoConflict(c *C) {
   470  	label := s.mockedSystemSeeds[0].label
   471  	otherLabel := s.mockedSystemSeeds[1].label
   472  
   473  	devicestate.SetSystemMode(s.mgr, "run")
   474  
   475  	modeenv := boot.Modeenv{
   476  		Mode:           "run",
   477  		RecoverySystem: label,
   478  	}
   479  	err := modeenv.WriteTo("")
   480  	c.Assert(err, IsNil)
   481  
   482  	s.state.Lock()
   483  	s.state.Set("seeded", nil)
   484  	s.state.Unlock()
   485  
   486  	// we can only go to install mode of other system when one is currently
   487  	// being seeded
   488  	err = s.bootloader.SetBootVars(map[string]string{
   489  		"snapd_recovery_mode":   "",
   490  		"snapd_recovery_system": label,
   491  	})
   492  	c.Assert(err, IsNil)
   493  	err = s.mgr.RequestSystemAction(otherLabel, devicestate.SystemAction{Mode: "install"})
   494  	c.Assert(err, IsNil)
   495  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   496  	c.Assert(err, IsNil)
   497  	c.Check(m, DeepEquals, map[string]string{
   498  		"snapd_recovery_system": otherLabel,
   499  		"snapd_recovery_mode":   "install",
   500  	})
   501  	c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action "Install"\n`, otherLabel))
   502  }
   503  
   504  func (s *deviceMgrSystemsSuite) testRequestModeWithRestart(c *C, toModes []string, label string) {
   505  	for _, mode := range toModes {
   506  		c.Logf("checking mode: %q", mode)
   507  		err := s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode})
   508  		c.Assert(err, IsNil)
   509  		m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   510  		c.Assert(err, IsNil)
   511  		c.Check(m, DeepEquals, map[string]string{
   512  			"snapd_recovery_system": label,
   513  			"snapd_recovery_mode":   mode,
   514  		})
   515  		c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   516  		s.restartRequests = nil
   517  		s.bootloader.BootVars = map[string]string{}
   518  
   519  		// TODO: also test correct action string logging
   520  		c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action ".*"\n`, label))
   521  		s.logbuf.Reset()
   522  	}
   523  }
   524  
   525  func (s *deviceMgrSystemsSuite) TestRequestModeRunInstallForRecover(c *C) {
   526  	// we are in recover mode here
   527  	devicestate.SetSystemMode(s.mgr, "recover")
   528  	// non run modes use modeenv
   529  	modeenv := boot.Modeenv{
   530  		Mode:           "recover",
   531  		RecoverySystem: s.mockedSystemSeeds[0].label,
   532  	}
   533  	err := modeenv.WriteTo("")
   534  	c.Assert(err, IsNil)
   535  
   536  	s.state.Lock()
   537  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   538  		{
   539  			System:  s.mockedSystemSeeds[0].label,
   540  			Model:   s.mockedSystemSeeds[0].model.Model(),
   541  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   542  		},
   543  	})
   544  	s.state.Unlock()
   545  
   546  	s.testRequestModeWithRestart(c, []string{"install", "run"}, s.mockedSystemSeeds[0].label)
   547  }
   548  
   549  func (s *deviceMgrSystemsSuite) TestRequestModeInstallRecoverForCurrent(c *C) {
   550  	devicestate.SetSystemMode(s.mgr, "run")
   551  	// non run modes use modeenv
   552  	modeenv := boot.Modeenv{
   553  		Mode: "run",
   554  	}
   555  	err := modeenv.WriteTo("")
   556  	c.Assert(err, IsNil)
   557  
   558  	s.state.Lock()
   559  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   560  		{
   561  			System:  s.mockedSystemSeeds[0].label,
   562  			Model:   s.mockedSystemSeeds[0].model.Model(),
   563  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   564  		},
   565  	})
   566  	s.state.Unlock()
   567  
   568  	s.testRequestModeWithRestart(c, []string{"install", "recover"}, s.mockedSystemSeeds[0].label)
   569  }
   570  
   571  func (s *deviceMgrSystemsSuite) TestRequestModeErrInBoot(c *C) {
   572  	s.bootloader.SetErr = errors.New("no can do")
   573  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"})
   574  	c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": no can do`)
   575  	c.Check(s.restartRequests, HasLen, 0)
   576  	c.Check(s.logbuf.String(), Equals, "")
   577  }
   578  
   579  func (s *deviceMgrSystemsSuite) TestRequestModeNotFound(c *C) {
   580  	err := s.mgr.RequestSystemAction("not-found", devicestate.SystemAction{Mode: "install"})
   581  	c.Assert(err, NotNil)
   582  	c.Assert(os.IsNotExist(err), Equals, true)
   583  	c.Check(s.restartRequests, HasLen, 0)
   584  	c.Check(s.logbuf.String(), Equals, "")
   585  }
   586  
   587  func (s *deviceMgrSystemsSuite) TestRequestModeBadMode(c *C) {
   588  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "unknown-mode"})
   589  	c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   590  	c.Check(s.restartRequests, HasLen, 0)
   591  	c.Check(s.logbuf.String(), Equals, "")
   592  }
   593  
   594  func (s *deviceMgrSystemsSuite) TestRequestModeBroken(c *C) {
   595  	// break the first seed
   596  	err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model"))
   597  	c.Assert(err, IsNil)
   598  
   599  	err = s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"})
   600  	c.Assert(err, ErrorMatches, "cannot load seed system: cannot load assertions: .*")
   601  	c.Check(s.restartRequests, HasLen, 0)
   602  	c.Check(s.logbuf.String(), Equals, "")
   603  }
   604  
   605  func (s *deviceMgrSystemsSuite) TestRequestModeNonUC20(c *C) {
   606  	s.setPCModelInState(c)
   607  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"})
   608  	c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": system mode is unsupported`)
   609  	c.Check(s.restartRequests, HasLen, 0)
   610  	c.Check(s.logbuf.String(), Equals, "")
   611  }
   612  
   613  func (s *deviceMgrSystemsSuite) TestRequestActionNoLabel(c *C) {
   614  	err := s.mgr.RequestSystemAction("", devicestate.SystemAction{Mode: "install"})
   615  	c.Assert(err, ErrorMatches, "internal error: system label is unset")
   616  	c.Check(s.logbuf.String(), Equals, "")
   617  }
   618  
   619  func (s *deviceMgrSystemsSuite) TestRequestModeForNonCurrent(c *C) {
   620  	s.state.Lock()
   621  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   622  		{
   623  			System:  s.mockedSystemSeeds[0].label,
   624  			Model:   s.mockedSystemSeeds[0].model.Model(),
   625  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   626  		},
   627  	})
   628  
   629  	s.state.Unlock()
   630  	s.setPCModelInState(c)
   631  	// request mode reserved for current system
   632  	err := s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "run"})
   633  	c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   634  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "recover"})
   635  	c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   636  	c.Check(s.restartRequests, HasLen, 0)
   637  	c.Check(s.logbuf.String(), Equals, "")
   638  }
   639  
   640  func (s *deviceMgrSystemsSuite) TestRequestInstallForOther(c *C) {
   641  	devicestate.SetSystemMode(s.mgr, "run")
   642  	// non run modes use modeenv
   643  	modeenv := boot.Modeenv{
   644  		Mode: "run",
   645  	}
   646  	err := modeenv.WriteTo("")
   647  	c.Assert(err, IsNil)
   648  
   649  	s.state.Lock()
   650  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   651  		{
   652  			System:  s.mockedSystemSeeds[0].label,
   653  			Model:   s.mockedSystemSeeds[0].model.Model(),
   654  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   655  		},
   656  	})
   657  	s.state.Unlock()
   658  	// reinstall from different system seed is ok
   659  	s.testRequestModeWithRestart(c, []string{"install"}, s.mockedSystemSeeds[1].label)
   660  }
   661  
   662  func (s *deviceMgrSystemsSuite) TestRequestAction1618(c *C) {
   663  	s.setPCModelInState(c)
   664  	// system mode is unset in 16/18
   665  	devicestate.SetSystemMode(s.mgr, "")
   666  	// no modeenv either
   667  	err := os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
   668  	c.Assert(err, IsNil)
   669  
   670  	s.state.Lock()
   671  	s.state.Set("seeded-systems", nil)
   672  	s.state.Set("seeded", nil)
   673  	s.state.Unlock()
   674  	// a label exists
   675  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"})
   676  	c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported")
   677  
   678  	s.state.Lock()
   679  	s.state.Set("seeded", true)
   680  	s.state.Unlock()
   681  
   682  	// even with system mode explicitly set, the action is not executed
   683  	devicestate.SetSystemMode(s.mgr, "run")
   684  
   685  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"})
   686  	c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported")
   687  
   688  	devicestate.SetSystemMode(s.mgr, "")
   689  	// also no UC20 style system seeds
   690  	for _, m := range s.mockedSystemSeeds {
   691  		os.RemoveAll(filepath.Join(dirs.SnapSeedDir, "systems", m.label))
   692  	}
   693  
   694  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"})
   695  	c.Assert(err, ErrorMatches, ".*/seed/systems/20191119: no such file or directory")
   696  	c.Check(s.logbuf.String(), Equals, "")
   697  }
   698  
   699  func (s *deviceMgrSystemsSuite) TestRebootNoLabelNoModeHappy(c *C) {
   700  	err := s.mgr.Reboot("", "")
   701  	c.Assert(err, IsNil)
   702  
   703  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   704  	c.Assert(err, IsNil)
   705  	// requested restart
   706  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   707  	// but no bootloader changes
   708  	c.Check(m, DeepEquals, map[string]string{
   709  		"snapd_recovery_system": "",
   710  		"snapd_recovery_mode":   "",
   711  	})
   712  	c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`)
   713  }
   714  
   715  func (s *deviceMgrSystemsSuite) TestRebootLabelAndModeHappy(c *C) {
   716  	s.state.Lock()
   717  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   718  		{
   719  			System:  s.mockedSystemSeeds[0].label,
   720  			Model:   s.mockedSystemSeeds[0].model.Model(),
   721  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   722  		},
   723  	})
   724  	s.state.Unlock()
   725  
   726  	err := s.mgr.Reboot("20191119", "install")
   727  	c.Assert(err, IsNil)
   728  
   729  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   730  	c.Assert(err, IsNil)
   731  	c.Check(m, DeepEquals, map[string]string{
   732  		"snapd_recovery_system": "20191119",
   733  		"snapd_recovery_mode":   "install",
   734  	})
   735  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   736  	c.Check(s.logbuf.String(), Matches, `.*: rebooting into system "20191119" in "install" mode\n`)
   737  }
   738  
   739  func (s *deviceMgrSystemsSuite) TestRebootModeOnlyHappy(c *C) {
   740  	s.state.Lock()
   741  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   742  		{
   743  			System:  s.mockedSystemSeeds[0].label,
   744  			Model:   s.mockedSystemSeeds[0].model.Model(),
   745  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   746  		},
   747  	})
   748  	s.state.Unlock()
   749  
   750  	for _, mode := range []string{"recover", "install"} {
   751  		s.restartRequests = nil
   752  		s.bootloader.BootVars = make(map[string]string)
   753  		s.logbuf.Reset()
   754  
   755  		err := s.mgr.Reboot("", mode)
   756  		c.Assert(err, IsNil)
   757  
   758  		m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   759  		c.Assert(err, IsNil)
   760  		c.Check(m, DeepEquals, map[string]string{
   761  			"snapd_recovery_system": s.mockedSystemSeeds[0].label,
   762  			"snapd_recovery_mode":   mode,
   763  		})
   764  		c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   765  		c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "20191119" in "%s" mode\n`, mode))
   766  	}
   767  }
   768  
   769  func (s *deviceMgrSystemsSuite) TestRebootFromRecoverToRun(c *C) {
   770  	modeenv := boot.Modeenv{
   771  		Mode:           "recover",
   772  		RecoverySystem: s.mockedSystemSeeds[0].label,
   773  	}
   774  	err := modeenv.WriteTo("")
   775  	c.Assert(err, IsNil)
   776  
   777  	devicestate.SetSystemMode(s.mgr, "recover")
   778  	err = s.bootloader.SetBootVars(map[string]string{
   779  		"snapd_recovery_mode":   "recover",
   780  		"snapd_recovery_system": s.mockedSystemSeeds[0].label,
   781  	})
   782  	c.Assert(err, IsNil)
   783  
   784  	s.state.Lock()
   785  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   786  		{
   787  			System:  s.mockedSystemSeeds[0].label,
   788  			Model:   s.mockedSystemSeeds[0].model.Model(),
   789  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   790  		},
   791  	})
   792  	s.state.Unlock()
   793  
   794  	err = s.mgr.Reboot("", "run")
   795  	c.Assert(err, IsNil)
   796  
   797  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   798  	c.Assert(err, IsNil)
   799  	c.Check(m, DeepEquals, map[string]string{
   800  		"snapd_recovery_mode":   "run",
   801  		"snapd_recovery_system": s.mockedSystemSeeds[0].label,
   802  	})
   803  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   804  	c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "%s" in "run" mode\n`, s.mockedSystemSeeds[0].label))
   805  }
   806  
   807  func (s *deviceMgrSystemsSuite) TestRebootAlreadyInRunMode(c *C) {
   808  	devicestate.SetSystemMode(s.mgr, "run")
   809  
   810  	s.state.Lock()
   811  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   812  		{
   813  			System:  s.mockedSystemSeeds[0].label,
   814  			Model:   s.mockedSystemSeeds[0].model.Model(),
   815  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   816  		},
   817  	})
   818  	s.state.Unlock()
   819  
   820  	// we are already in "run" mode so this should just reboot
   821  	err := s.mgr.Reboot("", "run")
   822  	c.Assert(err, IsNil)
   823  
   824  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   825  	c.Assert(err, IsNil)
   826  	c.Check(m, DeepEquals, map[string]string{
   827  		"snapd_recovery_mode":   "",
   828  		"snapd_recovery_system": "",
   829  	})
   830  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   831  	c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`)
   832  }
   833  
   834  func (s *deviceMgrSystemsSuite) TestRebootUnhappy(c *C) {
   835  	s.state.Lock()
   836  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   837  		{
   838  			System:  s.mockedSystemSeeds[0].label,
   839  			Model:   s.mockedSystemSeeds[0].model.Model(),
   840  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   841  		},
   842  	})
   843  	s.state.Unlock()
   844  
   845  	errUnsupportedActionStr := devicestate.ErrUnsupportedAction.Error()
   846  	for _, tc := range []struct {
   847  		systemLabel, mode string
   848  		expectedErr       string
   849  	}{
   850  		{"", "unknown-mode", errUnsupportedActionStr},
   851  		{"unknown-system", "run", `stat /.*: no such file or directory`},
   852  		{"unknown-system", "unknown-mode", `stat /.*: no such file or directory`},
   853  	} {
   854  		s.restartRequests = nil
   855  		s.bootloader.BootVars = make(map[string]string)
   856  
   857  		err := s.mgr.Reboot(tc.systemLabel, tc.mode)
   858  		c.Assert(err, ErrorMatches, tc.expectedErr)
   859  
   860  		c.Check(s.restartRequests, HasLen, 0)
   861  		c.Check(s.logbuf.String(), Equals, "")
   862  	}
   863  	c.Check(s.logbuf.String(), Equals, "")
   864  }
   865  
   866  func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemSuccessfuly(c *C) {
   867  	err := s.bootloader.SetBootVars(map[string]string{
   868  		"try_recovery_system":    "1234",
   869  		"recovery_system_status": "tried",
   870  	})
   871  	c.Assert(err, IsNil)
   872  	devicestate.SetBootOkRan(s.mgr, true)
   873  
   874  	modeenv := boot.Modeenv{
   875  		Mode: boot.ModeRun,
   876  		// the system is in CurrentRecoverySystems
   877  		CurrentRecoverySystems: []string{"29112019", "1234"},
   878  	}
   879  	err = modeenv.WriteTo("")
   880  	c.Assert(err, IsNil)
   881  
   882  	// system is considered successful, bootenv is cleared, the label is
   883  	// recorded in tried-systems
   884  	err = s.mgr.Ensure()
   885  	c.Assert(err, IsNil)
   886  
   887  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
   888  	c.Assert(err, IsNil)
   889  	c.Check(m, DeepEquals, map[string]string{
   890  		"try_recovery_system":    "",
   891  		"recovery_system_status": "",
   892  	})
   893  
   894  	var triedSystems []string
   895  	s.state.Lock()
   896  	err = s.state.Get("tried-systems", &triedSystems)
   897  	c.Assert(err, IsNil)
   898  	c.Check(triedSystems, DeepEquals, []string{"1234"})
   899  	// also logged
   900  	c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system "1234" was successful`)
   901  	s.state.Unlock()
   902  
   903  	// reset and run again, we need to populate boot variables again
   904  	err = s.bootloader.SetBootVars(map[string]string{
   905  		"try_recovery_system":    "1234",
   906  		"recovery_system_status": "tried",
   907  	})
   908  	c.Assert(err, IsNil)
   909  	devicestate.SetTriedSystemsRan(s.mgr, false)
   910  
   911  	err = s.mgr.Ensure()
   912  	c.Assert(err, IsNil)
   913  	s.state.Lock()
   914  	defer s.state.Unlock()
   915  	err = s.state.Get("tried-systems", &triedSystems)
   916  	c.Assert(err, IsNil)
   917  	// the system was already there, no duplicate got appended
   918  	c.Assert(triedSystems, DeepEquals, []string{"1234"})
   919  }
   920  
   921  func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemMissingInModeenvUnhappy(c *C) {
   922  	err := s.bootloader.SetBootVars(map[string]string{
   923  		"try_recovery_system":    "1234",
   924  		"recovery_system_status": "tried",
   925  	})
   926  	c.Assert(err, IsNil)
   927  	devicestate.SetBootOkRan(s.mgr, true)
   928  
   929  	modeenv := boot.Modeenv{
   930  		Mode: boot.ModeRun,
   931  		// the system is not in CurrentRecoverySystems
   932  		CurrentRecoverySystems: []string{"29112019"},
   933  	}
   934  	err = modeenv.WriteTo("")
   935  	c.Assert(err, IsNil)
   936  
   937  	// system is considered successful, bootenv is cleared, the label is
   938  	// recorded in tried-systems
   939  	err = s.mgr.Ensure()
   940  	c.Assert(err, IsNil)
   941  
   942  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
   943  	c.Assert(err, IsNil)
   944  	c.Check(m, DeepEquals, map[string]string{
   945  		"try_recovery_system":    "",
   946  		"recovery_system_status": "",
   947  	})
   948  
   949  	var triedSystems []string
   950  	s.state.Lock()
   951  	err = s.state.Get("tried-systems", &triedSystems)
   952  	c.Assert(err, Equals, state.ErrNoState)
   953  	// also logged
   954  	c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system outcome error: recovery system "1234" was tried, but is not present in the modeenv CurrentRecoverySystems`)
   955  	s.state.Unlock()
   956  }
   957  
   958  func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemBad(c *C) {
   959  	// after reboot, the recovery system status is still try
   960  	err := s.bootloader.SetBootVars(map[string]string{
   961  		"try_recovery_system":    "1234",
   962  		"recovery_system_status": "try",
   963  	})
   964  	c.Assert(err, IsNil)
   965  	devicestate.SetBootOkRan(s.mgr, true)
   966  
   967  	// thus the system is considered bad, bootenv is cleared, and system is
   968  	// not recorded as successful
   969  	err = s.mgr.Ensure()
   970  	c.Assert(err, IsNil)
   971  
   972  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
   973  	c.Assert(err, IsNil)
   974  	c.Check(m, DeepEquals, map[string]string{
   975  		"try_recovery_system":    "",
   976  		"recovery_system_status": "",
   977  	})
   978  
   979  	var triedSystems []string
   980  	s.state.Lock()
   981  	err = s.state.Get("tried-systems", &triedSystems)
   982  	c.Assert(err, Equals, state.ErrNoState)
   983  	c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system "1234" failed`)
   984  	s.state.Unlock()
   985  
   986  	// procure an inconsistent state, reset and run again
   987  	err = s.bootloader.SetBootVars(map[string]string{
   988  		"try_recovery_system":    "",
   989  		"recovery_system_status": "try",
   990  	})
   991  	c.Assert(err, IsNil)
   992  	devicestate.SetTriedSystemsRan(s.mgr, false)
   993  
   994  	// clear the log buffer
   995  	s.logbuf.Reset()
   996  
   997  	err = s.mgr.Ensure()
   998  	c.Assert(err, IsNil)
   999  	s.state.Lock()
  1000  	defer s.state.Unlock()
  1001  	err = s.state.Get("tried-systems", &triedSystems)
  1002  	c.Assert(err, Equals, state.ErrNoState)
  1003  	// bootenv got cleared
  1004  	m, err = s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1005  	c.Assert(err, IsNil)
  1006  	c.Check(m, DeepEquals, map[string]string{
  1007  		"try_recovery_system":    "",
  1008  		"recovery_system_status": "",
  1009  	})
  1010  	c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system outcome error: try recovery system is unset but status is "try"`)
  1011  	c.Check(s.logbuf.String(), testutil.Contains, `inconsistent outcome of a tried recovery system`)
  1012  }
  1013  
  1014  func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemManyLabels(c *C) {
  1015  	err := s.bootloader.SetBootVars(map[string]string{
  1016  		"try_recovery_system":    "1234",
  1017  		"recovery_system_status": "tried",
  1018  	})
  1019  	c.Assert(err, IsNil)
  1020  	devicestate.SetBootOkRan(s.mgr, true)
  1021  
  1022  	s.state.Lock()
  1023  	s.state.Set("tried-systems", []string{"0000", "1111"})
  1024  	s.state.Unlock()
  1025  
  1026  	modeenv := boot.Modeenv{
  1027  		Mode: boot.ModeRun,
  1028  		// the system is in CurrentRecoverySystems
  1029  		CurrentRecoverySystems: []string{"29112019", "1234"},
  1030  	}
  1031  	err = modeenv.WriteTo("")
  1032  	c.Assert(err, IsNil)
  1033  
  1034  	// successful system label is appended
  1035  	err = s.mgr.Ensure()
  1036  	c.Assert(err, IsNil)
  1037  
  1038  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1039  	c.Assert(err, IsNil)
  1040  	c.Check(m, DeepEquals, map[string]string{
  1041  		"try_recovery_system":    "",
  1042  		"recovery_system_status": "",
  1043  	})
  1044  
  1045  	s.state.Lock()
  1046  	defer s.state.Unlock()
  1047  
  1048  	var triedSystems []string
  1049  	err = s.state.Get("tried-systems", &triedSystems)
  1050  	c.Assert(err, IsNil)
  1051  	c.Assert(triedSystems, DeepEquals, []string{"0000", "1111", "1234"})
  1052  
  1053  	c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system "1234" was successful`)
  1054  }
  1055  
  1056  type deviceMgrSystemsCreateSuite struct {
  1057  	deviceMgrSystemsBaseSuite
  1058  
  1059  	bootloader *bootloadertest.MockRecoveryAwareTrustedAssetsBootloader
  1060  }
  1061  
  1062  func (s *deviceMgrSystemsCreateSuite) SetUpTest(c *C) {
  1063  	s.deviceMgrSystemsBaseSuite.SetUpTest(c)
  1064  
  1065  	s.bootloader = s.deviceMgrSystemsBaseSuite.bootloader.WithRecoveryAwareTrustedAssets()
  1066  	bootloader.Force(s.bootloader)
  1067  	s.AddCleanup(func() { bootloader.Force(nil) })
  1068  }
  1069  
  1070  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemTasksAndChange(c *C) {
  1071  	devicestate.SetBootOkRan(s.mgr, true)
  1072  
  1073  	s.state.Lock()
  1074  	defer s.state.Unlock()
  1075  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234")
  1076  	c.Assert(err, IsNil)
  1077  	c.Assert(chg, NotNil)
  1078  	tsks := chg.Tasks()
  1079  	c.Check(tsks, HasLen, 2)
  1080  	tskCreate := tsks[0]
  1081  	tskFinalize := tsks[1]
  1082  	c.Check(tskCreate.Summary(), Matches, `Create recovery system with label "1234"`)
  1083  	c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234"`)
  1084  	var systemSetupData map[string]interface{}
  1085  	err = tskCreate.Get("recovery-system-setup", &systemSetupData)
  1086  	c.Assert(err, IsNil)
  1087  	c.Assert(systemSetupData, DeepEquals, map[string]interface{}{
  1088  		"label":            "1234",
  1089  		"directory":        filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"),
  1090  		"snap-setup-tasks": nil,
  1091  	})
  1092  
  1093  	var otherTaskID string
  1094  	err = tskFinalize.Get("recovery-system-setup-task", &otherTaskID)
  1095  	c.Assert(err, IsNil)
  1096  	c.Assert(otherTaskID, Equals, tskCreate.ID())
  1097  }
  1098  
  1099  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemTasksWhenDirExists(c *C) {
  1100  	devicestate.SetBootOkRan(s.mgr, true)
  1101  
  1102  	c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"), 0755), IsNil)
  1103  
  1104  	s.state.Lock()
  1105  	defer s.state.Unlock()
  1106  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234")
  1107  	c.Assert(err, ErrorMatches, `recovery system "1234" already exists`)
  1108  	c.Check(chg, IsNil)
  1109  }
  1110  
  1111  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemNotSeeded(c *C) {
  1112  	devicestate.SetBootOkRan(s.mgr, true)
  1113  
  1114  	s.state.Lock()
  1115  	defer s.state.Unlock()
  1116  	s.state.Set("seeded", nil)
  1117  
  1118  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234")
  1119  	c.Assert(err, ErrorMatches, `cannot create new recovery systems until fully seeded`)
  1120  	c.Check(chg, IsNil)
  1121  }
  1122  
  1123  func (s *deviceMgrSystemsCreateSuite) makeSnapInState(c *C, name string, rev snap.Revision) *snap.Info {
  1124  	snapID := s.ss.AssertedSnapID(name)
  1125  	if rev.Unset() || rev.Local() {
  1126  		snapID = ""
  1127  	}
  1128  	si := &snap.SideInfo{
  1129  		RealName: name,
  1130  		SnapID:   snapID,
  1131  		Revision: rev,
  1132  	}
  1133  	info := snaptest.MakeSnapFileAndDir(c, snapYamls[name], snapFiles[name], si)
  1134  	// asserted?
  1135  	if !rev.Unset() && !rev.Local() {
  1136  		s.setupSnapDecl(c, info, "canonical")
  1137  		s.setupSnapRevision(c, info, "canonical", rev)
  1138  	}
  1139  	snapstate.Set(s.state, info.InstanceName(), &snapstate.SnapState{
  1140  		SnapType: string(info.Type()),
  1141  		Active:   true,
  1142  		Sequence: []*snap.SideInfo{si},
  1143  		Current:  si.Revision,
  1144  	})
  1145  
  1146  	return info
  1147  }
  1148  
  1149  func (s *deviceMgrSystemsCreateSuite) mockStandardSnapsModeenvAndBootloaderState(c *C) {
  1150  	s.makeSnapInState(c, "pc", snap.R(1))
  1151  	s.makeSnapInState(c, "pc-kernel", snap.R(2))
  1152  	s.makeSnapInState(c, "core20", snap.R(3))
  1153  	s.makeSnapInState(c, "snapd", snap.R(4))
  1154  
  1155  	err := s.bootloader.SetBootVars(map[string]string{
  1156  		"snap_kernel": "pc-kernel_2.snap",
  1157  		"snap_core":   "core20_3.snap",
  1158  	})
  1159  	c.Assert(err, IsNil)
  1160  	modeenv := boot.Modeenv{
  1161  		Mode:                   "run",
  1162  		Base:                   "core20_3.snap",
  1163  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1164  		CurrentRecoverySystems: []string{"othersystem"},
  1165  		GoodRecoverySystems:    []string{"othersystem"},
  1166  
  1167  		Model:          s.model.Model(),
  1168  		BrandID:        s.model.BrandID(),
  1169  		Grade:          string(s.model.Grade()),
  1170  		ModelSignKeyID: s.model.SignKeyID(),
  1171  	}
  1172  	err = modeenv.WriteTo("")
  1173  	c.Assert(err, IsNil)
  1174  }
  1175  
  1176  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemHappy(c *C) {
  1177  	devicestate.SetBootOkRan(s.mgr, true)
  1178  
  1179  	s.state.Lock()
  1180  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234")
  1181  	c.Assert(err, IsNil)
  1182  	c.Assert(chg, NotNil)
  1183  	tsks := chg.Tasks()
  1184  	c.Check(tsks, HasLen, 2)
  1185  	tskCreate := tsks[0]
  1186  	tskFinalize := tsks[1]
  1187  	c.Assert(tskCreate.Summary(), Matches, `Create recovery system with label "1234"`)
  1188  	c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234"`)
  1189  
  1190  	s.mockStandardSnapsModeenvAndBootloaderState(c)
  1191  
  1192  	s.state.Unlock()
  1193  	s.settle(c)
  1194  	s.state.Lock()
  1195  
  1196  	c.Assert(chg.Err(), IsNil)
  1197  	c.Assert(tskCreate.Status(), Equals, state.DoneStatus)
  1198  	c.Assert(tskFinalize.Status(), Equals, state.DoingStatus)
  1199  	// a reboot is expected
  1200  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  1201  
  1202  	validateCore20Seed(c, "1234", s.model, s.storeSigning.Trusted)
  1203  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1204  	c.Assert(err, IsNil)
  1205  	c.Check(m, DeepEquals, map[string]string{
  1206  		"try_recovery_system":    "1234",
  1207  		"recovery_system_status": "try",
  1208  	})
  1209  	modeenvAfterCreate, err := boot.ReadModeenv("")
  1210  	c.Assert(err, IsNil)
  1211  	c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{
  1212  		Mode:                   "run",
  1213  		Base:                   "core20_3.snap",
  1214  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1215  		CurrentRecoverySystems: []string{"othersystem", "1234"},
  1216  		GoodRecoverySystems:    []string{"othersystem"},
  1217  
  1218  		Model:          s.model.Model(),
  1219  		BrandID:        s.model.BrandID(),
  1220  		Grade:          string(s.model.Grade()),
  1221  		ModelSignKeyID: s.model.SignKeyID(),
  1222  		// try model is unset as its measured properties are identical
  1223  		// to current
  1224  	})
  1225  	// verify that new files are tracked correctly
  1226  	expectedFilesLog := &bytes.Buffer{}
  1227  	// new snap files are logged in this order
  1228  	for _, fname := range []string{"snapd_4.snap", "pc-kernel_2.snap", "core20_3.snap", "pc_1.snap"} {
  1229  		fmt.Fprintln(expectedFilesLog, filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", fname))
  1230  	}
  1231  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"),
  1232  		testutil.FileEquals, expectedFilesLog.String())
  1233  
  1234  	// these things happen on snapd startup
  1235  	state.MockRestarting(s.state, state.RestartUnset)
  1236  	s.state.Set("tried-systems", []string{"1234"})
  1237  	s.bootloader.SetBootVars(map[string]string{
  1238  		"try_recovery_system":    "",
  1239  		"recovery_system_status": "",
  1240  	})
  1241  	s.bootloader.SetBootVarsCalls = 0
  1242  
  1243  	s.state.Unlock()
  1244  	s.settle(c)
  1245  	s.state.Lock()
  1246  	defer s.state.Unlock()
  1247  
  1248  	c.Assert(chg.Err(), IsNil)
  1249  	c.Check(chg.IsReady(), Equals, true)
  1250  	c.Assert(tskCreate.Status(), Equals, state.DoneStatus)
  1251  	c.Assert(tskFinalize.Status(), Equals, state.DoneStatus)
  1252  
  1253  	var triedSystemsAfterFinalize []string
  1254  	err = s.state.Get("tried-systems", &triedSystemsAfterFinalize)
  1255  	c.Assert(err, Equals, state.ErrNoState)
  1256  
  1257  	modeenvAfterFinalize, err := boot.ReadModeenv("")
  1258  	c.Assert(err, IsNil)
  1259  	c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{
  1260  		Mode:                   "run",
  1261  		Base:                   "core20_3.snap",
  1262  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1263  		CurrentRecoverySystems: []string{"othersystem", "1234"},
  1264  		GoodRecoverySystems:    []string{"othersystem", "1234"},
  1265  
  1266  		Model:          s.model.Model(),
  1267  		BrandID:        s.model.BrandID(),
  1268  		Grade:          string(s.model.Grade()),
  1269  		ModelSignKeyID: s.model.SignKeyID(),
  1270  	})
  1271  	// no more calls to the bootloader past creating the system
  1272  	c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
  1273  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"), testutil.FileAbsent)
  1274  }
  1275  
  1276  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemRemodelDownloadingSnapsHappy(c *C) {
  1277  	devicestate.SetBootOkRan(s.mgr, true)
  1278  
  1279  	fooSnap := snaptest.MakeTestSnapWithFiles(c, "name: foo\nversion: 1.0\nbase: core20", nil)
  1280  	barSnap := snaptest.MakeTestSnapWithFiles(c, "name: bar\nversion: 1.0\nbase: core20", nil)
  1281  	s.state.Lock()
  1282  	// fake downloads are a nop
  1283  	tSnapsup1 := s.state.NewTask("fake-download", "dummy task carrying snap setup")
  1284  	tSnapsup2 := s.state.NewTask("fake-download", "dummy task carrying snap setup")
  1285  	// both snaps are asserted
  1286  	snapsupFoo := snapstate.SnapSetup{
  1287  		SideInfo: &snap.SideInfo{RealName: "foo", SnapID: s.ss.AssertedSnapID("foo"), Revision: snap.R(99)},
  1288  		SnapPath: fooSnap,
  1289  	}
  1290  	s.setupSnapDeclForNameAndID(c, "foo", s.ss.AssertedSnapID("foo"), "canonical")
  1291  	s.setupSnapRevisionForFileAndID(c, fooSnap, s.ss.AssertedSnapID("foo"), "canonical", snap.R(99))
  1292  	snapsupBar := snapstate.SnapSetup{
  1293  		SideInfo: &snap.SideInfo{RealName: "bar", SnapID: s.ss.AssertedSnapID("bar"), Revision: snap.R(100)},
  1294  		SnapPath: barSnap,
  1295  	}
  1296  	s.setupSnapDeclForNameAndID(c, "bar", s.ss.AssertedSnapID("bar"), "canonical")
  1297  	s.setupSnapRevisionForFileAndID(c, barSnap, s.ss.AssertedSnapID("bar"), "canonical", snap.R(100))
  1298  	// when download completes, the files will be at /var/lib/snapd/snap
  1299  	c.Assert(os.MkdirAll(filepath.Dir(snapsupFoo.MountFile()), 0755), IsNil)
  1300  	c.Assert(os.Rename(fooSnap, snapsupFoo.MountFile()), IsNil)
  1301  	c.Assert(os.MkdirAll(filepath.Dir(snapsupBar.MountFile()), 0755), IsNil)
  1302  	c.Assert(os.Rename(barSnap, snapsupBar.MountFile()), IsNil)
  1303  	tSnapsup1.Set("snap-setup", snapsupFoo)
  1304  	tSnapsup2.Set("snap-setup", snapsupBar)
  1305  
  1306  	tss, err := devicestate.CreateRecoverySystemTasks(s.state, "1234", []string{tSnapsup1.ID(), tSnapsup2.ID()})
  1307  	c.Assert(err, IsNil)
  1308  	tsks := tss.Tasks()
  1309  	c.Check(tsks, HasLen, 2)
  1310  	tskCreate := tsks[0]
  1311  	tskFinalize := tsks[1]
  1312  	c.Assert(tskCreate.Summary(), Matches, `Create recovery system with label "1234"`)
  1313  	c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234"`)
  1314  	var systemSetupData map[string]interface{}
  1315  	err = tskCreate.Get("recovery-system-setup", &systemSetupData)
  1316  	c.Assert(err, IsNil)
  1317  	c.Assert(systemSetupData, DeepEquals, map[string]interface{}{
  1318  		"label":            "1234",
  1319  		"directory":        filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"),
  1320  		"snap-setup-tasks": []interface{}{tSnapsup1.ID(), tSnapsup2.ID()},
  1321  	})
  1322  	tss.WaitFor(tSnapsup1)
  1323  	tss.WaitFor(tSnapsup2)
  1324  	// add the dummy tasks to the change
  1325  	chg := s.state.NewChange("create-recovery-system", "create recovery system")
  1326  	chg.AddTask(tSnapsup1)
  1327  	chg.AddTask(tSnapsup2)
  1328  	chg.AddAll(tss)
  1329  
  1330  	// downloads are only accepted if the tasks are executed as part of
  1331  	// remodel, so procure a new model
  1332  	newModel := s.brands.Model("canonical", "pc-20", map[string]interface{}{
  1333  		"architecture": "amd64",
  1334  		// UC20
  1335  		"grade": "dangerous",
  1336  		"base":  "core20",
  1337  		"snaps": []interface{}{
  1338  			map[string]interface{}{
  1339  				"name":            "pc-kernel",
  1340  				"id":              s.ss.AssertedSnapID("pc-kernel"),
  1341  				"type":            "kernel",
  1342  				"default-channel": "20",
  1343  			},
  1344  			map[string]interface{}{
  1345  				"name":            "pc",
  1346  				"id":              s.ss.AssertedSnapID("pc"),
  1347  				"type":            "gadget",
  1348  				"default-channel": "20",
  1349  			},
  1350  			map[string]interface{}{
  1351  				"name":     "foo",
  1352  				"id":       s.ss.AssertedSnapID("foo"),
  1353  				"presence": "required",
  1354  			},
  1355  			map[string]interface{}{
  1356  				"name":     "bar",
  1357  				"presence": "required",
  1358  			},
  1359  		},
  1360  		"revision": "2",
  1361  	})
  1362  	chg.Set("new-model", string(asserts.Encode(newModel)))
  1363  
  1364  	s.mockStandardSnapsModeenvAndBootloaderState(c)
  1365  
  1366  	s.state.Unlock()
  1367  	s.settle(c)
  1368  	s.state.Lock()
  1369  	defer s.state.Unlock()
  1370  
  1371  	c.Assert(chg.Err(), IsNil)
  1372  	c.Assert(tskCreate.Status(), Equals, state.DoneStatus)
  1373  	c.Assert(tskFinalize.Status(), Equals, state.DoingStatus)
  1374  	// a reboot is expected
  1375  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  1376  
  1377  	validateCore20Seed(c, "1234", newModel, s.storeSigning.Trusted, "foo", "bar")
  1378  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1379  	c.Assert(err, IsNil)
  1380  	c.Check(m, DeepEquals, map[string]string{
  1381  		"try_recovery_system":    "1234",
  1382  		"recovery_system_status": "try",
  1383  	})
  1384  	modeenvAfterCreate, err := boot.ReadModeenv("")
  1385  	c.Assert(err, IsNil)
  1386  	c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{
  1387  		Mode:                   "run",
  1388  		Base:                   "core20_3.snap",
  1389  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1390  		CurrentRecoverySystems: []string{"othersystem", "1234"},
  1391  		GoodRecoverySystems:    []string{"othersystem"},
  1392  
  1393  		Model:          s.model.Model(),
  1394  		BrandID:        s.model.BrandID(),
  1395  		Grade:          string(s.model.Grade()),
  1396  		ModelSignKeyID: s.model.SignKeyID(),
  1397  		// try model is unset as its measured properties are identical
  1398  	})
  1399  	// verify that new files are tracked correctly
  1400  	expectedFilesLog := &bytes.Buffer{}
  1401  	// new snap files are logged in this order
  1402  	for _, fname := range []string{
  1403  		"snapd_4.snap", "pc-kernel_2.snap", "core20_3.snap", "pc_1.snap",
  1404  		"foo_99.snap", "bar_100.snap",
  1405  	} {
  1406  		fmt.Fprintln(expectedFilesLog, filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", fname))
  1407  	}
  1408  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"),
  1409  		testutil.FileEquals, expectedFilesLog.String())
  1410  
  1411  	// these things happen on snapd startup
  1412  	state.MockRestarting(s.state, state.RestartUnset)
  1413  	s.state.Set("tried-systems", []string{"1234"})
  1414  	s.bootloader.SetBootVars(map[string]string{
  1415  		"try_recovery_system":    "",
  1416  		"recovery_system_status": "",
  1417  	})
  1418  	s.bootloader.SetBootVarsCalls = 0
  1419  
  1420  	s.state.Unlock()
  1421  	s.settle(c)
  1422  	s.state.Lock()
  1423  
  1424  	c.Assert(chg.Err(), IsNil)
  1425  	c.Check(chg.IsReady(), Equals, true)
  1426  	c.Assert(tskCreate.Status(), Equals, state.DoneStatus)
  1427  	c.Assert(tskFinalize.Status(), Equals, state.DoneStatus)
  1428  
  1429  	// this would be part of a remodel so some state is cleaned up only at the end of remodel change
  1430  	var triedSystemsAfterFinalize []string
  1431  	err = s.state.Get("tried-systems", &triedSystemsAfterFinalize)
  1432  	c.Assert(err, IsNil)
  1433  	c.Check(triedSystemsAfterFinalize, DeepEquals, []string{"1234"})
  1434  
  1435  	modeenvAfterFinalize, err := boot.ReadModeenv("")
  1436  	c.Assert(err, IsNil)
  1437  	c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{
  1438  		Mode:           "run",
  1439  		Base:           "core20_3.snap",
  1440  		CurrentKernels: []string{"pc-kernel_2.snap"},
  1441  		// the system is kept in the current list
  1442  		CurrentRecoverySystems: []string{"othersystem", "1234"},
  1443  		// but not promoted to good systems yet
  1444  		GoodRecoverySystems: []string{"othersystem"},
  1445  
  1446  		Model:          s.model.Model(),
  1447  		BrandID:        s.model.BrandID(),
  1448  		Grade:          string(s.model.Grade()),
  1449  		ModelSignKeyID: s.model.SignKeyID(),
  1450  	})
  1451  	// no more calls to the bootloader past creating the system
  1452  	c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
  1453  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"), testutil.FileAbsent)
  1454  }
  1455  
  1456  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemRemodelDownloadingMissingSnap(c *C) {
  1457  	devicestate.SetBootOkRan(s.mgr, true)
  1458  
  1459  	fooSnap := snaptest.MakeTestSnapWithFiles(c, "name: foo\nversion: 1.0\nbase: core20", nil)
  1460  	s.state.Lock()
  1461  	defer s.state.Unlock()
  1462  	// fake downloads are a nop
  1463  	tSnapsup1 := s.state.NewTask("fake-download", "dummy task carrying snap setup")
  1464  	// both snaps are asserted
  1465  	snapsupFoo := snapstate.SnapSetup{
  1466  		SideInfo: &snap.SideInfo{RealName: "foo", SnapID: s.ss.AssertedSnapID("foo"), Revision: snap.R(99)},
  1467  		SnapPath: fooSnap,
  1468  	}
  1469  	tSnapsup1.Set("snap-setup", snapsupFoo)
  1470  
  1471  	tss, err := devicestate.CreateRecoverySystemTasks(s.state, "1234missingdownload", []string{tSnapsup1.ID()})
  1472  	c.Assert(err, IsNil)
  1473  	tsks := tss.Tasks()
  1474  	c.Check(tsks, HasLen, 2)
  1475  	tskCreate := tsks[0]
  1476  	tskFinalize := tsks[1]
  1477  	c.Assert(tskCreate.Summary(), Matches, `Create recovery system with label "1234missingdownload"`)
  1478  	c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234missingdownload"`)
  1479  	var systemSetupData map[string]interface{}
  1480  	err = tskCreate.Get("recovery-system-setup", &systemSetupData)
  1481  	c.Assert(err, IsNil)
  1482  	c.Assert(systemSetupData, DeepEquals, map[string]interface{}{
  1483  		"label":            "1234missingdownload",
  1484  		"directory":        filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234missingdownload"),
  1485  		"snap-setup-tasks": []interface{}{tSnapsup1.ID()},
  1486  	})
  1487  	tss.WaitFor(tSnapsup1)
  1488  	// add the dummy task to the change
  1489  	chg := s.state.NewChange("create-recovery-system", "create recovery system")
  1490  	chg.AddTask(tSnapsup1)
  1491  	chg.AddAll(tss)
  1492  
  1493  	// downloads are only accepted if the tasks are executed as part of
  1494  	// remodel, so procure a new model
  1495  	newModel := s.brands.Model("canonical", "pc-20", map[string]interface{}{
  1496  		"architecture": "amd64",
  1497  		// UC20
  1498  		"grade": "dangerous",
  1499  		"base":  "core20",
  1500  		"snaps": []interface{}{
  1501  			map[string]interface{}{
  1502  				"name":            "pc-kernel",
  1503  				"id":              s.ss.AssertedSnapID("pc-kernel"),
  1504  				"type":            "kernel",
  1505  				"default-channel": "20",
  1506  			},
  1507  			map[string]interface{}{
  1508  				"name":            "pc",
  1509  				"id":              s.ss.AssertedSnapID("pc"),
  1510  				"type":            "gadget",
  1511  				"default-channel": "20",
  1512  			},
  1513  			// we have a download task for snap foo, but not for bar
  1514  			map[string]interface{}{
  1515  				"name":     "bar",
  1516  				"presence": "required",
  1517  			},
  1518  		},
  1519  		"revision": "2",
  1520  	})
  1521  	chg.Set("new-model", string(asserts.Encode(newModel)))
  1522  
  1523  	s.mockStandardSnapsModeenvAndBootloaderState(c)
  1524  
  1525  	s.state.Unlock()
  1526  	s.settle(c)
  1527  	s.state.Lock()
  1528  
  1529  	c.Assert(chg.Err(), ErrorMatches, `(?s).*cannot create a recovery system.*internal error: non-essential but required snap "bar" not present.`)
  1530  	c.Assert(tskCreate.Status(), Equals, state.ErrorStatus)
  1531  	c.Assert(tskFinalize.Status(), Equals, state.HoldStatus)
  1532  	// a reboot is expected
  1533  	c.Check(s.restartRequests, HasLen, 0)
  1534  	// single bootloader call to clear any recovery system variables
  1535  	c.Check(s.bootloader.SetBootVarsCalls, Equals, 1)
  1536  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1537  	c.Assert(err, IsNil)
  1538  	c.Check(m, DeepEquals, map[string]string{
  1539  		"try_recovery_system":    "",
  1540  		"recovery_system_status": "",
  1541  	})
  1542  	// system directory was removed
  1543  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234missingdownload"), testutil.FileAbsent)
  1544  }
  1545  
  1546  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemUndo(c *C) {
  1547  	devicestate.SetBootOkRan(s.mgr, true)
  1548  
  1549  	s.state.Lock()
  1550  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234undo")
  1551  	c.Assert(err, IsNil)
  1552  	c.Assert(chg, NotNil)
  1553  	tsks := chg.Tasks()
  1554  	c.Check(tsks, HasLen, 2)
  1555  	tskCreate := tsks[0]
  1556  	tskFinalize := tsks[1]
  1557  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1558  	terr.WaitFor(tskFinalize)
  1559  	chg.AddTask(terr)
  1560  
  1561  	s.mockStandardSnapsModeenvAndBootloaderState(c)
  1562  
  1563  	snaptest.PopulateDir(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps"), [][]string{
  1564  		{"core20_10.snap", "canary"},
  1565  		{"some-snap_1.snap", "canary"},
  1566  	})
  1567  
  1568  	s.state.Unlock()
  1569  	s.settle(c)
  1570  	s.state.Lock()
  1571  
  1572  	c.Assert(chg.Err(), IsNil)
  1573  	c.Assert(tskCreate.Status(), Equals, state.DoneStatus)
  1574  	c.Assert(tskFinalize.Status(), Equals, state.DoingStatus)
  1575  	// a reboot is expected
  1576  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  1577  	// sanity check asserted snaps location
  1578  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234undo"), testutil.FilePresent)
  1579  	p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*"))
  1580  	c.Assert(err, IsNil)
  1581  	c.Check(p, DeepEquals, []string{
  1582  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"),
  1583  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"),
  1584  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_2.snap"),
  1585  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_1.snap"),
  1586  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"),
  1587  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"),
  1588  	})
  1589  	// do more extensive validation
  1590  	validateCore20Seed(c, "1234undo", s.model, s.storeSigning.Trusted)
  1591  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1592  	c.Assert(err, IsNil)
  1593  	c.Check(m, DeepEquals, map[string]string{
  1594  		"try_recovery_system":    "1234undo",
  1595  		"recovery_system_status": "try",
  1596  	})
  1597  	modeenvAfterCreate, err := boot.ReadModeenv("")
  1598  	c.Assert(err, IsNil)
  1599  	c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{
  1600  		Mode:                   "run",
  1601  		Base:                   "core20_3.snap",
  1602  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1603  		CurrentRecoverySystems: []string{"othersystem", "1234undo"},
  1604  		GoodRecoverySystems:    []string{"othersystem"},
  1605  
  1606  		Model:          s.model.Model(),
  1607  		BrandID:        s.model.BrandID(),
  1608  		Grade:          string(s.model.Grade()),
  1609  		ModelSignKeyID: s.model.SignKeyID(),
  1610  	})
  1611  
  1612  	// these things happen on snapd startup
  1613  	state.MockRestarting(s.state, state.RestartUnset)
  1614  	s.state.Set("tried-systems", []string{"1234undo"})
  1615  	s.bootloader.SetBootVars(map[string]string{
  1616  		"try_recovery_system":    "",
  1617  		"recovery_system_status": "",
  1618  	})
  1619  	s.bootloader.SetBootVarsCalls = 0
  1620  
  1621  	s.state.Unlock()
  1622  	s.settle(c)
  1623  	s.state.Lock()
  1624  	defer s.state.Unlock()
  1625  
  1626  	c.Assert(chg.Err(), ErrorMatches, "(?s)cannot perform the following tasks.* provoking total undo.*")
  1627  	c.Check(chg.IsReady(), Equals, true)
  1628  	c.Assert(tskCreate.Status(), Equals, state.UndoneStatus)
  1629  	c.Assert(tskFinalize.Status(), Equals, state.UndoneStatus)
  1630  
  1631  	var triedSystemsAfter []string
  1632  	err = s.state.Get("tried-systems", &triedSystemsAfter)
  1633  	c.Assert(err, Equals, state.ErrNoState)
  1634  
  1635  	modeenvAfterFinalize, err := boot.ReadModeenv("")
  1636  	c.Assert(err, IsNil)
  1637  	c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{
  1638  		Mode:                   "run",
  1639  		Base:                   "core20_3.snap",
  1640  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1641  		CurrentRecoverySystems: []string{"othersystem"},
  1642  		GoodRecoverySystems:    []string{"othersystem"},
  1643  
  1644  		Model:          s.model.Model(),
  1645  		BrandID:        s.model.BrandID(),
  1646  		Grade:          string(s.model.Grade()),
  1647  		ModelSignKeyID: s.model.SignKeyID(),
  1648  	})
  1649  	// no more calls to the bootloader
  1650  	c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
  1651  	// system directory was removed
  1652  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234undo"), testutil.FileAbsent)
  1653  	// only the canary files are left now
  1654  	p, err = filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*"))
  1655  	c.Assert(err, IsNil)
  1656  	c.Check(p, DeepEquals, []string{
  1657  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"),
  1658  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"),
  1659  	})
  1660  }
  1661  
  1662  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemFinalizeErrsWhenSystemFailed(c *C) {
  1663  	devicestate.SetBootOkRan(s.mgr, true)
  1664  
  1665  	s.state.Lock()
  1666  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234")
  1667  	c.Assert(err, IsNil)
  1668  	c.Assert(chg, NotNil)
  1669  	tsks := chg.Tasks()
  1670  	c.Check(tsks, HasLen, 2)
  1671  	tskCreate := tsks[0]
  1672  	tskFinalize := tsks[1]
  1673  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1674  	terr.WaitFor(tskFinalize)
  1675  	chg.AddTask(terr)
  1676  
  1677  	s.mockStandardSnapsModeenvAndBootloaderState(c)
  1678  
  1679  	s.state.Unlock()
  1680  	s.settle(c)
  1681  	s.state.Lock()
  1682  
  1683  	c.Assert(chg.Err(), IsNil)
  1684  	c.Assert(tskCreate.Status(), Equals, state.DoneStatus)
  1685  	c.Assert(tskFinalize.Status(), Equals, state.DoingStatus)
  1686  	// a reboot is expected
  1687  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  1688  
  1689  	validateCore20Seed(c, "1234", s.model, s.storeSigning.Trusted)
  1690  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1691  	c.Assert(err, IsNil)
  1692  	c.Check(m, DeepEquals, map[string]string{
  1693  		"try_recovery_system":    "1234",
  1694  		"recovery_system_status": "try",
  1695  	})
  1696  	modeenvAfterCreate, err := boot.ReadModeenv("")
  1697  	c.Assert(err, IsNil)
  1698  	c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{
  1699  		Mode:                   "run",
  1700  		Base:                   "core20_3.snap",
  1701  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1702  		CurrentRecoverySystems: []string{"othersystem", "1234"},
  1703  		GoodRecoverySystems:    []string{"othersystem"},
  1704  
  1705  		Model:          s.model.Model(),
  1706  		BrandID:        s.model.BrandID(),
  1707  		Grade:          string(s.model.Grade()),
  1708  		ModelSignKeyID: s.model.SignKeyID(),
  1709  	})
  1710  
  1711  	// these things happen on snapd startup
  1712  	state.MockRestarting(s.state, state.RestartUnset)
  1713  	// after reboot the relevant startup code identified that the tried
  1714  	// system failed to operate properly
  1715  	s.state.Set("tried-systems", []string{})
  1716  	s.bootloader.SetBootVars(map[string]string{
  1717  		"try_recovery_system":    "",
  1718  		"recovery_system_status": "",
  1719  	})
  1720  	s.bootloader.SetBootVarsCalls = 0
  1721  
  1722  	s.state.Unlock()
  1723  	s.settle(c)
  1724  	s.state.Lock()
  1725  	defer s.state.Unlock()
  1726  
  1727  	c.Assert(chg.Err(), ErrorMatches, `(?s)cannot perform the following tasks.* Finalize recovery system with label "1234" \(cannot promote recovery system "1234": system has not been successfully tried\)`)
  1728  	c.Check(chg.IsReady(), Equals, true)
  1729  	c.Assert(tskCreate.Status(), Equals, state.UndoneStatus)
  1730  	c.Assert(tskFinalize.Status(), Equals, state.ErrorStatus)
  1731  
  1732  	var triedSystemsAfter []string
  1733  	err = s.state.Get("tried-systems", &triedSystemsAfter)
  1734  	c.Assert(err, IsNil)
  1735  	c.Assert(triedSystemsAfter, HasLen, 0)
  1736  
  1737  	modeenvAfterFinalize, err := boot.ReadModeenv("")
  1738  	c.Assert(err, IsNil)
  1739  	c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{
  1740  		Mode:                   "run",
  1741  		Base:                   "core20_3.snap",
  1742  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1743  		CurrentRecoverySystems: []string{"othersystem"},
  1744  		GoodRecoverySystems:    []string{"othersystem"},
  1745  
  1746  		Model:          s.model.Model(),
  1747  		BrandID:        s.model.BrandID(),
  1748  		Grade:          string(s.model.Grade()),
  1749  		ModelSignKeyID: s.model.SignKeyID(),
  1750  	})
  1751  	// no more calls to the bootloader
  1752  	c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
  1753  	// seed directory was removed
  1754  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"), testutil.FileAbsent)
  1755  	// all common snaps were cleaned up
  1756  	p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*"))
  1757  	c.Assert(err, IsNil)
  1758  	c.Check(p, HasLen, 0)
  1759  }
  1760  
  1761  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemErrCleanup(c *C) {
  1762  	devicestate.SetBootOkRan(s.mgr, true)
  1763  
  1764  	s.state.Lock()
  1765  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234error")
  1766  	c.Assert(err, IsNil)
  1767  	c.Assert(chg, NotNil)
  1768  	tsks := chg.Tasks()
  1769  	c.Check(tsks, HasLen, 2)
  1770  	tskCreate := tsks[0]
  1771  	tskFinalize := tsks[1]
  1772  
  1773  	s.mockStandardSnapsModeenvAndBootloaderState(c)
  1774  	s.bootloader.SetBootVarsCalls = 0
  1775  
  1776  	s.bootloader.SetErrFunc = func() error {
  1777  		c.Logf("boot calls: %v", s.bootloader.SetBootVarsCalls)
  1778  		// for simplicity error out only when we try to set the recovery
  1779  		// system variables in bootenv (and not in the cleanup path)
  1780  		if s.bootloader.SetBootVarsCalls == 1 {
  1781  			return fmt.Errorf("mock bootloader error")
  1782  		}
  1783  		return nil
  1784  	}
  1785  
  1786  	snaptest.PopulateDir(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps"), [][]string{
  1787  		{"core20_10.snap", "canary"},
  1788  		{"some-snap_1.snap", "canary"},
  1789  	})
  1790  
  1791  	s.state.Unlock()
  1792  	s.settle(c)
  1793  	s.state.Lock()
  1794  	defer s.state.Unlock()
  1795  
  1796  	c.Assert(chg.Err(), ErrorMatches, `(?s)cannot perform the following tasks.* \(cannot attempt booting into recovery system "1234error": mock bootloader error\)`)
  1797  	c.Check(chg.IsReady(), Equals, true)
  1798  	c.Assert(tskCreate.Status(), Equals, state.ErrorStatus)
  1799  	c.Assert(tskFinalize.Status(), Equals, state.HoldStatus)
  1800  
  1801  	c.Check(s.restartRequests, HasLen, 0)
  1802  	// sanity check asserted snaps location
  1803  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234error"), testutil.FileAbsent)
  1804  	p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*"))
  1805  	c.Assert(err, IsNil)
  1806  	c.Check(p, DeepEquals, []string{
  1807  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"),
  1808  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"),
  1809  	})
  1810  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1811  	c.Assert(err, IsNil)
  1812  	c.Check(m, DeepEquals, map[string]string{
  1813  		"try_recovery_system":    "",
  1814  		"recovery_system_status": "",
  1815  	})
  1816  	modeenvAfterCreate, err := boot.ReadModeenv("")
  1817  	c.Assert(err, IsNil)
  1818  	c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{
  1819  		Mode:                   "run",
  1820  		Base:                   "core20_3.snap",
  1821  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1822  		CurrentRecoverySystems: []string{"othersystem"},
  1823  		GoodRecoverySystems:    []string{"othersystem"},
  1824  
  1825  		Model:          s.model.Model(),
  1826  		BrandID:        s.model.BrandID(),
  1827  		Grade:          string(s.model.Grade()),
  1828  		ModelSignKeyID: s.model.SignKeyID(),
  1829  	})
  1830  }
  1831  
  1832  func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemReboot(c *C) {
  1833  	devicestate.SetBootOkRan(s.mgr, true)
  1834  
  1835  	s.state.Lock()
  1836  	chg, err := devicestate.CreateRecoverySystem(s.state, "1234reboot")
  1837  	c.Assert(err, IsNil)
  1838  	c.Assert(chg, NotNil)
  1839  	tsks := chg.Tasks()
  1840  	c.Check(tsks, HasLen, 2)
  1841  	tskCreate := tsks[0]
  1842  	tskFinalize := tsks[1]
  1843  
  1844  	s.mockStandardSnapsModeenvAndBootloaderState(c)
  1845  	s.bootloader.SetBootVarsCalls = 0
  1846  
  1847  	setBootVarsOk := true
  1848  	s.bootloader.SetErrFunc = func() error {
  1849  		c.Logf("boot calls: %v", s.bootloader.SetBootVarsCalls)
  1850  		if setBootVarsOk {
  1851  			return nil
  1852  		}
  1853  		return fmt.Errorf("unexpected call")
  1854  	}
  1855  
  1856  	snaptest.PopulateDir(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps"), [][]string{
  1857  		{"core20_10.snap", "canary"},
  1858  		{"some-snap_1.snap", "canary"},
  1859  	})
  1860  
  1861  	s.state.Unlock()
  1862  	s.settle(c)
  1863  	s.state.Lock()
  1864  
  1865  	// so far so good
  1866  	c.Assert(chg.Err(), IsNil)
  1867  	c.Assert(tskCreate.Status(), Equals, state.DoneStatus)
  1868  	c.Assert(tskFinalize.Status(), Equals, state.DoingStatus)
  1869  	// a reboot is expected
  1870  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  1871  	c.Check(s.bootloader.SetBootVarsCalls, Equals, 2)
  1872  	s.restartRequests = nil
  1873  
  1874  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234reboot"), testutil.FilePresent)
  1875  	// since we can't inject a panic into the task and recover from it in
  1876  	// the tests, reset the task states to as state which we would have if
  1877  	// the system unexpectedly reboots before the task is marked as done
  1878  	tskCreate.SetStatus(state.DoStatus)
  1879  	tskFinalize.SetStatus(state.DoStatus)
  1880  	state.MockRestarting(s.state, state.RestartUnset)
  1881  	// we may have rebooted just before the task was marked as done, in
  1882  	// which case tried systems would be populated
  1883  	s.state.Set("tried-systems", []string{"1234undo"})
  1884  	s.bootloader.SetBootVars(map[string]string{
  1885  		"try_recovery_system":    "",
  1886  		"recovery_system_status": "",
  1887  	})
  1888  	setBootVarsOk = false
  1889  
  1890  	s.state.Unlock()
  1891  	s.settle(c)
  1892  	s.state.Lock()
  1893  	defer s.state.Unlock()
  1894  
  1895  	c.Assert(chg.Err(), ErrorMatches, `(?s)cannot perform the following tasks.* \(cannot create a recovery system with label "1234reboot" for pc-20: system "1234reboot" already exists\)`)
  1896  	c.Assert(tskCreate.Status(), Equals, state.ErrorStatus)
  1897  	c.Assert(tskFinalize.Status(), Equals, state.HoldStatus)
  1898  	c.Check(s.restartRequests, HasLen, 0)
  1899  
  1900  	// recovery system was removed
  1901  	c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234reboot"), testutil.FileAbsent)
  1902  	// and so were the new snaps
  1903  	p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*"))
  1904  	c.Assert(err, IsNil)
  1905  	c.Check(p, DeepEquals, []string{
  1906  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"),
  1907  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"),
  1908  	})
  1909  	m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status")
  1910  	c.Assert(err, IsNil)
  1911  	c.Check(m, DeepEquals, map[string]string{
  1912  		"try_recovery_system":    "",
  1913  		"recovery_system_status": "",
  1914  	})
  1915  	modeenvAfterCreate, err := boot.ReadModeenv("")
  1916  	c.Assert(err, IsNil)
  1917  	c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{
  1918  		Mode:                   "run",
  1919  		Base:                   "core20_3.snap",
  1920  		CurrentKernels:         []string{"pc-kernel_2.snap"},
  1921  		CurrentRecoverySystems: []string{"othersystem"},
  1922  		GoodRecoverySystems:    []string{"othersystem"},
  1923  
  1924  		Model:          s.model.Model(),
  1925  		BrandID:        s.model.BrandID(),
  1926  		Grade:          string(s.model.Grade()),
  1927  		ModelSignKeyID: s.model.SignKeyID(),
  1928  	})
  1929  	var triedSystems []string
  1930  	s.state.Get("tried-systems", &triedSystems)
  1931  	c.Check(triedSystems, HasLen, 0)
  1932  }
  1933  
  1934  type systemSnapTrackingSuite struct {
  1935  	deviceMgrSystemsBaseSuite
  1936  }
  1937  
  1938  var _ = Suite(&systemSnapTrackingSuite{})
  1939  
  1940  func (s *systemSnapTrackingSuite) TestSnapFileTracking(c *C) {
  1941  	otherDir := c.MkDir()
  1942  	systemDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")
  1943  	flog := filepath.Join(otherDir, "files-log")
  1944  
  1945  	snaptest.PopulateDir(systemDir, [][]string{
  1946  		{"this-will-be-removed", "canary"},
  1947  		{"this-one-too", "canary"},
  1948  		{"this-one-stays", "canary"},
  1949  		{"snaps/to-be-removed", "canary"},
  1950  		{"snaps/this-one-stays", "canary"},
  1951  	})
  1952  
  1953  	// complain loudly if the file is under unexpected location
  1954  	err := devicestate.LogNewSystemSnapFile(flog, filepath.Join(otherDir, "some-file"))
  1955  	c.Assert(err, ErrorMatches, `internal error: unexpected recovery system snap location ".*/some-file"`)
  1956  	c.Check(flog, testutil.FileAbsent)
  1957  
  1958  	expectedContent := &bytes.Buffer{}
  1959  
  1960  	for _, p := range []string{
  1961  		filepath.Join(systemDir, "this-will-be-removed"),
  1962  		filepath.Join(systemDir, "this-one-too"),
  1963  		filepath.Join(systemDir, "does-not-exist"),
  1964  		filepath.Join(systemDir, "snaps/to-be-removed"),
  1965  	} {
  1966  		err = devicestate.LogNewSystemSnapFile(flog, p)
  1967  		c.Check(err, IsNil)
  1968  		fmt.Fprintln(expectedContent, p)
  1969  		// logged content is accumulated
  1970  		c.Check(flog, testutil.FileEquals, expectedContent.String())
  1971  	}
  1972  
  1973  	// add some empty spaces to log file, which should get ignored when purging
  1974  	f, err := os.OpenFile(flog, os.O_APPEND, 0644)
  1975  	c.Assert(err, IsNil)
  1976  	defer f.Close()
  1977  	fmt.Fprintln(f, "    ")
  1978  	fmt.Fprintln(f, "")
  1979  	// and double some entries
  1980  	fmt.Fprintln(f, filepath.Join(systemDir, "this-will-be-removed"))
  1981  
  1982  	err = devicestate.PurgeNewSystemSnapFiles(flog)
  1983  	c.Assert(err, IsNil)
  1984  
  1985  	// those are removed
  1986  	for _, p := range []string{
  1987  		filepath.Join(systemDir, "this-will-be-removed"),
  1988  		filepath.Join(systemDir, "this-one-too"),
  1989  		filepath.Join(systemDir, "snaps/to-be-removed"),
  1990  	} {
  1991  		c.Check(p, testutil.FileAbsent)
  1992  	}
  1993  	c.Check(filepath.Join(systemDir, "this-one-stays"), testutil.FileEquals, "canary")
  1994  	c.Check(filepath.Join(systemDir, "snaps/this-one-stays"), testutil.FileEquals, "canary")
  1995  }
  1996  
  1997  func (s *systemSnapTrackingSuite) TestSnapFilePurgeWhenNoLog(c *C) {
  1998  	otherDir := c.MkDir()
  1999  	flog := filepath.Join(otherDir, "files-log")
  2000  	// purge is still happy even if log file does not exist
  2001  	err := devicestate.PurgeNewSystemSnapFiles(flog)
  2002  	c.Assert(err, IsNil)
  2003  }