github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 snapsup, err := snapstate.TaskSnapSetup(task0) 183 c.Assert(err, IsNil, Commentf("%#v", task0)) 184 c.Check(snapsup.InstanceName(), Equals, snaps[matched]) 185 matched++ 186 if i == 0 { 187 c.Check(waitTasks, HasLen, 0) 188 } else { 189 c.Assert(waitTasks, HasLen, 1) 190 c.Assert(waitTasks[0].Kind(), Equals, prevTask.Kind()) 191 c.Check(waitTasks[0], Equals, prevTask) 192 } 193 194 // find setup-aliases task in current taskset; its position 195 // is not fixed due to e.g. optional update-gadget-assets task. 196 var aliasesTask *state.Task 197 for _, t := range ts.Tasks() { 198 if t.Kind() == "setup-aliases" { 199 aliasesTask = t 200 break 201 } 202 } 203 c.Assert(aliasesTask, NotNil) 204 prevTask = aliasesTask 205 } 206 207 c.Check(matched, Equals, len(snaps)) 208 } 209 210 func (s *firstbootPreseed16Suite) SetUpTest(c *C) { 211 s.TestingSeed16 = &seedtest.TestingSeed16{} 212 s.setup16BaseTest(c, &s.firstBootBaseTest) 213 214 s.SeedDir = dirs.SnapSeedDir 215 216 err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "assertions"), 0755) 217 c.Assert(err, IsNil) 218 219 s.AddCleanup(interfaces.MockSystemKey(`{"core": "123"}`)) 220 c.Assert(interfaces.WriteSystemKey(), IsNil) 221 } 222 223 func (s *firstbootPreseed16Suite) TestPreseedHappy(c *C) { 224 restore := snapdenv.MockPreseeding(true) 225 defer restore() 226 227 mockMountCmd := testutil.MockCommand(c, "mount", "") 228 defer mockMountCmd.Restore() 229 230 mockUmountCmd := testutil.MockCommand(c, "umount", "") 231 defer mockUmountCmd.Restore() 232 233 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 234 bootloader.Force(bloader) 235 defer bootloader.Force(nil) 236 bloader.SetBootKernel("pc-kernel_1.snap") 237 bloader.SetBootBase("core_1.snap") 238 239 s.startOverlord(c) 240 st := s.overlord.State() 241 opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true} 242 chg, _ := s.makeSeedChange(c, st, opts, checkPreseedTasks, checkPreseedOrder) 243 err := s.overlord.Settle(settleTimeout) 244 245 st.Lock() 246 defer st.Unlock() 247 248 c.Assert(err, IsNil) 249 c.Assert(chg.Err(), IsNil) 250 251 checkPreseedTaskStates(c, st) 252 } 253 254 func (s *firstbootPreseed16Suite) TestPreseedOnClassicHappy(c *C) { 255 restore := snapdenv.MockPreseeding(true) 256 defer restore() 257 258 restoreRelease := release.MockOnClassic(true) 259 defer restoreRelease() 260 261 mockMountCmd := testutil.MockCommand(c, "mount", "") 262 defer mockMountCmd.Restore() 263 264 mockUmountCmd := testutil.MockCommand(c, "umount", "") 265 defer mockUmountCmd.Restore() 266 267 coreFname, _, _ := s.makeCoreSnaps(c, "") 268 269 // put a firstboot snap into the SnapBlobDir 270 snapYaml := `name: foo 271 version: 1.0 272 ` 273 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 274 s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl) 275 276 // add a model assertion and its chain 277 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 278 s.WriteAssertions("model.asserts", assertsChain...) 279 280 // create a seed.yaml 281 content := []byte(fmt.Sprintf(` 282 snaps: 283 - name: foo 284 file: %s 285 - name: core 286 file: %s 287 `, fooFname, coreFname)) 288 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 289 c.Assert(err, IsNil) 290 291 // run the firstboot stuff 292 s.startOverlord(c) 293 st := s.overlord.State() 294 st.Lock() 295 defer st.Unlock() 296 297 opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true} 298 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings) 299 c.Assert(err, IsNil) 300 301 chg := st.NewChange("seed", "run the populate from seed changes") 302 for _, ts := range tsAll { 303 chg.AddAll(ts) 304 } 305 c.Assert(st.Changes(), HasLen, 1) 306 307 checkPreseedOrder(c, tsAll, "core", "foo") 308 309 st.Unlock() 310 err = s.overlord.Settle(settleTimeout) 311 st.Lock() 312 313 c.Assert(err, IsNil) 314 c.Assert(chg.Err(), IsNil) 315 316 checkPreseedTaskStates(c, st) 317 c.Check(chg.Status(), Equals, state.DoingStatus) 318 319 // verify 320 r, err := os.Open(dirs.SnapStateFile) 321 c.Assert(err, IsNil) 322 diskState, err := state.ReadState(nil, r) 323 c.Assert(err, IsNil) 324 325 diskState.Lock() 326 defer diskState.Unlock() 327 328 // seeded snaps are installed 329 _, err = snapstate.CurrentInfo(diskState, "core") 330 c.Check(err, IsNil) 331 _, err = snapstate.CurrentInfo(diskState, "foo") 332 c.Check(err, IsNil) 333 334 // but we're not considered seeded 335 var seeded bool 336 err = diskState.Get("seeded", &seeded) 337 c.Assert(err, Equals, state.ErrNoState) 338 } 339 340 func (s *firstbootPreseed16Suite) TestPreseedClassicWithSnapdOnlyHappy(c *C) { 341 restorePreseedMode := snapdenv.MockPreseeding(true) 342 defer restorePreseedMode() 343 344 restore := release.MockOnClassic(true) 345 defer restore() 346 347 mockMountCmd := testutil.MockCommand(c, "mount", "") 348 defer mockMountCmd.Restore() 349 350 mockUmountCmd := testutil.MockCommand(c, "umount", "") 351 defer mockUmountCmd.Restore() 352 353 core18Fname, snapdFname, _, _ := s.makeCore18Snaps(c, &core18SnapsOpts{ 354 classic: true, 355 }) 356 357 // put a firstboot snap into the SnapBlobDir 358 snapYaml := `name: foo 359 version: 1.0 360 base: core18 361 ` 362 fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid") 363 s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl) 364 365 // add a model assertion and its chain 366 assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil) 367 s.WriteAssertions("model.asserts", assertsChain...) 368 369 // create a seed.yaml 370 content := []byte(fmt.Sprintf(` 371 snaps: 372 - name: snapd 373 file: %s 374 - name: foo 375 file: %s 376 - name: core18 377 file: %s 378 `, snapdFname, fooFname, core18Fname)) 379 err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644) 380 c.Assert(err, IsNil) 381 382 // run the firstboot stuff 383 s.startOverlord(c) 384 st := s.overlord.State() 385 st.Lock() 386 defer st.Unlock() 387 388 opts := &devicestate.PopulateStateFromSeedOptions{Preseed: true} 389 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, opts, s.perfTimings) 390 c.Assert(err, IsNil) 391 392 checkPreseedOrder(c, tsAll, "snapd", "core18", "foo") 393 394 // now run the change and check the result 395 chg := st.NewChange("seed", "run the populate from seed changes") 396 for _, ts := range tsAll { 397 chg.AddAll(ts) 398 } 399 c.Assert(st.Changes(), HasLen, 1) 400 c.Assert(chg.Err(), IsNil) 401 402 st.Unlock() 403 err = s.overlord.Settle(settleTimeout) 404 st.Lock() 405 c.Assert(err, IsNil) 406 407 checkPreseedTaskStates(c, st) 408 c.Check(chg.Status(), Equals, state.DoingStatus) 409 410 // verify 411 r, err := os.Open(dirs.SnapStateFile) 412 c.Assert(err, IsNil) 413 diskState, err := state.ReadState(nil, r) 414 c.Assert(err, IsNil) 415 416 diskState.Lock() 417 defer diskState.Unlock() 418 419 // seeded snaps are installed 420 _, err = snapstate.CurrentInfo(diskState, "snapd") 421 c.Check(err, IsNil) 422 _, err = snapstate.CurrentInfo(diskState, "core18") 423 c.Check(err, IsNil) 424 _, err = snapstate.CurrentInfo(diskState, "foo") 425 c.Check(err, IsNil) 426 427 // but we're not considered seeded 428 var seeded bool 429 err = diskState.Get("seeded", &seeded) 430 c.Assert(err, Equals, state.ErrNoState) 431 }