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