github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/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  )
    39  
    40  type deviceMgrBootconfigSuite struct {
    41  	deviceMgrBaseSuite
    42  
    43  	managedbl *bootloadertest.MockTrustedAssetsBootloader
    44  }
    45  
    46  var _ = Suite(&deviceMgrBootconfigSuite{})
    47  
    48  func (s *deviceMgrBootconfigSuite) SetUpTest(c *C) {
    49  	s.deviceMgrBaseSuite.SetUpTest(c)
    50  
    51  	s.managedbl = bootloadertest.Mock("mock", c.MkDir()).WithTrustedAssets()
    52  	bootloader.Force(s.managedbl)
    53  
    54  	s.state.Lock()
    55  	defer s.state.Unlock()
    56  
    57  	devicestate.SetBootOkRan(s.mgr, true)
    58  	si := &snap.SideInfo{
    59  		RealName: "pc",
    60  		Revision: snap.R(33),
    61  		SnapID:   "foo-id",
    62  	}
    63  	snapstate.Set(s.state, "pc", &snapstate.SnapState{
    64  		SnapType: "gadget",
    65  		Sequence: []*snap.SideInfo{si},
    66  		Current:  si.Revision,
    67  		Active:   true,
    68  	})
    69  	s.state.Set("seeded", true)
    70  
    71  	// minimal mocking to reach the mocked bootloader API call
    72  	modeenv := boot.Modeenv{
    73  		Mode:           "run",
    74  		RecoverySystem: "",
    75  		CurrentKernelCommandLines: []string{
    76  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
    77  		},
    78  	}
    79  	err := modeenv.WriteTo("")
    80  	c.Assert(err, IsNil)
    81  }
    82  
    83  func (s *deviceMgrBootconfigSuite) setupUC20Model(c *C) *asserts.Model {
    84  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
    85  		Brand:  "canonical",
    86  		Model:  "pc-model-20",
    87  		Serial: "didididi",
    88  	})
    89  	return s.makeModelAssertionInState(c, "canonical", "pc-model-20", mockCore20ModelHeaders)
    90  }
    91  
    92  func (s *deviceMgrBootconfigSuite) testBootConfigUpdateRun(c *C, updateAttempted, applied bool, errMatch string) {
    93  	restore := release.MockOnClassic(false)
    94  	defer restore()
    95  
    96  	s.state.Lock()
    97  	tsk := s.state.NewTask("update-managed-boot-config", "update boot config")
    98  	chg := s.state.NewChange("dummy", "...")
    99  	chg.AddTask(tsk)
   100  	s.state.Unlock()
   101  
   102  	s.settle(c)
   103  
   104  	s.state.Lock()
   105  	defer s.state.Unlock()
   106  
   107  	c.Assert(chg.IsReady(), Equals, true)
   108  	if errMatch == "" {
   109  		c.Check(chg.Err(), IsNil)
   110  		c.Check(tsk.Status(), Equals, state.DoneStatus)
   111  	} else {
   112  		c.Check(chg.Err(), ErrorMatches, errMatch)
   113  		c.Check(tsk.Status(), Equals, state.ErrorStatus)
   114  	}
   115  	if updateAttempted {
   116  		c.Assert(s.managedbl.UpdateCalls, Equals, 1)
   117  		if errMatch == "" && applied {
   118  			// we log on success
   119  			log := tsk.Log()
   120  			c.Assert(log, HasLen, 1)
   121  			c.Check(log[0], Matches, ".* updated boot config assets")
   122  			// update was applied, thus a restart was requested
   123  			c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystem})
   124  		} else {
   125  			// update was not applied or failed
   126  			c.Check(s.restartRequests, HasLen, 0)
   127  		}
   128  	} else {
   129  		c.Assert(s.managedbl.UpdateCalls, Equals, 0)
   130  	}
   131  }
   132  
   133  func (s *deviceMgrBootconfigSuite) TestBootConfigUpdateRunSuccess(c *C) {
   134  	s.state.Lock()
   135  	s.setupUC20Model(c)
   136  	s.state.Unlock()
   137  
   138  	s.managedbl.Updated = true
   139  
   140  	updateAttempted := true
   141  	updateApplied := true
   142  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied, "")
   143  }
   144  
   145  func (s *deviceMgrBootconfigSuite) TestBootConfigUpdateRunButNotUpdated(c *C) {
   146  	s.state.Lock()
   147  	s.setupUC20Model(c)
   148  	s.state.Unlock()
   149  
   150  	s.managedbl.Updated = false
   151  
   152  	updateAttempted := true
   153  	updateApplied := false
   154  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied, "")
   155  }
   156  
   157  func (s *deviceMgrBootconfigSuite) TestBootConfigUpdateUpdateErr(c *C) {
   158  	s.state.Lock()
   159  	s.setupUC20Model(c)
   160  	s.state.Unlock()
   161  
   162  	s.managedbl.UpdateErr = errors.New("update fail")
   163  	// actually tried to update
   164  	updateAttempted := true
   165  	updateApplied := false
   166  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied,
   167  		`(?ms).*cannot update boot config assets: update fail\)`)
   168  
   169  }
   170  
   171  func (s *deviceMgrBootconfigSuite) TestBootConfigNoUC20(c *C) {
   172  	s.state.Lock()
   173  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   174  		Brand:  "canonical",
   175  		Model:  "pc-model",
   176  		Serial: "didididi",
   177  	})
   178  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   179  		"architecture": "amd64",
   180  		"kernel":       "pc-kernel",
   181  		"gadget":       "pc",
   182  		"base":         "core18",
   183  	})
   184  	s.state.Unlock()
   185  
   186  	updateAttempted := false
   187  	updateApplied := false
   188  	s.testBootConfigUpdateRun(c, updateAttempted, updateApplied, "")
   189  }
   190  
   191  func (s *deviceMgrBootconfigSuite) TestBootConfigRemodelDoNothing(c *C) {
   192  	restore := release.MockOnClassic(false)
   193  	defer restore()
   194  
   195  	s.state.Lock()
   196  
   197  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   198  		Brand:  "canonical",
   199  		Model:  "pc-model-20",
   200  		Serial: "didididi",
   201  	})
   202  
   203  	uc20Model := s.setupUC20Model(c)
   204  	// save the hassle and try a trivial remodel
   205  	newModel := s.brands.Model("canonical", "pc-model-20", map[string]interface{}{
   206  		"brand":        "canonical",
   207  		"model":        "pc-model-20",
   208  		"architecture": "amd64",
   209  		"grade":        "dangerous",
   210  		"base":         "core20",
   211  		"snaps":        mockCore20ModelSnaps,
   212  	})
   213  	remodCtx, err := devicestate.RemodelCtx(s.state, uc20Model, newModel)
   214  	c.Assert(err, IsNil)
   215  	// be extra sure
   216  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   217  	tsk := s.state.NewTask("update-managed-boot-config", "update boot config")
   218  	chg := s.state.NewChange("dummy", "...")
   219  	chg.AddTask(tsk)
   220  	remodCtx.Init(chg)
   221  	// replace the bootloader with something that always fails
   222  	bootloader.ForceError(errors.New("unexpected call"))
   223  	s.state.Unlock()
   224  
   225  	s.settle(c)
   226  
   227  	s.state.Lock()
   228  	defer s.state.Unlock()
   229  
   230  	c.Assert(chg.IsReady(), Equals, true)
   231  	c.Check(chg.Err(), IsNil)
   232  	c.Check(tsk.Status(), Equals, state.DoneStatus)
   233  }