github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/asserts/assertstest" 33 "github.com/snapcore/snapd/boot" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/logger" 36 "github.com/snapcore/snapd/overlord/auth" 37 "github.com/snapcore/snapd/overlord/devicestate" 38 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 39 "github.com/snapcore/snapd/overlord/state" 40 "github.com/snapcore/snapd/seed" 41 "github.com/snapcore/snapd/seed/seedtest" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/snaptest" 44 "github.com/snapcore/snapd/strutil" 45 ) 46 47 type mockedSystemSeed struct { 48 label string 49 model *asserts.Model 50 brand *asserts.Account 51 } 52 53 type deviceMgrSystemsSuite struct { 54 deviceMgrBaseSuite 55 56 logbuf *bytes.Buffer 57 mockedSystemSeeds []mockedSystemSeed 58 } 59 60 var _ = Suite(&deviceMgrSystemsSuite{}) 61 62 func (s *deviceMgrSystemsSuite) SetUpTest(c *C) { 63 s.deviceMgrBaseSuite.SetUpTest(c) 64 65 s.brands.Register("other-brand", brandPrivKey3, map[string]interface{}{ 66 "display-name": "other publisher", 67 }) 68 s.state.Lock() 69 defer s.state.Unlock() 70 s.makeModelAssertionInState(c, "canonical", "pc-20", map[string]interface{}{ 71 "architecture": "amd64", 72 // UC20 73 "grade": "dangerous", 74 "base": "core20", 75 "snaps": []interface{}{ 76 map[string]interface{}{ 77 "name": "pc-kernel", 78 "id": snaptest.AssertedSnapID("oc-kernel"), 79 "type": "kernel", 80 "default-channel": "20", 81 }, 82 map[string]interface{}{ 83 "name": "pc", 84 "id": snaptest.AssertedSnapID("pc"), 85 "type": "gadget", 86 "default-channel": "20", 87 }, 88 }, 89 }) 90 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 91 Brand: "canonical", 92 Model: "pc-20", 93 Serial: "serialserialserial", 94 }) 95 assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("my-brand")...) 96 assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("other-brand")...) 97 98 // now create a minimal uc20 seed dir with snaps/assertions 99 seed20 := &seedtest.TestingSeed20{ 100 SeedSnaps: seedtest.SeedSnaps{ 101 StoreSigning: s.storeSigning, 102 Brands: s.brands, 103 }, 104 105 SeedDir: dirs.SnapSeedDir, 106 } 107 108 restore := seed.MockTrusted(s.storeSigning.Trusted) 109 s.AddCleanup(restore) 110 111 myBrandAcc := s.brands.Account("my-brand") 112 otherBrandAcc := s.brands.Account("other-brand") 113 114 // add essential snaps 115 seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 116 seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 117 seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 118 seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 119 120 model1 := seed20.MakeSeed(c, "20191119", "my-brand", "my-model", map[string]interface{}{ 121 "display-name": "my fancy model", 122 "architecture": "amd64", 123 "base": "core20", 124 "snaps": []interface{}{ 125 map[string]interface{}{ 126 "name": "pc-kernel", 127 "id": seed20.AssertedSnapID("pc-kernel"), 128 "type": "kernel", 129 "default-channel": "20", 130 }, 131 map[string]interface{}{ 132 "name": "pc", 133 "id": seed20.AssertedSnapID("pc"), 134 "type": "gadget", 135 "default-channel": "20", 136 }}, 137 }, nil) 138 model2 := seed20.MakeSeed(c, "20200318", "my-brand", "my-model-2", map[string]interface{}{ 139 "display-name": "same brand different model", 140 "architecture": "amd64", 141 "base": "core20", 142 "snaps": []interface{}{ 143 map[string]interface{}{ 144 "name": "pc-kernel", 145 "id": seed20.AssertedSnapID("pc-kernel"), 146 "type": "kernel", 147 "default-channel": "20", 148 }, 149 map[string]interface{}{ 150 "name": "pc", 151 "id": seed20.AssertedSnapID("pc"), 152 "type": "gadget", 153 "default-channel": "20", 154 }}, 155 }, nil) 156 model3 := seed20.MakeSeed(c, "other-20200318", "other-brand", "other-model", map[string]interface{}{ 157 "display-name": "different brand different model", 158 "architecture": "amd64", 159 "base": "core20", 160 "snaps": []interface{}{ 161 map[string]interface{}{ 162 "name": "pc-kernel", 163 "id": seed20.AssertedSnapID("pc-kernel"), 164 "type": "kernel", 165 "default-channel": "20", 166 }, 167 map[string]interface{}{ 168 "name": "pc", 169 "id": seed20.AssertedSnapID("pc"), 170 "type": "gadget", 171 "default-channel": "20", 172 }}, 173 }, nil) 174 175 s.mockedSystemSeeds = []mockedSystemSeed{{ 176 label: "20191119", 177 model: model1, 178 brand: myBrandAcc, 179 }, { 180 label: "20200318", 181 model: model2, 182 brand: myBrandAcc, 183 }, { 184 label: "other-20200318", 185 model: model3, 186 brand: otherBrandAcc, 187 }} 188 189 // all tests should be in run mode by default, if they need to be in 190 // different modes they should set that individually 191 devicestate.SetSystemMode(s.mgr, "run") 192 193 // state after mark-seeded ran 194 modeenv := boot.Modeenv{ 195 Mode: "run", 196 RecoverySystem: "", 197 } 198 err := modeenv.WriteTo("") 199 s.state.Set("seeded", true) 200 201 c.Assert(err, IsNil) 202 203 logbuf, restore := logger.MockLogger() 204 s.logbuf = logbuf 205 s.AddCleanup(restore) 206 } 207 208 func (s *deviceMgrSystemsSuite) TestListNoSystems(c *C) { 209 dirs.SetRootDir(c.MkDir()) 210 211 systems, err := s.mgr.Systems() 212 c.Assert(err, Equals, devicestate.ErrNoSystems) 213 c.Assert(systems, HasLen, 0) 214 215 err = os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "systems"), 0755) 216 c.Assert(err, IsNil) 217 218 systems, err = s.mgr.Systems() 219 c.Assert(err, Equals, devicestate.ErrNoSystems) 220 c.Assert(systems, HasLen, 0) 221 } 222 223 func (s *deviceMgrSystemsSuite) TestListSystemsNotPossible(c *C) { 224 if os.Geteuid() == 0 { 225 c.Skip("this test cannot run as root") 226 } 227 err := os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0000) 228 c.Assert(err, IsNil) 229 defer os.Chmod(filepath.Join(dirs.SnapSeedDir, "systems"), 0755) 230 231 // stdlib swallows up the errors when opening the target directory 232 systems, err := s.mgr.Systems() 233 c.Assert(err, Equals, devicestate.ErrNoSystems) 234 c.Assert(systems, HasLen, 0) 235 } 236 237 // TODO:UC20 update once we can list actions 238 var defaultSystemActions []devicestate.SystemAction = []devicestate.SystemAction{ 239 {Title: "Install", Mode: "install"}, 240 } 241 var currentSystemActions []devicestate.SystemAction = []devicestate.SystemAction{ 242 {Title: "Reinstall", Mode: "install"}, 243 {Title: "Recover", Mode: "recover"}, 244 {Title: "Run normally", Mode: "run"}, 245 } 246 247 func (s *deviceMgrSystemsSuite) TestListSeedSystemsNoCurrent(c *C) { 248 systems, err := s.mgr.Systems() 249 c.Assert(err, IsNil) 250 c.Assert(systems, HasLen, 3) 251 c.Check(systems, DeepEquals, []*devicestate.System{{ 252 Current: false, 253 Label: s.mockedSystemSeeds[0].label, 254 Model: s.mockedSystemSeeds[0].model, 255 Brand: s.mockedSystemSeeds[0].brand, 256 Actions: defaultSystemActions, 257 }, { 258 Current: false, 259 Label: s.mockedSystemSeeds[1].label, 260 Model: s.mockedSystemSeeds[1].model, 261 Brand: s.mockedSystemSeeds[1].brand, 262 Actions: defaultSystemActions, 263 }, { 264 Current: false, 265 Label: s.mockedSystemSeeds[2].label, 266 Model: s.mockedSystemSeeds[2].model, 267 Brand: s.mockedSystemSeeds[2].brand, 268 Actions: defaultSystemActions, 269 }}) 270 } 271 272 func (s *deviceMgrSystemsSuite) TestListSeedSystemsCurrent(c *C) { 273 s.state.Lock() 274 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 275 { 276 System: s.mockedSystemSeeds[1].label, 277 Model: s.mockedSystemSeeds[1].model.Model(), 278 BrandID: s.mockedSystemSeeds[1].brand.AccountID(), 279 }, 280 }) 281 s.state.Unlock() 282 283 systems, err := s.mgr.Systems() 284 c.Assert(err, IsNil) 285 c.Assert(systems, HasLen, 3) 286 c.Check(systems, DeepEquals, []*devicestate.System{{ 287 Current: false, 288 Label: s.mockedSystemSeeds[0].label, 289 Model: s.mockedSystemSeeds[0].model, 290 Brand: s.mockedSystemSeeds[0].brand, 291 Actions: defaultSystemActions, 292 }, { 293 // this seed was used for installing the running system 294 Current: true, 295 Label: s.mockedSystemSeeds[1].label, 296 Model: s.mockedSystemSeeds[1].model, 297 Brand: s.mockedSystemSeeds[1].brand, 298 Actions: currentSystemActions, 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) TestBrokenSeedSystems(c *C) { 309 // break the first seed 310 err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model")) 311 c.Assert(err, IsNil) 312 313 systems, err := s.mgr.Systems() 314 c.Assert(err, IsNil) 315 c.Assert(systems, HasLen, 2) 316 c.Check(systems, DeepEquals, []*devicestate.System{{ 317 Current: false, 318 Label: s.mockedSystemSeeds[1].label, 319 Model: s.mockedSystemSeeds[1].model, 320 Brand: s.mockedSystemSeeds[1].brand, 321 Actions: defaultSystemActions, 322 }, { 323 Current: false, 324 Label: s.mockedSystemSeeds[2].label, 325 Model: s.mockedSystemSeeds[2].model, 326 Brand: s.mockedSystemSeeds[2].brand, 327 Actions: defaultSystemActions, 328 }}) 329 } 330 331 func (s *deviceMgrSystemsSuite) TestRequestModeInstallHappyForAny(c *C) { 332 // no current system 333 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install", Title: "Install"}) 334 c.Assert(err, IsNil) 335 336 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 337 c.Assert(err, IsNil) 338 c.Check(m, DeepEquals, map[string]string{ 339 "snapd_recovery_system": "20191119", 340 "snapd_recovery_mode": "install", 341 }) 342 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 343 c.Check(s.logbuf.String(), Matches, `.*: restarting into system "20191119" for action "Install"\n`) 344 } 345 346 func (s *deviceMgrSystemsSuite) TestRequestSameModeSameSystem(c *C) { 347 s.state.Lock() 348 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 349 { 350 System: s.mockedSystemSeeds[0].label, 351 Model: s.mockedSystemSeeds[0].model.Model(), 352 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 353 }, 354 }) 355 s.state.Unlock() 356 357 label := s.mockedSystemSeeds[0].label 358 359 happyModes := []string{"run"} 360 sadModes := []string{"install", "recover"} 361 362 for _, mode := range append(happyModes, sadModes...) { 363 s.logbuf.Reset() 364 365 c.Logf("checking mode: %q", mode) 366 // non run modes use modeenv 367 modeenv := boot.Modeenv{ 368 Mode: mode, 369 } 370 if mode != "run" { 371 modeenv.RecoverySystem = s.mockedSystemSeeds[0].label 372 } 373 err := modeenv.WriteTo("") 374 c.Assert(err, IsNil) 375 376 devicestate.SetSystemMode(s.mgr, mode) 377 err = s.bootloader.SetBootVars(map[string]string{ 378 "snapd_recovery_mode": mode, 379 "snapd_recovery_system": label, 380 }) 381 c.Assert(err, IsNil) 382 err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode}) 383 if strutil.ListContains(sadModes, mode) { 384 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 385 } else { 386 c.Assert(err, IsNil) 387 } 388 // bootloader vars shouldn't change 389 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 390 c.Assert(err, IsNil) 391 c.Check(m, DeepEquals, map[string]string{ 392 "snapd_recovery_mode": mode, 393 "snapd_recovery_system": label, 394 }) 395 // should never restart 396 c.Check(s.restartRequests, HasLen, 0) 397 // no log output 398 c.Check(s.logbuf.String(), Equals, "") 399 } 400 } 401 402 func (s *deviceMgrSystemsSuite) TestRequestSeedingSameConflict(c *C) { 403 label := s.mockedSystemSeeds[0].label 404 405 devicestate.SetSystemMode(s.mgr, "run") 406 407 s.state.Lock() 408 s.state.Set("seeded", nil) 409 s.state.Unlock() 410 411 for _, mode := range []string{"run", "install", "recover"} { 412 s.logbuf.Reset() 413 414 c.Logf("checking mode: %q", mode) 415 modeenv := boot.Modeenv{ 416 Mode: mode, 417 RecoverySystem: s.mockedSystemSeeds[0].label, 418 } 419 err := modeenv.WriteTo("") 420 c.Assert(err, IsNil) 421 422 err = s.bootloader.SetBootVars(map[string]string{ 423 "snapd_recovery_mode": "", 424 "snapd_recovery_system": label, 425 }) 426 c.Assert(err, IsNil) 427 err = s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode}) 428 c.Assert(err, ErrorMatches, "cannot request system action, system is seeding") 429 // no log output 430 c.Check(s.logbuf.String(), Equals, "") 431 } 432 } 433 434 func (s *deviceMgrSystemsSuite) TestRequestSeedingDifferentNoConflict(c *C) { 435 label := s.mockedSystemSeeds[0].label 436 otherLabel := s.mockedSystemSeeds[1].label 437 438 devicestate.SetSystemMode(s.mgr, "run") 439 440 modeenv := boot.Modeenv{ 441 Mode: "run", 442 RecoverySystem: label, 443 } 444 err := modeenv.WriteTo("") 445 c.Assert(err, IsNil) 446 447 s.state.Lock() 448 s.state.Set("seeded", nil) 449 s.state.Unlock() 450 451 // we can only go to install mode of other system when one is currently 452 // being seeded 453 err = s.bootloader.SetBootVars(map[string]string{ 454 "snapd_recovery_mode": "", 455 "snapd_recovery_system": label, 456 }) 457 c.Assert(err, IsNil) 458 err = s.mgr.RequestSystemAction(otherLabel, devicestate.SystemAction{Mode: "install"}) 459 c.Assert(err, IsNil) 460 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 461 c.Assert(err, IsNil) 462 c.Check(m, DeepEquals, map[string]string{ 463 "snapd_recovery_system": otherLabel, 464 "snapd_recovery_mode": "install", 465 }) 466 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action "Install"\n`, otherLabel)) 467 } 468 469 func (s *deviceMgrSystemsSuite) testRequestModeWithRestart(c *C, toModes []string, label string) { 470 for _, mode := range toModes { 471 c.Logf("checking mode: %q", mode) 472 err := s.mgr.RequestSystemAction(label, devicestate.SystemAction{Mode: mode}) 473 c.Assert(err, IsNil) 474 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 475 c.Assert(err, IsNil) 476 c.Check(m, DeepEquals, map[string]string{ 477 "snapd_recovery_system": label, 478 "snapd_recovery_mode": mode, 479 }) 480 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 481 s.restartRequests = nil 482 s.bootloader.BootVars = map[string]string{} 483 484 // TODO: also test correct action string logging 485 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: restarting into system "%s" for action ".*"\n`, label)) 486 s.logbuf.Reset() 487 } 488 } 489 490 func (s *deviceMgrSystemsSuite) TestRequestModeRunInstallForRecover(c *C) { 491 // we are in recover mode here 492 devicestate.SetSystemMode(s.mgr, "recover") 493 // non run modes use modeenv 494 modeenv := boot.Modeenv{ 495 Mode: "recover", 496 RecoverySystem: s.mockedSystemSeeds[0].label, 497 } 498 err := modeenv.WriteTo("") 499 c.Assert(err, IsNil) 500 501 s.state.Lock() 502 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 503 { 504 System: s.mockedSystemSeeds[0].label, 505 Model: s.mockedSystemSeeds[0].model.Model(), 506 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 507 }, 508 }) 509 s.state.Unlock() 510 511 s.testRequestModeWithRestart(c, []string{"install", "run"}, s.mockedSystemSeeds[0].label) 512 } 513 514 func (s *deviceMgrSystemsSuite) TestRequestModeInstallRecoverForCurrent(c *C) { 515 devicestate.SetSystemMode(s.mgr, "run") 516 // non run modes use modeenv 517 modeenv := boot.Modeenv{ 518 Mode: "run", 519 } 520 err := modeenv.WriteTo("") 521 c.Assert(err, IsNil) 522 523 s.state.Lock() 524 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 525 { 526 System: s.mockedSystemSeeds[0].label, 527 Model: s.mockedSystemSeeds[0].model.Model(), 528 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 529 }, 530 }) 531 s.state.Unlock() 532 533 s.testRequestModeWithRestart(c, []string{"install", "recover"}, s.mockedSystemSeeds[0].label) 534 } 535 536 func (s *deviceMgrSystemsSuite) TestRequestModeErrInBoot(c *C) { 537 s.bootloader.SetErr = errors.New("no can do") 538 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"}) 539 c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": no can do`) 540 c.Check(s.restartRequests, HasLen, 0) 541 c.Check(s.logbuf.String(), Equals, "") 542 } 543 544 func (s *deviceMgrSystemsSuite) TestRequestModeNotFound(c *C) { 545 err := s.mgr.RequestSystemAction("not-found", devicestate.SystemAction{Mode: "install"}) 546 c.Assert(err, NotNil) 547 c.Assert(os.IsNotExist(err), Equals, true) 548 c.Check(s.restartRequests, HasLen, 0) 549 c.Check(s.logbuf.String(), Equals, "") 550 } 551 552 func (s *deviceMgrSystemsSuite) TestRequestModeBadMode(c *C) { 553 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "unknown-mode"}) 554 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 555 c.Check(s.restartRequests, HasLen, 0) 556 c.Check(s.logbuf.String(), Equals, "") 557 } 558 559 func (s *deviceMgrSystemsSuite) TestRequestModeBroken(c *C) { 560 // break the first seed 561 err := os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", s.mockedSystemSeeds[0].label, "model")) 562 c.Assert(err, IsNil) 563 564 err = s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"}) 565 c.Assert(err, ErrorMatches, "cannot load seed system: cannot load assertions: .*") 566 c.Check(s.restartRequests, HasLen, 0) 567 c.Check(s.logbuf.String(), Equals, "") 568 } 569 570 func (s *deviceMgrSystemsSuite) TestRequestModeNonUC20(c *C) { 571 s.setPCModelInState(c) 572 err := s.mgr.RequestSystemAction("20191119", devicestate.SystemAction{Mode: "install"}) 573 c.Assert(err, ErrorMatches, `cannot set device to boot into system "20191119" in mode "install": system mode is unsupported`) 574 c.Check(s.restartRequests, HasLen, 0) 575 c.Check(s.logbuf.String(), Equals, "") 576 } 577 578 func (s *deviceMgrSystemsSuite) TestRequestActionNoLabel(c *C) { 579 err := s.mgr.RequestSystemAction("", devicestate.SystemAction{Mode: "install"}) 580 c.Assert(err, ErrorMatches, "internal error: system label is unset") 581 c.Check(s.logbuf.String(), Equals, "") 582 } 583 584 func (s *deviceMgrSystemsSuite) TestRequestModeForNonCurrent(c *C) { 585 s.state.Lock() 586 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 587 { 588 System: s.mockedSystemSeeds[0].label, 589 Model: s.mockedSystemSeeds[0].model.Model(), 590 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 591 }, 592 }) 593 594 s.state.Unlock() 595 s.setPCModelInState(c) 596 // request mode reserved for current system 597 err := s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "run"}) 598 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 599 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[1].label, devicestate.SystemAction{Mode: "recover"}) 600 c.Assert(err, Equals, devicestate.ErrUnsupportedAction) 601 c.Check(s.restartRequests, HasLen, 0) 602 c.Check(s.logbuf.String(), Equals, "") 603 } 604 605 func (s *deviceMgrSystemsSuite) TestRequestInstallForOther(c *C) { 606 devicestate.SetSystemMode(s.mgr, "run") 607 // non run modes use modeenv 608 modeenv := boot.Modeenv{ 609 Mode: "run", 610 } 611 err := modeenv.WriteTo("") 612 c.Assert(err, IsNil) 613 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 s.state.Unlock() 623 // reinstall from different system seed is ok 624 s.testRequestModeWithRestart(c, []string{"install"}, s.mockedSystemSeeds[1].label) 625 } 626 627 func (s *deviceMgrSystemsSuite) TestRequestAction1618(c *C) { 628 s.setPCModelInState(c) 629 // system mode is unset in 16/18 630 devicestate.SetSystemMode(s.mgr, "") 631 // no modeenv either 632 err := os.Remove(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir)) 633 c.Assert(err, IsNil) 634 635 s.state.Lock() 636 s.state.Set("seeded-systems", nil) 637 s.state.Set("seeded", nil) 638 s.state.Unlock() 639 // a label exists 640 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"}) 641 c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported") 642 643 s.state.Lock() 644 s.state.Set("seeded", true) 645 s.state.Unlock() 646 647 // even with system mode explicitly set, the action is not executed 648 devicestate.SetSystemMode(s.mgr, "run") 649 650 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"}) 651 c.Assert(err, ErrorMatches, "cannot set device to boot .*: system mode is unsupported") 652 653 devicestate.SetSystemMode(s.mgr, "") 654 // also no UC20 style system seeds 655 for _, m := range s.mockedSystemSeeds { 656 os.RemoveAll(filepath.Join(dirs.SnapSeedDir, "systems", m.label)) 657 } 658 659 err = s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, devicestate.SystemAction{Mode: "install"}) 660 c.Assert(err, ErrorMatches, ".*/seed/systems/20191119: no such file or directory") 661 c.Check(s.logbuf.String(), Equals, "") 662 } 663 664 func (s *deviceMgrSystemsSuite) TestRebootNoLabelNoModeHappy(c *C) { 665 err := s.mgr.Reboot("", "") 666 c.Assert(err, IsNil) 667 668 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 669 c.Assert(err, IsNil) 670 // requested restart 671 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 672 // but no bootloader changes 673 c.Check(m, DeepEquals, map[string]string{ 674 "snapd_recovery_system": "", 675 "snapd_recovery_mode": "", 676 }) 677 c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`) 678 } 679 680 func (s *deviceMgrSystemsSuite) TestRebootLabelAndModeHappy(c *C) { 681 s.state.Lock() 682 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 683 { 684 System: s.mockedSystemSeeds[0].label, 685 Model: s.mockedSystemSeeds[0].model.Model(), 686 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 687 }, 688 }) 689 s.state.Unlock() 690 691 err := s.mgr.Reboot("20191119", "install") 692 c.Assert(err, IsNil) 693 694 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 695 c.Assert(err, IsNil) 696 c.Check(m, DeepEquals, map[string]string{ 697 "snapd_recovery_system": "20191119", 698 "snapd_recovery_mode": "install", 699 }) 700 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 701 c.Check(s.logbuf.String(), Matches, `.*: rebooting into system "20191119" in "install" mode\n`) 702 } 703 704 func (s *deviceMgrSystemsSuite) TestRebootModeOnlyHappy(c *C) { 705 s.state.Lock() 706 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 707 { 708 System: s.mockedSystemSeeds[0].label, 709 Model: s.mockedSystemSeeds[0].model.Model(), 710 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 711 }, 712 }) 713 s.state.Unlock() 714 715 for _, mode := range []string{"recover", "install"} { 716 s.restartRequests = nil 717 s.bootloader.BootVars = make(map[string]string) 718 s.logbuf.Reset() 719 720 err := s.mgr.Reboot("", mode) 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": s.mockedSystemSeeds[0].label, 727 "snapd_recovery_mode": mode, 728 }) 729 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 730 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "20191119" in "%s" mode\n`, mode)) 731 } 732 } 733 734 func (s *deviceMgrSystemsSuite) TestRebootFromRecoverToRun(c *C) { 735 modeenv := boot.Modeenv{ 736 Mode: "recover", 737 RecoverySystem: s.mockedSystemSeeds[0].label, 738 } 739 err := modeenv.WriteTo("") 740 c.Assert(err, IsNil) 741 742 devicestate.SetSystemMode(s.mgr, "recover") 743 err = s.bootloader.SetBootVars(map[string]string{ 744 "snapd_recovery_mode": "recover", 745 "snapd_recovery_system": s.mockedSystemSeeds[0].label, 746 }) 747 c.Assert(err, IsNil) 748 749 s.state.Lock() 750 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 751 { 752 System: s.mockedSystemSeeds[0].label, 753 Model: s.mockedSystemSeeds[0].model.Model(), 754 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 755 }, 756 }) 757 s.state.Unlock() 758 759 err = s.mgr.Reboot("", "run") 760 c.Assert(err, IsNil) 761 762 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 763 c.Assert(err, IsNil) 764 c.Check(m, DeepEquals, map[string]string{ 765 "snapd_recovery_mode": "run", 766 "snapd_recovery_system": s.mockedSystemSeeds[0].label, 767 }) 768 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 769 c.Check(s.logbuf.String(), Matches, fmt.Sprintf(`.*: rebooting into system "%s" in "run" mode\n`, s.mockedSystemSeeds[0].label)) 770 } 771 772 func (s *deviceMgrSystemsSuite) TestRebootAlreadyInRunMode(c *C) { 773 devicestate.SetSystemMode(s.mgr, "run") 774 775 s.state.Lock() 776 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 777 { 778 System: s.mockedSystemSeeds[0].label, 779 Model: s.mockedSystemSeeds[0].model.Model(), 780 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 781 }, 782 }) 783 s.state.Unlock() 784 785 // we are already in "run" mode so this should just reboot 786 err := s.mgr.Reboot("", "run") 787 c.Assert(err, IsNil) 788 789 m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 790 c.Assert(err, IsNil) 791 c.Check(m, DeepEquals, map[string]string{ 792 "snapd_recovery_mode": "", 793 "snapd_recovery_system": "", 794 }) 795 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 796 c.Check(s.logbuf.String(), Matches, `.*: rebooting system\n`) 797 } 798 799 func (s *deviceMgrSystemsSuite) TestRebootUnhappy(c *C) { 800 s.state.Lock() 801 s.state.Set("seeded-systems", []devicestate.SeededSystem{ 802 { 803 System: s.mockedSystemSeeds[0].label, 804 Model: s.mockedSystemSeeds[0].model.Model(), 805 BrandID: s.mockedSystemSeeds[0].brand.AccountID(), 806 }, 807 }) 808 s.state.Unlock() 809 810 errUnsupportedActionStr := devicestate.ErrUnsupportedAction.Error() 811 for _, tc := range []struct { 812 systemLabel, mode string 813 expectedErr string 814 }{ 815 {"", "unknown-mode", errUnsupportedActionStr}, 816 {"unknown-system", "run", `stat /.*: no such file or directory`}, 817 {"unknown-system", "unknown-mode", `stat /.*: no such file or directory`}, 818 } { 819 s.restartRequests = nil 820 s.bootloader.BootVars = make(map[string]string) 821 822 err := s.mgr.Reboot(tc.systemLabel, tc.mode) 823 c.Assert(err, ErrorMatches, tc.expectedErr) 824 825 c.Check(s.restartRequests, HasLen, 0) 826 c.Check(s.logbuf.String(), Equals, "") 827 } 828 c.Check(s.logbuf.String(), Equals, "") 829 }