github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/devicestate/firstboot_preseed_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-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 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/boot/boottest" 31 "github.com/snapcore/snapd/bootloader" 32 "github.com/snapcore/snapd/bootloader/bootloadertest" 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/interfaces" 35 "github.com/snapcore/snapd/overlord/devicestate" 36 "github.com/snapcore/snapd/overlord/hookstate" 37 "github.com/snapcore/snapd/overlord/snapstate" 38 "github.com/snapcore/snapd/overlord/state" 39 "github.com/snapcore/snapd/release" 40 "github.com/snapcore/snapd/seed/seedtest" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snapdenv" 43 "github.com/snapcore/snapd/testutil" 44 ) 45 46 type firstbootPreseed16Suite struct { 47 firstBootBaseTest 48 firstBoot16BaseTest 49 } 50 51 var _ = Suite(&firstbootPreseed16Suite{}) 52 53 func checkPreseedTasks(c *C, tsAll []*state.TaskSet) { 54 // the tasks of the last taskset must be mark-preseeded, mark-seeded, in that order 55 lastTasks := tsAll[len(tsAll)-1].Tasks() 56 c.Check(lastTasks, HasLen, 2) 57 preseedTask := lastTasks[0] 58 markSeededTask := lastTasks[1] 59 c.Assert(preseedTask.Kind(), Equals, "mark-preseeded") 60 c.Check(markSeededTask.Kind(), Equals, "mark-seeded") 61 62 // mark-seeded waits for mark-preseeded 63 var waitsForPreseeded bool 64 for _, wt := range markSeededTask.WaitTasks() { 65 if wt.Kind() == "mark-preseeded" { 66 waitsForPreseeded = true 67 } 68 } 69 c.Check(waitsForPreseeded, Equals, true) 70 } 71 72 func checkPreseedTaskStates(c *C, st *state.State) { 73 doneTasks := map[string]bool{ 74 "prerequisites": true, 75 "prepare-snap": true, 76 "link-snap": true, 77 "mount-snap": true, 78 "setup-profiles": true, 79 "copy-snap-data": true, 80 "set-auto-aliases": true, 81 "setup-aliases": true, 82 "auto-connect": true, 83 } 84 if !release.OnClassic { 85 doneTasks["update-gadget-assets"] = true 86 doneTasks["update-gadget-cmdline"] = true 87 } 88 doTasks := map[string]bool{ 89 "run-hook": true, 90 "mark-seeded": true, 91 "start-snap-services": true, 92 } 93 seenDone := make(map[string]bool) 94 for _, t := range st.Tasks() { 95 if t.Status() == state.DoneStatus { 96 seenDone[t.Kind()] = true 97 } 98 switch { 99 case doneTasks[t.Kind()]: 100 c.Check(t.Status(), Equals, state.DoneStatus, Commentf("task: %s", t.Kind())) 101 case t.Kind() == "mark-preseeded": 102 c.Check(t.Status(), Equals, state.DoingStatus, Commentf("task: %s", t.Kind())) 103 case doTasks[t.Kind()]: 104 c.Check(t.Status(), Equals, state.DoStatus, Commentf("task: %s", t.Kind())) 105 default: 106 c.Fatalf("unhandled task kind %s", t.Kind()) 107 } 108 } 109 110 // sanity: check that doneTasks is not declaring more tasks than 111 // actually expected. 112 c.Check(doneTasks, DeepEquals, seenDone) 113 } 114 115 func markPreseededInWaitChain(t *state.Task) bool { 116 for _, wt := range t.WaitTasks() { 117 if wt.Kind() == "mark-preseeded" { 118 return true 119 } 120 if markPreseededInWaitChain(wt) { 121 return true 122 } 123 } 124 return false 125 } 126 127 func checkPreseedOrder(c *C, tsAll []*state.TaskSet, snaps ...string) { 128 matched := 0 129 markSeeded := 0 130 markPreseeded := 0 131 markPreseededWaitingForAliases := 0 132 133 for _, ts := range tsAll { 134 for _, t := range ts.Tasks() { 135 switch t.Kind() { 136 case "run-hook": 137 // ensure that hooks are run after mark-preseeded 138 c.Check(markPreseededInWaitChain(t), Equals, true) 139 case "mark-seeded": 140 // nothing waits for mark-seeded 141 c.Check(t.HaltTasks(), HasLen, 0) 142 markSeeded++ 143 c.Check(markPreseededInWaitChain(t), Equals, true) 144 case "mark-preseeded": 145 for _, wt := range t.WaitTasks() { 146 if wt.Kind() == "setup-aliases" { 147 markPreseededWaitingForAliases++ 148 } 149 } 150 markPreseeded++ 151 } 152 } 153 } 154 155 c.Check(markSeeded, Equals, 1) 156 c.Check(markPreseeded, Equals, 1) 157 c.Check(markPreseededWaitingForAliases, Equals, len(snaps)) 158 159 // check that prerequisites tasks for all snaps are present and 160 // are chained properly. 161 var prevTask *state.Task 162 for i, ts := range tsAll { 163 task0 := ts.Tasks()[0] 164 waitTasks := task0.WaitTasks() 165 // all tasksets start with prerequisites task, except for 166 // tasksets with just the configure hook of special snaps, 167 // or last taskset. 168 if task0.Kind() != "prerequisites" { 169 if i == len(tsAll)-1 { 170 c.Check(task0.Kind(), Equals, "mark-preseeded") 171 c.Check(ts.Tasks()[1].Kind(), Equals, "mark-seeded") 172 c.Check(ts.Tasks(), HasLen, 2) 173 } else { 174 c.Check(task0.Kind(), Equals, "run-hook") 175 var hsup hookstate.HookSetup 176 c.Assert(task0.Get("hook-setup", &hsup), IsNil) 177 c.Check(hsup.Hook, Equals, "configure") 178 c.Check(ts.Tasks(), HasLen, 1) 179 } 180 continue 181 } 182 183 if i == 0 { 184 c.Check(waitTasks, HasLen, 0) 185 } else { 186 c.Assert(waitTasks, HasLen, 1) 187 c.Assert(waitTasks[0].Kind(), Equals, prevTask.Kind()) 188 c.Check(waitTasks[0], Equals, prevTask) 189 } 190 191 // make sure that install-hooks wait for the previous snap, and for 192 // mark-preseeded. 193 hookEdgeTask, err := ts.Edge(snapstate.HooksEdge) 194 c.Assert(err, IsNil) 195 c.Assert(hookEdgeTask.Kind(), Equals, "run-hook") 196 var hsup hookstate.HookSetup 197 c.Assert(hookEdgeTask.Get("hook-setup", &hsup), IsNil) 198 c.Check(hsup.Hook, Equals, "install") 199 switch hsup.Snap { 200 case "core", "core18", "snapd": 201 // ignore 202 default: 203 // snaps other than core/core18/snapd 204 var waitsForMarkPreseeded, waitsForPreviousSnapHook, waitsForPreviousSnap bool 205 for _, wt := range hookEdgeTask.WaitTasks() { 206 switch wt.Kind() { 207 case "setup-aliases": 208 continue 209 case "run-hook": 210 var wtsup hookstate.HookSetup 211 c.Assert(wt.Get("hook-setup", &wtsup), IsNil) 212 c.Check(wtsup.Snap, Equals, snaps[matched-1]) 213 waitsForPreviousSnapHook = true 214 case "mark-preseeded": 215 waitsForMarkPreseeded = true 216 case "prerequisites": 217 default: 218 snapsup, err := snapstate.TaskSnapSetup(wt) 219 c.Assert(err, IsNil, Commentf("%#v", wt)) 220 c.Check(snapsup.SnapName(), Equals, snaps[matched-1], Commentf("%s: %#v", hsup.Snap, wt)) 221 waitsForPreviousSnap = true 222 } 223 } 224 c.Assert(waitsForMarkPreseeded, Equals, true) 225 c.Assert(waitsForPreviousSnapHook, Equals, true) 226 if snaps[matched-1] != "core" && snaps[matched-1] != "core18" && snaps[matched-1] != "pc" { 227 c.Check(waitsForPreviousSnap, Equals, true, Commentf("%s", snaps[matched-1])) 228 } 229 } 230 231 snapsup, err := snapstate.TaskSnapSetup(task0) 232 c.Assert(err, IsNil, Commentf("%#v", task0)) 233 c.Check(snapsup.InstanceName(), Equals, snaps[matched]) 234 matched++ 235 236 // find setup-aliases task in current taskset; its position 237 // is not fixed due to e.g. optional update-gadget-assets task. 238 var aliasesTask *state.Task 239 for _, t := range ts.Tasks() { 240 if t.Kind() == "setup-aliases" { 241 aliasesTask = t 242 break 243 } 244 } 245 c.Assert(aliasesTask, NotNil) 246 prevTask = aliasesTask 247 } 248 249 c.Check(matched, Equals, len(snaps)) 250 } 251 252 func (s *firstbootPreseed16Suite) SetUpTest(c *C) { 253 s.TestingSeed16 = &seedtest.TestingSeed16{} 254 s.setup16BaseTest(c, &s.firstBootBaseTest) 255 256 s.SeedDir = dirs.SnapSeedDir 257 258 err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "assertions"), 0755) 259 c.Assert(err, IsNil) 260 261 s.AddCleanup(interfaces.MockSystemKey(`{"core": "123"}`)) 262 c.Assert(interfaces.WriteSystemKey(), IsNil) 263 } 264 265 func (s *firstbootPreseed16Suite) TestPreseedHappy(c *C) { 266 restore := snapdenv.MockPreseeding(true) 267 defer restore() 268 269 mockMountCmd := testutil.MockCommand(c, "mount", "") 270 defer mockMountCmd.Restore() 271 272 mockUmountCmd := testutil.MockCommand(c, "umount", "") 273 defer mockUmountCmd.Restore() 274 275 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 276 bootloader.Force(bloader) 277 defer bootloader.Force(nil) 278 bloader.SetBootKernel("pc-kernel_1.snap") 279 bloader.SetBootBase("core_1.snap") 280 281 s.startOverlord(c) 282 st := s.overlord.State() 283 opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true} 284 chg, _ := s.makeSeedChange(c, st, opts, checkPreseedTasks, checkPreseedOrder) 285 err := s.overlord.Settle(settleTimeout) 286 287 st.Lock() 288 defer st.Unlock() 289 290 c.Assert(err, IsNil) 291 c.Assert(chg.Err(), IsNil) 292 293 checkPreseedTaskStates(c, st) 294 } 295 296 func (s *firstbootPreseed16Suite) TestPreseedOnClassicHappy(c *C) { 297 restore := snapdenv.MockPreseeding(true) 298 defer restore() 299 300 restoreRelease := release.MockOnClassic(true) 301 defer restoreRelease() 302 303 mockMountCmd := testutil.MockCommand(c, "mount", "") 304 defer mockMountCmd.Restore() 305 306 mockUmountCmd := testutil.MockCommand(c, "umount", "") 307 defer mockUmountCmd.Restore() 308 309 coreFname, _, _ := s.makeCoreSnaps(c, "") 310 311 // put a firstboot snap into the SnapBlobDir 312 snapYaml := `name: foo 313 version: 1.0 314 ` 315 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 316 s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl) 317 318 // put a firstboot snap into the SnapBlobDir 319 snapYaml2 := `name: bar 320 version: 1.0 321 ` 322 barFname, barDecl, barRev := s.MakeAssertedSnap(c, snapYaml2, nil, snap.R(33), "developerid") 323 s.WriteAssertions("bar.asserts", s.devAcct, barRev, barDecl) 324 325 // add a model assertion and its chain 326 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 327 s.WriteAssertions("model.asserts", assertsChain...) 328 329 // create a seed.yaml 330 content := []byte(fmt.Sprintf(` 331 snaps: 332 - name: foo 333 file: %s 334 - name: bar 335 file: %s 336 - name: core 337 file: %s 338 `, fooFname, barFname, coreFname)) 339 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 340 c.Assert(err, IsNil) 341 342 // run the firstboot stuff 343 s.startOverlord(c) 344 st := s.overlord.State() 345 st.Lock() 346 defer st.Unlock() 347 348 opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true} 349 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings) 350 c.Assert(err, IsNil) 351 352 chg := st.NewChange("seed", "run the populate from seed changes") 353 for _, ts := range tsAll { 354 chg.AddAll(ts) 355 } 356 c.Assert(st.Changes(), HasLen, 1) 357 358 checkPreseedOrder(c, tsAll, "core", "foo", "bar") 359 360 st.Unlock() 361 err = s.overlord.Settle(settleTimeout) 362 st.Lock() 363 364 c.Assert(err, IsNil) 365 c.Assert(chg.Err(), IsNil) 366 367 checkPreseedTaskStates(c, st) 368 c.Check(chg.Status(), Equals, state.DoingStatus) 369 370 // verify 371 r, err := os.Open(dirs.SnapStateFile) 372 c.Assert(err, IsNil) 373 diskState, err := state.ReadState(nil, r) 374 c.Assert(err, IsNil) 375 376 diskState.Lock() 377 defer diskState.Unlock() 378 379 // seeded snaps are installed 380 _, err = snapstate.CurrentInfo(diskState, "core") 381 c.Check(err, IsNil) 382 _, err = snapstate.CurrentInfo(diskState, "foo") 383 c.Check(err, IsNil) 384 _, err = snapstate.CurrentInfo(diskState, "bar") 385 c.Check(err, IsNil) 386 387 // but we're not considered seeded 388 var seeded bool 389 err = diskState.Get("seeded", &seeded) 390 c.Assert(err, Equals, state.ErrNoState) 391 } 392 393 func (s *firstbootPreseed16Suite) TestPreseedClassicWithSnapdOnlyHappy(c *C) { 394 restorePreseedMode := snapdenv.MockPreseeding(true) 395 defer restorePreseedMode() 396 397 restore := release.MockOnClassic(true) 398 defer restore() 399 400 mockMountCmd := testutil.MockCommand(c, "mount", "") 401 defer mockMountCmd.Restore() 402 403 mockUmountCmd := testutil.MockCommand(c, "umount", "") 404 defer mockUmountCmd.Restore() 405 406 core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{ 407 classic: true, 408 }) 409 410 // put a firstboot snap into the SnapBlobDir 411 snapYaml := `name: foo 412 version: 1.0 413 base: core18 414 ` 415 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 416 s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl) 417 418 // add a model assertion and its chain 419 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 420 s.WriteAssertions("model.asserts", assertsChain...) 421 422 // create a seed.yaml 423 content := []byte(fmt.Sprintf(` 424 snaps: 425 - name: snapd 426 file: %s 427 - name: foo 428 file: %s 429 - name: core18 430 file: %s 431 `, snapdFname, fooFname, core18Fname)) 432 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 433 c.Assert(err, IsNil) 434 435 // run the firstboot stuff 436 s.startOverlord(c) 437 st := s.overlord.State() 438 st.Lock() 439 defer st.Unlock() 440 441 opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true} 442 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings) 443 c.Assert(err, IsNil) 444 445 // now run the change and check the result 446 chg := st.NewChange("seed", "run the populate from seed changes") 447 for _, ts := range tsAll { 448 chg.AddAll(ts) 449 } 450 c.Assert(st.Changes(), HasLen, 1) 451 c.Assert(chg.Err(), IsNil) 452 453 checkPreseedOrder(c, tsAll, "snapd", "core18", "foo") 454 455 st.Unlock() 456 err = s.overlord.Settle(settleTimeout) 457 st.Lock() 458 c.Assert(err, IsNil) 459 460 checkPreseedTaskStates(c, st) 461 c.Check(chg.Status(), Equals, state.DoingStatus) 462 463 // verify 464 r, err := os.Open(dirs.SnapStateFile) 465 c.Assert(err, IsNil) 466 diskState, err := state.ReadState(nil, r) 467 c.Assert(err, IsNil) 468 469 diskState.Lock() 470 defer diskState.Unlock() 471 472 // seeded snaps are installed 473 _, err = snapstate.CurrentInfo(diskState, "snapd") 474 c.Check(err, IsNil) 475 _, err = snapstate.CurrentInfo(diskState, "core18") 476 c.Check(err, IsNil) 477 _, err = snapstate.CurrentInfo(diskState, "foo") 478 c.Check(err, IsNil) 479 480 // but we're not considered seeded 481 var seeded bool 482 err = diskState.Get("seeded", &seeded) 483 c.Assert(err, Equals, state.ErrNoState) 484 }