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 }