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