github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/asserts/assertstest"
    33  	"github.com/snapcore/snapd/boot"
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/logger"
    36  	"github.com/snapcore/snapd/overlord/auth"
    37  	"github.com/snapcore/snapd/overlord/devicestate"
    38  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    39  	"github.com/snapcore/snapd/overlord/state"
    40  	"github.com/snapcore/snapd/seed"
    41  	"github.com/snapcore/snapd/seed/seedtest"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/snap/snaptest"
    44  	"github.com/snapcore/snapd/strutil"
    45  )
    46  
    47  type mockedSystemSeed struct {
    48  	label string
    49  	model *asserts.Model
    50  	brand *asserts.Account
    51  }
    52  
    53  type deviceMgrSystemsSuite struct {
    54  	deviceMgrBaseSuite
    55  
    56  	logbuf            *bytes.Buffer
    57  	mockedSystemSeeds []mockedSystemSeed
    58  }
    59  
    60  var _ = Suite(&deviceMgrSystemsSuite{})
    61  
    62  func (s *deviceMgrSystemsSuite) SetUpTest(c *C) {
    63  	s.deviceMgrBaseSuite.SetUpTest(c)
    64  
    65  	s.brands.Register("other-brand", brandPrivKey3, map[string]interface{}{
    66  		"display-name": "other publisher",
    67  	})
    68  	s.state.Lock()
    69  	defer s.state.Unlock()
    70  	s.makeModelAssertionInState(c, "canonical", "pc-20", map[string]interface{}{
    71  		"architecture": "amd64",
    72  		// UC20
    73  		"grade": "dangerous",
    74  		"base":  "core20",
    75  		"snaps": []interface{}{
    76  			map[string]interface{}{
    77  				"name":            "pc-kernel",
    78  				"id":              snaptest.AssertedSnapID("oc-kernel"),
    79  				"type":            "kernel",
    80  				"default-channel": "20",
    81  			},
    82  			map[string]interface{}{
    83  				"name":            "pc",
    84  				"id":              snaptest.AssertedSnapID("pc"),
    85  				"type":            "gadget",
    86  				"default-channel": "20",
    87  			},
    88  		},
    89  	})
    90  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
    91  		Brand:  "canonical",
    92  		Model:  "pc-20",
    93  		Serial: "serialserialserial",
    94  	})
    95  	assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("my-brand")...)
    96  	assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("other-brand")...)
    97  
    98  	// now create a minimal uc20 seed dir with snaps/assertions
    99  	seed20 := &seedtest.TestingSeed20{
   100  		SeedSnaps: seedtest.SeedSnaps{
   101  			StoreSigning: s.storeSigning,
   102  			Brands:       s.brands,
   103  		},
   104  
   105  		SeedDir: dirs.SnapSeedDir,
   106  	}
   107  
   108  	restore := seed.MockTrusted(s.storeSigning.Trusted)
   109  	s.AddCleanup(restore)
   110  
   111  	myBrandAcc := s.brands.Account("my-brand")
   112  	otherBrandAcc := s.brands.Account("other-brand")
   113  
   114  	// add essential snaps
   115  	seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   116  	seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   117  	seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   118  	seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   119  
   120  	model1 := seed20.MakeSeed(c, "20191119", "my-brand", "my-model", map[string]interface{}{
   121  		"display-name": "my fancy model",
   122  		"architecture": "amd64",
   123  		"base":         "core20",
   124  		"snaps": []interface{}{
   125  			map[string]interface{}{
   126  				"name":            "pc-kernel",
   127  				"id":              seed20.AssertedSnapID("pc-kernel"),
   128  				"type":            "kernel",
   129  				"default-channel": "20",
   130  			},
   131  			map[string]interface{}{
   132  				"name":            "pc",
   133  				"id":              seed20.AssertedSnapID("pc"),
   134  				"type":            "gadget",
   135  				"default-channel": "20",
   136  			}},
   137  	}, nil)
   138  	model2 := seed20.MakeSeed(c, "20200318", "my-brand", "my-model-2", map[string]interface{}{
   139  		"display-name": "same brand different model",
   140  		"architecture": "amd64",
   141  		"base":         "core20",
   142  		"snaps": []interface{}{
   143  			map[string]interface{}{
   144  				"name":            "pc-kernel",
   145  				"id":              seed20.AssertedSnapID("pc-kernel"),
   146  				"type":            "kernel",
   147  				"default-channel": "20",
   148  			},
   149  			map[string]interface{}{
   150  				"name":            "pc",
   151  				"id":              seed20.AssertedSnapID("pc"),
   152  				"type":            "gadget",
   153  				"default-channel": "20",
   154  			}},
   155  	}, nil)
   156  	model3 := seed20.MakeSeed(c, "other-20200318", "other-brand", "other-model", map[string]interface{}{
   157  		"display-name": "different brand different model",
   158  		"architecture": "amd64",
   159  		"base":         "core20",
   160  		"snaps": []interface{}{
   161  			map[string]interface{}{
   162  				"name":            "pc-kernel",
   163  				"id":              seed20.AssertedSnapID("pc-kernel"),
   164  				"type":            "kernel",
   165  				"default-channel": "20",
   166  			},
   167  			map[string]interface{}{
   168  				"name":            "pc",
   169  				"id":              seed20.AssertedSnapID("pc"),
   170  				"type":            "gadget",
   171  				"default-channel": "20",
   172  			}},
   173  	}, nil)
   174  
   175  	s.mockedSystemSeeds = []mockedSystemSeed{{
   176  		label: "20191119",
   177  		model: model1,
   178  		brand: myBrandAcc,
   179  	}, {
   180  		label: "20200318",
   181  		model: model2,
   182  		brand: myBrandAcc,
   183  	}, {
   184  		label: "other-20200318",
   185  		model: model3,
   186  		brand: otherBrandAcc,
   187  	}}
   188  
   189  	// all tests should be in run mode by default, if they need to be in
   190  	// different modes they should set that individually
   191  	devicestate.SetSystemMode(s.mgr, "run")
   192  
   193  	// state after mark-seeded ran
   194  	modeenv := boot.Modeenv{
   195  		Mode:           "run",
   196  		RecoverySystem: "",
   197  	}
   198  	err := modeenv.WriteTo("")
   199  	s.state.Set("seeded", true)
   200  
   201  	c.Assert(err, IsNil)
   202  
   203  	logbuf, restore := logger.MockLogger()
   204  	s.logbuf = logbuf
   205  	s.AddCleanup(restore)
   206  }
   207  
   208  func (s *deviceMgrSystemsSuite) TestListNoSystems(c *C) {
   209  	dirs.SetRootDir(c.MkDir())
   210  
   211  	systems, err := s.mgr.Systems()
   212  	c.Assert(err, Equals, devicestate.ErrNoSystems)
   213  	c.Assert(systems, HasLen, 0)
   214  
   215  	err = os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "systems"), 0755)
   216  	c.Assert(err, IsNil)
   217  
   218  	systems, err = s.mgr.Systems()
   219  	c.Assert(err, Equals, devicestate.ErrNoSystems)
   220  	c.Assert(systems, HasLen, 0)
   221  }
   222  
   223  func (s *deviceMgrSystemsSuite) TestListSystemsNotPossible(c *C) {
   224  	if os.Geteuid() == 0 {
   225  		c.Skip("this test cannot run as root")
   226  	}
   227  	err := os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0000)
   228  	c.Assert(err, IsNil)
   229  	defer os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0755)
   230  
   231  	// stdlib swallows up the errors when opening the target directory
   232  	systems, err := s.mgr.Systems()
   233  	c.Assert(err, Equals, devicestate.ErrNoSystems)
   234  	c.Assert(systems, HasLen, 0)
   235  }
   236  
   237  // TODO:UC20 update once we can list actions
   238  var defaultSystemActions []devicestate.SystemAction = []devicestate.SystemAction{
   239  	{Title: "Install", Mode: "install"},
   240  }
   241  var currentSystemActions []devicestate.SystemAction = []devicestate.SystemAction{
   242  	{Title: "Reinstall", Mode: "install"},
   243  	{Title: "Recover", Mode: "recover"},
   244  	{Title: "Run normally", Mode: "run"},
   245  }
   246  
   247  func (s *deviceMgrSystemsSuite) TestListSeedSystemsNoCurrent(c *C) {
   248  	systems, err := s.mgr.Systems()
   249  	c.Assert(err, IsNil)
   250  	c.Assert(systems, HasLen, 3)
   251  	c.Check(systems, DeepEquals, []*devicestate.System{{
   252  		Current: false,
   253  		Label:   s.mockedSystemSeeds[0].label,
   254  		Model:   s.mockedSystemSeeds[0].model,
   255  		Brand:   s.mockedSystemSeeds[0].brand,
   256  		Actions: defaultSystemActions,
   257  	}, {
   258  		Current: false,
   259  		Label:   s.mockedSystemSeeds[1].label,
   260  		Model:   s.mockedSystemSeeds[1].model,
   261  		Brand:   s.mockedSystemSeeds[1].brand,
   262  		Actions: defaultSystemActions,
   263  	}, {
   264  		Current: false,
   265  		Label:   s.mockedSystemSeeds[2].label,
   266  		Model:   s.mockedSystemSeeds[2].model,
   267  		Brand:   s.mockedSystemSeeds[2].brand,
   268  		Actions: defaultSystemActions,
   269  	}})
   270  }
   271  
   272  func (s *deviceMgrSystemsSuite) TestListSeedSystemsCurrent(c *C) {
   273  	s.state.Lock()
   274  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   275  		{
   276  			System:  s.mockedSystemSeeds[1].label,
   277  			Model:   s.mockedSystemSeeds[1].model.Model(),
   278  			BrandID: s.mockedSystemSeeds[1].brand.AccountID(),
   279  		},
   280  	})
   281  	s.state.Unlock()
   282  
   283  	systems, err := s.mgr.Systems()
   284  	c.Assert(err, IsNil)
   285  	c.Assert(systems, HasLen, 3)
   286  	c.Check(systems, DeepEquals, []*devicestate.System{{
   287  		Current: false,
   288  		Label:   s.mockedSystemSeeds[0].label,
   289  		Model:   s.mockedSystemSeeds[0].model,
   290  		Brand:   s.mockedSystemSeeds[0].brand,
   291  		Actions: defaultSystemActions,
   292  	}, {
   293  		// this seed was used for installing the running system
   294  		Current: true,
   295  		Label:   s.mockedSystemSeeds[1].label,
   296  		Model:   s.mockedSystemSeeds[1].model,
   297  		Brand:   s.mockedSystemSeeds[1].brand,
   298  		Actions: currentSystemActions,
   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) TestBrokenSeedSystems(c *C) {
   309  	// break the first seed
   310  	err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model"))
   311  	c.Assert(err, IsNil)
   312  
   313  	systems, err := s.mgr.Systems()
   314  	c.Assert(err, IsNil)
   315  	c.Assert(systems, HasLen, 2)
   316  	c.Check(systems, DeepEquals, []*devicestate.System{{
   317  		Current: false,
   318  		Label:   s.mockedSystemSeeds[1].label,
   319  		Model:   s.mockedSystemSeeds[1].model,
   320  		Brand:   s.mockedSystemSeeds[1].brand,
   321  		Actions: defaultSystemActions,
   322  	}, {
   323  		Current: false,
   324  		Label:   s.mockedSystemSeeds[2].label,
   325  		Model:   s.mockedSystemSeeds[2].model,
   326  		Brand:   s.mockedSystemSeeds[2].brand,
   327  		Actions: defaultSystemActions,
   328  	}})
   329  }
   330  
   331  func (s *deviceMgrSystemsSuite) TestRequestModeInstallHappyForAny(c *C) {
   332  	// no current system
   333  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install", Title: "Install"})
   334  	c.Assert(err, IsNil)
   335  
   336  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   337  	c.Assert(err, IsNil)
   338  	c.Check(m, DeepEquals, map[string]string{
   339  		"snapd_recovery_system": "20191119",
   340  		"snapd_recovery_mode":   "install",
   341  	})
   342  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   343  	c.Check(s.logbuf.String(), Matches, `.*: restarting into system "20191119" for action "Install"\n`)
   344  }
   345  
   346  func (s *deviceMgrSystemsSuite) TestRequestSameModeSameSystem(c *C) {
   347  	s.state.Lock()
   348  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   349  		{
   350  			System:  s.mockedSystemSeeds[0].label,
   351  			Model:   s.mockedSystemSeeds[0].model.Model(),
   352  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   353  		},
   354  	})
   355  	s.state.Unlock()
   356  
   357  	label := s.mockedSystemSeeds[0].label
   358  
   359  	happyModes := []string{"run"}
   360  	sadModes := []string{"install", "recover"}
   361  
   362  	for _, mode := range append(happyModes, sadModes...) {
   363  		s.logbuf.Reset()
   364  
   365  		c.Logf("checking mode: %q", mode)
   366  		// non run modes use modeenv
   367  		modeenv := boot.Modeenv{
   368  			Mode: mode,
   369  		}
   370  		if mode != "run" {
   371  			modeenv.RecoverySystem = s.mockedSystemSeeds[0].label
   372  		}
   373  		err := modeenv.WriteTo("")
   374  		c.Assert(err, IsNil)
   375  
   376  		devicestate.SetSystemMode(s.mgr, mode)
   377  		err = s.bootloader.SetBootVars(map[string]string{
   378  			"snapd_recovery_mode":   mode,
   379  			"snapd_recovery_system": label,
   380  		})
   381  		c.Assert(err, IsNil)
   382  		err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode})
   383  		if strutil.ListContains(sadModes, mode) {
   384  			c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   385  		} else {
   386  			c.Assert(err, IsNil)
   387  		}
   388  		// bootloader vars shouldn't change
   389  		m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   390  		c.Assert(err, IsNil)
   391  		c.Check(m, DeepEquals, map[string]string{
   392  			"snapd_recovery_mode":   mode,
   393  			"snapd_recovery_system": label,
   394  		})
   395  		// should never restart
   396  		c.Check(s.restartRequests, HasLen, 0)
   397  		// no log output
   398  		c.Check(s.logbuf.String(), Equals, "")
   399  	}
   400  }
   401  
   402  func (s *deviceMgrSystemsSuite) TestRequestSeedingSameConflict(c *C) {
   403  	label := s.mockedSystemSeeds[0].label
   404  
   405  	devicestate.SetSystemMode(s.mgr, "run")
   406  
   407  	s.state.Lock()
   408  	s.state.Set("seeded", nil)
   409  	s.state.Unlock()
   410  
   411  	for _, mode := range []string{"run", "install", "recover"} {
   412  		s.logbuf.Reset()
   413  
   414  		c.Logf("checking mode: %q", mode)
   415  		modeenv := boot.Modeenv{
   416  			Mode:           mode,
   417  			RecoverySystem: s.mockedSystemSeeds[0].label,
   418  		}
   419  		err := modeenv.WriteTo("")
   420  		c.Assert(err, IsNil)
   421  
   422  		err = s.bootloader.SetBootVars(map[string]string{
   423  			"snapd_recovery_mode":   "",
   424  			"snapd_recovery_system": label,
   425  		})
   426  		c.Assert(err, IsNil)
   427  		err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode})
   428  		c.Assert(err, ErrorMatches, "cannot request system action, system is seeding")
   429  		// no log output
   430  		c.Check(s.logbuf.String(), Equals, "")
   431  	}
   432  }
   433  
   434  func (s *deviceMgrSystemsSuite) TestRequestSeedingDifferentNoConflict(c *C) {
   435  	label := s.mockedSystemSeeds[0].label
   436  	otherLabel := s.mockedSystemSeeds[1].label
   437  
   438  	devicestate.SetSystemMode(s.mgr, "run")
   439  
   440  	modeenv := boot.Modeenv{
   441  		Mode:           "run",
   442  		RecoverySystem: label,
   443  	}
   444  	err := modeenv.WriteTo("")
   445  	c.Assert(err, IsNil)
   446  
   447  	s.state.Lock()
   448  	s.state.Set("seeded", nil)
   449  	s.state.Unlock()
   450  
   451  	// we can only go to install mode of other system when one is currently
   452  	// being seeded
   453  	err = s.bootloader.SetBootVars(map[string]string{
   454  		"snapd_recovery_mode":   "",
   455  		"snapd_recovery_system": label,
   456  	})
   457  	c.Assert(err, IsNil)
   458  	err = s.mgr.RequestSystemAction(otherLabel, devicestate.SystemAction{Mode: "install"})
   459  	c.Assert(err, IsNil)
   460  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   461  	c.Assert(err, IsNil)
   462  	c.Check(m, DeepEquals, map[string]string{
   463  		"snapd_recovery_system": otherLabel,
   464  		"snapd_recovery_mode":   "install",
   465  	})
   466  	c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action "Install"\n`, otherLabel))
   467  }
   468  
   469  func (s *deviceMgrSystemsSuite) testRequestModeWithRestart(c *C, toModes []string, label string) {
   470  	for _, mode := range toModes {
   471  		c.Logf("checking mode: %q", mode)
   472  		err := s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode})
   473  		c.Assert(err, IsNil)
   474  		m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   475  		c.Assert(err, IsNil)
   476  		c.Check(m, DeepEquals, map[string]string{
   477  			"snapd_recovery_system": label,
   478  			"snapd_recovery_mode":   mode,
   479  		})
   480  		c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   481  		s.restartRequests = nil
   482  		s.bootloader.BootVars = map[string]string{}
   483  
   484  		// TODO: also test correct action string logging
   485  		c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action ".*"\n`, label))
   486  		s.logbuf.Reset()
   487  	}
   488  }
   489  
   490  func (s *deviceMgrSystemsSuite) TestRequestModeRunInstallForRecover(c *C) {
   491  	// we are in recover mode here
   492  	devicestate.SetSystemMode(s.mgr, "recover")
   493  	// non run modes use modeenv
   494  	modeenv := boot.Modeenv{
   495  		Mode:           "recover",
   496  		RecoverySystem: s.mockedSystemSeeds[0].label,
   497  	}
   498  	err := modeenv.WriteTo("")
   499  	c.Assert(err, IsNil)
   500  
   501  	s.state.Lock()
   502  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   503  		{
   504  			System:  s.mockedSystemSeeds[0].label,
   505  			Model:   s.mockedSystemSeeds[0].model.Model(),
   506  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   507  		},
   508  	})
   509  	s.state.Unlock()
   510  
   511  	s.testRequestModeWithRestart(c, []string{"install", "run"}, s.mockedSystemSeeds[0].label)
   512  }
   513  
   514  func (s *deviceMgrSystemsSuite) TestRequestModeInstallRecoverForCurrent(c *C) {
   515  	devicestate.SetSystemMode(s.mgr, "run")
   516  	// non run modes use modeenv
   517  	modeenv := boot.Modeenv{
   518  		Mode: "run",
   519  	}
   520  	err := modeenv.WriteTo("")
   521  	c.Assert(err, IsNil)
   522  
   523  	s.state.Lock()
   524  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   525  		{
   526  			System:  s.mockedSystemSeeds[0].label,
   527  			Model:   s.mockedSystemSeeds[0].model.Model(),
   528  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   529  		},
   530  	})
   531  	s.state.Unlock()
   532  
   533  	s.testRequestModeWithRestart(c, []string{"install", "recover"}, s.mockedSystemSeeds[0].label)
   534  }
   535  
   536  func (s *deviceMgrSystemsSuite) TestRequestModeErrInBoot(c *C) {
   537  	s.bootloader.SetErr = errors.New("no can do")
   538  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"})
   539  	c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": no can do`)
   540  	c.Check(s.restartRequests, HasLen, 0)
   541  	c.Check(s.logbuf.String(), Equals, "")
   542  }
   543  
   544  func (s *deviceMgrSystemsSuite) TestRequestModeNotFound(c *C) {
   545  	err := s.mgr.RequestSystemAction("not-found", devicestate.SystemAction{Mode: "install"})
   546  	c.Assert(err, NotNil)
   547  	c.Assert(os.IsNotExist(err), Equals, true)
   548  	c.Check(s.restartRequests, HasLen, 0)
   549  	c.Check(s.logbuf.String(), Equals, "")
   550  }
   551  
   552  func (s *deviceMgrSystemsSuite) TestRequestModeBadMode(c *C) {
   553  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "unknown-mode"})
   554  	c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   555  	c.Check(s.restartRequests, HasLen, 0)
   556  	c.Check(s.logbuf.String(), Equals, "")
   557  }
   558  
   559  func (s *deviceMgrSystemsSuite) TestRequestModeBroken(c *C) {
   560  	// break the first seed
   561  	err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model"))
   562  	c.Assert(err, IsNil)
   563  
   564  	err = s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"})
   565  	c.Assert(err, ErrorMatches, "cannot load seed system: cannot load assertions: .*")
   566  	c.Check(s.restartRequests, HasLen, 0)
   567  	c.Check(s.logbuf.String(), Equals, "")
   568  }
   569  
   570  func (s *deviceMgrSystemsSuite) TestRequestModeNonUC20(c *C) {
   571  	s.setPCModelInState(c)
   572  	err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"})
   573  	c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": system mode is unsupported`)
   574  	c.Check(s.restartRequests, HasLen, 0)
   575  	c.Check(s.logbuf.String(), Equals, "")
   576  }
   577  
   578  func (s *deviceMgrSystemsSuite) TestRequestActionNoLabel(c *C) {
   579  	err := s.mgr.RequestSystemAction("", devicestate.SystemAction{Mode: "install"})
   580  	c.Assert(err, ErrorMatches, "internal error: system label is unset")
   581  	c.Check(s.logbuf.String(), Equals, "")
   582  }
   583  
   584  func (s *deviceMgrSystemsSuite) TestRequestModeForNonCurrent(c *C) {
   585  	s.state.Lock()
   586  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   587  		{
   588  			System:  s.mockedSystemSeeds[0].label,
   589  			Model:   s.mockedSystemSeeds[0].model.Model(),
   590  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   591  		},
   592  	})
   593  
   594  	s.state.Unlock()
   595  	s.setPCModelInState(c)
   596  	// request mode reserved for current system
   597  	err := s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "run"})
   598  	c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   599  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "recover"})
   600  	c.Assert(err, Equals, devicestate.ErrUnsupportedAction)
   601  	c.Check(s.restartRequests, HasLen, 0)
   602  	c.Check(s.logbuf.String(), Equals, "")
   603  }
   604  
   605  func (s *deviceMgrSystemsSuite) TestRequestInstallForOther(c *C) {
   606  	devicestate.SetSystemMode(s.mgr, "run")
   607  	// non run modes use modeenv
   608  	modeenv := boot.Modeenv{
   609  		Mode: "run",
   610  	}
   611  	err := modeenv.WriteTo("")
   612  	c.Assert(err, IsNil)
   613  
   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  	s.state.Unlock()
   623  	// reinstall from different system seed is ok
   624  	s.testRequestModeWithRestart(c, []string{"install"}, s.mockedSystemSeeds[1].label)
   625  }
   626  
   627  func (s *deviceMgrSystemsSuite) TestRequestAction1618(c *C) {
   628  	s.setPCModelInState(c)
   629  	// system mode is unset in 16/18
   630  	devicestate.SetSystemMode(s.mgr, "")
   631  	// no modeenv either
   632  	err := os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir))
   633  	c.Assert(err, IsNil)
   634  
   635  	s.state.Lock()
   636  	s.state.Set("seeded-systems", nil)
   637  	s.state.Set("seeded", nil)
   638  	s.state.Unlock()
   639  	// a label exists
   640  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"})
   641  	c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported")
   642  
   643  	s.state.Lock()
   644  	s.state.Set("seeded", true)
   645  	s.state.Unlock()
   646  
   647  	// even with system mode explicitly set, the action is not executed
   648  	devicestate.SetSystemMode(s.mgr, "run")
   649  
   650  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"})
   651  	c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported")
   652  
   653  	devicestate.SetSystemMode(s.mgr, "")
   654  	// also no UC20 style system seeds
   655  	for _, m := range s.mockedSystemSeeds {
   656  		os.RemoveAll(filepath.Join(dirs.SnapSeedDir, "systems", m.label))
   657  	}
   658  
   659  	err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"})
   660  	c.Assert(err, ErrorMatches, ".*/seed/systems/20191119: no such file or directory")
   661  	c.Check(s.logbuf.String(), Equals, "")
   662  }
   663  
   664  func (s *deviceMgrSystemsSuite) TestRebootNoLabelNoModeHappy(c *C) {
   665  	err := s.mgr.Reboot("", "")
   666  	c.Assert(err, IsNil)
   667  
   668  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   669  	c.Assert(err, IsNil)
   670  	// requested restart
   671  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   672  	// but no bootloader changes
   673  	c.Check(m, DeepEquals, map[string]string{
   674  		"snapd_recovery_system": "",
   675  		"snapd_recovery_mode":   "",
   676  	})
   677  	c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`)
   678  }
   679  
   680  func (s *deviceMgrSystemsSuite) TestRebootLabelAndModeHappy(c *C) {
   681  	s.state.Lock()
   682  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   683  		{
   684  			System:  s.mockedSystemSeeds[0].label,
   685  			Model:   s.mockedSystemSeeds[0].model.Model(),
   686  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   687  		},
   688  	})
   689  	s.state.Unlock()
   690  
   691  	err := s.mgr.Reboot("20191119", "install")
   692  	c.Assert(err, IsNil)
   693  
   694  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   695  	c.Assert(err, IsNil)
   696  	c.Check(m, DeepEquals, map[string]string{
   697  		"snapd_recovery_system": "20191119",
   698  		"snapd_recovery_mode":   "install",
   699  	})
   700  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   701  	c.Check(s.logbuf.String(), Matches, `.*: rebooting into system "20191119" in "install" mode\n`)
   702  }
   703  
   704  func (s *deviceMgrSystemsSuite) TestRebootModeOnlyHappy(c *C) {
   705  	s.state.Lock()
   706  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   707  		{
   708  			System:  s.mockedSystemSeeds[0].label,
   709  			Model:   s.mockedSystemSeeds[0].model.Model(),
   710  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   711  		},
   712  	})
   713  	s.state.Unlock()
   714  
   715  	for _, mode := range []string{"recover", "install"} {
   716  		s.restartRequests = nil
   717  		s.bootloader.BootVars = make(map[string]string)
   718  		s.logbuf.Reset()
   719  
   720  		err := s.mgr.Reboot("", mode)
   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": s.mockedSystemSeeds[0].label,
   727  			"snapd_recovery_mode":   mode,
   728  		})
   729  		c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   730  		c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "20191119" in "%s" mode\n`, mode))
   731  	}
   732  }
   733  
   734  func (s *deviceMgrSystemsSuite) TestRebootFromRecoverToRun(c *C) {
   735  	modeenv := boot.Modeenv{
   736  		Mode:           "recover",
   737  		RecoverySystem: s.mockedSystemSeeds[0].label,
   738  	}
   739  	err := modeenv.WriteTo("")
   740  	c.Assert(err, IsNil)
   741  
   742  	devicestate.SetSystemMode(s.mgr, "recover")
   743  	err = s.bootloader.SetBootVars(map[string]string{
   744  		"snapd_recovery_mode":   "recover",
   745  		"snapd_recovery_system": s.mockedSystemSeeds[0].label,
   746  	})
   747  	c.Assert(err, IsNil)
   748  
   749  	s.state.Lock()
   750  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   751  		{
   752  			System:  s.mockedSystemSeeds[0].label,
   753  			Model:   s.mockedSystemSeeds[0].model.Model(),
   754  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   755  		},
   756  	})
   757  	s.state.Unlock()
   758  
   759  	err = s.mgr.Reboot("", "run")
   760  	c.Assert(err, IsNil)
   761  
   762  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   763  	c.Assert(err, IsNil)
   764  	c.Check(m, DeepEquals, map[string]string{
   765  		"snapd_recovery_mode":   "run",
   766  		"snapd_recovery_system": s.mockedSystemSeeds[0].label,
   767  	})
   768  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   769  	c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "%s" in "run" mode\n`, s.mockedSystemSeeds[0].label))
   770  }
   771  
   772  func (s *deviceMgrSystemsSuite) TestRebootAlreadyInRunMode(c *C) {
   773  	devicestate.SetSystemMode(s.mgr, "run")
   774  
   775  	s.state.Lock()
   776  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   777  		{
   778  			System:  s.mockedSystemSeeds[0].label,
   779  			Model:   s.mockedSystemSeeds[0].model.Model(),
   780  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   781  		},
   782  	})
   783  	s.state.Unlock()
   784  
   785  	// we are already in "run" mode so this should just reboot
   786  	err := s.mgr.Reboot("", "run")
   787  	c.Assert(err, IsNil)
   788  
   789  	m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
   790  	c.Assert(err, IsNil)
   791  	c.Check(m, DeepEquals, map[string]string{
   792  		"snapd_recovery_mode":   "",
   793  		"snapd_recovery_system": "",
   794  	})
   795  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   796  	c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`)
   797  }
   798  
   799  func (s *deviceMgrSystemsSuite) TestRebootUnhappy(c *C) {
   800  	s.state.Lock()
   801  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
   802  		{
   803  			System:  s.mockedSystemSeeds[0].label,
   804  			Model:   s.mockedSystemSeeds[0].model.Model(),
   805  			BrandID: s.mockedSystemSeeds[0].brand.AccountID(),
   806  		},
   807  	})
   808  	s.state.Unlock()
   809  
   810  	errUnsupportedActionStr := devicestate.ErrUnsupportedAction.Error()
   811  	for _, tc := range []struct {
   812  		systemLabel, mode string
   813  		expectedErr       string
   814  	}{
   815  		{"", "unknown-mode", errUnsupportedActionStr},
   816  		{"unknown-system", "run", `stat /.*: no such file or directory`},
   817  		{"unknown-system", "unknown-mode", `stat /.*: no such file or directory`},
   818  	} {
   819  		s.restartRequests = nil
   820  		s.bootloader.BootVars = make(map[string]string)
   821  
   822  		err := s.mgr.Reboot(tc.systemLabel, tc.mode)
   823  		c.Assert(err, ErrorMatches, tc.expectedErr)
   824  
   825  		c.Check(s.restartRequests, HasLen, 0)
   826  		c.Check(s.logbuf.String(), Equals, "")
   827  	}
   828  	c.Check(s.logbuf.String(), Equals, "")
   829  }