github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/devicestate/firstboot_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2018 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 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strconv" 28 "strings" 29 "time" 30 31 . "gopkg.in/check.v1" 32 "gopkg.in/tomb.v2" 33 34 "github.com/snapcore/snapd/asserts" 35 "github.com/snapcore/snapd/asserts/assertstest" 36 "github.com/snapcore/snapd/bootloader" 37 "github.com/snapcore/snapd/bootloader/bootloadertest" 38 "github.com/snapcore/snapd/dirs" 39 "github.com/snapcore/snapd/osutil" 40 "github.com/snapcore/snapd/overlord" 41 "github.com/snapcore/snapd/overlord/assertstate" 42 "github.com/snapcore/snapd/overlord/auth" 43 "github.com/snapcore/snapd/overlord/configstate" 44 "github.com/snapcore/snapd/overlord/configstate/config" 45 "github.com/snapcore/snapd/overlord/devicestate" 46 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 47 "github.com/snapcore/snapd/overlord/hookstate" 48 "github.com/snapcore/snapd/overlord/ifacestate" 49 "github.com/snapcore/snapd/overlord/snapstate" 50 "github.com/snapcore/snapd/overlord/state" 51 "github.com/snapcore/snapd/release" 52 "github.com/snapcore/snapd/seed" 53 "github.com/snapcore/snapd/seed/seedtest" 54 "github.com/snapcore/snapd/snap" 55 "github.com/snapcore/snapd/snap/snaptest" 56 "github.com/snapcore/snapd/systemd" 57 "github.com/snapcore/snapd/testutil" 58 "github.com/snapcore/snapd/timings" 59 ) 60 61 type FirstBootTestSuite struct { 62 testutil.BaseTest 63 64 systemctl *testutil.MockCmd 65 66 // TestingSeed helps populating seeds (it provides 67 // MakeAssertedSnap, WriteAssertions etc.) for tests. 68 *seedtest.TestingSeed 69 70 devAcct *asserts.Account 71 72 overlord *overlord.Overlord 73 74 perfTimings timings.Measurer 75 } 76 77 var _ = Suite(&FirstBootTestSuite{}) 78 79 func (s *FirstBootTestSuite) SetUpTest(c *C) { 80 s.BaseTest.SetUpTest(c) 81 82 tempdir := c.MkDir() 83 dirs.SetRootDir(tempdir) 84 s.AddCleanup(func() { dirs.SetRootDir("/") }) 85 86 s.AddCleanup(release.MockOnClassic(false)) 87 88 // mock the world! 89 err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "snaps"), 0755) 90 c.Assert(err, IsNil) 91 err = os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "assertions"), 0755) 92 c.Assert(err, IsNil) 93 94 err = os.MkdirAll(dirs.SnapServicesDir, 0755) 95 c.Assert(err, IsNil) 96 os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1") 97 s.AddCleanup(func() { os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS") }) 98 s.systemctl = testutil.MockCommand(c, "systemctl", "") 99 s.AddCleanup(s.systemctl.Restore) 100 101 err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), nil, 0644) 102 c.Assert(err, IsNil) 103 104 s.TestingSeed = &seedtest.TestingSeed{} 105 s.SetupAssertSigning("can0nical", s) 106 s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{ 107 "verification": "verified", 108 }) 109 110 s.SnapsDir = filepath.Join(dirs.SnapSeedDir, "snaps") 111 s.AssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") 112 113 s.devAcct = assertstest.NewAccount(s.StoreSigning, "developer", map[string]interface{}{ 114 "account-id": "developerid", 115 }, "") 116 117 s.AddCleanup(ifacestate.MockSecurityBackends(nil)) 118 119 ovld, err := overlord.New(nil) 120 c.Assert(err, IsNil) 121 ovld.InterfaceManager().DisableUDevMonitor() 122 s.overlord = ovld 123 c.Assert(ovld.StartUp(), IsNil) 124 125 // don't actually try to talk to the store on snapstate.Ensure 126 // needs doing after the call to devicestate.Manager (which happens in overlord.New) 127 snapstate.CanAutoRefresh = nil 128 129 s.perfTimings = timings.New(nil) 130 } 131 132 func checkTrivialSeeding(c *C, tsAll []*state.TaskSet) { 133 // run internal core config and mark seeded 134 c.Check(tsAll, HasLen, 2) 135 tasks := tsAll[0].Tasks() 136 c.Check(tasks, HasLen, 1) 137 c.Assert(tasks[0].Kind(), Equals, "run-hook") 138 var hooksup hookstate.HookSetup 139 err := tasks[0].Get("hook-setup", &hooksup) 140 c.Assert(err, IsNil) 141 c.Check(hooksup.Hook, Equals, "configure") 142 c.Check(hooksup.Snap, Equals, "core") 143 tasks = tsAll[1].Tasks() 144 c.Check(tasks, HasLen, 1) 145 c.Check(tasks[0].Kind(), Equals, "mark-seeded") 146 } 147 148 func (s *FirstBootTestSuite) modelHeaders(modelStr string, reqSnaps ...string) map[string]interface{} { 149 headers := map[string]interface{}{ 150 "architecture": "amd64", 151 "store": "canonical", 152 } 153 if strings.HasSuffix(modelStr, "-classic") { 154 headers["classic"] = "true" 155 } else { 156 headers["kernel"] = "pc-kernel" 157 headers["gadget"] = "pc" 158 } 159 if len(reqSnaps) != 0 { 160 reqs := make([]interface{}, len(reqSnaps)) 161 for i, req := range reqSnaps { 162 reqs[i] = req 163 } 164 headers["required-snaps"] = reqs 165 } 166 return headers 167 } 168 169 func (s *FirstBootTestSuite) makeModelAssertionChain(c *C, modName string, extraHeaders map[string]interface{}, reqSnaps ...string) []asserts.Assertion { 170 return s.MakeModelAssertionChain("my-brand", modName, s.modelHeaders(modName, reqSnaps...), extraHeaders) 171 } 172 173 func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicNoop(c *C) { 174 restore := release.MockOnClassic(true) 175 defer restore() 176 177 st := s.overlord.State() 178 st.Lock() 179 defer st.Unlock() 180 181 err := os.Remove(filepath.Join(dirs.SnapSeedDir, "assertions")) 182 c.Assert(err, IsNil) 183 184 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 185 c.Assert(err, IsNil) 186 checkTrivialSeeding(c, tsAll) 187 188 // already set the fallback model 189 190 // verify that the model was added 191 db := assertstate.DB(st) 192 as, err := db.Find(asserts.ModelType, map[string]string{ 193 "series": "16", 194 "brand-id": "generic", 195 "model": "generic-classic", 196 }) 197 c.Assert(err, IsNil) 198 _, ok := as.(*asserts.Model) 199 c.Check(ok, Equals, true) 200 201 ds, err := devicestatetest.Device(st) 202 c.Assert(err, IsNil) 203 c.Check(ds.Brand, Equals, "generic") 204 c.Check(ds.Model, Equals, "generic-classic") 205 } 206 207 func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicNoSeedYaml(c *C) { 208 restore := release.MockOnClassic(true) 209 defer restore() 210 211 ovld, err := overlord.New(nil) 212 c.Assert(err, IsNil) 213 st := ovld.State() 214 215 // add the model assertion and its chain 216 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 217 s.WriteAssertions("model.asserts", assertsChain...) 218 219 err = os.Remove(filepath.Join(dirs.SnapSeedDir, "seed.yaml")) 220 c.Assert(err, IsNil) 221 222 st.Lock() 223 defer st.Unlock() 224 225 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 226 c.Assert(err, IsNil) 227 checkTrivialSeeding(c, tsAll) 228 229 ds, err := devicestatetest.Device(st) 230 c.Assert(err, IsNil) 231 c.Check(ds.Brand, Equals, "my-brand") 232 c.Check(ds.Model, Equals, "my-model-classic") 233 } 234 235 func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicEmptySeedYaml(c *C) { 236 restore := release.MockOnClassic(true) 237 defer restore() 238 239 ovld, err := overlord.New(nil) 240 c.Assert(err, IsNil) 241 st := ovld.State() 242 243 // add the model assertion and its chain 244 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 245 s.WriteAssertions("model.asserts", assertsChain...) 246 247 // create an empty seed.yaml 248 err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), nil, 0644) 249 c.Assert(err, IsNil) 250 251 st.Lock() 252 defer st.Unlock() 253 254 _, err = devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 255 c.Assert(err, ErrorMatches, "cannot proceed, no snaps to seed") 256 } 257 258 func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicNoSeedYamlWithCloudInstanceData(c *C) { 259 restore := release.MockOnClassic(true) 260 defer restore() 261 262 st := s.overlord.State() 263 264 // add the model assertion and its chain 265 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 266 s.WriteAssertions("model.asserts", assertsChain...) 267 268 err := os.Remove(filepath.Join(dirs.SnapSeedDir, "seed.yaml")) 269 c.Assert(err, IsNil) 270 271 // write cloud instance data 272 const instData = `{ 273 "v1": { 274 "availability-zone": "us-east-2b", 275 "cloud-name": "aws", 276 "instance-id": "i-03bdbe0d89f4c8ec9", 277 "local-hostname": "ip-10-41-41-143", 278 "region": "us-east-2" 279 } 280 }` 281 err = os.MkdirAll(filepath.Dir(dirs.CloudInstanceDataFile), 0755) 282 c.Assert(err, IsNil) 283 err = ioutil.WriteFile(dirs.CloudInstanceDataFile, []byte(instData), 0600) 284 c.Assert(err, IsNil) 285 286 st.Lock() 287 defer st.Unlock() 288 289 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 290 c.Assert(err, IsNil) 291 checkTrivialSeeding(c, tsAll) 292 293 ds, err := devicestatetest.Device(st) 294 c.Assert(err, IsNil) 295 c.Check(ds.Brand, Equals, "my-brand") 296 c.Check(ds.Model, Equals, "my-model-classic") 297 298 // now run the change and check the result 299 // use the expected kind otherwise settle will start another one 300 chg := st.NewChange("seed", "run the populate from seed changes") 301 for _, ts := range tsAll { 302 chg.AddAll(ts) 303 } 304 c.Assert(st.Changes(), HasLen, 1) 305 306 // avoid device reg 307 chg1 := st.NewChange("become-operational", "init device") 308 chg1.SetStatus(state.DoingStatus) 309 310 st.Unlock() 311 err = s.overlord.Settle(settleTimeout) 312 st.Lock() 313 c.Assert(chg.Err(), IsNil) 314 c.Assert(err, IsNil) 315 316 // check marked seeded 317 var seeded bool 318 err = st.Get("seeded", &seeded) 319 c.Assert(err, IsNil) 320 c.Check(seeded, Equals, true) 321 322 // check captured cloud information 323 tr := config.NewTransaction(st) 324 var cloud auth.CloudInfo 325 err = tr.Get("core", "cloud", &cloud) 326 c.Assert(err, IsNil) 327 c.Check(cloud.Name, Equals, "aws") 328 c.Check(cloud.Region, Equals, "us-east-2") 329 c.Check(cloud.AvailabilityZone, Equals, "us-east-2b") 330 } 331 332 func (s *FirstBootTestSuite) TestPopulateFromSeedErrorsOnState(c *C) { 333 st := s.overlord.State() 334 st.Lock() 335 defer st.Unlock() 336 st.Set("seeded", true) 337 338 _, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 339 c.Assert(err, ErrorMatches, "cannot populate state: already seeded") 340 } 341 342 func (s *FirstBootTestSuite) makeCoreSnaps(c *C, extraGadgetYaml string) (coreFname, kernelFname, gadgetFname string) { 343 files := [][]string{} 344 if strings.Contains(extraGadgetYaml, "defaults:") { 345 files = [][]string{{"meta/hooks/configure", ""}} 346 } 347 348 // put core snap into the SnapBlobDir 349 snapYaml := `name: core 350 version: 1.0 351 type: os` 352 coreFname, coreDecl, coreRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical") 353 s.WriteAssertions("core.asserts", coreRev, coreDecl) 354 355 // put kernel snap into the SnapBlobDir 356 snapYaml = `name: pc-kernel 357 version: 1.0 358 type: kernel` 359 kernelFname, kernelDecl, kernelRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical") 360 s.WriteAssertions("kernel.asserts", kernelRev, kernelDecl) 361 362 gadgetYaml := ` 363 volumes: 364 volume-id: 365 bootloader: grub 366 ` 367 gadgetYaml += extraGadgetYaml 368 369 // put gadget snap into the SnapBlobDir 370 files = append(files, []string{"meta/gadget.yaml", gadgetYaml}) 371 372 snapYaml = `name: pc 373 version: 1.0 374 type: gadget` 375 gadgetFname, gadgetDecl, gadgetRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical") 376 s.WriteAssertions("gadget.asserts", gadgetRev, gadgetDecl) 377 378 return coreFname, kernelFname, gadgetFname 379 } 380 381 func checkOrder(c *C, tsAll []*state.TaskSet, snaps ...string) { 382 matched := 0 383 var prevTask *state.Task 384 for i, ts := range tsAll { 385 task0 := ts.Tasks()[0] 386 waitTasks := task0.WaitTasks() 387 if i == 0 { 388 c.Check(waitTasks, HasLen, 0) 389 } else { 390 c.Check(waitTasks, testutil.Contains, prevTask) 391 } 392 prevTask = task0 393 if task0.Kind() != "prerequisites" { 394 continue 395 } 396 snapsup, err := snapstate.TaskSnapSetup(task0) 397 c.Assert(err, IsNil, Commentf("%#v", task0)) 398 c.Check(snapsup.InstanceName(), Equals, snaps[matched]) 399 matched++ 400 } 401 c.Check(matched, Equals, len(snaps)) 402 } 403 404 func checkSeedTasks(c *C, tsAll []*state.TaskSet) { 405 // the tasks of the last taskset must be gadget-connect, mark-seeded 406 lastTasks := tsAll[len(tsAll)-1].Tasks() 407 c.Check(lastTasks, HasLen, 2) 408 gadgetConnectTask := lastTasks[0] 409 markSeededTask := lastTasks[1] 410 c.Check(gadgetConnectTask.Kind(), Equals, "gadget-connect") 411 c.Check(markSeededTask.Kind(), Equals, "mark-seeded") 412 // and the gadget-connect must wait for the other tasks 413 prevTasks := tsAll[len(tsAll)-2].Tasks() 414 otherTask := prevTasks[len(prevTasks)-1] 415 c.Check(gadgetConnectTask.WaitTasks(), testutil.Contains, otherTask) 416 // add the mark-seeded waits for gadget-connects 417 c.Check(markSeededTask.WaitTasks(), DeepEquals, []*state.Task{gadgetConnectTask}) 418 } 419 420 func (s *FirstBootTestSuite) makeSeedChange(c *C, st *state.State) *state.Change { 421 coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "") 422 423 s.WriteAssertions("developer.account", s.devAcct) 424 425 // put a firstboot snap into the SnapBlobDir 426 snapYaml := `name: foo 427 version: 1.0` 428 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 429 s.WriteAssertions("foo.snap-declaration", fooDecl) 430 s.WriteAssertions("foo.snap-revision", fooRev) 431 432 // put a firstboot local snap into the SnapBlobDir 433 snapYaml = `name: local 434 version: 1.0` 435 mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) 436 targetSnapFile2 := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile)) 437 err := os.Rename(mockSnapFile, targetSnapFile2) 438 c.Assert(err, IsNil) 439 440 // add a model assertion and its chain 441 assertsChain := s.makeModelAssertionChain(c, "my-model", nil, "foo") 442 for i, as := range assertsChain { 443 s.WriteAssertions(strconv.Itoa(i), as) 444 } 445 446 // create a seed.yaml 447 content := []byte(fmt.Sprintf(` 448 snaps: 449 - name: core 450 file: %s 451 - name: pc-kernel 452 file: %s 453 - name: pc 454 file: %s 455 - name: foo 456 file: %s 457 devmode: true 458 contact: mailto:some.guy@example.com 459 - name: local 460 unasserted: true 461 file: %s 462 `, coreFname, kernelFname, gadgetFname, fooFname, filepath.Base(targetSnapFile2))) 463 err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 464 c.Assert(err, IsNil) 465 466 // run the firstboot stuff 467 st.Lock() 468 defer st.Unlock() 469 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 470 c.Assert(err, IsNil) 471 472 checkOrder(c, tsAll, "core", "pc-kernel", "pc", "foo", "local") 473 checkSeedTasks(c, tsAll) 474 475 // now run the change and check the result 476 // use the expected kind otherwise settle with start another one 477 chg := st.NewChange("seed", "run the populate from seed changes") 478 for _, ts := range tsAll { 479 chg.AddAll(ts) 480 } 481 c.Assert(st.Changes(), HasLen, 1) 482 483 // avoid device reg 484 chg1 := st.NewChange("become-operational", "init device") 485 chg1.SetStatus(state.DoingStatus) 486 487 return chg 488 } 489 490 func (s *FirstBootTestSuite) TestPopulateFromSeedHappy(c *C) { 491 bloader := bootloadertest.Mock("mock", c.MkDir()) 492 bootloader.Force(bloader) 493 defer bootloader.Force(nil) 494 bloader.SetBootKernel("pc-kernel_1.snap") 495 bloader.SetBootBase("core_1.snap") 496 497 st := s.overlord.State() 498 chg := s.makeSeedChange(c, st) 499 err := s.overlord.Settle(settleTimeout) 500 c.Assert(err, IsNil) 501 502 st.Lock() 503 defer st.Unlock() 504 505 c.Assert(chg.Err(), IsNil) 506 507 // and check the snap got correctly installed 508 c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true) 509 510 c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "local", "x1", "meta", "snap.yaml")), Equals, true) 511 512 // verify 513 r, err := os.Open(dirs.SnapStateFile) 514 c.Assert(err, IsNil) 515 state, err := state.ReadState(nil, r) 516 c.Assert(err, IsNil) 517 518 state.Lock() 519 defer state.Unlock() 520 // check core, kernel, gadget 521 _, err = snapstate.CurrentInfo(state, "core") 522 c.Assert(err, IsNil) 523 _, err = snapstate.CurrentInfo(state, "pc-kernel") 524 c.Assert(err, IsNil) 525 _, err = snapstate.CurrentInfo(state, "pc") 526 c.Assert(err, IsNil) 527 528 // ensure required flag is set on all essential snaps 529 var snapst snapstate.SnapState 530 for _, reqName := range []string{"core", "pc-kernel", "pc"} { 531 err = snapstate.Get(state, reqName, &snapst) 532 c.Assert(err, IsNil) 533 c.Assert(snapst.Required, Equals, true, Commentf("required not set for %v", reqName)) 534 } 535 536 // check foo 537 info, err := snapstate.CurrentInfo(state, "foo") 538 c.Assert(err, IsNil) 539 c.Assert(info.SnapID, Equals, "foodidididididididididididididid") 540 c.Assert(info.Revision, Equals, snap.R(128)) 541 c.Assert(info.Contact, Equals, "mailto:some.guy@example.com") 542 pubAcct, err := assertstate.Publisher(st, info.SnapID) 543 c.Assert(err, IsNil) 544 c.Check(pubAcct.AccountID(), Equals, "developerid") 545 546 err = snapstate.Get(state, "foo", &snapst) 547 c.Assert(err, IsNil) 548 c.Assert(snapst.DevMode, Equals, true) 549 c.Assert(snapst.Required, Equals, true) 550 551 // check local 552 info, err = snapstate.CurrentInfo(state, "local") 553 c.Assert(err, IsNil) 554 c.Assert(info.SnapID, Equals, "") 555 c.Assert(info.Revision, Equals, snap.R("x1")) 556 557 var snapst2 snapstate.SnapState 558 err = snapstate.Get(state, "local", &snapst2) 559 c.Assert(err, IsNil) 560 c.Assert(snapst2.Required, Equals, false) 561 562 // and ensure state is now considered seeded 563 var seeded bool 564 err = state.Get("seeded", &seeded) 565 c.Assert(err, IsNil) 566 c.Check(seeded, Equals, true) 567 568 // check we set seed-time 569 var seedTime time.Time 570 err = state.Get("seed-time", &seedTime) 571 c.Assert(err, IsNil) 572 c.Check(seedTime.IsZero(), Equals, false) 573 } 574 575 func (s *FirstBootTestSuite) TestPopulateFromSeedMissingBootloader(c *C) { 576 st0 := s.overlord.State() 577 st0.Lock() 578 db := assertstate.DB(st0) 579 st0.Unlock() 580 581 // we run only with the relevant managers to produce the error 582 // situation 583 o := overlord.Mock() 584 st := o.State() 585 snapmgr, err := snapstate.Manager(st, o.TaskRunner()) 586 c.Assert(err, IsNil) 587 o.AddManager(snapmgr) 588 589 ifacemgr, err := ifacestate.Manager(st, nil, o.TaskRunner(), nil, nil) 590 c.Assert(err, IsNil) 591 o.AddManager(ifacemgr) 592 c.Assert(o.StartUp(), IsNil) 593 594 st.Lock() 595 assertstate.ReplaceDB(st, db.(*asserts.Database)) 596 st.Unlock() 597 598 o.AddManager(o.TaskRunner()) 599 600 chg := s.makeSeedChange(c, st) 601 602 se := o.StateEngine() 603 // we cannot use Settle because the Change will not become Clean 604 // under the subset of managers 605 for i := 0; i < 25 && !chg.IsReady(); i++ { 606 se.Ensure() 607 se.Wait() 608 } 609 610 st.Lock() 611 defer st.Unlock() 612 c.Assert(chg.Err(), ErrorMatches, `(?s).* cannot determine bootloader.*`) 613 } 614 615 func (s *FirstBootTestSuite) TestPopulateFromSeedHappyMultiAssertsFiles(c *C) { 616 bloader := bootloadertest.Mock("mock", c.MkDir()) 617 bootloader.Force(bloader) 618 defer bootloader.Force(nil) 619 bloader.SetBootKernel("pc-kernel_1.snap") 620 bloader.SetBootBase("core_1.snap") 621 622 coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "") 623 624 // put a firstboot snap into the SnapBlobDir 625 snapYaml := `name: foo 626 version: 1.0` 627 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 628 s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl) 629 630 // put a 2nd firstboot snap into the SnapBlobDir 631 snapYaml = `name: bar 632 version: 1.0` 633 barFname, barDecl, barRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(65), "developerid") 634 s.WriteAssertions("bar.asserts", s.devAcct, barDecl, barRev) 635 636 // add a model assertion and its chain 637 assertsChain := s.makeModelAssertionChain(c, "my-model", nil) 638 s.WriteAssertions("model.asserts", assertsChain...) 639 640 // create a seed.yaml 641 content := []byte(fmt.Sprintf(` 642 snaps: 643 - name: core 644 file: %s 645 - name: pc-kernel 646 file: %s 647 - name: pc 648 file: %s 649 - name: foo 650 file: %s 651 - name: bar 652 file: %s 653 `, coreFname, kernelFname, gadgetFname, fooFname, barFname)) 654 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 655 c.Assert(err, IsNil) 656 657 // run the firstboot stuff 658 st := s.overlord.State() 659 st.Lock() 660 defer st.Unlock() 661 662 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 663 c.Assert(err, IsNil) 664 // use the expected kind otherwise settle with start another one 665 chg := st.NewChange("seed", "run the populate from seed changes") 666 for _, ts := range tsAll { 667 chg.AddAll(ts) 668 } 669 c.Assert(st.Changes(), HasLen, 1) 670 671 // avoid device reg 672 chg1 := st.NewChange("become-operational", "init device") 673 chg1.SetStatus(state.DoingStatus) 674 675 st.Unlock() 676 err = s.overlord.Settle(settleTimeout) 677 st.Lock() 678 c.Assert(chg.Err(), IsNil) 679 c.Assert(err, IsNil) 680 681 // and check the snap got correctly installed 682 c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true) 683 684 // and check the snap got correctly installed 685 c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "bar", "65", "meta", "snap.yaml")), Equals, true) 686 687 // verify 688 r, err := os.Open(dirs.SnapStateFile) 689 c.Assert(err, IsNil) 690 state, err := state.ReadState(nil, r) 691 c.Assert(err, IsNil) 692 693 state.Lock() 694 defer state.Unlock() 695 // check foo 696 info, err := snapstate.CurrentInfo(state, "foo") 697 c.Assert(err, IsNil) 698 c.Check(info.SnapID, Equals, "foodidididididididididididididid") 699 c.Check(info.Revision, Equals, snap.R(128)) 700 pubAcct, err := assertstate.Publisher(st, info.SnapID) 701 c.Assert(err, IsNil) 702 c.Check(pubAcct.AccountID(), Equals, "developerid") 703 704 // check bar 705 info, err = snapstate.CurrentInfo(state, "bar") 706 c.Assert(err, IsNil) 707 c.Check(info.SnapID, Equals, "bardidididididididididididididid") 708 c.Check(info.Revision, Equals, snap.R(65)) 709 pubAcct, err = assertstate.Publisher(st, info.SnapID) 710 c.Assert(err, IsNil) 711 c.Check(pubAcct.AccountID(), Equals, "developerid") 712 } 713 714 func (s *FirstBootTestSuite) TestPopulateFromSeedConfigureHappy(c *C) { 715 bloader := bootloadertest.Mock("mock", c.MkDir()) 716 bootloader.Force(bloader) 717 defer bootloader.Force(nil) 718 bloader.SetBootKernel("pc-kernel_1.snap") 719 bloader.SetBootBase("core_1.snap") 720 721 const defaultsYaml = ` 722 defaults: 723 foodidididididididididididididid: 724 foo-cfg: foo. 725 coreidididididididididididididid: 726 core-cfg: core_cfg_defl 727 pckernelidididididididididididid: 728 pc-kernel-cfg: pc-kernel_cfg_defl 729 pcididididididididididididididid: 730 pc-cfg: pc_cfg_defl 731 ` 732 coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, defaultsYaml) 733 734 s.WriteAssertions("developer.account", s.devAcct) 735 736 // put a firstboot snap into the SnapBlobDir 737 files := [][]string{{"meta/hooks/configure", ""}} 738 snapYaml := `name: foo 739 version: 1.0` 740 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(128), "developerid") 741 s.WriteAssertions("foo.asserts", fooDecl, fooRev) 742 743 // add a model assertion and its chain 744 assertsChain := s.makeModelAssertionChain(c, "my-model", nil, "foo") 745 s.WriteAssertions("model.asserts", assertsChain...) 746 747 // create a seed.yaml 748 content := []byte(fmt.Sprintf(` 749 snaps: 750 - name: core 751 file: %s 752 - name: pc-kernel 753 file: %s 754 - name: pc 755 file: %s 756 - name: foo 757 file: %s 758 `, coreFname, kernelFname, gadgetFname, fooFname)) 759 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 760 c.Assert(err, IsNil) 761 762 // run the firstboot stuff 763 st := s.overlord.State() 764 st.Lock() 765 defer st.Unlock() 766 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 767 c.Assert(err, IsNil) 768 769 checkSeedTasks(c, tsAll) 770 771 // now run the change and check the result 772 // use the expected kind otherwise settle with start another one 773 chg := st.NewChange("seed", "run the populate from seed changes") 774 for _, ts := range tsAll { 775 chg.AddAll(ts) 776 } 777 c.Assert(st.Changes(), HasLen, 1) 778 779 var configured []string 780 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 781 ctx.Lock() 782 defer ctx.Unlock() 783 // we have a gadget at this point(s) 784 ok, err := snapstate.HasSnapOfType(st, snap.TypeGadget) 785 c.Check(err, IsNil) 786 c.Check(ok, Equals, true) 787 configured = append(configured, ctx.InstanceName()) 788 return nil, nil 789 } 790 791 rhk := hookstate.MockRunHook(hookInvoke) 792 defer rhk() 793 794 // ensure we have something that captures the core config 795 restore := configstate.MockConfigcoreRun(func(config.Conf) error { 796 configured = append(configured, "configcore") 797 return nil 798 }) 799 defer restore() 800 801 // avoid device reg 802 chg1 := st.NewChange("become-operational", "init device") 803 chg1.SetStatus(state.DoingStatus) 804 805 st.Unlock() 806 err = s.overlord.Settle(settleTimeout) 807 st.Lock() 808 c.Assert(chg.Err(), IsNil) 809 c.Assert(err, IsNil) 810 811 // and check the snap got correctly installed 812 c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true) 813 814 // verify 815 r, err := os.Open(dirs.SnapStateFile) 816 c.Assert(err, IsNil) 817 state, err := state.ReadState(nil, r) 818 c.Assert(err, IsNil) 819 820 state.Lock() 821 defer state.Unlock() 822 tr := config.NewTransaction(state) 823 var val string 824 825 // check core, kernel, gadget 826 _, err = snapstate.CurrentInfo(state, "core") 827 c.Assert(err, IsNil) 828 err = tr.Get("core", "core-cfg", &val) 829 c.Assert(err, IsNil) 830 c.Check(val, Equals, "core_cfg_defl") 831 832 _, err = snapstate.CurrentInfo(state, "pc-kernel") 833 c.Assert(err, IsNil) 834 err = tr.Get("pc-kernel", "pc-kernel-cfg", &val) 835 c.Assert(err, IsNil) 836 c.Check(val, Equals, "pc-kernel_cfg_defl") 837 838 _, err = snapstate.CurrentInfo(state, "pc") 839 c.Assert(err, IsNil) 840 err = tr.Get("pc", "pc-cfg", &val) 841 c.Assert(err, IsNil) 842 c.Check(val, Equals, "pc_cfg_defl") 843 844 // check foo 845 info, err := snapstate.CurrentInfo(state, "foo") 846 c.Assert(err, IsNil) 847 c.Assert(info.SnapID, Equals, "foodidididididididididididididid") 848 c.Assert(info.Revision, Equals, snap.R(128)) 849 pubAcct, err := assertstate.Publisher(st, info.SnapID) 850 c.Assert(err, IsNil) 851 c.Check(pubAcct.AccountID(), Equals, "developerid") 852 853 // check foo config 854 err = tr.Get("foo", "foo-cfg", &val) 855 c.Assert(err, IsNil) 856 c.Check(val, Equals, "foo.") 857 858 c.Check(configured, DeepEquals, []string{"configcore", "pc-kernel", "pc", "foo"}) 859 860 // and ensure state is now considered seeded 861 var seeded bool 862 err = state.Get("seeded", &seeded) 863 c.Assert(err, IsNil) 864 c.Check(seeded, Equals, true) 865 } 866 867 func (s *FirstBootTestSuite) TestPopulateFromSeedGadgetConnectHappy(c *C) { 868 bloader := bootloadertest.Mock("mock", c.MkDir()) 869 bootloader.Force(bloader) 870 defer bootloader.Force(nil) 871 bloader.SetBootKernel("pc-kernel_1.snap") 872 bloader.SetBootBase("core_1.snap") 873 874 const connectionsYaml = ` 875 connections: 876 - plug: foodidididididididididididididid:network-control 877 ` 878 coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, connectionsYaml) 879 880 s.WriteAssertions("developer.account", s.devAcct) 881 882 snapYaml := `name: foo 883 version: 1.0 884 plugs: 885 network-control: 886 ` 887 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 888 s.WriteAssertions("foo.asserts", fooDecl, fooRev) 889 890 // add a model assertion and its chain 891 assertsChain := s.makeModelAssertionChain(c, "my-model", nil, "foo") 892 s.WriteAssertions("model.asserts", assertsChain...) 893 894 // create a seed.yaml 895 content := []byte(fmt.Sprintf(` 896 snaps: 897 - name: core 898 file: %s 899 - name: pc-kernel 900 file: %s 901 - name: pc 902 file: %s 903 - name: foo 904 file: %s 905 `, coreFname, kernelFname, gadgetFname, fooFname)) 906 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 907 c.Assert(err, IsNil) 908 909 // run the firstboot stuff 910 st := s.overlord.State() 911 st.Lock() 912 defer st.Unlock() 913 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 914 c.Assert(err, IsNil) 915 916 checkSeedTasks(c, tsAll) 917 918 // now run the change and check the result 919 // use the expected kind otherwise settle with start another one 920 chg := st.NewChange("seed", "run the populate from seed changes") 921 for _, ts := range tsAll { 922 chg.AddAll(ts) 923 } 924 c.Assert(st.Changes(), HasLen, 1) 925 926 // avoid device reg 927 chg1 := st.NewChange("become-operational", "init device") 928 chg1.SetStatus(state.DoingStatus) 929 930 st.Unlock() 931 err = s.overlord.Settle(settleTimeout) 932 st.Lock() 933 c.Assert(chg.Err(), IsNil) 934 c.Assert(err, IsNil) 935 936 // and check the snap got correctly installed 937 c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true) 938 939 // verify 940 r, err := os.Open(dirs.SnapStateFile) 941 c.Assert(err, IsNil) 942 state, err := state.ReadState(nil, r) 943 c.Assert(err, IsNil) 944 945 state.Lock() 946 defer state.Unlock() 947 948 // check foo 949 info, err := snapstate.CurrentInfo(state, "foo") 950 c.Assert(err, IsNil) 951 c.Assert(info.SnapID, Equals, "foodidididididididididididididid") 952 c.Assert(info.Revision, Equals, snap.R(128)) 953 pubAcct, err := assertstate.Publisher(st, info.SnapID) 954 c.Assert(err, IsNil) 955 c.Check(pubAcct.AccountID(), Equals, "developerid") 956 957 // check connection 958 var conns map[string]interface{} 959 err = state.Get("conns", &conns) 960 c.Assert(err, IsNil) 961 c.Check(conns, HasLen, 1) 962 c.Check(conns, DeepEquals, map[string]interface{}{ 963 "foo:network-control core:network-control": map[string]interface{}{ 964 "interface": "network-control", "auto": true, "by-gadget": true, 965 }, 966 }) 967 968 // and ensure state is now considered seeded 969 var seeded bool 970 err = state.Get("seeded", &seeded) 971 c.Assert(err, IsNil) 972 c.Check(seeded, Equals, true) 973 } 974 975 func (s *FirstBootTestSuite) TestImportAssertionsFromSeedClassicModelMismatch(c *C) { 976 restore := release.MockOnClassic(true) 977 defer restore() 978 979 ovld, err := overlord.New(nil) 980 c.Assert(err, IsNil) 981 st := ovld.State() 982 983 // add the odel assertion and its chain 984 assertsChain := s.makeModelAssertionChain(c, "my-model", nil) 985 s.WriteAssertions("model.asserts", assertsChain...) 986 987 // import them 988 st.Lock() 989 defer st.Unlock() 990 991 deviceSeed, err := seed.Open(dirs.SnapSeedDir) 992 c.Assert(err, IsNil) 993 994 _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed) 995 c.Assert(err, ErrorMatches, "cannot seed a classic system with an all-snaps model") 996 } 997 998 func (s *FirstBootTestSuite) TestImportAssertionsFromSeedAllSnapsModelMismatch(c *C) { 999 ovld, err := overlord.New(nil) 1000 c.Assert(err, IsNil) 1001 st := ovld.State() 1002 1003 // add the model assertion and its chain 1004 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 1005 s.WriteAssertions("model.asserts", assertsChain...) 1006 1007 // import them 1008 st.Lock() 1009 defer st.Unlock() 1010 1011 deviceSeed, err := seed.Open(dirs.SnapSeedDir) 1012 c.Assert(err, IsNil) 1013 1014 _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed) 1015 c.Assert(err, ErrorMatches, "cannot seed an all-snaps system with a classic model") 1016 } 1017 1018 func (s *FirstBootTestSuite) TestImportAssertionsFromSeedHappy(c *C) { 1019 ovld, err := overlord.New(nil) 1020 c.Assert(err, IsNil) 1021 st := ovld.State() 1022 1023 // add a bunch of assertions (model assertion and its chain) 1024 assertsChain := s.makeModelAssertionChain(c, "my-model", nil) 1025 for i, as := range assertsChain { 1026 fname := strconv.Itoa(i) 1027 if as.Type() == asserts.ModelType { 1028 fname = "model" 1029 } 1030 s.WriteAssertions(fname, as) 1031 } 1032 1033 // import them 1034 st.Lock() 1035 defer st.Unlock() 1036 1037 deviceSeed, err := seed.Open(dirs.SnapSeedDir) 1038 c.Assert(err, IsNil) 1039 1040 model, err := devicestate.ImportAssertionsFromSeed(st, deviceSeed) 1041 c.Assert(err, IsNil) 1042 c.Assert(model, NotNil) 1043 1044 // verify that the model was added 1045 db := assertstate.DB(st) 1046 as, err := db.Find(asserts.ModelType, map[string]string{ 1047 "series": "16", 1048 "brand-id": "my-brand", 1049 "model": "my-model", 1050 }) 1051 c.Assert(err, IsNil) 1052 _, ok := as.(*asserts.Model) 1053 c.Check(ok, Equals, true) 1054 1055 ds, err := devicestatetest.Device(st) 1056 c.Assert(err, IsNil) 1057 c.Check(ds.Brand, Equals, "my-brand") 1058 c.Check(ds.Model, Equals, "my-model") 1059 1060 c.Check(model.BrandID(), Equals, "my-brand") 1061 c.Check(model.Model(), Equals, "my-model") 1062 } 1063 1064 func (s *FirstBootTestSuite) TestImportAssertionsFromSeedMissingSig(c *C) { 1065 st := s.overlord.State() 1066 st.Lock() 1067 defer st.Unlock() 1068 1069 // write out only the model assertion 1070 assertsChain := s.makeModelAssertionChain(c, "my-model", nil) 1071 for _, as := range assertsChain { 1072 if as.Type() == asserts.ModelType { 1073 s.WriteAssertions("model", as) 1074 break 1075 } 1076 } 1077 1078 deviceSeed, err := seed.Open(dirs.SnapSeedDir) 1079 c.Assert(err, IsNil) 1080 1081 // try import and verify that its rejects because other assertions are 1082 // missing 1083 _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed) 1084 c.Assert(err, ErrorMatches, "cannot resolve prerequisite assertion: account-key .*") 1085 } 1086 1087 func (s *FirstBootTestSuite) TestImportAssertionsFromSeedTwoModelAsserts(c *C) { 1088 st := s.overlord.State() 1089 st.Lock() 1090 defer st.Unlock() 1091 1092 // write out two model assertions 1093 model := s.Brands.Model("my-brand", "my-model", s.modelHeaders("my-model")) 1094 s.WriteAssertions("model", model) 1095 1096 model2 := s.Brands.Model("my-brand", "my-second-model", s.modelHeaders("my-second-model")) 1097 s.WriteAssertions("model2", model2) 1098 1099 deviceSeed, err := seed.Open(dirs.SnapSeedDir) 1100 c.Assert(err, IsNil) 1101 1102 // try import and verify that its rejects because other assertions are 1103 // missing 1104 _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed) 1105 c.Assert(err, ErrorMatches, "cannot have multiple model assertions in seed") 1106 } 1107 1108 func (s *FirstBootTestSuite) TestImportAssertionsFromSeedNoModelAsserts(c *C) { 1109 st := s.overlord.State() 1110 st.Lock() 1111 defer st.Unlock() 1112 1113 assertsChain := s.makeModelAssertionChain(c, "my-model", nil) 1114 for i, as := range assertsChain { 1115 if as.Type() != asserts.ModelType { 1116 s.WriteAssertions(strconv.Itoa(i), as) 1117 } 1118 } 1119 1120 deviceSeed, err := seed.Open(dirs.SnapSeedDir) 1121 c.Assert(err, IsNil) 1122 1123 // try import and verify that its rejects because other assertions are 1124 // missing 1125 _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed) 1126 c.Assert(err, ErrorMatches, "seed must have a model assertion") 1127 } 1128 1129 type core18SnapsOpts struct { 1130 classic bool 1131 gadget bool 1132 } 1133 1134 func (s *FirstBootTestSuite) makeCore18Snaps(c *C, opts *core18SnapsOpts) (core18Fn, snapdFn, kernelFn, gadgetFn string) { 1135 if opts == nil { 1136 opts = &core18SnapsOpts{} 1137 } 1138 1139 files := [][]string{} 1140 1141 core18Yaml := `name: core18 1142 version: 1.0 1143 type: base` 1144 core18Fname, core18Decl, core18Rev := s.MakeAssertedSnap(c, core18Yaml, files, snap.R(1), "canonical") 1145 s.WriteAssertions("core18.asserts", core18Rev, core18Decl) 1146 1147 snapdYaml := `name: snapd 1148 version: 1.0 1149 ` 1150 snapdFname, snapdDecl, snapdRev := s.MakeAssertedSnap(c, snapdYaml, nil, snap.R(2), "canonical") 1151 s.WriteAssertions("snapd.asserts", snapdRev, snapdDecl) 1152 1153 var kernelFname string 1154 if !opts.classic { 1155 kernelYaml := `name: pc-kernel 1156 version: 1.0 1157 type: kernel` 1158 fname, kernelDecl, kernelRev := s.MakeAssertedSnap(c, kernelYaml, files, snap.R(1), "canonical") 1159 s.WriteAssertions("kernel.asserts", kernelRev, kernelDecl) 1160 kernelFname = fname 1161 } 1162 1163 if !opts.classic { 1164 gadgetYaml := ` 1165 volumes: 1166 volume-id: 1167 bootloader: grub 1168 ` 1169 files = append(files, []string{"meta/gadget.yaml", gadgetYaml}) 1170 } 1171 1172 var gadgetFname string 1173 if !opts.classic || opts.gadget { 1174 gaYaml := `name: pc 1175 version: 1.0 1176 type: gadget 1177 base: core18 1178 ` 1179 fname, gadgetDecl, gadgetRev := s.MakeAssertedSnap(c, gaYaml, files, snap.R(1), "canonical") 1180 s.WriteAssertions("gadget.asserts", gadgetRev, gadgetDecl) 1181 gadgetFname = fname 1182 } 1183 1184 return core18Fname, snapdFname, kernelFname, gadgetFname 1185 } 1186 1187 func (s *FirstBootTestSuite) TestPopulateFromSeedWithBaseHappy(c *C) { 1188 var sysdLog [][]string 1189 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 1190 sysdLog = append(sysdLog, cmd) 1191 return []byte("ActiveState=inactive\n"), nil 1192 }) 1193 defer systemctlRestorer() 1194 1195 bloader := bootloadertest.Mock("mock", c.MkDir()) 1196 bootloader.Force(bloader) 1197 defer bootloader.Force(nil) 1198 bloader.SetBootKernel("pc-kernel_1.snap") 1199 bloader.SetBootBase("core18_1.snap") 1200 1201 core18Fname, snapdFname, kernelFname, gadgetFname := s.makeCore18Snaps(c, nil) 1202 1203 s.WriteAssertions("developer.account", s.devAcct) 1204 1205 // add a model assertion and its chain 1206 assertsChain := s.makeModelAssertionChain(c, "my-model", map[string]interface{}{"base": "core18"}) 1207 s.WriteAssertions("model.asserts", assertsChain...) 1208 1209 // create a seed.yaml 1210 content := []byte(fmt.Sprintf(` 1211 snaps: 1212 - name: snapd 1213 file: %s 1214 - name: core18 1215 file: %s 1216 - name: pc-kernel 1217 file: %s 1218 - name: pc 1219 file: %s 1220 `, snapdFname, core18Fname, kernelFname, gadgetFname)) 1221 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 1222 c.Assert(err, IsNil) 1223 1224 // run the firstboot stuff 1225 st := s.overlord.State() 1226 st.Lock() 1227 defer st.Unlock() 1228 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 1229 c.Assert(err, IsNil) 1230 1231 checkOrder(c, tsAll, "snapd", "pc-kernel", "core18", "pc") 1232 1233 // now run the change and check the result 1234 // use the expected kind otherwise settle with start another one 1235 chg := st.NewChange("seed", "run the populate from seed changes") 1236 for _, ts := range tsAll { 1237 chg.AddAll(ts) 1238 } 1239 c.Assert(st.Changes(), HasLen, 1) 1240 1241 c.Assert(chg.Err(), IsNil) 1242 1243 // avoid device reg 1244 chg1 := st.NewChange("become-operational", "init device") 1245 chg1.SetStatus(state.DoingStatus) 1246 1247 // run change until it wants to restart 1248 st.Unlock() 1249 err = s.overlord.Settle(settleTimeout) 1250 st.Lock() 1251 c.Assert(err, IsNil) 1252 1253 // at this point the system is "restarting", pretend the restart has 1254 // happened 1255 c.Assert(chg.Status(), Equals, state.DoingStatus) 1256 state.MockRestarting(st, state.RestartUnset) 1257 st.Unlock() 1258 err = s.overlord.Settle(settleTimeout) 1259 st.Lock() 1260 c.Assert(err, IsNil) 1261 c.Assert(chg.Status(), Equals, state.DoneStatus) 1262 1263 // verify 1264 r, err := os.Open(dirs.SnapStateFile) 1265 c.Assert(err, IsNil) 1266 state, err := state.ReadState(nil, r) 1267 c.Assert(err, IsNil) 1268 1269 state.Lock() 1270 defer state.Unlock() 1271 // check snapd, core18, kernel, gadget 1272 _, err = snapstate.CurrentInfo(state, "snapd") 1273 c.Check(err, IsNil) 1274 _, err = snapstate.CurrentInfo(state, "core18") 1275 c.Check(err, IsNil) 1276 _, err = snapstate.CurrentInfo(state, "pc-kernel") 1277 c.Check(err, IsNil) 1278 _, err = snapstate.CurrentInfo(state, "pc") 1279 c.Check(err, IsNil) 1280 1281 // ensure required flag is set on all essential snaps 1282 var snapst snapstate.SnapState 1283 for _, reqName := range []string{"snapd", "core18", "pc-kernel", "pc"} { 1284 err = snapstate.Get(state, reqName, &snapst) 1285 c.Assert(err, IsNil) 1286 c.Assert(snapst.Required, Equals, true, Commentf("required not set for %v", reqName)) 1287 } 1288 1289 // the right systemd commands were run 1290 c.Check(sysdLog, testutil.DeepContains, []string{"start", "usr-lib-snapd.mount"}) 1291 1292 // and ensure state is now considered seeded 1293 var seeded bool 1294 err = state.Get("seeded", &seeded) 1295 c.Assert(err, IsNil) 1296 c.Check(seeded, Equals, true) 1297 1298 // check we set seed-time 1299 var seedTime time.Time 1300 err = state.Get("seed-time", &seedTime) 1301 c.Assert(err, IsNil) 1302 c.Check(seedTime.IsZero(), Equals, false) 1303 } 1304 1305 func (s *FirstBootTestSuite) TestPopulateFromSeedOrdering(c *C) { 1306 s.WriteAssertions("developer.account", s.devAcct) 1307 1308 // add a model assertion and its chain 1309 assertsChain := s.makeModelAssertionChain(c, "my-model", map[string]interface{}{"base": "core18"}) 1310 s.WriteAssertions("model.asserts", assertsChain...) 1311 1312 core18Fname, snapdFname, kernelFname, gadgetFname := s.makeCore18Snaps(c, nil) 1313 1314 snapYaml := `name: snap-req-other-base 1315 version: 1.0 1316 base: other-base 1317 ` 1318 snapFname, snapDecl, snapRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 1319 s.WriteAssertions("snap-req-other-base.asserts", s.devAcct, snapRev, snapDecl) 1320 baseYaml := `name: other-base 1321 version: 1.0 1322 type: base 1323 ` 1324 baseFname, baseDecl, baseRev := s.MakeAssertedSnap(c, baseYaml, nil, snap.R(127), "developerid") 1325 s.WriteAssertions("other-base.asserts", s.devAcct, baseRev, baseDecl) 1326 1327 // create a seed.yaml 1328 content := []byte(fmt.Sprintf(` 1329 snaps: 1330 - name: snapd 1331 file: %s 1332 - name: core18 1333 file: %s 1334 - name: pc-kernel 1335 file: %s 1336 - name: pc 1337 file: %s 1338 - name: snap-req-other-base 1339 file: %s 1340 - name: other-base 1341 file: %s 1342 `, snapdFname, core18Fname, kernelFname, gadgetFname, snapFname, baseFname)) 1343 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 1344 c.Assert(err, IsNil) 1345 1346 // run the firstboot stuff 1347 st := s.overlord.State() 1348 st.Lock() 1349 defer st.Unlock() 1350 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 1351 c.Assert(err, IsNil) 1352 1353 checkOrder(c, tsAll, "snapd", "pc-kernel", "core18", "pc", "other-base", "snap-req-other-base") 1354 } 1355 1356 func (s *FirstBootTestSuite) TestFirstbootGadgetBaseModelBaseMismatch(c *C) { 1357 s.WriteAssertions("developer.account", s.devAcct) 1358 1359 // add a model assertion and its chain 1360 assertsChain := s.makeModelAssertionChain(c, "my-model", map[string]interface{}{"base": "core18"}) 1361 s.WriteAssertions("model.asserts", assertsChain...) 1362 1363 core18Fname, snapdFname, kernelFname, _ := s.makeCore18Snaps(c, nil) 1364 // take the gadget without "base: core18" 1365 _, _, gadgetFname := s.makeCoreSnaps(c, "") 1366 1367 // create a seed.yaml 1368 content := []byte(fmt.Sprintf(` 1369 snaps: 1370 - name: snapd 1371 file: %s 1372 - name: core18 1373 file: %s 1374 - name: pc-kernel 1375 file: %s 1376 - name: pc 1377 file: %s 1378 `, snapdFname, core18Fname, kernelFname, gadgetFname)) 1379 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 1380 c.Assert(err, IsNil) 1381 1382 // run the firstboot stuff 1383 st := s.overlord.State() 1384 st.Lock() 1385 defer st.Unlock() 1386 1387 _, err = devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 1388 c.Assert(err, ErrorMatches, `cannot use gadget snap because its base "core" is different from model base "core18"`) 1389 } 1390 1391 func (s *FirstBootTestSuite) TestPopulateFromSeedWrongContentProviderOrder(c *C) { 1392 bloader := bootloadertest.Mock("mock", c.MkDir()) 1393 bootloader.Force(bloader) 1394 defer bootloader.Force(nil) 1395 bloader.SetBootKernel("pc-kernel_1.snap") 1396 bloader.SetBootBase("core_1.snap") 1397 1398 coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "") 1399 1400 // a snap that uses content providers 1401 snapYaml := `name: gnome-calculator 1402 version: 1.0 1403 plugs: 1404 gtk-3-themes: 1405 interface: content 1406 default-provider: gtk-common-themes 1407 target: $SNAP/data-dir/themes 1408 ` 1409 calcFname, calcDecl, calcRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 1410 s.WriteAssertions("calc.asserts", s.devAcct, calcRev, calcDecl) 1411 1412 // put a 2nd firstboot snap into the SnapBlobDir 1413 snapYaml = `name: gtk-common-themes 1414 version: 1.0 1415 slots: 1416 gtk-3-themes: 1417 interface: content 1418 source: 1419 read: 1420 - $SNAP/share/themes/Adawaita 1421 ` 1422 themesFname, themesDecl, themesRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(65), "developerid") 1423 s.WriteAssertions("themes.asserts", s.devAcct, themesDecl, themesRev) 1424 1425 // add a model assertion and its chain 1426 assertsChain := s.makeModelAssertionChain(c, "my-model", nil) 1427 s.WriteAssertions("model.asserts", assertsChain...) 1428 1429 // create a seed.yaml 1430 content := []byte(fmt.Sprintf(` 1431 snaps: 1432 - name: core 1433 file: %s 1434 - name: pc-kernel 1435 file: %s 1436 - name: pc 1437 file: %s 1438 - name: gnome-calculator 1439 file: %s 1440 - name: gtk-common-themes 1441 file: %s 1442 `, coreFname, kernelFname, gadgetFname, calcFname, themesFname)) 1443 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 1444 c.Assert(err, IsNil) 1445 1446 // run the firstboot stuff 1447 st := s.overlord.State() 1448 st.Lock() 1449 defer st.Unlock() 1450 1451 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 1452 c.Assert(err, IsNil) 1453 // use the expected kind otherwise settle with start another one 1454 chg := st.NewChange("seed", "run the populate from seed changes") 1455 for _, ts := range tsAll { 1456 chg.AddAll(ts) 1457 } 1458 c.Assert(st.Changes(), HasLen, 1) 1459 1460 // avoid device reg 1461 chg1 := st.NewChange("become-operational", "init device") 1462 chg1.SetStatus(state.DoingStatus) 1463 1464 st.Unlock() 1465 err = s.overlord.Settle(settleTimeout) 1466 st.Lock() 1467 1468 c.Assert(chg.Err(), IsNil) 1469 c.Assert(err, IsNil) 1470 1471 // verify the result 1472 var conns map[string]interface{} 1473 err = st.Get("conns", &conns) 1474 c.Assert(err, IsNil) 1475 c.Check(conns, HasLen, 1) 1476 conn, hasConn := conns["gnome-calculator:gtk-3-themes gtk-common-themes:gtk-3-themes"] 1477 c.Check(hasConn, Equals, true) 1478 c.Check(conn.(map[string]interface{})["auto"], Equals, true) 1479 c.Check(conn.(map[string]interface{})["interface"], Equals, "content") 1480 } 1481 1482 func (s *FirstBootTestSuite) TestPopulateFromSeedMissingBase(c *C) { 1483 s.WriteAssertions("developer.account", s.devAcct) 1484 1485 // add a model assertion and its chain 1486 assertsChain := s.makeModelAssertionChain(c, "my-model", nil) 1487 s.WriteAssertions("model.asserts", assertsChain...) 1488 1489 coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "") 1490 1491 // TODO: this test doesn't particularly need to use a local snap 1492 // local snap with unknown base 1493 snapYaml = `name: local 1494 base: foo 1495 version: 1.0` 1496 mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil) 1497 localFname := filepath.Base(mockSnapFile) 1498 targetSnapFile2 := filepath.Join(dirs.SnapSeedDir, "snaps", localFname) 1499 c.Assert(os.Rename(mockSnapFile, targetSnapFile2), IsNil) 1500 1501 // create a seed.yaml 1502 content := []byte(fmt.Sprintf(` 1503 snaps: 1504 - name: core 1505 file: %s 1506 - name: pc-kernel 1507 file: %s 1508 - name: pc 1509 file: %s 1510 - name: local 1511 unasserted: true 1512 file: %s 1513 `, coreFname, kernelFname, gadgetFname, localFname)) 1514 1515 c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644), IsNil) 1516 1517 // run the firstboot stuff 1518 st := s.overlord.State() 1519 st.Lock() 1520 defer st.Unlock() 1521 _, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 1522 c.Assert(err, ErrorMatches, `cannot use snap "local": base "foo" is missing`) 1523 } 1524 1525 func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicWithSnapdOnlyHappy(c *C) { 1526 restore := release.MockOnClassic(true) 1527 defer restore() 1528 1529 var sysdLog [][]string 1530 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 1531 sysdLog = append(sysdLog, cmd) 1532 return []byte("ActiveState=inactive\n"), nil 1533 }) 1534 defer systemctlRestorer() 1535 1536 core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{ 1537 classic: true, 1538 }) 1539 1540 // put a firstboot snap into the SnapBlobDir 1541 snapYaml := `name: foo 1542 version: 1.0 1543 base: core18 1544 ` 1545 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 1546 s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl) 1547 1548 // add a model assertion and its chain 1549 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 1550 s.WriteAssertions("model.asserts", assertsChain...) 1551 1552 // create a seed.yaml 1553 content := []byte(fmt.Sprintf(` 1554 snaps: 1555 - name: snapd 1556 file: %s 1557 - name: foo 1558 file: %s 1559 - name: core18 1560 file: %s 1561 `, snapdFname, fooFname, core18Fname)) 1562 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 1563 c.Assert(err, IsNil) 1564 1565 // run the firstboot stuff 1566 st := s.overlord.State() 1567 st.Lock() 1568 defer st.Unlock() 1569 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 1570 c.Assert(err, IsNil) 1571 1572 checkOrder(c, tsAll, "snapd", "core18", "foo") 1573 1574 // now run the change and check the result 1575 // use the expected kind otherwise settle with start another one 1576 chg := st.NewChange("seed", "run the populate from seed changes") 1577 for _, ts := range tsAll { 1578 chg.AddAll(ts) 1579 } 1580 c.Assert(st.Changes(), HasLen, 1) 1581 1582 c.Assert(chg.Err(), IsNil) 1583 1584 // avoid device reg 1585 chg1 := st.NewChange("become-operational", "init device") 1586 chg1.SetStatus(state.DoingStatus) 1587 1588 // run change until it wants to restart 1589 st.Unlock() 1590 err = s.overlord.Settle(settleTimeout) 1591 st.Lock() 1592 c.Assert(err, IsNil) 1593 1594 // at this point the system is "restarting", pretend the restart has 1595 // happened 1596 c.Assert(chg.Status(), Equals, state.DoingStatus) 1597 state.MockRestarting(st, state.RestartUnset) 1598 st.Unlock() 1599 err = s.overlord.Settle(settleTimeout) 1600 st.Lock() 1601 c.Assert(err, IsNil) 1602 c.Assert(chg.Status(), Equals, state.DoneStatus) 1603 1604 // verify 1605 r, err := os.Open(dirs.SnapStateFile) 1606 c.Assert(err, IsNil) 1607 state, err := state.ReadState(nil, r) 1608 c.Assert(err, IsNil) 1609 1610 state.Lock() 1611 defer state.Unlock() 1612 // check snapd, core18, kernel, gadget 1613 _, err = snapstate.CurrentInfo(state, "snapd") 1614 c.Check(err, IsNil) 1615 _, err = snapstate.CurrentInfo(state, "core18") 1616 c.Check(err, IsNil) 1617 _, err = snapstate.CurrentInfo(state, "foo") 1618 c.Check(err, IsNil) 1619 1620 // and ensure state is now considered seeded 1621 var seeded bool 1622 err = state.Get("seeded", &seeded) 1623 c.Assert(err, IsNil) 1624 c.Check(seeded, Equals, true) 1625 1626 // check we set seed-time 1627 var seedTime time.Time 1628 err = state.Get("seed-time", &seedTime) 1629 c.Assert(err, IsNil) 1630 c.Check(seedTime.IsZero(), Equals, false) 1631 } 1632 1633 func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicWithSnapdOnlyAndGadgetHappy(c *C) { 1634 restore := release.MockOnClassic(true) 1635 defer restore() 1636 1637 var sysdLog [][]string 1638 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 1639 sysdLog = append(sysdLog, cmd) 1640 return []byte("ActiveState=inactive\n"), nil 1641 }) 1642 defer systemctlRestorer() 1643 1644 core18Fname, snapdFname, _, gadgetFname := s.makeCore18Snaps(c, &core18SnapsOpts{ 1645 classic: true, 1646 gadget: true, 1647 }) 1648 1649 // put a firstboot snap into the SnapBlobDir 1650 snapYaml := `name: foo 1651 version: 1.0 1652 base: core18 1653 ` 1654 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 1655 1656 s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl) 1657 1658 // add a model assertion and its chain 1659 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", map[string]interface{}{"gadget": "pc"}) 1660 s.WriteAssertions("model.asserts", assertsChain...) 1661 1662 // create a seed.yaml 1663 content := []byte(fmt.Sprintf(` 1664 snaps: 1665 - name: snapd 1666 file: %s 1667 - name: foo 1668 file: %s 1669 - name: core18 1670 file: %s 1671 - name: pc 1672 file: %s 1673 `, snapdFname, fooFname, core18Fname, gadgetFname)) 1674 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 1675 c.Assert(err, IsNil) 1676 1677 // run the firstboot stuff 1678 st := s.overlord.State() 1679 st.Lock() 1680 defer st.Unlock() 1681 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings) 1682 c.Assert(err, IsNil) 1683 1684 checkOrder(c, tsAll, "snapd", "core18", "pc", "foo") 1685 1686 // now run the change and check the result 1687 // use the expected kind otherwise settle with start another one 1688 chg := st.NewChange("seed", "run the populate from seed changes") 1689 for _, ts := range tsAll { 1690 chg.AddAll(ts) 1691 } 1692 c.Assert(st.Changes(), HasLen, 1) 1693 1694 c.Assert(chg.Err(), IsNil) 1695 1696 // avoid device reg 1697 chg1 := st.NewChange("become-operational", "init device") 1698 chg1.SetStatus(state.DoingStatus) 1699 1700 // run change until it wants to restart 1701 st.Unlock() 1702 err = s.overlord.Settle(settleTimeout) 1703 st.Lock() 1704 c.Assert(err, IsNil) 1705 1706 // at this point the system is "restarting", pretend the restart has 1707 // happened 1708 c.Assert(chg.Status(), Equals, state.DoingStatus) 1709 state.MockRestarting(st, state.RestartUnset) 1710 st.Unlock() 1711 err = s.overlord.Settle(settleTimeout) 1712 st.Lock() 1713 c.Assert(err, IsNil) 1714 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%s", chg.Err())) 1715 1716 // verify 1717 r, err := os.Open(dirs.SnapStateFile) 1718 c.Assert(err, IsNil) 1719 state, err := state.ReadState(nil, r) 1720 c.Assert(err, IsNil) 1721 1722 state.Lock() 1723 defer state.Unlock() 1724 // check snapd, core18, kernel, gadget 1725 _, err = snapstate.CurrentInfo(state, "snapd") 1726 c.Check(err, IsNil) 1727 _, err = snapstate.CurrentInfo(state, "core18") 1728 c.Check(err, IsNil) 1729 _, err = snapstate.CurrentInfo(state, "pc") 1730 c.Check(err, IsNil) 1731 _, err = snapstate.CurrentInfo(state, "foo") 1732 c.Check(err, IsNil) 1733 1734 // and ensure state is now considered seeded 1735 var seeded bool 1736 err = state.Get("seeded", &seeded) 1737 c.Assert(err, IsNil) 1738 c.Check(seeded, Equals, true) 1739 1740 // check we set seed-time 1741 var seedTime time.Time 1742 err = state.Get("seed-time", &seedTime) 1743 c.Assert(err, IsNil) 1744 c.Check(seedTime.IsZero(), Equals, false) 1745 }