github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/devicestate/devicestate_bootconfig_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package devicestate_test
    21  
    22  import (
    23  	"errors"
    24  
    25  	. "gopkg.in/check.v1"
    26  
    27  	"github.com/snapcore/snapd/asserts"
    28  	"github.com/snapcore/snapd/boot"
    29  	"github.com/snapcore/snapd/bootloader"
    30  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    31  	"github.com/snapcore/snapd/overlord/auth"
    32  	"github.com/snapcore/snapd/overlord/devicestate"
    33  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    34  	"github.com/snapcore/snapd/overlord/snapstate"
    35  	"github.com/snapcore/snapd/overlord/state"
    36  	"github.com/snapcore/snapd/release"
    37  	"github.com/snapcore/snapd/snap"
    38  	"github.com/snapcore/snapd/snap/snaptest"
    39  )
    40  
    41  type deviceMgrBootconfigSuite struct {
    42  	deviceMgrBaseSuite
    43  
    44  	managedbl      *bootloadertest.MockTrustedAssetsBootloader
    45  	gadgetSnapInfo *snap.Info
    46  }
    47  
    48  var _ = Suite(&deviceMgrBootconfigSuite{})
    49  
    50  func (s *deviceMgrBootconfigSuite) SetUpTest(c *C) {
    51  	s.deviceMgrBaseSuite.SetUpTest(c)
    52  
    53  	s.managedbl = bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets()
    54  	bootloader.Force(s.managedbl)
    55  	s.managedbl.StaticCommandLine = "console=ttyS0 console=tty1 panic=-1"
    56  	s.managedbl.CandidateStaticCommandLine = "console=ttyS0 console=tty1 panic=-1 candidate"
    57  
    58  	s.state.Lock()
    59  	defer s.state.Unlock()
    60  
    61  	devicestate.SetBootOkRan(s.mgr, true)
    62  	si := &snap.SideInfo{
    63  		RealName: "pc",
    64  		Revision: snap.R(33),
    65  		SnapID:   "foo-id",
    66  	}
    67  	snapstate.Set(s.state, "pc", &snapstate.SnapState{
    68  		SnapType: "gadget",
    69  		Sequence: []*snap.SideInfo{si},
    70  		Current:  si.Revision,
    71  		Active:   true,
    72  	})
    73  	s.state.Set("seeded", true)
    74  
    75  	s.gadgetSnapInfo = snaptest.MockSnapWithFiles(c, pcGadgetSnapYaml, si, [][]string{
    76  		{"meta/gadget.yaml", gadgetYaml},
    77  	})
    78  
    79  	// minimal mocking to reach the mocked bootloader API call
    80  	modeenv := boot.Modeenv{
    81  		Mode:           "run",
    82  		RecoverySystem: "",
    83  		CurrentKernelCommandLines: []string{
    84  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
    85  		},
    86  	}
    87  	err := modeenv.WriteTo("")
    88  	c.Assert(err, IsNil)
    89  }
    90  
    91  func (s *deviceMgrBootconfigSuite) setupUC20Model(c *C) *asserts.Model {
    92  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
    93  		Brand:  "canonical",
    94  		Model:  "pc-model-20",
    95  		Serial: "didididi",
    96  	})
    97  	return s.makeModelAssertionInState(c, "canonical", "pc-model-20", mockCore20ModelHeaders)
    98  }
    99  
   100  func (s *deviceMgrBootconfigSuite) testBootConfigUpdateRun(c *C, updateAttempted, applied bool, errMatch string) {
   101  	restore := release.MockOnClassic(false)
   102  	defer restore()
   103  
   104  	s.state.Lock()
   105  	tsk := s.state.NewTask("update-managed-boot-config", "update boot config")
   106  	chg := s.state.NewChange("dummy", "...")
   107  	chg.AddTask(tsk)
   108  	s.state.Unlock()
   109  
   110  	s.settle(c)
   111  
   112  	s.state.Lock()
   113  	defer s.state.Unlock()
   114  
   115  	c.Assert(chg.IsReady(), Equals, true)
   116  	if errMatch == "" {
   117  		c.Check(chg.Err(), IsNil)
   118  		c.Check(tsk.Status(), Equals, state.DoneStatus)
   119  	} else {
   120  		c.Check(chg.Err(), ErrorMatches, errMatch)
   121  		c.Check(tsk.Status(), Equals, state.ErrorStatus)
   122  	}
   123  	if updateAttempted {
   124  		c.Assert(s.managedbl.UpdateCalls, Equals, 1)
   125  		if errMatch == "" && applied {
   126  			// we log on success
   127  			log := tsk.Log()
   128  			c.Assert(log, HasLen, 1)
   129  			c.Check(log[0], Matches, ".* updated boot config assets")
   130  			// update was applied, thus a restart was requested
   131  			c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystem})
   132  		} else {
   133  			// update was not applied or failed
   134  			c.Check(s.restartRequests, HasLen, 0)
   135  		}
   136  	} else {
   137  		c.Assert(s.managedbl.UpdateCalls, Equals, 0)
   138  	}
   139  }
   140  
   141  func (s *deviceMgrBootconfigSuite) TestBootConfigUpdateRunSuccess(c *C) {
   142  	s.state.Lock()
   143  	s.setupUC20Model(c)
   144  	s.state.Unlock()
   145  
   146  	s.managedbl.Updated = true
   147  
   148  	updateAttempted := true
   149  	updateApplied := true
   150  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied, "")
   151  
   152  	m, err := boot.ReadModeenv("")
   153  	c.Assert(err, IsNil)
   154  	c.Check([]string(m.CurrentKernelCommandLines), DeepEquals, []string{
   155  		"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   156  		"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 candidate",
   157  	})
   158  }
   159  
   160  func (s *deviceMgrBootconfigSuite) TestBootConfigUpdateWithGadgetExtra(c *C) {
   161  	s.state.Lock()
   162  	s.setupUC20Model(c)
   163  	s.state.Unlock()
   164  
   165  	s.managedbl.Updated = true
   166  
   167  	// drop the file for gadget
   168  	snaptest.PopulateDir(s.gadgetSnapInfo.MountDir(), [][]string{
   169  		{"cmdline.extra", "args from gadget"},
   170  	})
   171  
   172  	// update the modeenv to have the gadget arguments included to mimic the
   173  	// state we would have in the system
   174  	m, err := boot.ReadModeenv("")
   175  	c.Assert(err, IsNil)
   176  	m.CurrentKernelCommandLines = []string{
   177  		"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 args from gadget",
   178  	}
   179  	c.Assert(m.Write(), IsNil)
   180  
   181  	updateAttempted := true
   182  	updateApplied := true
   183  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied, "")
   184  
   185  	m, err = boot.ReadModeenv("")
   186  	c.Assert(err, IsNil)
   187  	c.Check([]string(m.CurrentKernelCommandLines), DeepEquals, []string{
   188  		"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 args from gadget",
   189  		// gadget arguments are picked up for the candidate command line
   190  		"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 candidate args from gadget",
   191  	})
   192  }
   193  
   194  func (s *deviceMgrBootconfigSuite) TestBootConfigUpdateRunButNotUpdated(c *C) {
   195  	s.state.Lock()
   196  	s.setupUC20Model(c)
   197  	s.state.Unlock()
   198  
   199  	s.managedbl.Updated = false
   200  
   201  	updateAttempted := true
   202  	updateApplied := false
   203  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied, "")
   204  }
   205  
   206  func (s *deviceMgrBootconfigSuite) TestBootConfigUpdateUpdateErr(c *C) {
   207  	s.state.Lock()
   208  	s.setupUC20Model(c)
   209  	s.state.Unlock()
   210  
   211  	s.managedbl.UpdateErr = errors.New("update fail")
   212  	// actually tried to update
   213  	updateAttempted := true
   214  	updateApplied := false
   215  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied,
   216  		`(?ms).*cannot update boot config assets: update fail\)`)
   217  
   218  }
   219  
   220  func (s *deviceMgrBootconfigSuite) TestBootConfigNoUC20(c *C) {
   221  	s.state.Lock()
   222  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   223  		Brand:  "canonical",
   224  		Model:  "pc-model",
   225  		Serial: "didididi",
   226  	})
   227  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   228  		"architecture": "amd64",
   229  		"kernel":       "pc-kernel",
   230  		"gadget":       "pc",
   231  		"base":         "core18",
   232  	})
   233  	s.state.Unlock()
   234  
   235  	updateAttempted := false
   236  	updateApplied := false
   237  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied, "")
   238  }
   239  
   240  func (s *deviceMgrBootconfigSuite) TestBootConfigRemodelDoNothing(c *C) {
   241  	restore := release.MockOnClassic(false)
   242  	defer restore()
   243  
   244  	s.state.Lock()
   245  
   246  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   247  		Brand:  "canonical",
   248  		Model:  "pc-model-20",
   249  		Serial: "didididi",
   250  	})
   251  
   252  	uc20Model := s.setupUC20Model(c)
   253  	// save the hassle and try a trivial remodel
   254  	newModel := s.brands.Model("canonical", "pc-model-20", map[string]interface{}{
   255  		"brand":        "canonical",
   256  		"model":        "pc-model-20",
   257  		"architecture": "amd64",
   258  		"grade":        "dangerous",
   259  		"base":         "core20",
   260  		"snaps":        mockCore20ModelSnaps,
   261  	})
   262  	remodCtx, err := devicestate.RemodelCtx(s.state, uc20Model, newModel)
   263  	c.Assert(err, IsNil)
   264  	// be extra sure
   265  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   266  	tsk := s.state.NewTask("update-managed-boot-config", "update boot config")
   267  	chg := s.state.NewChange("dummy", "...")
   268  	chg.AddTask(tsk)
   269  	remodCtx.Init(chg)
   270  	// replace the bootloader with something that always fails
   271  	bootloader.ForceError(errors.New("unexpected call"))
   272  	s.state.Unlock()
   273  
   274  	s.settle(c)
   275  
   276  	s.state.Lock()
   277  	defer s.state.Unlock()
   278  
   279  	c.Assert(chg.IsReady(), Equals, true)
   280  	c.Check(chg.Err(), IsNil)
   281  	c.Check(tsk.Status(), Equals, state.DoneStatus)
   282  }