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