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