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