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 }