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