gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/devicestate/firstboot20_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/asserts" 31 "github.com/snapcore/snapd/boot" 32 "github.com/snapcore/snapd/bootloader" 33 "github.com/snapcore/snapd/bootloader/bootloadertest" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/features" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/overlord" 38 "github.com/snapcore/snapd/overlord/assertstate" 39 "github.com/snapcore/snapd/overlord/configstate/config" 40 "github.com/snapcore/snapd/overlord/devicestate" 41 "github.com/snapcore/snapd/overlord/ifacestate" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/state" 44 "github.com/snapcore/snapd/release" 45 "github.com/snapcore/snapd/seed/seedtest" 46 "github.com/snapcore/snapd/snap" 47 "github.com/snapcore/snapd/strutil" 48 "github.com/snapcore/snapd/systemd" 49 "github.com/snapcore/snapd/testutil" 50 ) 51 52 type firstBoot20Suite struct { 53 firstBootBaseTest 54 55 extraSnapYaml map[string]string 56 57 // TestingSeed20 helps populating seeds (it provides 58 // MakeAssertedSnap, MakeSeed) for tests. 59 *seedtest.TestingSeed20 60 } 61 62 var ( 63 allGrades = []asserts.ModelGrade{ 64 asserts.ModelDangerous, 65 } 66 ) 67 68 var _ = Suite(&firstBoot20Suite{}) 69 70 func (s *firstBoot20Suite) SetUpTest(c *C) { 71 s.extraSnapYaml = make(map[string]string) 72 73 s.TestingSeed20 = &seedtest.TestingSeed20{} 74 75 s.setupBaseTest(c, &s.TestingSeed20.SeedSnaps) 76 77 // don't start the overlord here so that we can mock different modeenvs 78 // later, which is needed by devicestart manager startup with uc20 booting 79 80 s.SeedDir = dirs.SnapSeedDir 81 82 // mock the snap mapper as snapd here 83 s.AddCleanup(ifacestate.MockSnapMapper(&ifacestate.CoreSnapdSystemMapper{})) 84 85 r := release.MockReleaseInfo(&release.OS{ID: "ubuntu-core", VersionID: "20"}) 86 s.AddCleanup(r) 87 } 88 89 func (s *firstBoot20Suite) snapYaml(snp string) string { 90 if yml, ok := seedtest.SampleSnapYaml[snp]; ok { 91 return yml 92 } 93 return s.extraSnapYaml[snp] 94 } 95 96 func (s *firstBoot20Suite) setupCore20Seed(c *C, sysLabel string, modelGrade asserts.ModelGrade, extraGadgetYaml string, extraSnaps ...string) *asserts.Model { 97 gadgetYaml := ` 98 volumes: 99 volume-id: 100 bootloader: grub 101 structure: 102 - name: ubuntu-seed 103 role: system-seed 104 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 105 size: 1G 106 - name: ubuntu-data 107 role: system-data 108 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 109 size: 2G 110 ` 111 112 gadgetYaml += extraGadgetYaml 113 114 makeSnap := func(yamlKey string) { 115 var files [][]string 116 if yamlKey == "pc=20" { 117 files = append(files, []string{"meta/gadget.yaml", gadgetYaml}) 118 } 119 s.MakeAssertedSnap(c, s.snapYaml(yamlKey), files, snap.R(1), "canonical", s.StoreSigning.Database) 120 } 121 122 makeSnap("snapd") 123 makeSnap("pc-kernel=20") 124 makeSnap("core20") 125 makeSnap("pc=20") 126 for _, sn := range extraSnaps { 127 makeSnap(sn) 128 } 129 130 model := map[string]interface{}{ 131 "display-name": "my model", 132 "architecture": "amd64", 133 "base": "core20", 134 "grade": string(modelGrade), 135 "snaps": []interface{}{ 136 map[string]interface{}{ 137 "name": "pc-kernel", 138 "id": s.AssertedSnapID("pc-kernel"), 139 "type": "kernel", 140 "default-channel": "20", 141 }, 142 map[string]interface{}{ 143 "name": "pc", 144 "id": s.AssertedSnapID("pc"), 145 "type": "gadget", 146 "default-channel": "20", 147 }, 148 map[string]interface{}{ 149 "name": "snapd", 150 "id": s.AssertedSnapID("snapd"), 151 "type": "snapd", 152 }, 153 map[string]interface{}{ 154 "name": "core20", 155 "id": s.AssertedSnapID("core20"), 156 "type": "base", 157 }, 158 }, 159 } 160 161 for _, sn := range extraSnaps { 162 name, channel := splitSnapNameWithChannel(sn) 163 model["snaps"] = append(model["snaps"].([]interface{}), map[string]interface{}{ 164 "name": name, 165 "type": "app", 166 "id": s.AssertedSnapID(name), 167 "default-channel": channel, 168 }) 169 } 170 171 return s.MakeSeed(c, sysLabel, "my-brand", "my-model", model, nil) 172 } 173 174 func splitSnapNameWithChannel(sn string) (name, channel string) { 175 nameParts := strings.SplitN(sn, "=", 2) 176 name = nameParts[0] 177 channel = "" 178 if len(nameParts) == 2 { 179 channel = nameParts[1] 180 } 181 return name, channel 182 } 183 184 func stripSnapNamesWithChannels(snaps []string) []string { 185 names := []string{} 186 for _, sn := range snaps { 187 name, _ := splitSnapNameWithChannel(sn) 188 names = append(names, name) 189 } 190 return names 191 } 192 193 func checkSnapstateDevModeFlags(c *C, tsAll []*state.TaskSet, snapsWithDevModeFlag ...string) { 194 allDevModeSnaps := stripSnapNamesWithChannels(snapsWithDevModeFlag) 195 196 // XXX: mostly same code from checkOrder helper in firstboot_test.go, maybe 197 // combine someday? 198 matched := 0 199 var prevTask *state.Task 200 for i, ts := range tsAll { 201 task0 := ts.Tasks()[0] 202 waitTasks := task0.WaitTasks() 203 if i == 0 { 204 c.Check(waitTasks, HasLen, 0) 205 } else { 206 c.Check(waitTasks, testutil.Contains, prevTask) 207 } 208 prevTask = task0 209 if task0.Kind() != "prerequisites" { 210 continue 211 } 212 snapsup, err := snapstate.TaskSnapSetup(task0) 213 c.Assert(err, IsNil, Commentf("%#v", task0)) 214 if strutil.ListContains(allDevModeSnaps, snapsup.InstanceName()) { 215 c.Assert(snapsup.DevMode, Equals, true) 216 matched++ 217 } else { 218 // it should not have DevMode true 219 c.Assert(snapsup.DevMode, Equals, false) 220 } 221 } 222 c.Check(matched, Equals, len(snapsWithDevModeFlag)) 223 } 224 225 func (s *firstBoot20Suite) earlySetup(c *C, m *boot.Modeenv, modelGrade asserts.ModelGrade, extraGadgetYaml string, extraSnaps ...string) (model *asserts.Model, bloader *bootloadertest.MockExtractedRunKernelImageBootloader) { 226 c.Assert(m, NotNil, Commentf("missing modeenv test data")) 227 err := m.WriteTo("") 228 c.Assert(err, IsNil) 229 230 sysLabel := m.RecoverySystem 231 model = s.setupCore20Seed(c, sysLabel, modelGrade, extraGadgetYaml, extraSnaps...) 232 // sanity check that our returned model has the expected grade 233 c.Assert(model.Grade(), Equals, modelGrade) 234 235 bloader = bootloadertest.Mock("mock", c.MkDir()).WithExtractedRunKernelImage() 236 bootloader.Force(bloader) 237 s.AddCleanup(func() { bootloader.Force(nil) }) 238 239 // since we are in runmode, MakeBootable will already have run from install 240 // mode, and extracted the kernel assets for the kernel snap into the 241 // bootloader, so set the current kernel there 242 kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap") 243 c.Assert(err, IsNil) 244 r := bloader.SetEnabledKernel(kernel) 245 s.AddCleanup(r) 246 247 return model, bloader 248 } 249 250 func (s *firstBoot20Suite) testPopulateFromSeedCore20Happy(c *C, m *boot.Modeenv, modelGrade asserts.ModelGrade, extraDevModeSnaps ...string) { 251 var sysdLog [][]string 252 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 253 sysdLog = append(sysdLog, cmd) 254 return []byte("ActiveState=inactive\n"), nil 255 }) 256 defer systemctlRestorer() 257 258 model, bloader := s.earlySetup(c, m, modelGrade, "", extraDevModeSnaps...) 259 // create overlord and pick up the modeenv 260 s.startOverlord(c) 261 262 opts := devicestate.PopulateStateFromSeedOptions{ 263 Label: m.RecoverySystem, 264 Mode: m.Mode, 265 } 266 267 // run the firstboot stuff 268 st := s.overlord.State() 269 st.Lock() 270 defer st.Unlock() 271 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, &opts, s.perfTimings) 272 c.Assert(err, IsNil) 273 274 snaps := []string{"snapd", "pc-kernel", "core20", "pc"} 275 allDevModeSnaps := stripSnapNamesWithChannels(extraDevModeSnaps) 276 if len(extraDevModeSnaps) != 0 { 277 snaps = append(snaps, allDevModeSnaps...) 278 } 279 checkOrder(c, tsAll, snaps...) 280 281 // if the model is dangerous check that the devmode snaps in the model have 282 // the flag set in snapstate for DevMode confinement 283 // XXX: eventually we may need more complicated checks here and for 284 // non-dangerous models only specific snaps may have this flag set 285 if modelGrade == asserts.ModelDangerous { 286 checkSnapstateDevModeFlags(c, tsAll, allDevModeSnaps...) 287 } 288 289 // now run the change and check the result 290 // use the expected kind otherwise settle with start another one 291 chg := st.NewChange("seed", "run the populate from seed changes") 292 for _, ts := range tsAll { 293 chg.AddAll(ts) 294 } 295 c.Assert(st.Changes(), HasLen, 1) 296 297 c.Assert(chg.Err(), IsNil) 298 299 // avoid device reg 300 chg1 := st.NewChange("become-operational", "init device") 301 chg1.SetStatus(state.DoingStatus) 302 303 // run change until it wants to restart 304 st.Unlock() 305 err = s.overlord.Settle(settleTimeout) 306 st.Lock() 307 c.Assert(err, IsNil) 308 309 // at this point the system is "restarting", pretend the restart has 310 // happened 311 c.Assert(chg.Status(), Equals, state.DoingStatus) 312 state.MockRestarting(st, state.RestartUnset) 313 st.Unlock() 314 err = s.overlord.Settle(settleTimeout) 315 st.Lock() 316 c.Assert(err, IsNil) 317 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%s", chg.Err())) 318 319 // verify 320 f, err := os.Open(dirs.SnapStateFile) 321 c.Assert(err, IsNil) 322 state, err := state.ReadState(nil, f) 323 c.Assert(err, IsNil) 324 325 state.Lock() 326 defer state.Unlock() 327 // check snapd, core20, kernel, gadget 328 _, err = snapstate.CurrentInfo(state, "snapd") 329 c.Check(err, IsNil) 330 _, err = snapstate.CurrentInfo(state, "core20") 331 c.Check(err, IsNil) 332 _, err = snapstate.CurrentInfo(state, "pc-kernel") 333 c.Check(err, IsNil) 334 _, err = snapstate.CurrentInfo(state, "pc") 335 c.Check(err, IsNil) 336 337 // No kernel extraction happens during seeding, the kernel is already 338 // there either from ubuntu-image or from "install" mode. 339 c.Check(bloader.ExtractKernelAssetsCalls, HasLen, 0) 340 341 // ensure required flag is set on all essential snaps 342 var snapst snapstate.SnapState 343 for _, reqName := range []string{"snapd", "core20", "pc-kernel", "pc"} { 344 err = snapstate.Get(state, reqName, &snapst) 345 c.Assert(err, IsNil) 346 c.Assert(snapst.Required, Equals, true, Commentf("required not set for %v", reqName)) 347 348 if m.Mode == "run" { 349 // also ensure that in run mode none of the snaps are installed as 350 // symlinks, they must be copied onto ubuntu-data 351 files, err := filepath.Glob(filepath.Join(dirs.SnapBlobDir, reqName+"_*.snap")) 352 c.Assert(err, IsNil) 353 c.Assert(files, HasLen, 1) 354 c.Assert(osutil.IsSymlink(files[0]), Equals, false) 355 } 356 } 357 358 // the right systemd commands were run 359 c.Check(sysdLog, testutil.DeepContains, []string{"start", "usr-lib-snapd.mount"}) 360 361 // and ensure state is now considered seeded 362 var seeded bool 363 err = state.Get("seeded", &seeded) 364 c.Assert(err, IsNil) 365 c.Check(seeded, Equals, true) 366 367 // check we set seed-time 368 var seedTime time.Time 369 err = state.Get("seed-time", &seedTime) 370 c.Assert(err, IsNil) 371 c.Check(seedTime.IsZero(), Equals, false) 372 373 // check that we removed recovery_system from modeenv 374 m2, err := boot.ReadModeenv("") 375 c.Assert(err, IsNil) 376 if m.Mode == "run" { 377 // recovery system is cleared in run mode 378 c.Assert(m2.RecoverySystem, Equals, "") 379 } else { 380 // but kept intact in other modes 381 c.Assert(m2.RecoverySystem, Equals, m.RecoverySystem) 382 } 383 c.Assert(m2.Base, Equals, m.Base) 384 c.Assert(m2.Mode, Equals, m.Mode) 385 // Note that we don't check CurrentKernels in the modeenv, even though in a 386 // real first boot that would also be set here, because setting that is done 387 // in the snapstate manager, not the devicestate manager 388 389 // check that the default device ctx has a Modeenv 390 dev, err := devicestate.DeviceCtx(s.overlord.State(), nil, nil) 391 c.Assert(err, IsNil) 392 c.Assert(dev.HasModeenv(), Equals, true) 393 394 // check that we marked the boot successful with bootstate20 methods, namely 395 // that we called SetNext, which since it was called on the kernel we 396 // already booted from, we should only have checked what the current kernel 397 // is 398 399 if m.Mode == "run" { 400 // only relevant in run mode 401 402 // the 3 calls here are : 403 // * 1 from MarkBootSuccessful() from ensureBootOk() before we restart 404 // * 1 from boot.SetNextBoot() from LinkSnap() from doInstall() from InstallPath() from 405 // installSeedSnap() after restart 406 // * 1 from boot.GetCurrentBoot() from FinishRestart after restart 407 _, numKernelCalls := bloader.GetRunKernelImageFunctionSnapCalls("Kernel") 408 c.Assert(numKernelCalls, Equals, 3) 409 } 410 actual, _ := bloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 411 c.Assert(actual, HasLen, 0) 412 actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 413 c.Assert(actual, HasLen, 0) 414 actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel") 415 c.Assert(actual, HasLen, 0) 416 417 var whatseeded []devicestate.SeededSystem 418 err = state.Get("seeded-systems", &whatseeded) 419 if m.Mode == "run" { 420 c.Assert(err, IsNil) 421 c.Assert(whatseeded, DeepEquals, []devicestate.SeededSystem{{ 422 System: m.RecoverySystem, 423 Model: "my-model", 424 BrandID: "my-brand", 425 Revision: model.Revision(), 426 Timestamp: model.Timestamp(), 427 SeedTime: seedTime, 428 }}) 429 } else { 430 c.Assert(err, NotNil) 431 } 432 } 433 434 func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunModeDangerousWithDevmode(c *C) { 435 m := boot.Modeenv{ 436 Mode: "run", 437 RecoverySystem: "20191018", 438 Base: "core20_1.snap", 439 } 440 s.testPopulateFromSeedCore20Happy(c, &m, asserts.ModelDangerous, "test-devmode=20") 441 } 442 443 func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunMode(c *C) { 444 m := boot.Modeenv{ 445 Mode: "run", 446 RecoverySystem: "20191018", 447 Base: "core20_1.snap", 448 } 449 for _, grade := range allGrades { 450 s.testPopulateFromSeedCore20Happy(c, &m, grade) 451 } 452 } 453 454 func (s *firstBoot20Suite) TestPopulateFromSeedCore20InstallMode(c *C) { 455 m := boot.Modeenv{ 456 Mode: "install", 457 RecoverySystem: "20191019", 458 Base: "core20_1.snap", 459 } 460 for _, grade := range allGrades { 461 s.testPopulateFromSeedCore20Happy(c, &m, grade) 462 } 463 } 464 465 func (s *firstBoot20Suite) TestPopulateFromSeedCore20RecoverMode(c *C) { 466 m := boot.Modeenv{ 467 Mode: "recover", 468 RecoverySystem: "20191020", 469 Base: "core20_1.snap", 470 } 471 for _, grade := range allGrades { 472 s.testPopulateFromSeedCore20Happy(c, &m, grade) 473 } 474 } 475 476 func (s *firstBoot20Suite) TestLoadDeviceSeedCore20(c *C) { 477 m := boot.Modeenv{ 478 Mode: "run", 479 RecoverySystem: "20191018", 480 Base: "core20_1.snap", 481 } 482 483 s.earlySetup(c, &m, "signed", "") 484 485 o, err := overlord.New(nil) 486 c.Assert(err, IsNil) 487 st := o.State() 488 489 st.Lock() 490 defer st.Unlock() 491 492 deviceSeed, err := devicestate.LoadDeviceSeed(st, m.RecoverySystem) 493 c.Assert(err, IsNil) 494 495 c.Check(deviceSeed.Model().BrandID(), Equals, "my-brand") 496 c.Check(deviceSeed.Model().Model(), Equals, "my-model") 497 c.Check(deviceSeed.Model().Base(), Equals, "core20") 498 499 // verify that the model was added 500 db := assertstate.DB(st) 501 as, err := db.Find(asserts.ModelType, map[string]string{ 502 "series": "16", 503 "brand-id": "my-brand", 504 "model": "my-model", 505 }) 506 c.Assert(err, IsNil) 507 c.Check(as, DeepEquals, deviceSeed.Model()) 508 509 // inconsistent seed request 510 _, err = devicestate.LoadDeviceSeed(st, "20210201") 511 c.Assert(err, ErrorMatches, `internal error: requested inconsistent device seed: 20210201 \(was 20191018\)`) 512 } 513 514 func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunModeUserServiceTasks(c *C) { 515 // check that this is test is still valid 516 // TODO: have a test for an early config option that is not an 517 // experimental flag 518 c.Assert(features.UserDaemons.IsEnabledWhenUnset(), Equals, false, Commentf("user-daemons is not experimental anymore, this test is not useful anymore")) 519 520 s.extraSnapYaml["user-daemons1"] = `name: user-daemons1 521 version: 1.0 522 type: app 523 base: core20 524 525 apps: 526 foo: 527 daemon: simple 528 daemon-scope: user 529 ` 530 m := boot.Modeenv{ 531 Mode: "run", 532 RecoverySystem: "20191018", 533 Base: "core20_1.snap", 534 } 535 536 defaultsGadgetYaml := ` 537 defaults: 538 system: 539 experimental: 540 user-daemons: true 541 ` 542 543 s.earlySetup(c, &m, "signed", defaultsGadgetYaml, "user-daemons1") 544 545 // create a new overlord and pick up the modeenv 546 // this overlord will use the proper EarlyConfig implementation 547 o, err := overlord.New(nil) 548 c.Assert(err, IsNil) 549 o.InterfaceManager().DisableUDevMonitor() 550 c.Assert(o.StartUp(), IsNil) 551 552 st := o.State() 553 st.Lock() 554 defer st.Unlock() 555 556 // early config set the flag to enabled 557 tr := config.NewTransaction(st) 558 enabled, _ := features.Flag(tr, features.UserDaemons) 559 c.Check(enabled, Equals, true) 560 561 opts := devicestate.PopulateStateFromSeedOptions{ 562 Label: m.RecoverySystem, 563 Mode: m.Mode, 564 } 565 566 _, err = devicestate.PopulateStateFromSeedImpl(st, &opts, s.perfTimings) 567 c.Assert(err, IsNil) 568 } 569 570 func (s *firstBoot20Suite) TestUsersCreateAutomaticIsAvailableEarly(c *C) { 571 m := boot.Modeenv{ 572 Mode: "run", 573 RecoverySystem: "20191018", 574 Base: "core20_1.snap", 575 } 576 577 defaultsGadgetYaml := ` 578 defaults: 579 system: 580 users: 581 create.automatic: false 582 ` 583 584 s.earlySetup(c, &m, "signed", defaultsGadgetYaml) 585 586 // create a new overlord and pick up the modeenv 587 // this overlord will use the proper EarlyConfig implementation 588 o, err := overlord.New(nil) 589 c.Assert(err, IsNil) 590 o.InterfaceManager().DisableUDevMonitor() 591 c.Assert(o.StartUp(), IsNil) 592 593 st := o.State() 594 st.Lock() 595 defer st.Unlock() 596 597 // early config in StartUp made the option available already 598 tr := config.NewTransaction(st) 599 var enabled bool 600 err = tr.Get("core", "users.create.automatic", &enabled) 601 c.Assert(err, IsNil) 602 c.Check(enabled, Equals, false) 603 }