gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/snapstate/booted_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 snapstate_test 21 22 // test the boot related code 23 24 import ( 25 "errors" 26 "os" 27 "path/filepath" 28 "time" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/boot" 33 "github.com/snapcore/snapd/boot/boottest" 34 "github.com/snapcore/snapd/bootloader" 35 "github.com/snapcore/snapd/bootloader/bootloadertest" 36 "github.com/snapcore/snapd/dirs" 37 "github.com/snapcore/snapd/overlord" 38 "github.com/snapcore/snapd/overlord/servicestate" 39 "github.com/snapcore/snapd/overlord/snapstate" 40 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 41 "github.com/snapcore/snapd/overlord/state" 42 "github.com/snapcore/snapd/release" 43 "github.com/snapcore/snapd/snap" 44 "github.com/snapcore/snapd/snap/snaptest" 45 "github.com/snapcore/snapd/testutil" 46 ) 47 48 type bootedSuite struct { 49 testutil.BaseTest 50 51 bootloader *boottest.Bootenv16 52 53 o *overlord.Overlord 54 state *state.State 55 snapmgr *snapstate.SnapManager 56 fakeBackend *fakeSnappyBackend 57 restore func() 58 } 59 60 var _ = Suite(&bootedSuite{}) 61 62 func (bs *bootedSuite) SetUpTest(c *C) { 63 bs.BaseTest.SetUpTest(c) 64 65 dirs.SetRootDir(c.MkDir()) 66 err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) 67 c.Assert(err, IsNil) 68 69 bs.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 70 71 // booted is not running on classic 72 release.MockOnClassic(false) 73 74 bs.bootloader = boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 75 bs.bootloader.SetBootKernel("canonical-pc-linux_2.snap") 76 bs.bootloader.SetBootBase("core_2.snap") 77 bootloader.Force(bs.bootloader) 78 79 bs.fakeBackend = &fakeSnappyBackend{} 80 bs.o = overlord.Mock() 81 bs.state = bs.o.State() 82 bs.snapmgr, err = snapstate.Manager(bs.state, bs.o.TaskRunner()) 83 c.Assert(err, IsNil) 84 85 AddForeignTaskHandlers(bs.o.TaskRunner(), bs.fakeBackend) 86 87 bs.o.AddManager(bs.snapmgr) 88 bs.o.AddManager(bs.o.TaskRunner()) 89 90 c.Assert(bs.o.StartUp(), IsNil) 91 92 snapstate.SetSnapManagerBackend(bs.snapmgr, bs.fakeBackend) 93 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 94 return nil, nil 95 } 96 bs.restore = snapstatetest.MockDeviceModel(DefaultModel()) 97 98 oldSnapServiceOptions := snapstate.SnapServiceOptions 99 snapstate.SnapServiceOptions = servicestate.SnapServiceOptions 100 bs.AddCleanup(func() { 101 snapstate.SnapServiceOptions = oldSnapServiceOptions 102 }) 103 } 104 105 func (bs *bootedSuite) TearDownTest(c *C) { 106 bs.BaseTest.TearDownTest(c) 107 snapstate.AutoAliases = nil 108 bs.restore() 109 release.MockOnClassic(true) 110 dirs.SetRootDir("") 111 bootloader.Force(nil) 112 } 113 114 var osSI1 = &snap.SideInfo{RealName: "core", Revision: snap.R(1)} 115 var osSI2 = &snap.SideInfo{RealName: "core", Revision: snap.R(2)} 116 var kernelSI1 = &snap.SideInfo{RealName: "canonical-pc-linux", Revision: snap.R(1)} 117 var kernelSI2 = &snap.SideInfo{RealName: "canonical-pc-linux", Revision: snap.R(2)} 118 119 func (bs *bootedSuite) settle() { 120 bs.o.Settle(5 * time.Second) 121 } 122 123 func (bs *bootedSuite) makeInstalledKernelOS(c *C, st *state.State) { 124 snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", osSI1) 125 snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", osSI2) 126 snapstate.Set(st, "core", &snapstate.SnapState{ 127 SnapType: "os", 128 Active: true, 129 Sequence: []*snap.SideInfo{osSI1, osSI2}, 130 Current: snap.R(2), 131 }) 132 133 snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 1", kernelSI1) 134 snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", kernelSI2) 135 snapstate.Set(st, "canonical-pc-linux", &snapstate.SnapState{ 136 SnapType: "kernel", 137 Active: true, 138 Sequence: []*snap.SideInfo{kernelSI1, kernelSI2}, 139 Current: snap.R(2), 140 }) 141 142 } 143 144 func (bs *bootedSuite) TestUpdateBootRevisionsOSSimple(c *C) { 145 st := bs.state 146 st.Lock() 147 defer st.Unlock() 148 149 bs.makeInstalledKernelOS(c, st) 150 151 bs.bootloader.SetBootBase("core_1.snap") 152 err := snapstate.UpdateBootRevisions(st) 153 c.Assert(err, IsNil) 154 155 st.Unlock() 156 bs.settle() 157 st.Lock() 158 159 c.Assert(st.Changes(), HasLen, 1) 160 chg := st.Changes()[0] 161 c.Assert(chg.Err(), IsNil) 162 c.Assert(chg.Kind(), Equals, "update-revisions") 163 c.Assert(chg.IsReady(), Equals, true) 164 165 // core "current" got reverted but canonical-pc-linux did not 166 var snapst snapstate.SnapState 167 err = snapstate.Get(st, "core", &snapst) 168 c.Assert(err, IsNil) 169 c.Assert(snapst.Current, Equals, snap.R(1)) 170 c.Assert(snapst.Active, Equals, true) 171 172 err = snapstate.Get(st, "canonical-pc-linux", &snapst) 173 c.Assert(err, IsNil) 174 c.Assert(snapst.Current, Equals, snap.R(2)) 175 c.Assert(snapst.Active, Equals, true) 176 } 177 178 func (bs *bootedSuite) TestUpdateBootRevisionsKernelSimple(c *C) { 179 st := bs.state 180 st.Lock() 181 defer st.Unlock() 182 183 bs.makeInstalledKernelOS(c, st) 184 185 bs.bootloader.SetBootKernel("canonical-pc-linux_1.snap") 186 err := snapstate.UpdateBootRevisions(st) 187 c.Assert(err, IsNil) 188 189 st.Unlock() 190 bs.settle() 191 st.Lock() 192 193 c.Assert(st.Changes(), HasLen, 1) 194 chg := st.Changes()[0] 195 c.Assert(chg.Err(), IsNil) 196 c.Assert(chg.Kind(), Equals, "update-revisions") 197 c.Assert(chg.IsReady(), Equals, true) 198 199 // canonical-pc-linux "current" got reverted but core did not 200 var snapst snapstate.SnapState 201 err = snapstate.Get(st, "canonical-pc-linux", &snapst) 202 c.Assert(err, IsNil) 203 c.Assert(snapst.Current, Equals, snap.R(1)) 204 c.Assert(snapst.Active, Equals, true) 205 206 err = snapstate.Get(st, "core", &snapst) 207 c.Assert(err, IsNil) 208 c.Assert(snapst.Current, Equals, snap.R(2)) 209 c.Assert(snapst.Active, Equals, true) 210 } 211 212 func (bs *bootedSuite) TestUpdateBootRevisionsDeviceCtxErrors(c *C) { 213 st := bs.state 214 st.Lock() 215 defer st.Unlock() 216 217 bs.makeInstalledKernelOS(c, st) 218 219 errBoom := errors.New("boom") 220 221 r := snapstatetest.ReplaceDeviceCtxHook(func(*state.State, *state.Task, snapstate.DeviceContext) (snapstate.DeviceContext, error) { 222 return nil, errBoom 223 }) 224 defer r() 225 226 err := snapstate.UpdateBootRevisions(st) 227 c.Assert(err, Equals, errBoom) 228 } 229 230 func (bs *bootedSuite) TestUpdateBootRevisionsKernelErrorsEarly(c *C) { 231 st := bs.state 232 st.Lock() 233 defer st.Unlock() 234 235 bs.makeInstalledKernelOS(c, st) 236 237 bs.bootloader.SetBootKernel("canonical-pc-linux_99.snap") 238 err := snapstate.UpdateBootRevisions(st) 239 c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "canonical-pc-linux"`) 240 } 241 242 func (bs *bootedSuite) TestUpdateBootRevisionsOSErrorsEarly(c *C) { 243 st := bs.state 244 st.Lock() 245 defer st.Unlock() 246 247 bs.makeInstalledKernelOS(c, st) 248 249 bs.bootloader.SetBootBase("core_99.snap") 250 err := snapstate.UpdateBootRevisions(st) 251 c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "core"`) 252 } 253 254 func (bs *bootedSuite) TestUpdateBootRevisionsOSErrorsLate(c *C) { 255 st := bs.state 256 st.Lock() 257 defer st.Unlock() 258 259 // have a kernel 260 snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", kernelSI2) 261 snapstate.Set(st, "canonical-pc-linux", &snapstate.SnapState{ 262 SnapType: "kernel", 263 Active: true, 264 Sequence: []*snap.SideInfo{kernelSI2}, 265 Current: snap.R(2), 266 }) 267 268 // put core into the state but add no files on disk 269 // will break in the tasks 270 snapstate.Set(st, "core", &snapstate.SnapState{ 271 SnapType: "os", 272 Active: true, 273 Sequence: []*snap.SideInfo{osSI1, osSI2}, 274 Current: snap.R(2), 275 }) 276 bs.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/core/1") 277 278 bs.bootloader.SetBootBase("core_1.snap") 279 err := snapstate.UpdateBootRevisions(st) 280 c.Assert(err, IsNil) 281 282 st.Unlock() 283 bs.settle() 284 st.Lock() 285 286 c.Assert(st.Changes(), HasLen, 1) 287 chg := st.Changes()[0] 288 c.Assert(chg.Kind(), Equals, "update-revisions") 289 c.Assert(chg.IsReady(), Equals, true) 290 c.Assert(chg.Err(), ErrorMatches, `(?ms).*Make snap "core" \(1\) available to the system \(fail\).*`) 291 } 292 293 func (bs *bootedSuite) TestFinishRestartCore(c *C) { 294 st := bs.state 295 st.Lock() 296 defer st.Unlock() 297 298 task := st.NewTask("auto-connect", "...") 299 300 // not core snap 301 si := &snap.SideInfo{RealName: "some-app"} 302 snaptest.MockSnap(c, "name: some-app\nversion: 1", si) 303 err := snapstate.FinishRestart(task, &snapstate.SnapSetup{SideInfo: si}) 304 c.Check(err, IsNil) 305 306 si = &snap.SideInfo{RealName: "core"} 307 snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeOS} 308 309 // core snap, restarting ... wait 310 state.MockRestarting(st, state.RestartSystem) 311 snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", si) 312 err = snapstate.FinishRestart(task, snapsup) 313 c.Check(err, FitsTypeOf, &state.Retry{}) 314 315 // core snap, restarted, waiting for current core revision 316 state.MockRestarting(st, state.RestartUnset) 317 bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus 318 err = snapstate.FinishRestart(task, snapsup) 319 c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second}) 320 321 // core snap updated 322 si.Revision = snap.R(2) 323 snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", si) 324 325 // core snap, restarted, right core revision, no rollback 326 bs.bootloader.BootVars["snap_mode"] = "" 327 err = snapstate.FinishRestart(task, snapsup) 328 c.Check(err, IsNil) 329 330 // core snap, restarted, wrong core revision, rollback! 331 bs.bootloader.SetBootBase("core_1.snap") 332 err = snapstate.FinishRestart(task, snapsup) 333 c.Check(err, ErrorMatches, `cannot finish core installation, there was a rollback across reboot`) 334 } 335 336 func (bs *bootedSuite) TestFinishRestartBootableBase(c *C) { 337 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 338 defer r() 339 340 st := bs.state 341 st.Lock() 342 defer st.Unlock() 343 344 task := st.NewTask("auto-connect", "...") 345 346 // not core snap 347 si := &snap.SideInfo{RealName: "some-app", Revision: snap.R(1)} 348 snaptest.MockSnap(c, "name: some-app\nversion: 1", si) 349 err := snapstate.FinishRestart(task, &snapstate.SnapSetup{SideInfo: si}) 350 c.Check(err, IsNil) 351 352 // core snap but we are on a model with a different base 353 si = &snap.SideInfo{RealName: "core"} 354 snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", si) 355 err = snapstate.FinishRestart(task, &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeOS}) 356 c.Check(err, IsNil) 357 358 si = &snap.SideInfo{RealName: "core18"} 359 snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeBase} 360 snaptest.MockSnap(c, "name: core18\ntype: base\nversion: 1", si) 361 // core snap, restarting ... wait 362 state.MockRestarting(st, state.RestartSystem) 363 err = snapstate.FinishRestart(task, snapsup) 364 c.Check(err, FitsTypeOf, &state.Retry{}) 365 366 // core snap, restarted, waiting for current core revision 367 state.MockRestarting(st, state.RestartUnset) 368 bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus 369 err = snapstate.FinishRestart(task, snapsup) 370 c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second}) 371 372 // core18 snap updated 373 si.Revision = snap.R(2) 374 snaptest.MockSnap(c, "name: core18\ntype: base\nversion: 2", si) 375 376 // core snap, restarted, right core revision, no rollback 377 bs.bootloader.BootVars["snap_mode"] = "" 378 bs.bootloader.SetBootBase("core18_2.snap") 379 err = snapstate.FinishRestart(task, snapsup) 380 c.Check(err, IsNil) 381 382 // core snap, restarted, wrong core revision, rollback! 383 bs.bootloader.SetBootBase("core18_1.snap") 384 err = snapstate.FinishRestart(task, snapsup) 385 c.Check(err, ErrorMatches, `cannot finish core18 installation, there was a rollback across reboot`) 386 } 387 388 func (bs *bootedSuite) TestFinishRestartKernel(c *C) { 389 r := snapstatetest.MockDeviceModel(DefaultModel()) 390 defer r() 391 392 st := bs.state 393 st.Lock() 394 defer st.Unlock() 395 396 task := st.NewTask("auto-connect", "...") 397 398 // not kernel snap 399 si := &snap.SideInfo{RealName: "some-app", Revision: snap.R(1)} 400 snaptest.MockSnap(c, "name: some-app\nversion: 1", si) 401 err := snapstate.FinishRestart(task, &snapstate.SnapSetup{SideInfo: si}) 402 c.Check(err, IsNil) 403 404 // different kernel (may happen with remodel) 405 si = &snap.SideInfo{RealName: "other-kernel"} 406 snaptest.MockSnap(c, "name: other-kernel\ntype: kernel\nversion: 1", si) 407 err = snapstate.FinishRestart(task, &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeKernel}) 408 c.Check(err, IsNil) 409 410 si = &snap.SideInfo{RealName: "kernel"} 411 snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeKernel} 412 snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 1", si) 413 // kernel snap, restarting ... wait 414 state.MockRestarting(st, state.RestartSystem) 415 err = snapstate.FinishRestart(task, snapsup) 416 c.Check(err, FitsTypeOf, &state.Retry{}) 417 418 // kernel snap, restarted, waiting for current core revision 419 state.MockRestarting(st, state.RestartUnset) 420 bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus 421 err = snapstate.FinishRestart(task, snapsup) 422 c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second}) 423 424 // kernel snap updated 425 si.Revision = snap.R(2) 426 snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 2", si) 427 428 // kernel snap, restarted, right kernel revision, no rollback 429 bs.bootloader.BootVars["snap_mode"] = "" 430 bs.bootloader.SetBootKernel("kernel_2.snap") 431 err = snapstate.FinishRestart(task, snapsup) 432 c.Check(err, IsNil) 433 434 // kernel snap, restarted, wrong core revision, rollback! 435 bs.bootloader.SetBootKernel("kernel_1.snap") 436 err = snapstate.FinishRestart(task, snapsup) 437 c.Check(err, ErrorMatches, `cannot finish kernel installation, there was a rollback across reboot`) 438 } 439 440 func (bs *bootedSuite) TestFinishRestartEphemeralModeSkipsRollbackDetection(c *C) { 441 r := snapstatetest.MockDeviceModel(DefaultModel()) 442 defer r() 443 444 st := bs.state 445 st.Lock() 446 defer st.Unlock() 447 448 task := st.NewTask("auto-connect", "...") 449 450 si := &snap.SideInfo{RealName: "kernel"} 451 snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeKernel} 452 snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 1", si) 453 // kernel snap, restarted, wrong core revision, rollback detected! 454 bs.bootloader.SetBootKernel("kernel_1.snap") 455 err := snapstate.FinishRestart(task, snapsup) 456 c.Check(err, ErrorMatches, `cannot finish kernel installation, there was a rollback across reboot`) 457 458 // but *not* in an ephemeral mode like "recover" - we skip the rollback 459 // detection here 460 r = snapstatetest.MockDeviceModelAndMode(DefaultModel(), "install") 461 defer r() 462 err = snapstate.FinishRestart(task, snapsup) 463 c.Check(err, IsNil) 464 }