github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/devicestate/systems_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "sort" 29 "strings" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/boot" 35 "github.com/snapcore/snapd/bootloader" 36 "github.com/snapcore/snapd/bootloader/bootloadertest" 37 "github.com/snapcore/snapd/logger" 38 "github.com/snapcore/snapd/osutil" 39 "github.com/snapcore/snapd/overlord/devicestate" 40 "github.com/snapcore/snapd/seed/seedtest" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snap/snaptest" 43 "github.com/snapcore/snapd/testutil" 44 ) 45 46 type createSystemSuite struct { 47 deviceMgrBaseSuite 48 49 ss *seedtest.SeedSnaps 50 51 logbuf *bytes.Buffer 52 } 53 54 var _ = Suite(&createSystemSuite{}) 55 56 var ( 57 genericSnapYaml = "name: %s\nversion: 1.0\n%s" 58 snapYamls = map[string]string{ 59 "pc-kernel": "name: pc-kernel\nversion: 1.0\ntype: kernel", 60 "pc": "name: pc\nversion: 1.0\ntype: gadget\nbase: core20", 61 "core20": "name: core20\nversion: 20.1\ntype: base", 62 "core18": "name: core18\nversion: 18.1\ntype: base", 63 "snapd": "name: snapd\nversion: 2.2.2\ntype: snapd", 64 "other-required": fmt.Sprintf(genericSnapYaml, "other-required", "base: core20"), 65 "other-present": fmt.Sprintf(genericSnapYaml, "other-present", "base: core20"), 66 "other-core18": fmt.Sprintf(genericSnapYaml, "other-present", "base: core18"), 67 "other-unasserted": fmt.Sprintf(genericSnapYaml, "other-unasserted", "base: core20"), 68 } 69 snapFiles = map[string][][]string{ 70 "pc": { 71 {"meta/gadget.yaml", gadgetYaml}, 72 {"cmdline.extra", "args from gadget"}, 73 }, 74 } 75 ) 76 77 func (s *createSystemSuite) SetUpTest(c *C) { 78 s.deviceMgrBaseSuite.SetUpTest(c) 79 80 s.ss = &seedtest.SeedSnaps{ 81 StoreSigning: s.storeSigning, 82 Brands: s.brands, 83 } 84 s.AddCleanup(func() { bootloader.Force(nil) }) 85 86 buf, restore := logger.MockLogger() 87 s.AddCleanup(restore) 88 s.logbuf = buf 89 } 90 91 func (s *createSystemSuite) makeSnap(c *C, name string, rev snap.Revision) *snap.Info { 92 snapID := s.ss.AssertedSnapID(name) 93 if rev.Unset() || rev.Local() { 94 snapID = "" 95 } 96 si := &snap.SideInfo{ 97 RealName: name, 98 SnapID: snapID, 99 Revision: rev, 100 } 101 where, info := snaptest.MakeTestSnapInfoWithFiles(c, snapYamls[name], snapFiles[name], si) 102 c.Assert(os.MkdirAll(filepath.Dir(info.MountFile()), 0755), IsNil) 103 c.Assert(os.Rename(where, info.MountFile()), IsNil) 104 if !rev.Unset() && !rev.Local() { 105 // snap is non local, generate relevant assertions 106 s.setupSnapDecl(c, info, "my-brand") 107 s.setupSnapRevision(c, info, "my-brand", rev) 108 } 109 return info 110 } 111 112 func (s *createSystemSuite) makeEssentialSnapInfos(c *C) map[string]*snap.Info { 113 infos := map[string]*snap.Info{} 114 infos["pc-kernel"] = s.makeSnap(c, "pc-kernel", snap.R(1)) 115 infos["pc"] = s.makeSnap(c, "pc", snap.R(2)) 116 infos["core20"] = s.makeSnap(c, "core20", snap.R(3)) 117 infos["snapd"] = s.makeSnap(c, "snapd", snap.R(4)) 118 return infos 119 } 120 121 func validateCore20Seed(c *C, name string, expectedModel *asserts.Model, trusted []asserts.Assertion, runModeSnapNames ...string) { 122 const usesSnapd = true 123 sd := seedtest.ValidateSeed(c, boot.InitramfsUbuntuSeedDir, name, usesSnapd, trusted) 124 125 snaps, err := sd.ModeSnaps(boot.ModeRun) 126 c.Assert(err, IsNil) 127 seenSnaps := []string{} 128 for _, sn := range snaps { 129 seenSnaps = append(seenSnaps, sn.SnapName()) 130 } 131 sort.Strings(seenSnaps) 132 sort.Strings(runModeSnapNames) 133 if len(runModeSnapNames) != 0 { 134 c.Check(seenSnaps, DeepEquals, runModeSnapNames) 135 } else { 136 c.Check(seenSnaps, HasLen, 0) 137 } 138 139 c.Assert(sd.Model(), DeepEquals, expectedModel) 140 } 141 142 func (s *createSystemSuite) TestCreateSystemFromAssertedSnaps(c *C) { 143 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 144 // make it simple for now, no assets 145 bl.TrustedAssetsList = nil 146 bl.StaticCommandLine = "mock static" 147 bl.CandidateStaticCommandLine = "unused" 148 bootloader.Force(bl) 149 150 s.state.Lock() 151 defer s.state.Unlock() 152 s.setupBrands(c) 153 infos := s.makeEssentialSnapInfos(c) 154 infos["other-present"] = s.makeSnap(c, "other-present", snap.R(5)) 155 infos["other-required"] = s.makeSnap(c, "other-required", snap.R(6)) 156 infos["other-core18"] = s.makeSnap(c, "other-core18", snap.R(7)) 157 infos["core18"] = s.makeSnap(c, "core18", snap.R(8)) 158 159 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 160 "architecture": "amd64", 161 "grade": "dangerous", 162 "base": "core20", 163 "snaps": []interface{}{ 164 map[string]interface{}{ 165 "name": "pc-kernel", 166 "id": s.ss.AssertedSnapID("pc-kernel"), 167 "type": "kernel", 168 "default-channel": "20", 169 }, 170 map[string]interface{}{ 171 "name": "pc", 172 "id": s.ss.AssertedSnapID("pc"), 173 "type": "gadget", 174 "default-channel": "20", 175 }, 176 map[string]interface{}{ 177 "name": "snapd", 178 "id": s.ss.AssertedSnapID("snapd"), 179 "type": "snapd", 180 }, 181 // optional but not present 182 map[string]interface{}{ 183 "name": "other-not-present", 184 "id": s.ss.AssertedSnapID("other-not-present"), 185 "presence": "optional", 186 }, 187 // optional and present 188 map[string]interface{}{ 189 "name": "other-present", 190 "id": s.ss.AssertedSnapID("other-present"), 191 "presence": "optional", 192 }, 193 // required 194 map[string]interface{}{ 195 "name": "other-required", 196 "id": s.ss.AssertedSnapID("other-required"), 197 "presence": "required", 198 }, 199 // different base 200 map[string]interface{}{ 201 "name": "other-core18", 202 "id": s.ss.AssertedSnapID("other-core18"), 203 }, 204 // and the actual base for that snap 205 map[string]interface{}{ 206 "name": "core18", 207 "id": s.ss.AssertedSnapID("core18"), 208 "type": "base", 209 }, 210 }, 211 }) 212 expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234") 213 214 infoGetter := func(name string) (*snap.Info, bool, error) { 215 c.Logf("called for: %q", name) 216 info, present := infos[name] 217 return info, present, nil 218 } 219 var newFiles []string 220 snapWriteObserver := func(dir, where string) error { 221 c.Check(dir, Equals, expectedDir) 222 c.Check(where, testutil.FileAbsent) 223 newFiles = append(newFiles, where) 224 return nil 225 } 226 227 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, infoGetter, snapWriteObserver) 228 c.Assert(err, IsNil) 229 c.Check(newFiles, DeepEquals, []string{ 230 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"), 231 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"), 232 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"), 233 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"), 234 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/other-present_5.snap"), 235 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/other-required_6.snap"), 236 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/other-core18_7.snap"), 237 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core18_8.snap"), 238 }) 239 c.Check(dir, Equals, expectedDir) 240 // naive check for files being present 241 for _, info := range infos { 242 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", filepath.Base(info.MountFile())), 243 testutil.FileEquals, 244 testutil.FileContentRef(info.MountFile())) 245 } 246 // recovery system bootenv was set 247 c.Check(bl.RecoverySystemDir, Equals, "/systems/1234") 248 c.Check(bl.RecoverySystemBootVars, DeepEquals, map[string]string{ 249 "snapd_full_cmdline_args": "", 250 "snapd_extra_cmdline_args": "args from gadget", 251 "snapd_recovery_kernel": "/snaps/pc-kernel_1.snap", 252 }) 253 // load the seed 254 validateCore20Seed(c, "1234", model, s.storeSigning.Trusted, 255 "other-core18", "core18", "other-present", "other-required") 256 } 257 258 func (s *createSystemSuite) TestCreateSystemFromUnassertedSnaps(c *C) { 259 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 260 // make it simple for now, no assets 261 bl.TrustedAssetsList = nil 262 bl.StaticCommandLine = "mock static" 263 bl.CandidateStaticCommandLine = "unused" 264 bootloader.Force(bl) 265 266 s.state.Lock() 267 defer s.state.Unlock() 268 s.setupBrands(c) 269 infos := s.makeEssentialSnapInfos(c) 270 // unasserted with local revision 271 infos["other-unasserted"] = s.makeSnap(c, "other-unasserted", snap.R(-1)) 272 273 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 274 "architecture": "amd64", 275 "grade": "dangerous", 276 "base": "core20", 277 "snaps": []interface{}{ 278 map[string]interface{}{ 279 "name": "pc-kernel", 280 "id": s.ss.AssertedSnapID("pc-kernel"), 281 "type": "kernel", 282 "default-channel": "20", 283 }, 284 map[string]interface{}{ 285 "name": "pc", 286 "id": s.ss.AssertedSnapID("pc"), 287 "type": "gadget", 288 "default-channel": "20", 289 }, 290 map[string]interface{}{ 291 "name": "snapd", 292 "id": s.ss.AssertedSnapID("snapd"), 293 "type": "snapd", 294 }, 295 // required 296 map[string]interface{}{ 297 "name": "other-unasserted", 298 "presence": "required", 299 }, 300 }, 301 }) 302 expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234") 303 304 infoGetter := func(name string) (*snap.Info, bool, error) { 305 c.Logf("called for: %q", name) 306 info, present := infos[name] 307 return info, present, nil 308 } 309 var newFiles []string 310 snapWriteObserver := func(dir, where string) error { 311 c.Check(dir, Equals, expectedDir) 312 c.Check(where, testutil.FileAbsent) 313 newFiles = append(newFiles, where) 314 return nil 315 } 316 317 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, infoGetter, snapWriteObserver) 318 c.Assert(err, IsNil) 319 c.Check(newFiles, DeepEquals, []string{ 320 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"), 321 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"), 322 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"), 323 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"), 324 // this snap unasserted and lands under the system 325 filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234/snaps/other-unasserted_1.0.snap"), 326 }) 327 c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")) 328 // naive check for files being present 329 for _, info := range infos { 330 if info.Revision.Store() { 331 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", filepath.Base(info.MountFile())), 332 testutil.FileEquals, 333 testutil.FileContentRef(info.MountFile())) 334 } else { 335 fileName := fmt.Sprintf("%s_%s.snap", info.SnapName(), info.Version) 336 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234/snaps", fileName), 337 testutil.FileEquals, 338 testutil.FileContentRef(info.MountFile())) 339 } 340 } 341 // load the seed 342 validateCore20Seed(c, "1234", model, s.storeSigning.Trusted, "other-unasserted") 343 // we have unasserted snaps, so a warning should have been logged 344 c.Check(s.logbuf.String(), testutil.Contains, `system "1234" contains unasserted snaps "other-unasserted"`) 345 } 346 347 func (s *createSystemSuite) TestCreateSystemWithSomeSnapsAlreadyExisting(c *C) { 348 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 349 bootloader.Force(bl) 350 351 s.state.Lock() 352 defer s.state.Unlock() 353 s.setupBrands(c) 354 infos := s.makeEssentialSnapInfos(c) 355 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 356 "architecture": "amd64", 357 "grade": "dangerous", 358 "base": "core20", 359 "snaps": []interface{}{ 360 map[string]interface{}{ 361 "name": "pc-kernel", 362 "id": s.ss.AssertedSnapID("pc-kernel"), 363 "type": "kernel", 364 "default-channel": "20", 365 }, 366 map[string]interface{}{ 367 "name": "pc", 368 "id": s.ss.AssertedSnapID("pc"), 369 "type": "gadget", 370 "default-channel": "20", 371 }, 372 map[string]interface{}{ 373 "name": "snapd", 374 "id": s.ss.AssertedSnapID("snapd"), 375 "type": "snapd", 376 }, 377 }, 378 }) 379 expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234") 380 381 infoGetter := func(name string) (*snap.Info, bool, error) { 382 c.Logf("called for: %q", name) 383 info, present := infos[name] 384 return info, present, nil 385 } 386 var newFiles []string 387 snapWriteObserver := func(dir, where string) error { 388 c.Check(dir, Equals, expectedDir) 389 // we are not called for the snap which already exists 390 c.Check(where, testutil.FileAbsent) 391 newFiles = append(newFiles, where) 392 return nil 393 } 394 395 assertedSnapsDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps") 396 c.Assert(os.MkdirAll(assertedSnapsDir, 0755), IsNil) 397 // procure the file in place 398 err := osutil.CopyFile(infos["core20"].MountFile(), filepath.Join(assertedSnapsDir, "core20_3.snap"), 0) 399 c.Assert(err, IsNil) 400 401 // when a given snap in asserted snaps directory already exists, it is 402 // not copied over 403 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, infoGetter, snapWriteObserver) 404 c.Assert(err, IsNil) 405 c.Check(newFiles, DeepEquals, []string{ 406 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"), 407 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"), 408 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"), 409 }) 410 c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")) 411 // naive check for files being present 412 for _, info := range infos { 413 c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", filepath.Base(info.MountFile())), 414 testutil.FileEquals, 415 testutil.FileContentRef(info.MountFile())) 416 } 417 // recovery system bootenv was set 418 c.Check(bl.RecoverySystemDir, Equals, "/systems/1234") 419 c.Check(bl.RecoverySystemBootVars, DeepEquals, map[string]string{ 420 "snapd_full_cmdline_args": "", 421 "snapd_extra_cmdline_args": "args from gadget", 422 "snapd_recovery_kernel": "/snaps/pc-kernel_1.snap", 423 }) 424 // load the seed 425 validateCore20Seed(c, "1234", model, s.storeSigning.Trusted) 426 427 // add an unasserted snap 428 infos["other-unasserted"] = s.makeSnap(c, "other-unasserted", snap.R(-1)) 429 modelWithUnasserted := s.makeModelAssertionInState(c, "my-brand", "pc-with-unasserted", map[string]interface{}{ 430 "architecture": "amd64", 431 "grade": "dangerous", 432 "base": "core20", 433 "snaps": []interface{}{ 434 map[string]interface{}{ 435 "name": "pc-kernel", 436 "id": s.ss.AssertedSnapID("pc-kernel"), 437 "type": "kernel", 438 "default-channel": "20", 439 }, 440 map[string]interface{}{ 441 "name": "pc", 442 "id": s.ss.AssertedSnapID("pc"), 443 "type": "gadget", 444 "default-channel": "20", 445 }, 446 map[string]interface{}{ 447 "name": "snapd", 448 "id": s.ss.AssertedSnapID("snapd"), 449 "type": "snapd", 450 }, 451 // required 452 map[string]interface{}{ 453 "name": "other-unasserted", 454 "presence": "required", 455 }, 456 }, 457 }) 458 459 unassertedSnapsDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234unasserted/snaps") 460 c.Assert(os.MkdirAll(unassertedSnapsDir, 0755), IsNil) 461 err = osutil.CopyFile(infos["other-unasserted"].MountFile(), 462 filepath.Join(unassertedSnapsDir, "other-unasserted_1.0.snap"), 0) 463 c.Assert(err, IsNil) 464 465 newFiles = nil 466 // the unasserted snap goes into the snaps directory under the system 467 // directory, which triggers the error in creating the directory by 468 // seed writer 469 dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(modelWithUnasserted, "1234unasserted", s.db, 470 infoGetter, snapWriteObserver) 471 472 c.Assert(err, ErrorMatches, `system "1234unasserted" already exists`) 473 // we failed early, no files were written yet 474 c.Check(dir, Equals, "") 475 c.Check(newFiles, IsNil) 476 } 477 478 func (s *createSystemSuite) TestCreateSystemInfoAndAssertsChecks(c *C) { 479 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 480 bootloader.Force(bl) 481 infos := map[string]*snap.Info{} 482 483 s.state.Lock() 484 defer s.state.Unlock() 485 s.setupBrands(c) 486 // missing info for the pc snap 487 infos["pc-kernel"] = s.makeSnap(c, "pc-kernel", snap.R(1)) 488 infos["core20"] = s.makeSnap(c, "core20", snap.R(3)) 489 infos["snapd"] = s.makeSnap(c, "snapd", snap.R(4)) 490 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 491 "architecture": "amd64", 492 "grade": "dangerous", 493 "base": "core20", 494 "snaps": []interface{}{ 495 map[string]interface{}{ 496 "name": "pc-kernel", 497 "id": s.ss.AssertedSnapID("pc-kernel"), 498 "type": "kernel", 499 "default-channel": "20", 500 }, 501 // pc snap is the gadget, but we have no info for it 502 map[string]interface{}{ 503 "name": "pc", 504 "id": s.ss.AssertedSnapID("pc"), 505 "type": "gadget", 506 "default-channel": "20", 507 }, 508 map[string]interface{}{ 509 "name": "snapd", 510 "id": s.ss.AssertedSnapID("snapd"), 511 "type": "snapd", 512 }, 513 map[string]interface{}{ 514 "name": "other-required", 515 "id": s.ss.AssertedSnapID("other-required"), 516 "presence": "required", 517 }, 518 }, 519 }) 520 521 infoGetter := func(name string) (*snap.Info, bool, error) { 522 c.Logf("called for: %q", name) 523 info, present := infos[name] 524 return info, present, nil 525 } 526 var observerCalls int 527 snapWriteObserver := func(dir, where string) error { 528 observerCalls++ 529 return fmt.Errorf("unexpected call") 530 } 531 532 systemDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234") 533 534 // when a given snap in asserted snaps directory already exists, it is 535 // not copied over 536 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 537 infoGetter, snapWriteObserver) 538 c.Assert(err, ErrorMatches, `internal error: essential snap "pc" not present`) 539 c.Check(dir, Equals, "") 540 c.Check(observerCalls, Equals, 0) 541 542 // the directory shouldn't be there, as we haven't written anything yet 543 c.Check(osutil.IsDirectory(systemDir), Equals, false) 544 545 // create the info now 546 infos["pc"] = s.makeSnap(c, "pc", snap.R(2)) 547 548 // and try with with a non essential snap 549 dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 550 infoGetter, snapWriteObserver) 551 c.Assert(err, ErrorMatches, `internal error: non-essential but required snap "other-required" not present`) 552 c.Check(dir, Equals, "") 553 c.Check(observerCalls, Equals, 0) 554 // the directory shouldn't be there, as we haven't written anything yet 555 c.Check(osutil.IsDirectory(systemDir), Equals, false) 556 557 // create the info now 558 infos["other-required"] = s.makeSnap(c, "other-required", snap.R(5)) 559 560 // but change the file contents of 'pc' snap so that deriving side info fails 561 c.Assert(ioutil.WriteFile(infos["pc"].MountFile(), []byte("canary"), 0644), IsNil) 562 dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 563 infoGetter, snapWriteObserver) 564 c.Assert(err, ErrorMatches, `internal error: no assertions for asserted snap with ID: pcididididididididididididididid`) 565 // we're past the start, so the system directory is there 566 c.Check(dir, Equals, systemDir) 567 c.Check(osutil.IsDirectory(systemDir), Equals, true) 568 // but no files were copied 569 c.Check(observerCalls, Equals, 0) 570 } 571 572 func (s *createSystemSuite) TestCreateSystemGetInfoErr(c *C) { 573 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 574 bootloader.Force(bl) 575 576 s.state.Lock() 577 defer s.state.Unlock() 578 s.setupBrands(c) 579 // missing info for the pc snap 580 infos := s.makeEssentialSnapInfos(c) 581 infos["other-required"] = s.makeSnap(c, "other-required", snap.R(5)) 582 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 583 "architecture": "amd64", 584 "grade": "dangerous", 585 "base": "core20", 586 "snaps": []interface{}{ 587 map[string]interface{}{ 588 "name": "pc-kernel", 589 "id": s.ss.AssertedSnapID("pc-kernel"), 590 "type": "kernel", 591 "default-channel": "20", 592 }, 593 // pc snap is the gadget, but we have no info for it 594 map[string]interface{}{ 595 "name": "pc", 596 "id": s.ss.AssertedSnapID("pc"), 597 "type": "gadget", 598 "default-channel": "20", 599 }, 600 map[string]interface{}{ 601 "name": "snapd", 602 "id": s.ss.AssertedSnapID("snapd"), 603 "type": "snapd", 604 }, 605 map[string]interface{}{ 606 "name": "other-required", 607 "id": s.ss.AssertedSnapID("other-required"), 608 "presence": "required", 609 }, 610 }, 611 }) 612 613 failOn := map[string]bool{} 614 615 infoGetter := func(name string) (*snap.Info, bool, error) { 616 c.Logf("called for: %q", name) 617 if failOn[name] { 618 return nil, false, fmt.Errorf("mock failure for snap %q", name) 619 } 620 info, present := infos[name] 621 return info, present, nil 622 } 623 var observerCalls int 624 snapWriteObserver := func(dir, where string) error { 625 observerCalls++ 626 return fmt.Errorf("unexpected call") 627 } 628 629 systemDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234") 630 631 // when a given snap in asserted snaps directory already exists, it is 632 // not copied over 633 634 failOn["pc"] = true 635 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 636 infoGetter, snapWriteObserver) 637 c.Assert(err, ErrorMatches, `cannot obtain essential snap information: mock failure for snap "pc"`) 638 c.Check(dir, Equals, "") 639 c.Check(observerCalls, Equals, 0) 640 c.Check(osutil.IsDirectory(systemDir), Equals, false) 641 642 failOn["pc"] = false 643 failOn["other-required"] = true 644 dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 645 infoGetter, snapWriteObserver) 646 c.Assert(err, ErrorMatches, `cannot obtain non-essential but required snap information: mock failure for snap "other-required"`) 647 c.Check(dir, Equals, "") 648 c.Check(observerCalls, Equals, 0) 649 c.Check(osutil.IsDirectory(systemDir), Equals, false) 650 } 651 652 func (s *createSystemSuite) TestCreateSystemNonUC20(c *C) { 653 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 654 bootloader.Force(bl) 655 656 s.state.Lock() 657 defer s.state.Unlock() 658 s.setupBrands(c) 659 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 660 "architecture": "amd64", 661 "base": "core18", 662 "kernel": "pc-kernel", 663 "gadget": "pc", 664 }) 665 666 infoGetter := func(name string) (*snap.Info, bool, error) { 667 c.Fatalf("unexpected call") 668 return nil, false, fmt.Errorf("unexpected call") 669 } 670 snapWriteObserver := func(dir, where string) error { 671 c.Fatalf("unexpected call") 672 return fmt.Errorf("unexpected call") 673 } 674 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 675 infoGetter, snapWriteObserver) 676 c.Assert(err, ErrorMatches, `cannot create a system for non UC20 model`) 677 c.Check(dir, Equals, "") 678 } 679 680 func (s *createSystemSuite) TestCreateSystemImplicitSnaps(c *C) { 681 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 682 bootloader.Force(bl) 683 684 s.state.Lock() 685 defer s.state.Unlock() 686 s.setupBrands(c) 687 infos := s.makeEssentialSnapInfos(c) 688 689 // snapd snap is implicitly required 690 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 691 "architecture": "amd64", 692 "grade": "dangerous", 693 // base does not need to be listed among snaps 694 "base": "core20", 695 "snaps": []interface{}{ 696 map[string]interface{}{ 697 "name": "pc-kernel", 698 "id": s.ss.AssertedSnapID("pc-kernel"), 699 "type": "kernel", 700 "default-channel": "20", 701 }, 702 map[string]interface{}{ 703 "name": "pc", 704 "id": s.ss.AssertedSnapID("pc"), 705 "type": "gadget", 706 "default-channel": "20", 707 }, 708 }, 709 }) 710 expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234") 711 712 infoGetter := func(name string) (*snap.Info, bool, error) { 713 c.Logf("called for: %q", name) 714 info, present := infos[name] 715 return info, present, nil 716 } 717 var newFiles []string 718 snapWriteObserver := func(dir, where string) error { 719 c.Check(dir, Equals, expectedDir) 720 newFiles = append(newFiles, where) 721 return nil 722 } 723 724 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 725 infoGetter, snapWriteObserver) 726 c.Assert(err, IsNil) 727 c.Check(newFiles, DeepEquals, []string{ 728 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"), 729 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"), 730 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"), 731 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"), 732 }) 733 c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")) 734 // validate the seed 735 validateCore20Seed(c, "1234", model, s.ss.StoreSigning.Trusted) 736 } 737 738 func (s *createSystemSuite) TestCreateSystemObserverErr(c *C) { 739 bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 740 bootloader.Force(bl) 741 742 s.state.Lock() 743 defer s.state.Unlock() 744 s.setupBrands(c) 745 infos := s.makeEssentialSnapInfos(c) 746 747 // snapd snap is implicitly required 748 model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{ 749 "architecture": "amd64", 750 "grade": "dangerous", 751 // base does not need to be listed among snaps 752 "base": "core20", 753 "snaps": []interface{}{ 754 map[string]interface{}{ 755 "name": "pc-kernel", 756 "id": s.ss.AssertedSnapID("pc-kernel"), 757 "type": "kernel", 758 "default-channel": "20", 759 }, 760 map[string]interface{}{ 761 "name": "pc", 762 "id": s.ss.AssertedSnapID("pc"), 763 "type": "gadget", 764 "default-channel": "20", 765 }, 766 }, 767 }) 768 769 infoGetter := func(name string) (*snap.Info, bool, error) { 770 info, present := infos[name] 771 return info, present, nil 772 } 773 var newFiles []string 774 snapWriteObserver := func(dir, where string) error { 775 newFiles = append(newFiles, where) 776 if strings.HasSuffix(where, "/core20_3.snap") { 777 return fmt.Errorf("mocked observer failure") 778 } 779 return nil 780 } 781 782 dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, 783 infoGetter, snapWriteObserver) 784 c.Assert(err, ErrorMatches, "mocked observer failure") 785 c.Check(newFiles, DeepEquals, []string{ 786 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"), 787 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"), 788 // we failed on this one 789 filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"), 790 }) 791 c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")) 792 }