github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/devicestate/devicestate_systems_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 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 "bytes" 24 "errors" 25 "fmt" 26 "os" 27 "path/filepath" 28 29 . "gopkg.in/check.v1" 30 "gopkg.in/tomb.v2" 31 32 "github.com/snapcore/snapd/asserts" 33 "github.com/snapcore/snapd/asserts/assertstest" 34 "github.com/snapcore/snapd/boot" 35 "github.com/snapcore/snapd/bootloader" 36 "github.com/snapcore/snapd/bootloader/bootloadertest" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/logger" 39 "github.com/snapcore/snapd/overlord/auth" 40 "github.com/snapcore/snapd/overlord/devicestate" 41 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/state" 44 "github.com/snapcore/snapd/seed" 45 "github.com/snapcore/snapd/seed/seedtest" 46 "github.com/snapcore/snapd/snap" 47 "github.com/snapcore/snapd/snap/snaptest" 48 "github.com/snapcore/snapd/strutil" 49 "github.com/snapcore/snapd/testutil" 50 ) 51 52 type mockedSystemSeed struct { 53 label string 54 model *asserts.Model 55 brand *asserts.Account 56 } 57 58 type deviceMgrSystemsBaseSuite struct { 59 deviceMgrBaseSuite 60 61 logbuf *bytes.Buffer 62 mockedSystemSeeds []mockedSystemSeed 63 ss *seedtest.SeedSnaps 64 model *asserts.Model 65 } 66 67 type deviceMgrSystemsSuite struct { 68 deviceMgrSystemsBaseSuite 69 } 70 71 var _ = Suite(&deviceMgrSystemsSuite{}) 72 var _ = Suite(&deviceMgrSystemsCreateSuite{}) 73 74 func (s *deviceMgrSystemsBaseSuite) SetUpTest(c *C) { 75 s.deviceMgrBaseSuite.SetUpTest(c) 76 77 s.brands.Register("other-brand", brandPrivKey3, map[string]interface{}{ 78 "display-name": "other publisher", 79 }) 80 s.state.Lock() 81 defer s.state.Unlock() 82 s.ss = &seedtest.SeedSnaps{ 83 StoreSigning: s.storeSigning, 84 Brands: s.brands, 85 } 86 87 s.model = s.makeModelAssertionInState(c, "canonical", "pc-20", map[string]interface{}{ 88 "architecture": "amd64", 89 // UC20 90 "grade": "dangerous", 91 "base": "core20", 92 "snaps": []interface{}{ 93 map[string]interface{}{ 94 "name": "pc-kernel", 95 "id": s.ss.AssertedSnapID("pc-kernel"), 96 "type": "kernel", 97 "default-channel": "20", 98 }, 99 map[string]interface{}{ 100 "name": "pc", 101 "id": s.ss.AssertedSnapID("pc"), 102 "type": "gadget", 103 "default-channel": "20", 104 }, 105 map[string]interface{}{ 106 "name": "core20", 107 "id": s.ss.AssertedSnapID("core20"), 108 "type": "base", 109 }, 110 map[string]interface{}{ 111 "name": "snapd", 112 "id": s.ss.AssertedSnapID("snapd"), 113 "type": "snapd", 114 }, 115 }, 116 }) 117 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 118 Brand: "canonical", 119 Model: "pc-20", 120 Serial: "serialserialserial", 121 }) 122 assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("my-brand")...) 123 assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("other-brand")...) 124 125 // all tests should be in run mode by default, if they need to be in 126 // different modes they should set that individually 127 devicestate.SetSystemMode(s.mgr, "run") 128 129 // state after mark-seeded ran 130 modeenv := boot.Modeenv{ 131 Mode: "run", 132 RecoverySystem: "", 133 134 Model: s.model.Model(), 135 BrandID: s.model.BrandID(), 136 Grade: string(s.model.Grade()), 137 ModelSignKeyID: s.model.SignKeyID(), 138 } 139 err := modeenv.WriteTo("") 140 s.state.Set("seeded", true) 141 142 c.Assert(err, IsNil) 143 144 logbuf, restore := logger.MockLogger() 145 s.logbuf = logbuf 146 s.AddCleanup(restore) 147 148 nopHandler := func(task *state.Task, _ *tomb.Tomb) error { return nil } 149 s.o.TaskRunner().AddHandler("fake-download", nopHandler, nil) 150 } 151 152 func (s *deviceMgrSystemsSuite) SetUpTest(c *C) { 153 s.deviceMgrSystemsBaseSuite.SetUpTest(c) 154 155 // now create a minimal uc20 seed dir with snaps/assertions 156 seed20 := &seedtest.TestingSeed20{ 157 SeedSnaps: *s.ss, 158 SeedDir: dirs.SnapSeedDir, 159 } 160 161 restore := seed.MockTrusted(s.storeSigning.Trusted) 162 s.AddCleanup(restore) 163 164 myBrandAcc := s.brands.Account("my-brand") 165 otherBrandAcc := s.brands.Account("other-brand") 166 167 // add essential snaps 168 seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 169 seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 170 seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 171 seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 172 173 model1 := seed20.MakeSeed(c, "20191119", "my-brand", "my-model", map[string]interface{}{ 174 "display-name": "my fancy model", 175 "architecture": "amd64", 176 "base": "core20", 177 "snaps": []interface{}{ 178 map[string]interface{}{ 179 "name": "pc-kernel", 180 "id": seed20.AssertedSnapID("pc-kernel"), 181 "type": "kernel", 182 "default-channel": "20", 183 }, 184 map[string]interface{}{ 185 "name": "pc", 186 "id": seed20.AssertedSnapID("pc"), 187 "type": "gadget", 188 "default-channel": "20", 189 }}, 190 }, nil) 191 model2 := seed20.MakeSeed(c, "20200318", "my-brand", "my-model-2", map[string]interface{}{ 192 "display-name": "same brand different model", 193 "architecture": "amd64", 194 "base": "core20", 195 "snaps": []interface{}{ 196 map[string]interface{}{ 197 "name": "pc-kernel", 198 "id": seed20.AssertedSnapID("pc-kernel"), 199 "type": "kernel", 200 "default-channel": "20", 201 }, 202 map[string]interface{}{ 203 "name": "pc", 204 "id": seed20.AssertedSnapID("pc"), 205 "type": "gadget", 206 "default-channel": "20", 207 }}, 208 }, nil) 209 model3 := seed20.MakeSeed(c, "other-20200318", "other-brand", "other-model", map[string]interface{}{ 210 "display-name": "different brand different model", 211 "architecture": "amd64", 212 "base": "core20", 213 "snaps": []interface{}{ 214 map[string]interface{}{ 215 "name": "pc-kernel", 216 "id": seed20.AssertedSnapID("pc-kernel"), 217 "type": "kernel", 218 "default-channel": "20", 219 }, 220 map[string]interface{}{ 221 "name": "pc", 222 "id": seed20.AssertedSnapID("pc"), 223 "type": "gadget", 224 "default-channel": "20", 225 }}, 226 }, nil) 227 228 s.mockedSystemSeeds = []mockedSystemSeed{{ 229 label: "20191119", 230 model: model1, 231 brand: myBrandAcc, 232 }, { 233 label: "20200318", 234 model: model2, 235 brand: myBrandAcc, 236 }, { 237 label: "other-20200318", 238 model: model3, 239 brand: otherBrandAcc, 240 }} 241 } 242 243 func (s *deviceMgrSystemsSuite) TestListNoSystems(c *C) { 244 dirs.SetRootDir(c.MkDir()) 245 246 systems, err := s.mgr.Systems() 247 c.Assert(err, Equals, devicestate.ErrNoSystems) 248 c.Assert(systems, HasLen, 0) 249 250 err = os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "systems"), 0755) 251 c.Assert(err, IsNil) 252 253 systems, err = s.mgr.Systems() 254 c.Assert(err, Equals, devicestate.ErrNoSystems) 255 c.Assert(systems, HasLen, 0) 256 } 257 258 func (s *deviceMgrSystemsSuite) TestListSystemsNotPossible(c *C) { 259 if os.Geteuid() == 0 { 260 c.Skip("this test cannot run as root") 261 } 262 err := os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0000) 263 c.Assert(err, IsNil) 264 defer os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0755) 265 266 // stdlib swallows up the errors when opening the target directory 267 systems, err := s.mgr.Systems() 268 c.Assert(err, Equals, devicestate.ErrNoSystems) 269 c.Assert(systems, HasLen, 0) 270 } 271 272 // TODO:UC20 update once we can list actions 273 var defaultSystemActions []devicestate.SystemAction = []devicestate.SystemAction{ 274 {Title: "Install", Mode: "install"}, 275 } 276 var currentSystemActions []devicestate.SystemAction = []devicestate.SystemAction{ 277 {Title: "Reinstall", Mode: "install"}, 278 {Title: "Recover", Mode: "recover"}, 279 {Title: "Run normally", Mode: "run"}, 280 } 281 282 func (s *deviceMgrSystemsSuite) TestListSeedSystemsNoCurrent(c *C) { 283 systems, err := s.mgr.Systems() 284 c.Assert(err, IsNil) 285 c.Assert(systems, HasLen, 3) 286 c.Check(systems, DeepEquals, []*devicestate.System{{ 287 Current: false, 288 Label: s.mockedSystemSeeds[0].label, 289 Model: s.mockedSystemSeeds[0].model, 290 Brand: s.mockedSystemSeeds[0].brand, 291 Actions: defaultSystemActions, 292 }, { 293 Current: false, 294 Label: s.mockedSystemSeeds[1].label, 295 Model: s.mockedSystemSeeds[1].model, 296 Brand: s.mockedSystemSeeds[1].brand, 297 Actions: defaultSystemActions, 298 }, { 299 Current: false, 300 Label: s.mockedSystemSeeds[2].label, 301 Model: s.mockedSystemSeeds[2].model, 302 Brand: s.mockedSystemSeeds[2].brand, 303 Actions: defaultSystemActions, 304 }}) 305 } 306 307 func (s *deviceMgrSystemsSuite) TestListSeedSystemsCurrent(c *C) { 308 s.state.Lock() 309 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 310 { 311 System: s.mockedSystemSeeds[1].label, 312 Model: s.mockedSystemSeeds[1].model.Model(), 313 BrandID: s.mockedSystemSeeds[1].brand.AccountID(), 314 }, 315 }) 316 s.state.Unlock() 317 318 systems, err := s.mgr.Systems() 319 c.Assert(err, IsNil) 320 c.Assert(systems, HasLen, 3) 321 c.Check(systems, DeepEquals, []*devicestate.System{{ 322 Current: false, 323 Label: s.mockedSystemSeeds[0].label, 324 Model: s.mockedSystemSeeds[0].model, 325 Brand: s.mockedSystemSeeds[0].brand, 326 Actions: defaultSystemActions, 327 }, { 328 // this seed was used for installing the running system 329 Current: true, 330 Label: s.mockedSystemSeeds[1].label, 331 Model: s.mockedSystemSeeds[1].model, 332 Brand: s.mockedSystemSeeds[1].brand, 333 Actions: currentSystemActions, 334 }, { 335 Current: false, 336 Label: s.mockedSystemSeeds[2].label, 337 Model: s.mockedSystemSeeds[2].model, 338 Brand: s.mockedSystemSeeds[2].brand, 339 Actions: defaultSystemActions, 340 }}) 341 } 342 343 func (s *deviceMgrSystemsSuite) TestBrokenSeedSystems(c *C) { 344 // break the first seed 345 err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model")) 346 c.Assert(err, IsNil) 347 348 systems, err := s.mgr.Systems() 349 c.Assert(err, IsNil) 350 c.Assert(systems, HasLen, 2) 351 c.Check(systems, DeepEquals, []*devicestate.System{{ 352 Current: false, 353 Label: s.mockedSystemSeeds[1].label, 354 Model: s.mockedSystemSeeds[1].model, 355 Brand: s.mockedSystemSeeds[1].brand, 356 Actions: defaultSystemActions, 357 }, { 358 Current: false, 359 Label: s.mockedSystemSeeds[2].label, 360 Model: s.mockedSystemSeeds[2].model, 361 Brand: s.mockedSystemSeeds[2].brand, 362 Actions: defaultSystemActions, 363 }}) 364 } 365 366 func (s *deviceMgrSystemsSuite) TestRequestModeInstallHappyForAny(c *C) { 367 // no current system 368 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install", Title: "Install"}) 369 c.Assert(err, IsNil) 370 371 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 372 c.Assert(err, IsNil) 373 c.Check(m, DeepEquals, map[string]string{ 374 "snapd_recovery_system": "20191119", 375 "snapd_recovery_mode": "install", 376 }) 377 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 378 c.Check(s.logbuf.String(), Matches, `.*: restarting into system "20191119" for action "Install"\n`) 379 } 380 381 func (s *deviceMgrSystemsSuite) TestRequestSameModeSameSystem(c *C) { 382 s.state.Lock() 383 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 384 { 385 System: s.mockedSystemSeeds[0].label, 386 Model: s.mockedSystemSeeds[0].model.Model(), 387 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 388 }, 389 }) 390 s.state.Unlock() 391 392 label := s.mockedSystemSeeds[0].label 393 394 happyModes := []string{"run"} 395 sadModes := []string{"install", "recover"} 396 397 for _, mode := range append(happyModes, sadModes...) { 398 s.logbuf.Reset() 399 400 c.Logf("checking mode: %q", mode) 401 // non run modes use modeenv 402 modeenv := boot.Modeenv{ 403 Mode: mode, 404 } 405 if mode != "run" { 406 modeenv.RecoverySystem = s.mockedSystemSeeds[0].label 407 } 408 err := modeenv.WriteTo("") 409 c.Assert(err, IsNil) 410 411 devicestate.SetSystemMode(s.mgr, mode) 412 err = s.bootloader.SetBootVars(map[string]string{ 413 "snapd_recovery_mode": mode, 414 "snapd_recovery_system": label, 415 }) 416 c.Assert(err, IsNil) 417 err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode}) 418 if strutil.ListContains(sadModes, mode) { 419 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 420 } else { 421 c.Assert(err, IsNil) 422 } 423 // bootloader vars shouldn't change 424 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 425 c.Assert(err, IsNil) 426 c.Check(m, DeepEquals, map[string]string{ 427 "snapd_recovery_mode": mode, 428 "snapd_recovery_system": label, 429 }) 430 // should never restart 431 c.Check(s.restartRequests, HasLen, 0) 432 // no log output 433 c.Check(s.logbuf.String(), Equals, "") 434 } 435 } 436 437 func (s *deviceMgrSystemsSuite) TestRequestSeedingSameConflict(c *C) { 438 label := s.mockedSystemSeeds[0].label 439 440 devicestate.SetSystemMode(s.mgr, "run") 441 442 s.state.Lock() 443 s.state.Set("seeded", nil) 444 s.state.Unlock() 445 446 for _, mode := range []string{"run", "install", "recover"} { 447 s.logbuf.Reset() 448 449 c.Logf("checking mode: %q", mode) 450 modeenv := boot.Modeenv{ 451 Mode: mode, 452 RecoverySystem: s.mockedSystemSeeds[0].label, 453 } 454 err := modeenv.WriteTo("") 455 c.Assert(err, IsNil) 456 457 err = s.bootloader.SetBootVars(map[string]string{ 458 "snapd_recovery_mode": "", 459 "snapd_recovery_system": label, 460 }) 461 c.Assert(err, IsNil) 462 err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode}) 463 c.Assert(err, ErrorMatches, "cannot request system action, system is seeding") 464 // no log output 465 c.Check(s.logbuf.String(), Equals, "") 466 } 467 } 468 469 func (s *deviceMgrSystemsSuite) TestRequestSeedingDifferentNoConflict(c *C) { 470 label := s.mockedSystemSeeds[0].label 471 otherLabel := s.mockedSystemSeeds[1].label 472 473 devicestate.SetSystemMode(s.mgr, "run") 474 475 modeenv := boot.Modeenv{ 476 Mode: "run", 477 RecoverySystem: label, 478 } 479 err := modeenv.WriteTo("") 480 c.Assert(err, IsNil) 481 482 s.state.Lock() 483 s.state.Set("seeded", nil) 484 s.state.Unlock() 485 486 // we can only go to install mode of other system when one is currently 487 // being seeded 488 err = s.bootloader.SetBootVars(map[string]string{ 489 "snapd_recovery_mode": "", 490 "snapd_recovery_system": label, 491 }) 492 c.Assert(err, IsNil) 493 err = s.mgr.RequestSystemAction(otherLabel, devicestate.SystemAction{Mode: "install"}) 494 c.Assert(err, IsNil) 495 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 496 c.Assert(err, IsNil) 497 c.Check(m, DeepEquals, map[string]string{ 498 "snapd_recovery_system": otherLabel, 499 "snapd_recovery_mode": "install", 500 }) 501 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action "Install"\n`, otherLabel)) 502 } 503 504 func (s *deviceMgrSystemsSuite) testRequestModeWithRestart(c *C, toModes []string, label string) { 505 for _, mode := range toModes { 506 c.Logf("checking mode: %q", mode) 507 err := s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode}) 508 c.Assert(err, IsNil) 509 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 510 c.Assert(err, IsNil) 511 c.Check(m, DeepEquals, map[string]string{ 512 "snapd_recovery_system": label, 513 "snapd_recovery_mode": mode, 514 }) 515 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 516 s.restartRequests = nil 517 s.bootloader.BootVars = map[string]string{} 518 519 // TODO: also test correct action string logging 520 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action ".*"\n`, label)) 521 s.logbuf.Reset() 522 } 523 } 524 525 func (s *deviceMgrSystemsSuite) TestRequestModeRunInstallForRecover(c *C) { 526 // we are in recover mode here 527 devicestate.SetSystemMode(s.mgr, "recover") 528 // non run modes use modeenv 529 modeenv := boot.Modeenv{ 530 Mode: "recover", 531 RecoverySystem: s.mockedSystemSeeds[0].label, 532 } 533 err := modeenv.WriteTo("") 534 c.Assert(err, IsNil) 535 536 s.state.Lock() 537 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 538 { 539 System: s.mockedSystemSeeds[0].label, 540 Model: s.mockedSystemSeeds[0].model.Model(), 541 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 542 }, 543 }) 544 s.state.Unlock() 545 546 s.testRequestModeWithRestart(c, []string{"install", "run"}, s.mockedSystemSeeds[0].label) 547 } 548 549 func (s *deviceMgrSystemsSuite) TestRequestModeInstallRecoverForCurrent(c *C) { 550 devicestate.SetSystemMode(s.mgr, "run") 551 // non run modes use modeenv 552 modeenv := boot.Modeenv{ 553 Mode: "run", 554 } 555 err := modeenv.WriteTo("") 556 c.Assert(err, IsNil) 557 558 s.state.Lock() 559 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 560 { 561 System: s.mockedSystemSeeds[0].label, 562 Model: s.mockedSystemSeeds[0].model.Model(), 563 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 564 }, 565 }) 566 s.state.Unlock() 567 568 s.testRequestModeWithRestart(c, []string{"install", "recover"}, s.mockedSystemSeeds[0].label) 569 } 570 571 func (s *deviceMgrSystemsSuite) TestRequestModeErrInBoot(c *C) { 572 s.bootloader.SetErr = errors.New("no can do") 573 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"}) 574 c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": no can do`) 575 c.Check(s.restartRequests, HasLen, 0) 576 c.Check(s.logbuf.String(), Equals, "") 577 } 578 579 func (s *deviceMgrSystemsSuite) TestRequestModeNotFound(c *C) { 580 err := s.mgr.RequestSystemAction("not-found", devicestate.SystemAction{Mode: "install"}) 581 c.Assert(err, NotNil) 582 c.Assert(os.IsNotExist(err), Equals, true) 583 c.Check(s.restartRequests, HasLen, 0) 584 c.Check(s.logbuf.String(), Equals, "") 585 } 586 587 func (s *deviceMgrSystemsSuite) TestRequestModeBadMode(c *C) { 588 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "unknown-mode"}) 589 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 590 c.Check(s.restartRequests, HasLen, 0) 591 c.Check(s.logbuf.String(), Equals, "") 592 } 593 594 func (s *deviceMgrSystemsSuite) TestRequestModeBroken(c *C) { 595 // break the first seed 596 err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model")) 597 c.Assert(err, IsNil) 598 599 err = s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"}) 600 c.Assert(err, ErrorMatches, "cannot load seed system: cannot load assertions: .*") 601 c.Check(s.restartRequests, HasLen, 0) 602 c.Check(s.logbuf.String(), Equals, "") 603 } 604 605 func (s *deviceMgrSystemsSuite) TestRequestModeNonUC20(c *C) { 606 s.setPCModelInState(c) 607 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"}) 608 c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": system mode is unsupported`) 609 c.Check(s.restartRequests, HasLen, 0) 610 c.Check(s.logbuf.String(), Equals, "") 611 } 612 613 func (s *deviceMgrSystemsSuite) TestRequestActionNoLabel(c *C) { 614 err := s.mgr.RequestSystemAction("", devicestate.SystemAction{Mode: "install"}) 615 c.Assert(err, ErrorMatches, "internal error: system label is unset") 616 c.Check(s.logbuf.String(), Equals, "") 617 } 618 619 func (s *deviceMgrSystemsSuite) TestRequestModeForNonCurrent(c *C) { 620 s.state.Lock() 621 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 622 { 623 System: s.mockedSystemSeeds[0].label, 624 Model: s.mockedSystemSeeds[0].model.Model(), 625 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 626 }, 627 }) 628 629 s.state.Unlock() 630 s.setPCModelInState(c) 631 // request mode reserved for current system 632 err := s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "run"}) 633 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 634 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "recover"}) 635 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 636 c.Check(s.restartRequests, HasLen, 0) 637 c.Check(s.logbuf.String(), Equals, "") 638 } 639 640 func (s *deviceMgrSystemsSuite) TestRequestInstallForOther(c *C) { 641 devicestate.SetSystemMode(s.mgr, "run") 642 // non run modes use modeenv 643 modeenv := boot.Modeenv{ 644 Mode: "run", 645 } 646 err := modeenv.WriteTo("") 647 c.Assert(err, IsNil) 648 649 s.state.Lock() 650 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 651 { 652 System: s.mockedSystemSeeds[0].label, 653 Model: s.mockedSystemSeeds[0].model.Model(), 654 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 655 }, 656 }) 657 s.state.Unlock() 658 // reinstall from different system seed is ok 659 s.testRequestModeWithRestart(c, []string{"install"}, s.mockedSystemSeeds[1].label) 660 } 661 662 func (s *deviceMgrSystemsSuite) TestRequestAction1618(c *C) { 663 s.setPCModelInState(c) 664 // system mode is unset in 16/18 665 devicestate.SetSystemMode(s.mgr, "") 666 // no modeenv either 667 err := os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir)) 668 c.Assert(err, IsNil) 669 670 s.state.Lock() 671 s.state.Set("seeded-systems", nil) 672 s.state.Set("seeded", nil) 673 s.state.Unlock() 674 // a label exists 675 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"}) 676 c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported") 677 678 s.state.Lock() 679 s.state.Set("seeded", true) 680 s.state.Unlock() 681 682 // even with system mode explicitly set, the action is not executed 683 devicestate.SetSystemMode(s.mgr, "run") 684 685 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"}) 686 c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported") 687 688 devicestate.SetSystemMode(s.mgr, "") 689 // also no UC20 style system seeds 690 for _, m := range s.mockedSystemSeeds { 691 os.RemoveAll(filepath.Join(dirs.SnapSeedDir, "systems", m.label)) 692 } 693 694 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"}) 695 c.Assert(err, ErrorMatches, ".*/seed/systems/20191119: no such file or directory") 696 c.Check(s.logbuf.String(), Equals, "") 697 } 698 699 func (s *deviceMgrSystemsSuite) TestRebootNoLabelNoModeHappy(c *C) { 700 err := s.mgr.Reboot("", "") 701 c.Assert(err, IsNil) 702 703 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 704 c.Assert(err, IsNil) 705 // requested restart 706 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 707 // but no bootloader changes 708 c.Check(m, DeepEquals, map[string]string{ 709 "snapd_recovery_system": "", 710 "snapd_recovery_mode": "", 711 }) 712 c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`) 713 } 714 715 func (s *deviceMgrSystemsSuite) TestRebootLabelAndModeHappy(c *C) { 716 s.state.Lock() 717 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 718 { 719 System: s.mockedSystemSeeds[0].label, 720 Model: s.mockedSystemSeeds[0].model.Model(), 721 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 722 }, 723 }) 724 s.state.Unlock() 725 726 err := s.mgr.Reboot("20191119", "install") 727 c.Assert(err, IsNil) 728 729 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 730 c.Assert(err, IsNil) 731 c.Check(m, DeepEquals, map[string]string{ 732 "snapd_recovery_system": "20191119", 733 "snapd_recovery_mode": "install", 734 }) 735 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 736 c.Check(s.logbuf.String(), Matches, `.*: rebooting into system "20191119" in "install" mode\n`) 737 } 738 739 func (s *deviceMgrSystemsSuite) TestRebootModeOnlyHappy(c *C) { 740 s.state.Lock() 741 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 742 { 743 System: s.mockedSystemSeeds[0].label, 744 Model: s.mockedSystemSeeds[0].model.Model(), 745 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 746 }, 747 }) 748 s.state.Unlock() 749 750 for _, mode := range []string{"recover", "install"} { 751 s.restartRequests = nil 752 s.bootloader.BootVars = make(map[string]string) 753 s.logbuf.Reset() 754 755 err := s.mgr.Reboot("", mode) 756 c.Assert(err, IsNil) 757 758 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 759 c.Assert(err, IsNil) 760 c.Check(m, DeepEquals, map[string]string{ 761 "snapd_recovery_system": s.mockedSystemSeeds[0].label, 762 "snapd_recovery_mode": mode, 763 }) 764 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 765 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "20191119" in "%s" mode\n`, mode)) 766 } 767 } 768 769 func (s *deviceMgrSystemsSuite) TestRebootFromRecoverToRun(c *C) { 770 modeenv := boot.Modeenv{ 771 Mode: "recover", 772 RecoverySystem: s.mockedSystemSeeds[0].label, 773 } 774 err := modeenv.WriteTo("") 775 c.Assert(err, IsNil) 776 777 devicestate.SetSystemMode(s.mgr, "recover") 778 err = s.bootloader.SetBootVars(map[string]string{ 779 "snapd_recovery_mode": "recover", 780 "snapd_recovery_system": s.mockedSystemSeeds[0].label, 781 }) 782 c.Assert(err, IsNil) 783 784 s.state.Lock() 785 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 786 { 787 System: s.mockedSystemSeeds[0].label, 788 Model: s.mockedSystemSeeds[0].model.Model(), 789 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 790 }, 791 }) 792 s.state.Unlock() 793 794 err = s.mgr.Reboot("", "run") 795 c.Assert(err, IsNil) 796 797 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 798 c.Assert(err, IsNil) 799 c.Check(m, DeepEquals, map[string]string{ 800 "snapd_recovery_mode": "run", 801 "snapd_recovery_system": s.mockedSystemSeeds[0].label, 802 }) 803 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 804 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "%s" in "run" mode\n`, s.mockedSystemSeeds[0].label)) 805 } 806 807 func (s *deviceMgrSystemsSuite) TestRebootAlreadyInRunMode(c *C) { 808 devicestate.SetSystemMode(s.mgr, "run") 809 810 s.state.Lock() 811 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 812 { 813 System: s.mockedSystemSeeds[0].label, 814 Model: s.mockedSystemSeeds[0].model.Model(), 815 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 816 }, 817 }) 818 s.state.Unlock() 819 820 // we are already in "run" mode so this should just reboot 821 err := s.mgr.Reboot("", "run") 822 c.Assert(err, IsNil) 823 824 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 825 c.Assert(err, IsNil) 826 c.Check(m, DeepEquals, map[string]string{ 827 "snapd_recovery_mode": "", 828 "snapd_recovery_system": "", 829 }) 830 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 831 c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`) 832 } 833 834 func (s *deviceMgrSystemsSuite) TestRebootUnhappy(c *C) { 835 s.state.Lock() 836 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 837 { 838 System: s.mockedSystemSeeds[0].label, 839 Model: s.mockedSystemSeeds[0].model.Model(), 840 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 841 }, 842 }) 843 s.state.Unlock() 844 845 errUnsupportedActionStr := devicestate.ErrUnsupportedAction.Error() 846 for _, tc := range []struct { 847 systemLabel, mode string 848 expectedErr string 849 }{ 850 {"", "unknown-mode", errUnsupportedActionStr}, 851 {"unknown-system", "run", `stat /.*: no such file or directory`}, 852 {"unknown-system", "unknown-mode", `stat /.*: no such file or directory`}, 853 } { 854 s.restartRequests = nil 855 s.bootloader.BootVars = make(map[string]string) 856 857 err := s.mgr.Reboot(tc.systemLabel, tc.mode) 858 c.Assert(err, ErrorMatches, tc.expectedErr) 859 860 c.Check(s.restartRequests, HasLen, 0) 861 c.Check(s.logbuf.String(), Equals, "") 862 } 863 c.Check(s.logbuf.String(), Equals, "") 864 } 865 866 func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemSuccessfuly(c *C) { 867 err := s.bootloader.SetBootVars(map[string]string{ 868 "try_recovery_system": "1234", 869 "recovery_system_status": "tried", 870 }) 871 c.Assert(err, IsNil) 872 devicestate.SetBootOkRan(s.mgr, true) 873 874 modeenv := boot.Modeenv{ 875 Mode: boot.ModeRun, 876 // the system is in CurrentRecoverySystems 877 CurrentRecoverySystems: []string{"29112019", "1234"}, 878 } 879 err = modeenv.WriteTo("") 880 c.Assert(err, IsNil) 881 882 // system is considered successful, bootenv is cleared, the label is 883 // recorded in tried-systems 884 err = s.mgr.Ensure() 885 c.Assert(err, IsNil) 886 887 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 888 c.Assert(err, IsNil) 889 c.Check(m, DeepEquals, map[string]string{ 890 "try_recovery_system": "", 891 "recovery_system_status": "", 892 }) 893 894 var triedSystems []string 895 s.state.Lock() 896 err = s.state.Get("tried-systems", &triedSystems) 897 c.Assert(err, IsNil) 898 c.Check(triedSystems, DeepEquals, []string{"1234"}) 899 // also logged 900 c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system "1234" was successful`) 901 s.state.Unlock() 902 903 // reset and run again, we need to populate boot variables again 904 err = s.bootloader.SetBootVars(map[string]string{ 905 "try_recovery_system": "1234", 906 "recovery_system_status": "tried", 907 }) 908 c.Assert(err, IsNil) 909 devicestate.SetTriedSystemsRan(s.mgr, false) 910 911 err = s.mgr.Ensure() 912 c.Assert(err, IsNil) 913 s.state.Lock() 914 defer s.state.Unlock() 915 err = s.state.Get("tried-systems", &triedSystems) 916 c.Assert(err, IsNil) 917 // the system was already there, no duplicate got appended 918 c.Assert(triedSystems, DeepEquals, []string{"1234"}) 919 } 920 921 func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemMissingInModeenvUnhappy(c *C) { 922 err := s.bootloader.SetBootVars(map[string]string{ 923 "try_recovery_system": "1234", 924 "recovery_system_status": "tried", 925 }) 926 c.Assert(err, IsNil) 927 devicestate.SetBootOkRan(s.mgr, true) 928 929 modeenv := boot.Modeenv{ 930 Mode: boot.ModeRun, 931 // the system is not in CurrentRecoverySystems 932 CurrentRecoverySystems: []string{"29112019"}, 933 } 934 err = modeenv.WriteTo("") 935 c.Assert(err, IsNil) 936 937 // system is considered successful, bootenv is cleared, the label is 938 // recorded in tried-systems 939 err = s.mgr.Ensure() 940 c.Assert(err, IsNil) 941 942 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 943 c.Assert(err, IsNil) 944 c.Check(m, DeepEquals, map[string]string{ 945 "try_recovery_system": "", 946 "recovery_system_status": "", 947 }) 948 949 var triedSystems []string 950 s.state.Lock() 951 err = s.state.Get("tried-systems", &triedSystems) 952 c.Assert(err, Equals, state.ErrNoState) 953 // also logged 954 c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system outcome error: recovery system "1234" was tried, but is not present in the modeenv CurrentRecoverySystems`) 955 s.state.Unlock() 956 } 957 958 func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemBad(c *C) { 959 // after reboot, the recovery system status is still try 960 err := s.bootloader.SetBootVars(map[string]string{ 961 "try_recovery_system": "1234", 962 "recovery_system_status": "try", 963 }) 964 c.Assert(err, IsNil) 965 devicestate.SetBootOkRan(s.mgr, true) 966 967 // thus the system is considered bad, bootenv is cleared, and system is 968 // not recorded as successful 969 err = s.mgr.Ensure() 970 c.Assert(err, IsNil) 971 972 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 973 c.Assert(err, IsNil) 974 c.Check(m, DeepEquals, map[string]string{ 975 "try_recovery_system": "", 976 "recovery_system_status": "", 977 }) 978 979 var triedSystems []string 980 s.state.Lock() 981 err = s.state.Get("tried-systems", &triedSystems) 982 c.Assert(err, Equals, state.ErrNoState) 983 c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system "1234" failed`) 984 s.state.Unlock() 985 986 // procure an inconsistent state, reset and run again 987 err = s.bootloader.SetBootVars(map[string]string{ 988 "try_recovery_system": "", 989 "recovery_system_status": "try", 990 }) 991 c.Assert(err, IsNil) 992 devicestate.SetTriedSystemsRan(s.mgr, false) 993 994 // clear the log buffer 995 s.logbuf.Reset() 996 997 err = s.mgr.Ensure() 998 c.Assert(err, IsNil) 999 s.state.Lock() 1000 defer s.state.Unlock() 1001 err = s.state.Get("tried-systems", &triedSystems) 1002 c.Assert(err, Equals, state.ErrNoState) 1003 // bootenv got cleared 1004 m, err = s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1005 c.Assert(err, IsNil) 1006 c.Check(m, DeepEquals, map[string]string{ 1007 "try_recovery_system": "", 1008 "recovery_system_status": "", 1009 }) 1010 c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system outcome error: try recovery system is unset but status is "try"`) 1011 c.Check(s.logbuf.String(), testutil.Contains, `inconsistent outcome of a tried recovery system`) 1012 } 1013 1014 func (s *deviceMgrSystemsSuite) TestDeviceManagerEnsureTriedSystemManyLabels(c *C) { 1015 err := s.bootloader.SetBootVars(map[string]string{ 1016 "try_recovery_system": "1234", 1017 "recovery_system_status": "tried", 1018 }) 1019 c.Assert(err, IsNil) 1020 devicestate.SetBootOkRan(s.mgr, true) 1021 1022 s.state.Lock() 1023 s.state.Set("tried-systems", []string{"0000", "1111"}) 1024 s.state.Unlock() 1025 1026 modeenv := boot.Modeenv{ 1027 Mode: boot.ModeRun, 1028 // the system is in CurrentRecoverySystems 1029 CurrentRecoverySystems: []string{"29112019", "1234"}, 1030 } 1031 err = modeenv.WriteTo("") 1032 c.Assert(err, IsNil) 1033 1034 // successful system label is appended 1035 err = s.mgr.Ensure() 1036 c.Assert(err, IsNil) 1037 1038 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1039 c.Assert(err, IsNil) 1040 c.Check(m, DeepEquals, map[string]string{ 1041 "try_recovery_system": "", 1042 "recovery_system_status": "", 1043 }) 1044 1045 s.state.Lock() 1046 defer s.state.Unlock() 1047 1048 var triedSystems []string 1049 err = s.state.Get("tried-systems", &triedSystems) 1050 c.Assert(err, IsNil) 1051 c.Assert(triedSystems, DeepEquals, []string{"0000", "1111", "1234"}) 1052 1053 c.Check(s.logbuf.String(), testutil.Contains, `tried recovery system "1234" was successful`) 1054 } 1055 1056 type deviceMgrSystemsCreateSuite struct { 1057 deviceMgrSystemsBaseSuite 1058 1059 bootloader *bootloadertest.MockRecoveryAwareTrustedAssetsBootloader 1060 } 1061 1062 func (s *deviceMgrSystemsCreateSuite) SetUpTest(c *C) { 1063 s.deviceMgrSystemsBaseSuite.SetUpTest(c) 1064 1065 s.bootloader = s.deviceMgrSystemsBaseSuite.bootloader.WithRecoveryAwareTrustedAssets() 1066 bootloader.Force(s.bootloader) 1067 s.AddCleanup(func() { bootloader.Force(nil) }) 1068 } 1069 1070 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemTasksAndChange(c *C) { 1071 devicestate.SetBootOkRan(s.mgr, true) 1072 1073 s.state.Lock() 1074 defer s.state.Unlock() 1075 chg, err := devicestate.CreateRecoverySystem(s.state, "1234") 1076 c.Assert(err, IsNil) 1077 c.Assert(chg, NotNil) 1078 tsks := chg.Tasks() 1079 c.Check(tsks, HasLen, 2) 1080 tskCreate := tsks[0] 1081 tskFinalize := tsks[1] 1082 c.Check(tskCreate.Summary(), Matches, `Create recovery system with label "1234"`) 1083 c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234"`) 1084 var systemSetupData map[string]interface{} 1085 err = tskCreate.Get("recovery-system-setup", &systemSetupData) 1086 c.Assert(err, IsNil) 1087 c.Assert(systemSetupData, DeepEquals, map[string]interface{}{ 1088 "label": "1234", 1089 "directory": filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"), 1090 "snap-setup-tasks": nil, 1091 }) 1092 1093 var otherTaskID string 1094 err = tskFinalize.Get("recovery-system-setup-task", &otherTaskID) 1095 c.Assert(err, IsNil) 1096 c.Assert(otherTaskID, Equals, tskCreate.ID()) 1097 } 1098 1099 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemTasksWhenDirExists(c *C) { 1100 devicestate.SetBootOkRan(s.mgr, true) 1101 1102 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"), 0755), IsNil) 1103 1104 s.state.Lock() 1105 defer s.state.Unlock() 1106 chg, err := devicestate.CreateRecoverySystem(s.state, "1234") 1107 c.Assert(err, ErrorMatches, `recovery system "1234" already exists`) 1108 c.Check(chg, IsNil) 1109 } 1110 1111 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemNotSeeded(c *C) { 1112 devicestate.SetBootOkRan(s.mgr, true) 1113 1114 s.state.Lock() 1115 defer s.state.Unlock() 1116 s.state.Set("seeded", nil) 1117 1118 chg, err := devicestate.CreateRecoverySystem(s.state, "1234") 1119 c.Assert(err, ErrorMatches, `cannot create new recovery systems until fully seeded`) 1120 c.Check(chg, IsNil) 1121 } 1122 1123 func (s *deviceMgrSystemsCreateSuite) makeSnapInState(c *C, name string, rev snap.Revision) *snap.Info { 1124 snapID := s.ss.AssertedSnapID(name) 1125 if rev.Unset() || rev.Local() { 1126 snapID = "" 1127 } 1128 si := &snap.SideInfo{ 1129 RealName: name, 1130 SnapID: snapID, 1131 Revision: rev, 1132 } 1133 info := snaptest.MakeSnapFileAndDir(c, snapYamls[name], snapFiles[name], si) 1134 // asserted? 1135 if !rev.Unset() && !rev.Local() { 1136 s.setupSnapDecl(c, info, "canonical") 1137 s.setupSnapRevision(c, info, "canonical", rev) 1138 } 1139 snapstate.Set(s.state, info.InstanceName(), &snapstate.SnapState{ 1140 SnapType: string(info.Type()), 1141 Active: true, 1142 Sequence: []*snap.SideInfo{si}, 1143 Current: si.Revision, 1144 }) 1145 1146 return info 1147 } 1148 1149 func (s *deviceMgrSystemsCreateSuite) mockStandardSnapsModeenvAndBootloaderState(c *C) { 1150 s.makeSnapInState(c, "pc", snap.R(1)) 1151 s.makeSnapInState(c, "pc-kernel", snap.R(2)) 1152 s.makeSnapInState(c, "core20", snap.R(3)) 1153 s.makeSnapInState(c, "snapd", snap.R(4)) 1154 1155 err := s.bootloader.SetBootVars(map[string]string{ 1156 "snap_kernel": "pc-kernel_2.snap", 1157 "snap_core": "core20_3.snap", 1158 }) 1159 c.Assert(err, IsNil) 1160 modeenv := boot.Modeenv{ 1161 Mode: "run", 1162 Base: "core20_3.snap", 1163 CurrentKernels: []string{"pc-kernel_2.snap"}, 1164 CurrentRecoverySystems: []string{"othersystem"}, 1165 GoodRecoverySystems: []string{"othersystem"}, 1166 1167 Model: s.model.Model(), 1168 BrandID: s.model.BrandID(), 1169 Grade: string(s.model.Grade()), 1170 ModelSignKeyID: s.model.SignKeyID(), 1171 } 1172 err = modeenv.WriteTo("") 1173 c.Assert(err, IsNil) 1174 } 1175 1176 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemHappy(c *C) { 1177 devicestate.SetBootOkRan(s.mgr, true) 1178 1179 s.state.Lock() 1180 chg, err := devicestate.CreateRecoverySystem(s.state, "1234") 1181 c.Assert(err, IsNil) 1182 c.Assert(chg, NotNil) 1183 tsks := chg.Tasks() 1184 c.Check(tsks, HasLen, 2) 1185 tskCreate := tsks[0] 1186 tskFinalize := tsks[1] 1187 c.Assert(tskCreate.Summary(), Matches, `Create recovery system with label "1234"`) 1188 c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234"`) 1189 1190 s.mockStandardSnapsModeenvAndBootloaderState(c) 1191 1192 s.state.Unlock() 1193 s.settle(c) 1194 s.state.Lock() 1195 1196 c.Assert(chg.Err(), IsNil) 1197 c.Assert(tskCreate.Status(), Equals, state.DoneStatus) 1198 c.Assert(tskFinalize.Status(), Equals, state.DoingStatus) 1199 // a reboot is expected 1200 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 1201 1202 validateCore20Seed(c, "1234", s.model, s.storeSigning.Trusted) 1203 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1204 c.Assert(err, IsNil) 1205 c.Check(m, DeepEquals, map[string]string{ 1206 "try_recovery_system": "1234", 1207 "recovery_system_status": "try", 1208 }) 1209 modeenvAfterCreate, err := boot.ReadModeenv("") 1210 c.Assert(err, IsNil) 1211 c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{ 1212 Mode: "run", 1213 Base: "core20_3.snap", 1214 CurrentKernels: []string{"pc-kernel_2.snap"}, 1215 CurrentRecoverySystems: []string{"othersystem", "1234"}, 1216 GoodRecoverySystems: []string{"othersystem"}, 1217 1218 Model: s.model.Model(), 1219 BrandID: s.model.BrandID(), 1220 Grade: string(s.model.Grade()), 1221 ModelSignKeyID: s.model.SignKeyID(), 1222 // try model is unset as its measured properties are identical 1223 // to current 1224 }) 1225 // verify that new files are tracked correctly 1226 expectedFilesLog := &bytes.Buffer{} 1227 // new snap files are logged in this order 1228 for _, fname := range []string{"snapd_4.snap", "pc-kernel_2.snap", "core20_3.snap", "pc_1.snap"} { 1229 fmt.Fprintln(expectedFilesLog, filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", fname)) 1230 } 1231 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"), 1232 testutil.FileEquals, expectedFilesLog.String()) 1233 1234 // these things happen on snapd startup 1235 state.MockRestarting(s.state, state.RestartUnset) 1236 s.state.Set("tried-systems", []string{"1234"}) 1237 s.bootloader.SetBootVars(map[string]string{ 1238 "try_recovery_system": "", 1239 "recovery_system_status": "", 1240 }) 1241 s.bootloader.SetBootVarsCalls = 0 1242 1243 s.state.Unlock() 1244 s.settle(c) 1245 s.state.Lock() 1246 defer s.state.Unlock() 1247 1248 c.Assert(chg.Err(), IsNil) 1249 c.Check(chg.IsReady(), Equals, true) 1250 c.Assert(tskCreate.Status(), Equals, state.DoneStatus) 1251 c.Assert(tskFinalize.Status(), Equals, state.DoneStatus) 1252 1253 var triedSystemsAfterFinalize []string 1254 err = s.state.Get("tried-systems", &triedSystemsAfterFinalize) 1255 c.Assert(err, Equals, state.ErrNoState) 1256 1257 modeenvAfterFinalize, err := boot.ReadModeenv("") 1258 c.Assert(err, IsNil) 1259 c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{ 1260 Mode: "run", 1261 Base: "core20_3.snap", 1262 CurrentKernels: []string{"pc-kernel_2.snap"}, 1263 CurrentRecoverySystems: []string{"othersystem", "1234"}, 1264 GoodRecoverySystems: []string{"othersystem", "1234"}, 1265 1266 Model: s.model.Model(), 1267 BrandID: s.model.BrandID(), 1268 Grade: string(s.model.Grade()), 1269 ModelSignKeyID: s.model.SignKeyID(), 1270 }) 1271 // no more calls to the bootloader past creating the system 1272 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 1273 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"), testutil.FileAbsent) 1274 } 1275 1276 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemRemodelDownloadingSnapsHappy(c *C) { 1277 devicestate.SetBootOkRan(s.mgr, true) 1278 1279 fooSnap := snaptest.MakeTestSnapWithFiles(c, "name: foo\nversion: 1.0\nbase: core20", nil) 1280 barSnap := snaptest.MakeTestSnapWithFiles(c, "name: bar\nversion: 1.0\nbase: core20", nil) 1281 s.state.Lock() 1282 // fake downloads are a nop 1283 tSnapsup1 := s.state.NewTask("fake-download", "dummy task carrying snap setup") 1284 tSnapsup2 := s.state.NewTask("fake-download", "dummy task carrying snap setup") 1285 // both snaps are asserted 1286 snapsupFoo := snapstate.SnapSetup{ 1287 SideInfo: &snap.SideInfo{RealName: "foo", SnapID: s.ss.AssertedSnapID("foo"), Revision: snap.R(99)}, 1288 SnapPath: fooSnap, 1289 } 1290 s.setupSnapDeclForNameAndID(c, "foo", s.ss.AssertedSnapID("foo"), "canonical") 1291 s.setupSnapRevisionForFileAndID(c, fooSnap, s.ss.AssertedSnapID("foo"), "canonical", snap.R(99)) 1292 snapsupBar := snapstate.SnapSetup{ 1293 SideInfo: &snap.SideInfo{RealName: "bar", SnapID: s.ss.AssertedSnapID("bar"), Revision: snap.R(100)}, 1294 SnapPath: barSnap, 1295 } 1296 s.setupSnapDeclForNameAndID(c, "bar", s.ss.AssertedSnapID("bar"), "canonical") 1297 s.setupSnapRevisionForFileAndID(c, barSnap, s.ss.AssertedSnapID("bar"), "canonical", snap.R(100)) 1298 // when download completes, the files will be at /var/lib/snapd/snap 1299 c.Assert(os.MkdirAll(filepath.Dir(snapsupFoo.MountFile()), 0755), IsNil) 1300 c.Assert(os.Rename(fooSnap, snapsupFoo.MountFile()), IsNil) 1301 c.Assert(os.MkdirAll(filepath.Dir(snapsupBar.MountFile()), 0755), IsNil) 1302 c.Assert(os.Rename(barSnap, snapsupBar.MountFile()), IsNil) 1303 tSnapsup1.Set("snap-setup", snapsupFoo) 1304 tSnapsup2.Set("snap-setup", snapsupBar) 1305 1306 tss, err := devicestate.CreateRecoverySystemTasks(s.state, "1234", []string{tSnapsup1.ID(), tSnapsup2.ID()}) 1307 c.Assert(err, IsNil) 1308 tsks := tss.Tasks() 1309 c.Check(tsks, HasLen, 2) 1310 tskCreate := tsks[0] 1311 tskFinalize := tsks[1] 1312 c.Assert(tskCreate.Summary(), Matches, `Create recovery system with label "1234"`) 1313 c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234"`) 1314 var systemSetupData map[string]interface{} 1315 err = tskCreate.Get("recovery-system-setup", &systemSetupData) 1316 c.Assert(err, IsNil) 1317 c.Assert(systemSetupData, DeepEquals, map[string]interface{}{ 1318 "label": "1234", 1319 "directory": filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"), 1320 "snap-setup-tasks": []interface{}{tSnapsup1.ID(), tSnapsup2.ID()}, 1321 }) 1322 tss.WaitFor(tSnapsup1) 1323 tss.WaitFor(tSnapsup2) 1324 // add the dummy tasks to the change 1325 chg := s.state.NewChange("create-recovery-system", "create recovery system") 1326 chg.AddTask(tSnapsup1) 1327 chg.AddTask(tSnapsup2) 1328 chg.AddAll(tss) 1329 1330 // downloads are only accepted if the tasks are executed as part of 1331 // remodel, so procure a new model 1332 newModel := s.brands.Model("canonical", "pc-20", map[string]interface{}{ 1333 "architecture": "amd64", 1334 // UC20 1335 "grade": "dangerous", 1336 "base": "core20", 1337 "snaps": []interface{}{ 1338 map[string]interface{}{ 1339 "name": "pc-kernel", 1340 "id": s.ss.AssertedSnapID("pc-kernel"), 1341 "type": "kernel", 1342 "default-channel": "20", 1343 }, 1344 map[string]interface{}{ 1345 "name": "pc", 1346 "id": s.ss.AssertedSnapID("pc"), 1347 "type": "gadget", 1348 "default-channel": "20", 1349 }, 1350 map[string]interface{}{ 1351 "name": "foo", 1352 "id": s.ss.AssertedSnapID("foo"), 1353 "presence": "required", 1354 }, 1355 map[string]interface{}{ 1356 "name": "bar", 1357 "presence": "required", 1358 }, 1359 }, 1360 "revision": "2", 1361 }) 1362 chg.Set("new-model", string(asserts.Encode(newModel))) 1363 1364 s.mockStandardSnapsModeenvAndBootloaderState(c) 1365 1366 s.state.Unlock() 1367 s.settle(c) 1368 s.state.Lock() 1369 defer s.state.Unlock() 1370 1371 c.Assert(chg.Err(), IsNil) 1372 c.Assert(tskCreate.Status(), Equals, state.DoneStatus) 1373 c.Assert(tskFinalize.Status(), Equals, state.DoingStatus) 1374 // a reboot is expected 1375 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 1376 1377 validateCore20Seed(c, "1234", newModel, s.storeSigning.Trusted, "foo", "bar") 1378 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1379 c.Assert(err, IsNil) 1380 c.Check(m, DeepEquals, map[string]string{ 1381 "try_recovery_system": "1234", 1382 "recovery_system_status": "try", 1383 }) 1384 modeenvAfterCreate, err := boot.ReadModeenv("") 1385 c.Assert(err, IsNil) 1386 c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{ 1387 Mode: "run", 1388 Base: "core20_3.snap", 1389 CurrentKernels: []string{"pc-kernel_2.snap"}, 1390 CurrentRecoverySystems: []string{"othersystem", "1234"}, 1391 GoodRecoverySystems: []string{"othersystem"}, 1392 1393 Model: s.model.Model(), 1394 BrandID: s.model.BrandID(), 1395 Grade: string(s.model.Grade()), 1396 ModelSignKeyID: s.model.SignKeyID(), 1397 // try model is unset as its measured properties are identical 1398 }) 1399 // verify that new files are tracked correctly 1400 expectedFilesLog := &bytes.Buffer{} 1401 // new snap files are logged in this order 1402 for _, fname := range []string{ 1403 "snapd_4.snap", "pc-kernel_2.snap", "core20_3.snap", "pc_1.snap", 1404 "foo_99.snap", "bar_100.snap", 1405 } { 1406 fmt.Fprintln(expectedFilesLog, filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", fname)) 1407 } 1408 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"), 1409 testutil.FileEquals, expectedFilesLog.String()) 1410 1411 // these things happen on snapd startup 1412 state.MockRestarting(s.state, state.RestartUnset) 1413 s.state.Set("tried-systems", []string{"1234"}) 1414 s.bootloader.SetBootVars(map[string]string{ 1415 "try_recovery_system": "", 1416 "recovery_system_status": "", 1417 }) 1418 s.bootloader.SetBootVarsCalls = 0 1419 1420 s.state.Unlock() 1421 s.settle(c) 1422 s.state.Lock() 1423 1424 c.Assert(chg.Err(), IsNil) 1425 c.Check(chg.IsReady(), Equals, true) 1426 c.Assert(tskCreate.Status(), Equals, state.DoneStatus) 1427 c.Assert(tskFinalize.Status(), Equals, state.DoneStatus) 1428 1429 // this would be part of a remodel so some state is cleaned up only at the end of remodel change 1430 var triedSystemsAfterFinalize []string 1431 err = s.state.Get("tried-systems", &triedSystemsAfterFinalize) 1432 c.Assert(err, IsNil) 1433 c.Check(triedSystemsAfterFinalize, DeepEquals, []string{"1234"}) 1434 1435 modeenvAfterFinalize, err := boot.ReadModeenv("") 1436 c.Assert(err, IsNil) 1437 c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{ 1438 Mode: "run", 1439 Base: "core20_3.snap", 1440 CurrentKernels: []string{"pc-kernel_2.snap"}, 1441 // the system is kept in the current list 1442 CurrentRecoverySystems: []string{"othersystem", "1234"}, 1443 // but not promoted to good systems yet 1444 GoodRecoverySystems: []string{"othersystem"}, 1445 1446 Model: s.model.Model(), 1447 BrandID: s.model.BrandID(), 1448 Grade: string(s.model.Grade()), 1449 ModelSignKeyID: s.model.SignKeyID(), 1450 }) 1451 // no more calls to the bootloader past creating the system 1452 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 1453 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", "1234", "snapd-new-file-log"), testutil.FileAbsent) 1454 } 1455 1456 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemRemodelDownloadingMissingSnap(c *C) { 1457 devicestate.SetBootOkRan(s.mgr, true) 1458 1459 fooSnap := snaptest.MakeTestSnapWithFiles(c, "name: foo\nversion: 1.0\nbase: core20", nil) 1460 s.state.Lock() 1461 defer s.state.Unlock() 1462 // fake downloads are a nop 1463 tSnapsup1 := s.state.NewTask("fake-download", "dummy task carrying snap setup") 1464 // both snaps are asserted 1465 snapsupFoo := snapstate.SnapSetup{ 1466 SideInfo: &snap.SideInfo{RealName: "foo", SnapID: s.ss.AssertedSnapID("foo"), Revision: snap.R(99)}, 1467 SnapPath: fooSnap, 1468 } 1469 tSnapsup1.Set("snap-setup", snapsupFoo) 1470 1471 tss, err := devicestate.CreateRecoverySystemTasks(s.state, "1234missingdownload", []string{tSnapsup1.ID()}) 1472 c.Assert(err, IsNil) 1473 tsks := tss.Tasks() 1474 c.Check(tsks, HasLen, 2) 1475 tskCreate := tsks[0] 1476 tskFinalize := tsks[1] 1477 c.Assert(tskCreate.Summary(), Matches, `Create recovery system with label "1234missingdownload"`) 1478 c.Check(tskFinalize.Summary(), Matches, `Finalize recovery system with label "1234missingdownload"`) 1479 var systemSetupData map[string]interface{} 1480 err = tskCreate.Get("recovery-system-setup", &systemSetupData) 1481 c.Assert(err, IsNil) 1482 c.Assert(systemSetupData, DeepEquals, map[string]interface{}{ 1483 "label": "1234missingdownload", 1484 "directory": filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234missingdownload"), 1485 "snap-setup-tasks": []interface{}{tSnapsup1.ID()}, 1486 }) 1487 tss.WaitFor(tSnapsup1) 1488 // add the dummy task to the change 1489 chg := s.state.NewChange("create-recovery-system", "create recovery system") 1490 chg.AddTask(tSnapsup1) 1491 chg.AddAll(tss) 1492 1493 // downloads are only accepted if the tasks are executed as part of 1494 // remodel, so procure a new model 1495 newModel := s.brands.Model("canonical", "pc-20", map[string]interface{}{ 1496 "architecture": "amd64", 1497 // UC20 1498 "grade": "dangerous", 1499 "base": "core20", 1500 "snaps": []interface{}{ 1501 map[string]interface{}{ 1502 "name": "pc-kernel", 1503 "id": s.ss.AssertedSnapID("pc-kernel"), 1504 "type": "kernel", 1505 "default-channel": "20", 1506 }, 1507 map[string]interface{}{ 1508 "name": "pc", 1509 "id": s.ss.AssertedSnapID("pc"), 1510 "type": "gadget", 1511 "default-channel": "20", 1512 }, 1513 // we have a download task for snap foo, but not for bar 1514 map[string]interface{}{ 1515 "name": "bar", 1516 "presence": "required", 1517 }, 1518 }, 1519 "revision": "2", 1520 }) 1521 chg.Set("new-model", string(asserts.Encode(newModel))) 1522 1523 s.mockStandardSnapsModeenvAndBootloaderState(c) 1524 1525 s.state.Unlock() 1526 s.settle(c) 1527 s.state.Lock() 1528 1529 c.Assert(chg.Err(), ErrorMatches, `(?s).*cannot create a recovery system.*internal error: non-essential but required snap "bar" not present.`) 1530 c.Assert(tskCreate.Status(), Equals, state.ErrorStatus) 1531 c.Assert(tskFinalize.Status(), Equals, state.HoldStatus) 1532 // a reboot is expected 1533 c.Check(s.restartRequests, HasLen, 0) 1534 // single bootloader call to clear any recovery system variables 1535 c.Check(s.bootloader.SetBootVarsCalls, Equals, 1) 1536 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1537 c.Assert(err, IsNil) 1538 c.Check(m, DeepEquals, map[string]string{ 1539 "try_recovery_system": "", 1540 "recovery_system_status": "", 1541 }) 1542 // system directory was removed 1543 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234missingdownload"), testutil.FileAbsent) 1544 } 1545 1546 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemUndo(c *C) { 1547 devicestate.SetBootOkRan(s.mgr, true) 1548 1549 s.state.Lock() 1550 chg, err := devicestate.CreateRecoverySystem(s.state, "1234undo") 1551 c.Assert(err, IsNil) 1552 c.Assert(chg, NotNil) 1553 tsks := chg.Tasks() 1554 c.Check(tsks, HasLen, 2) 1555 tskCreate := tsks[0] 1556 tskFinalize := tsks[1] 1557 terr := s.state.NewTask("error-trigger", "provoking total undo") 1558 terr.WaitFor(tskFinalize) 1559 chg.AddTask(terr) 1560 1561 s.mockStandardSnapsModeenvAndBootloaderState(c) 1562 1563 snaptest.PopulateDir(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps"), [][]string{ 1564 {"core20_10.snap", "canary"}, 1565 {"some-snap_1.snap", "canary"}, 1566 }) 1567 1568 s.state.Unlock() 1569 s.settle(c) 1570 s.state.Lock() 1571 1572 c.Assert(chg.Err(), IsNil) 1573 c.Assert(tskCreate.Status(), Equals, state.DoneStatus) 1574 c.Assert(tskFinalize.Status(), Equals, state.DoingStatus) 1575 // a reboot is expected 1576 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 1577 // sanity check asserted snaps location 1578 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234undo"), testutil.FilePresent) 1579 p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*")) 1580 c.Assert(err, IsNil) 1581 c.Check(p, DeepEquals, []string{ 1582 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"), 1583 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"), 1584 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_2.snap"), 1585 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_1.snap"), 1586 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"), 1587 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"), 1588 }) 1589 // do more extensive validation 1590 validateCore20Seed(c, "1234undo", s.model, s.storeSigning.Trusted) 1591 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1592 c.Assert(err, IsNil) 1593 c.Check(m, DeepEquals, map[string]string{ 1594 "try_recovery_system": "1234undo", 1595 "recovery_system_status": "try", 1596 }) 1597 modeenvAfterCreate, err := boot.ReadModeenv("") 1598 c.Assert(err, IsNil) 1599 c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{ 1600 Mode: "run", 1601 Base: "core20_3.snap", 1602 CurrentKernels: []string{"pc-kernel_2.snap"}, 1603 CurrentRecoverySystems: []string{"othersystem", "1234undo"}, 1604 GoodRecoverySystems: []string{"othersystem"}, 1605 1606 Model: s.model.Model(), 1607 BrandID: s.model.BrandID(), 1608 Grade: string(s.model.Grade()), 1609 ModelSignKeyID: s.model.SignKeyID(), 1610 }) 1611 1612 // these things happen on snapd startup 1613 state.MockRestarting(s.state, state.RestartUnset) 1614 s.state.Set("tried-systems", []string{"1234undo"}) 1615 s.bootloader.SetBootVars(map[string]string{ 1616 "try_recovery_system": "", 1617 "recovery_system_status": "", 1618 }) 1619 s.bootloader.SetBootVarsCalls = 0 1620 1621 s.state.Unlock() 1622 s.settle(c) 1623 s.state.Lock() 1624 defer s.state.Unlock() 1625 1626 c.Assert(chg.Err(), ErrorMatches, "(?s)cannot perform the following tasks.* provoking total undo.*") 1627 c.Check(chg.IsReady(), Equals, true) 1628 c.Assert(tskCreate.Status(), Equals, state.UndoneStatus) 1629 c.Assert(tskFinalize.Status(), Equals, state.UndoneStatus) 1630 1631 var triedSystemsAfter []string 1632 err = s.state.Get("tried-systems", &triedSystemsAfter) 1633 c.Assert(err, Equals, state.ErrNoState) 1634 1635 modeenvAfterFinalize, err := boot.ReadModeenv("") 1636 c.Assert(err, IsNil) 1637 c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{ 1638 Mode: "run", 1639 Base: "core20_3.snap", 1640 CurrentKernels: []string{"pc-kernel_2.snap"}, 1641 CurrentRecoverySystems: []string{"othersystem"}, 1642 GoodRecoverySystems: []string{"othersystem"}, 1643 1644 Model: s.model.Model(), 1645 BrandID: s.model.BrandID(), 1646 Grade: string(s.model.Grade()), 1647 ModelSignKeyID: s.model.SignKeyID(), 1648 }) 1649 // no more calls to the bootloader 1650 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 1651 // system directory was removed 1652 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234undo"), testutil.FileAbsent) 1653 // only the canary files are left now 1654 p, err = filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*")) 1655 c.Assert(err, IsNil) 1656 c.Check(p, DeepEquals, []string{ 1657 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"), 1658 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"), 1659 }) 1660 } 1661 1662 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemFinalizeErrsWhenSystemFailed(c *C) { 1663 devicestate.SetBootOkRan(s.mgr, true) 1664 1665 s.state.Lock() 1666 chg, err := devicestate.CreateRecoverySystem(s.state, "1234") 1667 c.Assert(err, IsNil) 1668 c.Assert(chg, NotNil) 1669 tsks := chg.Tasks() 1670 c.Check(tsks, HasLen, 2) 1671 tskCreate := tsks[0] 1672 tskFinalize := tsks[1] 1673 terr := s.state.NewTask("error-trigger", "provoking total undo") 1674 terr.WaitFor(tskFinalize) 1675 chg.AddTask(terr) 1676 1677 s.mockStandardSnapsModeenvAndBootloaderState(c) 1678 1679 s.state.Unlock() 1680 s.settle(c) 1681 s.state.Lock() 1682 1683 c.Assert(chg.Err(), IsNil) 1684 c.Assert(tskCreate.Status(), Equals, state.DoneStatus) 1685 c.Assert(tskFinalize.Status(), Equals, state.DoingStatus) 1686 // a reboot is expected 1687 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 1688 1689 validateCore20Seed(c, "1234", s.model, s.storeSigning.Trusted) 1690 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1691 c.Assert(err, IsNil) 1692 c.Check(m, DeepEquals, map[string]string{ 1693 "try_recovery_system": "1234", 1694 "recovery_system_status": "try", 1695 }) 1696 modeenvAfterCreate, err := boot.ReadModeenv("") 1697 c.Assert(err, IsNil) 1698 c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{ 1699 Mode: "run", 1700 Base: "core20_3.snap", 1701 CurrentKernels: []string{"pc-kernel_2.snap"}, 1702 CurrentRecoverySystems: []string{"othersystem", "1234"}, 1703 GoodRecoverySystems: []string{"othersystem"}, 1704 1705 Model: s.model.Model(), 1706 BrandID: s.model.BrandID(), 1707 Grade: string(s.model.Grade()), 1708 ModelSignKeyID: s.model.SignKeyID(), 1709 }) 1710 1711 // these things happen on snapd startup 1712 state.MockRestarting(s.state, state.RestartUnset) 1713 // after reboot the relevant startup code identified that the tried 1714 // system failed to operate properly 1715 s.state.Set("tried-systems", []string{}) 1716 s.bootloader.SetBootVars(map[string]string{ 1717 "try_recovery_system": "", 1718 "recovery_system_status": "", 1719 }) 1720 s.bootloader.SetBootVarsCalls = 0 1721 1722 s.state.Unlock() 1723 s.settle(c) 1724 s.state.Lock() 1725 defer s.state.Unlock() 1726 1727 c.Assert(chg.Err(), ErrorMatches, `(?s)cannot perform the following tasks.* Finalize recovery system with label "1234" \(cannot promote recovery system "1234": system has not been successfully tried\)`) 1728 c.Check(chg.IsReady(), Equals, true) 1729 c.Assert(tskCreate.Status(), Equals, state.UndoneStatus) 1730 c.Assert(tskFinalize.Status(), Equals, state.ErrorStatus) 1731 1732 var triedSystemsAfter []string 1733 err = s.state.Get("tried-systems", &triedSystemsAfter) 1734 c.Assert(err, IsNil) 1735 c.Assert(triedSystemsAfter, HasLen, 0) 1736 1737 modeenvAfterFinalize, err := boot.ReadModeenv("") 1738 c.Assert(err, IsNil) 1739 c.Check(modeenvAfterFinalize, testutil.JsonEquals, boot.Modeenv{ 1740 Mode: "run", 1741 Base: "core20_3.snap", 1742 CurrentKernels: []string{"pc-kernel_2.snap"}, 1743 CurrentRecoverySystems: []string{"othersystem"}, 1744 GoodRecoverySystems: []string{"othersystem"}, 1745 1746 Model: s.model.Model(), 1747 BrandID: s.model.BrandID(), 1748 Grade: string(s.model.Grade()), 1749 ModelSignKeyID: s.model.SignKeyID(), 1750 }) 1751 // no more calls to the bootloader 1752 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 1753 // seed directory was removed 1754 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"), testutil.FileAbsent) 1755 // all common snaps were cleaned up 1756 p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*")) 1757 c.Assert(err, IsNil) 1758 c.Check(p, HasLen, 0) 1759 } 1760 1761 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemErrCleanup(c *C) { 1762 devicestate.SetBootOkRan(s.mgr, true) 1763 1764 s.state.Lock() 1765 chg, err := devicestate.CreateRecoverySystem(s.state, "1234error") 1766 c.Assert(err, IsNil) 1767 c.Assert(chg, NotNil) 1768 tsks := chg.Tasks() 1769 c.Check(tsks, HasLen, 2) 1770 tskCreate := tsks[0] 1771 tskFinalize := tsks[1] 1772 1773 s.mockStandardSnapsModeenvAndBootloaderState(c) 1774 s.bootloader.SetBootVarsCalls = 0 1775 1776 s.bootloader.SetErrFunc = func() error { 1777 c.Logf("boot calls: %v", s.bootloader.SetBootVarsCalls) 1778 // for simplicity error out only when we try to set the recovery 1779 // system variables in bootenv (and not in the cleanup path) 1780 if s.bootloader.SetBootVarsCalls == 1 { 1781 return fmt.Errorf("mock bootloader error") 1782 } 1783 return nil 1784 } 1785 1786 snaptest.PopulateDir(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps"), [][]string{ 1787 {"core20_10.snap", "canary"}, 1788 {"some-snap_1.snap", "canary"}, 1789 }) 1790 1791 s.state.Unlock() 1792 s.settle(c) 1793 s.state.Lock() 1794 defer s.state.Unlock() 1795 1796 c.Assert(chg.Err(), ErrorMatches, `(?s)cannot perform the following tasks.* \(cannot attempt booting into recovery system "1234error": mock bootloader error\)`) 1797 c.Check(chg.IsReady(), Equals, true) 1798 c.Assert(tskCreate.Status(), Equals, state.ErrorStatus) 1799 c.Assert(tskFinalize.Status(), Equals, state.HoldStatus) 1800 1801 c.Check(s.restartRequests, HasLen, 0) 1802 // sanity check asserted snaps location 1803 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234error"), testutil.FileAbsent) 1804 p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*")) 1805 c.Assert(err, IsNil) 1806 c.Check(p, DeepEquals, []string{ 1807 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"), 1808 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"), 1809 }) 1810 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1811 c.Assert(err, IsNil) 1812 c.Check(m, DeepEquals, map[string]string{ 1813 "try_recovery_system": "", 1814 "recovery_system_status": "", 1815 }) 1816 modeenvAfterCreate, err := boot.ReadModeenv("") 1817 c.Assert(err, IsNil) 1818 c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{ 1819 Mode: "run", 1820 Base: "core20_3.snap", 1821 CurrentKernels: []string{"pc-kernel_2.snap"}, 1822 CurrentRecoverySystems: []string{"othersystem"}, 1823 GoodRecoverySystems: []string{"othersystem"}, 1824 1825 Model: s.model.Model(), 1826 BrandID: s.model.BrandID(), 1827 Grade: string(s.model.Grade()), 1828 ModelSignKeyID: s.model.SignKeyID(), 1829 }) 1830 } 1831 1832 func (s *deviceMgrSystemsCreateSuite) TestDeviceManagerCreateRecoverySystemReboot(c *C) { 1833 devicestate.SetBootOkRan(s.mgr, true) 1834 1835 s.state.Lock() 1836 chg, err := devicestate.CreateRecoverySystem(s.state, "1234reboot") 1837 c.Assert(err, IsNil) 1838 c.Assert(chg, NotNil) 1839 tsks := chg.Tasks() 1840 c.Check(tsks, HasLen, 2) 1841 tskCreate := tsks[0] 1842 tskFinalize := tsks[1] 1843 1844 s.mockStandardSnapsModeenvAndBootloaderState(c) 1845 s.bootloader.SetBootVarsCalls = 0 1846 1847 setBootVarsOk := true 1848 s.bootloader.SetErrFunc = func() error { 1849 c.Logf("boot calls: %v", s.bootloader.SetBootVarsCalls) 1850 if setBootVarsOk { 1851 return nil 1852 } 1853 return fmt.Errorf("unexpected call") 1854 } 1855 1856 snaptest.PopulateDir(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps"), [][]string{ 1857 {"core20_10.snap", "canary"}, 1858 {"some-snap_1.snap", "canary"}, 1859 }) 1860 1861 s.state.Unlock() 1862 s.settle(c) 1863 s.state.Lock() 1864 1865 // so far so good 1866 c.Assert(chg.Err(), IsNil) 1867 c.Assert(tskCreate.Status(), Equals, state.DoneStatus) 1868 c.Assert(tskFinalize.Status(), Equals, state.DoingStatus) 1869 // a reboot is expected 1870 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 1871 c.Check(s.bootloader.SetBootVarsCalls, Equals, 2) 1872 s.restartRequests = nil 1873 1874 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234reboot"), testutil.FilePresent) 1875 // since we can't inject a panic into the task and recover from it in 1876 // the tests, reset the task states to as state which we would have if 1877 // the system unexpectedly reboots before the task is marked as done 1878 tskCreate.SetStatus(state.DoStatus) 1879 tskFinalize.SetStatus(state.DoStatus) 1880 state.MockRestarting(s.state, state.RestartUnset) 1881 // we may have rebooted just before the task was marked as done, in 1882 // which case tried systems would be populated 1883 s.state.Set("tried-systems", []string{"1234undo"}) 1884 s.bootloader.SetBootVars(map[string]string{ 1885 "try_recovery_system": "", 1886 "recovery_system_status": "", 1887 }) 1888 setBootVarsOk = false 1889 1890 s.state.Unlock() 1891 s.settle(c) 1892 s.state.Lock() 1893 defer s.state.Unlock() 1894 1895 c.Assert(chg.Err(), ErrorMatches, `(?s)cannot perform the following tasks.* \(cannot create a recovery system with label "1234reboot" for pc-20: system "1234reboot" already exists\)`) 1896 c.Assert(tskCreate.Status(), Equals, state.ErrorStatus) 1897 c.Assert(tskFinalize.Status(), Equals, state.HoldStatus) 1898 c.Check(s.restartRequests, HasLen, 0) 1899 1900 // recovery system was removed 1901 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234reboot"), testutil.FileAbsent) 1902 // and so were the new snaps 1903 p, err := filepath.Glob(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/*")) 1904 c.Assert(err, IsNil) 1905 c.Check(p, DeepEquals, []string{ 1906 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_10.snap"), 1907 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/some-snap_1.snap"), 1908 }) 1909 m, err := s.bootloader.GetBootVars("try_recovery_system", "recovery_system_status") 1910 c.Assert(err, IsNil) 1911 c.Check(m, DeepEquals, map[string]string{ 1912 "try_recovery_system": "", 1913 "recovery_system_status": "", 1914 }) 1915 modeenvAfterCreate, err := boot.ReadModeenv("") 1916 c.Assert(err, IsNil) 1917 c.Check(modeenvAfterCreate, testutil.JsonEquals, boot.Modeenv{ 1918 Mode: "run", 1919 Base: "core20_3.snap", 1920 CurrentKernels: []string{"pc-kernel_2.snap"}, 1921 CurrentRecoverySystems: []string{"othersystem"}, 1922 GoodRecoverySystems: []string{"othersystem"}, 1923 1924 Model: s.model.Model(), 1925 BrandID: s.model.BrandID(), 1926 Grade: string(s.model.Grade()), 1927 ModelSignKeyID: s.model.SignKeyID(), 1928 }) 1929 var triedSystems []string 1930 s.state.Get("tried-systems", &triedSystems) 1931 c.Check(triedSystems, HasLen, 0) 1932 } 1933 1934 type systemSnapTrackingSuite struct { 1935 deviceMgrSystemsBaseSuite 1936 } 1937 1938 var _ = Suite(&systemSnapTrackingSuite{}) 1939 1940 func (s *systemSnapTrackingSuite) TestSnapFileTracking(c *C) { 1941 otherDir := c.MkDir() 1942 systemDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234") 1943 flog := filepath.Join(otherDir, "files-log") 1944 1945 snaptest.PopulateDir(systemDir, [][]string{ 1946 {"this-will-be-removed", "canary"}, 1947 {"this-one-too", "canary"}, 1948 {"this-one-stays", "canary"}, 1949 {"snaps/to-be-removed", "canary"}, 1950 {"snaps/this-one-stays", "canary"}, 1951 }) 1952 1953 // complain loudly if the file is under unexpected location 1954 err := devicestate.LogNewSystemSnapFile(flog, filepath.Join(otherDir, "some-file")) 1955 c.Assert(err, ErrorMatches, `internal error: unexpected recovery system snap location ".*/some-file"`) 1956 c.Check(flog, testutil.FileAbsent) 1957 1958 expectedContent := &bytes.Buffer{} 1959 1960 for _, p := range []string{ 1961 filepath.Join(systemDir, "this-will-be-removed"), 1962 filepath.Join(systemDir, "this-one-too"), 1963 filepath.Join(systemDir, "does-not-exist"), 1964 filepath.Join(systemDir, "snaps/to-be-removed"), 1965 } { 1966 err = devicestate.LogNewSystemSnapFile(flog, p) 1967 c.Check(err, IsNil) 1968 fmt.Fprintln(expectedContent, p) 1969 // logged content is accumulated 1970 c.Check(flog, testutil.FileEquals, expectedContent.String()) 1971 } 1972 1973 // add some empty spaces to log file, which should get ignored when purging 1974 f, err := os.OpenFile(flog, os.O_APPEND, 0644) 1975 c.Assert(err, IsNil) 1976 defer f.Close() 1977 fmt.Fprintln(f, " ") 1978 fmt.Fprintln(f, "") 1979 // and double some entries 1980 fmt.Fprintln(f, filepath.Join(systemDir, "this-will-be-removed")) 1981 1982 err = devicestate.PurgeNewSystemSnapFiles(flog) 1983 c.Assert(err, IsNil) 1984 1985 // those are removed 1986 for _, p := range []string{ 1987 filepath.Join(systemDir, "this-will-be-removed"), 1988 filepath.Join(systemDir, "this-one-too"), 1989 filepath.Join(systemDir, "snaps/to-be-removed"), 1990 } { 1991 c.Check(p, testutil.FileAbsent) 1992 } 1993 c.Check(filepath.Join(systemDir, "this-one-stays"), testutil.FileEquals, "canary") 1994 c.Check(filepath.Join(systemDir, "snaps/this-one-stays"), testutil.FileEquals, "canary") 1995 } 1996 1997 func (s *systemSnapTrackingSuite) TestSnapFilePurgeWhenNoLog(c *C) { 1998 otherDir := c.MkDir() 1999 flog := filepath.Join(otherDir, "files-log") 2000 // purge is still happy even if log file does not exist 2001 err := devicestate.PurgeNewSystemSnapFiles(flog) 2002 c.Assert(err, IsNil) 2003 }