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