github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/overlord/devicestate/devicestate_gadget_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2020 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 "errors" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strings" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/boot" 32 "github.com/snapcore/snapd/bootloader" 33 "github.com/snapcore/snapd/bootloader/bootloadertest" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/gadget" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/overlord/auth" 38 "github.com/snapcore/snapd/overlord/devicestate" 39 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 40 "github.com/snapcore/snapd/overlord/snapstate" 41 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 42 "github.com/snapcore/snapd/overlord/state" 43 "github.com/snapcore/snapd/release" 44 "github.com/snapcore/snapd/snap" 45 "github.com/snapcore/snapd/snap/snaptest" 46 "github.com/snapcore/snapd/testutil" 47 ) 48 49 type deviceMgrGadgetSuite struct { 50 deviceMgrBaseSuite 51 } 52 53 var _ = Suite(&deviceMgrGadgetSuite{}) 54 55 var snapYaml = ` 56 name: foo-gadget 57 type: gadget 58 ` 59 60 var gadgetYaml = ` 61 volumes: 62 pc: 63 bootloader: grub 64 ` 65 66 var uc20gadgetYaml = ` 67 volumes: 68 pc: 69 bootloader: grub 70 structure: 71 - name: ubuntu-seed 72 role: system-seed 73 type: 21686148-6449-6E6F-744E-656564454649 74 size: 20M 75 - name: ubuntu-boot 76 role: system-boot 77 type: 21686148-6449-6E6F-744E-656564454649 78 size: 10M 79 - name: ubuntu-data 80 role: system-data 81 type: 21686148-6449-6E6F-744E-656564454649 82 size: 50M 83 ` 84 85 var uc20gadgetYamlWithSave = uc20gadgetYaml + ` 86 - name: ubuntu-save 87 role: system-save 88 type: 21686148-6449-6E6F-744E-656564454649 89 size: 50M 90 ` 91 92 // this is the kind of volumes setup recommended to be prepared for a possible 93 // UC18 -> UC20 transition 94 var hybridGadgetYaml = ` 95 volumes: 96 hybrid: 97 bootloader: grub 98 structure: 99 - name: mbr 100 type: mbr 101 size: 440 102 content: 103 - image: pc-boot.img 104 - name: BIOS Boot 105 type: DA,21686148-6449-6E6F-744E-656564454649 106 size: 1M 107 offset: 1M 108 offset-write: mbr+92 109 content: 110 - image: pc-core.img 111 - name: EFI System 112 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 113 filesystem: vfat 114 filesystem-label: system-boot 115 size: 1200M 116 content: 117 - source: grubx64.efi 118 target: EFI/boot/grubx64.efi 119 - source: shim.efi.signed 120 target: EFI/boot/bootx64.efi 121 - source: mmx64.efi 122 target: EFI/boot/mmx64.efi 123 - source: grub.cfg 124 target: EFI/ubuntu/grub.cfg 125 - name: Ubuntu Boot 126 type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4 127 filesystem: ext4 128 filesystem-label: ubuntu-boot 129 size: 750M 130 ` 131 132 func (s *deviceMgrGadgetSuite) setupModelWithGadget(c *C, gadget string) { 133 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 134 "architecture": "amd64", 135 "kernel": "pc-kernel", 136 "gadget": gadget, 137 "base": "core18", 138 }) 139 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 140 Brand: "canonical", 141 Model: "pc-model", 142 Serial: "serial", 143 }) 144 } 145 146 func (s *deviceMgrGadgetSuite) setupUC20ModelWithGadget(c *C, gadget string) { 147 s.makeModelAssertionInState(c, "canonical", "pc20-model", map[string]interface{}{ 148 "display-name": "UC20 pc model", 149 "architecture": "amd64", 150 "base": "core20", 151 // enough to have a grade set 152 "grade": "dangerous", 153 "snaps": []interface{}{ 154 map[string]interface{}{ 155 "name": "pc-kernel", 156 "id": "pckernelidididididididididididid", 157 "type": "kernel", 158 "default-channel": "20", 159 }, 160 map[string]interface{}{ 161 "name": gadget, 162 "id": "pcididididididididididididididid", 163 "type": "gadget", 164 "default-channel": "20", 165 }}, 166 }) 167 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 168 Brand: "canonical", 169 Model: "pc20-model", 170 Serial: "serial", 171 }) 172 } 173 174 func (s *deviceMgrGadgetSuite) setupGadgetUpdate(c *C, modelGrade, gadgetYamlContent, gadgetYamlContentNext string) (chg *state.Change, tsk *state.Task) { 175 siCurrent := &snap.SideInfo{ 176 RealName: "foo-gadget", 177 Revision: snap.R(33), 178 SnapID: "foo-id", 179 } 180 si := &snap.SideInfo{ 181 RealName: "foo-gadget", 182 Revision: snap.R(34), 183 SnapID: "foo-id", 184 } 185 snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, [][]string{ 186 {"meta/gadget.yaml", gadgetYamlContent}, 187 {"managed-asset", "managed asset rev 33"}, 188 {"trusted-asset", "trusted asset rev 33"}, 189 }) 190 if gadgetYamlContentNext == "" { 191 gadgetYamlContentNext = gadgetYamlContent 192 } 193 snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 194 {"meta/gadget.yaml", gadgetYamlContentNext}, 195 {"managed-asset", "managed asset rev 34"}, 196 // SHA3-384: 88478d8afe6925b348b9cd00085f3535959fde7029a64d7841b031acc39415c690796757afab1852a9e09da913a0151b 197 {"trusted-asset", "trusted asset rev 34"}, 198 }) 199 200 s.state.Lock() 201 defer s.state.Unlock() 202 203 if modelGrade == "" { 204 s.setupModelWithGadget(c, "foo-gadget") 205 } else { 206 s.setupUC20ModelWithGadget(c, "foo-gadget") 207 } 208 209 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 210 SnapType: "gadget", 211 Sequence: []*snap.SideInfo{siCurrent}, 212 Current: siCurrent.Revision, 213 Active: true, 214 }) 215 216 tsk = s.state.NewTask("update-gadget-assets", "update gadget") 217 tsk.Set("snap-setup", &snapstate.SnapSetup{ 218 SideInfo: si, 219 Type: snap.TypeGadget, 220 }) 221 chg = s.state.NewChange("dummy", "...") 222 chg.AddTask(tsk) 223 224 return chg, tsk 225 } 226 227 func (s *deviceMgrGadgetSuite) testUpdateGadgetOnCoreSimple(c *C, grade string, encryption bool, gadgetYamlCont, gadgetYamlContNext string) { 228 var updateCalled bool 229 var passedRollbackDir string 230 231 if grade != "" { 232 bootDir := c.MkDir() 233 tbl := bootloadertest.Mock("trusted", bootDir).WithTrustedAssets() 234 tbl.TrustedAssetsList = []string{"trusted-asset"} 235 tbl.ManagedAssetsList = []string{"managed-asset"} 236 bootloader.Force(tbl) 237 defer func() { bootloader.Force(nil) }() 238 } 239 240 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, observer gadget.ContentUpdateObserver) error { 241 updateCalled = true 242 passedRollbackDir = path 243 st, err := os.Stat(path) 244 c.Assert(err, IsNil) 245 m := st.Mode() 246 c.Assert(m.IsDir(), Equals, true) 247 c.Check(m.Perm(), Equals, os.FileMode(0750)) 248 if grade == "" { 249 // non UC20 model 250 c.Check(observer, IsNil) 251 } else { 252 c.Check(observer, NotNil) 253 // expecting a very specific observer 254 trustedUpdateObserver, ok := observer.(*boot.TrustedAssetsUpdateObserver) 255 c.Assert(ok, Equals, true, Commentf("unexpected type: %T", observer)) 256 c.Assert(trustedUpdateObserver, NotNil) 257 258 // check that observer is behaving correctly with 259 // respect to trusted and managed assets 260 targetDir := c.MkDir() 261 sourceStruct := &gadget.LaidOutStructure{ 262 VolumeStructure: &gadget.VolumeStructure{ 263 Role: gadget.SystemSeed, 264 }, 265 } 266 act, err := observer.Observe(gadget.ContentUpdate, sourceStruct, targetDir, "managed-asset", 267 &gadget.ContentChange{After: filepath.Join(update.RootDir, "managed-asset")}) 268 c.Assert(err, IsNil) 269 c.Check(act, Equals, gadget.ChangeIgnore) 270 act, err = observer.Observe(gadget.ContentUpdate, sourceStruct, targetDir, "trusted-asset", 271 &gadget.ContentChange{After: filepath.Join(update.RootDir, "trusted-asset")}) 272 c.Assert(err, IsNil) 273 c.Check(act, Equals, gadget.ChangeApply) 274 // check that the behavior is correct 275 m, err := boot.ReadModeenv("") 276 c.Assert(err, IsNil) 277 if encryption { 278 // with encryption enabled, trusted asset would 279 // have been picked up by the the observer and 280 // added to modenv 281 c.Assert(m.CurrentTrustedRecoveryBootAssets, NotNil) 282 c.Check(m.CurrentTrustedRecoveryBootAssets["trusted-asset"], DeepEquals, 283 []string{"88478d8afe6925b348b9cd00085f3535959fde7029a64d7841b031acc39415c690796757afab1852a9e09da913a0151b"}) 284 } else { 285 c.Check(m.CurrentTrustedRecoveryBootAssets, HasLen, 0) 286 } 287 } 288 return nil 289 }) 290 defer restore() 291 292 chg, t := s.setupGadgetUpdate(c, grade, gadgetYamlCont, gadgetYamlContNext) 293 294 // procure modeenv and stamp that we sealed keys 295 if grade != "" { 296 // state after mark-seeded ran 297 modeenv := boot.Modeenv{ 298 Mode: "run", 299 RecoverySystem: "", 300 } 301 err := modeenv.WriteTo("") 302 c.Assert(err, IsNil) 303 304 if encryption { 305 // sealed keys stamp 306 stamp := filepath.Join(dirs.SnapFDEDir, "sealed-keys") 307 c.Assert(os.MkdirAll(filepath.Dir(stamp), 0755), IsNil) 308 err = ioutil.WriteFile(stamp, nil, 0644) 309 c.Assert(err, IsNil) 310 } 311 } 312 devicestate.SetBootOkRan(s.mgr, true) 313 314 s.state.Lock() 315 s.state.Set("seeded", true) 316 s.state.Unlock() 317 318 s.settle(c) 319 320 s.state.Lock() 321 defer s.state.Unlock() 322 c.Assert(chg.IsReady(), Equals, true) 323 c.Check(chg.Err(), IsNil) 324 c.Check(t.Status(), Equals, state.DoneStatus) 325 c.Check(updateCalled, Equals, true) 326 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 327 c.Check(rollbackDir, Equals, passedRollbackDir) 328 // should have been removed right after update 329 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 330 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystem}) 331 } 332 333 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreSimple(c *C) { 334 // unset grade 335 encryption := false 336 s.testUpdateGadgetOnCoreSimple(c, "", encryption, gadgetYaml, "") 337 } 338 339 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnUC20CoreSimpleWithEncryption(c *C) { 340 encryption := true 341 s.testUpdateGadgetOnCoreSimple(c, "dangerous", encryption, uc20gadgetYaml, "") 342 } 343 344 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnUC20CoreSimpleNoEncryption(c *C) { 345 encryption := false 346 s.testUpdateGadgetOnCoreSimple(c, "dangerous", encryption, uc20gadgetYaml, "") 347 } 348 349 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreNoUpdateNeeded(c *C) { 350 var called bool 351 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 352 called = true 353 return gadget.ErrNoUpdate 354 }) 355 defer restore() 356 357 chg, t := s.setupGadgetUpdate(c, "", gadgetYaml, "") 358 359 s.se.Ensure() 360 s.se.Wait() 361 362 s.state.Lock() 363 defer s.state.Unlock() 364 c.Assert(chg.IsReady(), Equals, true) 365 c.Check(chg.Err(), IsNil) 366 c.Check(t.Status(), Equals, state.DoneStatus) 367 c.Check(t.Log(), HasLen, 1) 368 c.Check(t.Log()[0], Matches, ".* INFO No gadget assets update needed") 369 c.Check(called, Equals, true) 370 c.Check(s.restartRequests, HasLen, 0) 371 } 372 373 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreRollbackDirCreateFailed(c *C) { 374 if os.Geteuid() == 0 { 375 c.Skip("this test cannot run as root (permissions are not honored)") 376 } 377 378 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 379 return errors.New("unexpected call") 380 }) 381 defer restore() 382 383 chg, t := s.setupGadgetUpdate(c, "", gadgetYaml, "") 384 385 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 386 err := os.MkdirAll(dirs.SnapRollbackDir, 0000) 387 c.Assert(err, IsNil) 388 389 s.state.Lock() 390 s.state.Set("seeded", true) 391 s.state.Unlock() 392 393 s.settle(c) 394 395 s.state.Lock() 396 defer s.state.Unlock() 397 c.Assert(chg.IsReady(), Equals, true) 398 c.Check(chg.Err(), ErrorMatches, `(?s).*cannot prepare update rollback directory: .*`) 399 c.Check(t.Status(), Equals, state.ErrorStatus) 400 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 401 c.Check(s.restartRequests, HasLen, 0) 402 } 403 404 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreUpdateFailed(c *C) { 405 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 406 return errors.New("gadget exploded") 407 }) 408 defer restore() 409 chg, t := s.setupGadgetUpdate(c, "", gadgetYaml, "") 410 411 s.state.Lock() 412 s.state.Set("seeded", true) 413 s.state.Unlock() 414 415 s.settle(c) 416 417 s.state.Lock() 418 defer s.state.Unlock() 419 c.Assert(chg.IsReady(), Equals, true) 420 c.Check(chg.Err(), ErrorMatches, `(?s).*update gadget \(gadget exploded\).*`) 421 c.Check(t.Status(), Equals, state.ErrorStatus) 422 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 423 // update rollback left for inspection 424 c.Check(osutil.IsDirectory(rollbackDir), Equals, true) 425 c.Check(s.restartRequests, HasLen, 0) 426 } 427 428 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreNotDuringFirstboot(c *C) { 429 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 430 return errors.New("unexpected call") 431 }) 432 defer restore() 433 434 // simulate first-boot/seeding, there is no existing snap state information 435 436 si := &snap.SideInfo{ 437 RealName: "foo-gadget", 438 Revision: snap.R(34), 439 SnapID: "foo-id", 440 } 441 snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 442 {"meta/gadget.yaml", gadgetYaml}, 443 }) 444 445 s.state.Lock() 446 s.state.Set("seeded", true) 447 448 s.setupModelWithGadget(c, "foo-gadget") 449 450 t := s.state.NewTask("update-gadget-assets", "update gadget") 451 t.Set("snap-setup", &snapstate.SnapSetup{ 452 SideInfo: si, 453 Type: snap.TypeGadget, 454 }) 455 chg := s.state.NewChange("dummy", "...") 456 chg.AddTask(t) 457 458 s.state.Unlock() 459 460 s.settle(c) 461 462 s.state.Lock() 463 defer s.state.Unlock() 464 c.Assert(chg.IsReady(), Equals, true) 465 c.Check(chg.Err(), IsNil) 466 c.Check(t.Status(), Equals, state.DoneStatus) 467 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget") 468 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 469 c.Check(s.restartRequests, HasLen, 0) 470 } 471 472 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreBadGadgetYaml(c *C) { 473 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 474 return errors.New("unexpected call") 475 }) 476 defer restore() 477 siCurrent := &snap.SideInfo{ 478 RealName: "foo-gadget", 479 Revision: snap.R(33), 480 SnapID: "foo-id", 481 } 482 si := &snap.SideInfo{ 483 RealName: "foo-gadget", 484 Revision: snap.R(34), 485 SnapID: "foo-id", 486 } 487 snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, [][]string{ 488 {"meta/gadget.yaml", gadgetYaml}, 489 }) 490 // invalid gadget.yaml data 491 snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 492 {"meta/gadget.yaml", "foobar"}, 493 }) 494 495 s.state.Lock() 496 s.state.Set("seeded", true) 497 498 s.setupModelWithGadget(c, "foo-gadget") 499 500 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 501 SnapType: "gadget", 502 Sequence: []*snap.SideInfo{siCurrent}, 503 Current: siCurrent.Revision, 504 Active: true, 505 }) 506 507 t := s.state.NewTask("update-gadget-assets", "update gadget") 508 t.Set("snap-setup", &snapstate.SnapSetup{ 509 SideInfo: si, 510 Type: snap.TypeGadget, 511 }) 512 chg := s.state.NewChange("dummy", "...") 513 chg.AddTask(t) 514 515 s.state.Unlock() 516 517 s.settle(c) 518 519 s.state.Lock() 520 defer s.state.Unlock() 521 c.Assert(chg.IsReady(), Equals, true) 522 c.Check(chg.Err(), ErrorMatches, `(?s).*update gadget \(cannot read candidate snap gadget metadata: .*\).*`) 523 c.Check(t.Status(), Equals, state.ErrorStatus) 524 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget") 525 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 526 c.Check(s.restartRequests, HasLen, 0) 527 } 528 529 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreParanoidChecks(c *C) { 530 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 531 return errors.New("unexpected call") 532 }) 533 defer restore() 534 siCurrent := &snap.SideInfo{ 535 RealName: "foo-gadget", 536 Revision: snap.R(33), 537 SnapID: "foo-id", 538 } 539 si := &snap.SideInfo{ 540 RealName: "foo-gadget-unexpected", 541 Revision: snap.R(34), 542 SnapID: "foo-id", 543 } 544 545 s.state.Lock() 546 547 s.state.Set("seeded", true) 548 549 s.setupModelWithGadget(c, "foo-gadget") 550 551 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 552 SnapType: "gadget", 553 Sequence: []*snap.SideInfo{siCurrent}, 554 Current: siCurrent.Revision, 555 Active: true, 556 }) 557 558 t := s.state.NewTask("update-gadget-assets", "update gadget") 559 t.Set("snap-setup", &snapstate.SnapSetup{ 560 SideInfo: si, 561 Type: snap.TypeGadget, 562 }) 563 chg := s.state.NewChange("dummy", "...") 564 chg.AddTask(t) 565 566 s.state.Unlock() 567 568 s.settle(c) 569 570 s.state.Lock() 571 defer s.state.Unlock() 572 c.Assert(chg.IsReady(), Equals, true) 573 c.Assert(chg.Err(), ErrorMatches, `(?s).*\(cannot apply gadget assets update from non-model gadget snap "foo-gadget-unexpected", expected "foo-gadget" snap\)`) 574 c.Check(t.Status(), Equals, state.ErrorStatus) 575 c.Check(s.restartRequests, HasLen, 0) 576 } 577 578 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnClassicErrorsOut(c *C) { 579 restore := release.MockOnClassic(true) 580 defer restore() 581 582 restore = devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 583 return errors.New("unexpected call") 584 }) 585 defer restore() 586 587 s.state.Lock() 588 589 s.state.Set("seeded", true) 590 591 t := s.state.NewTask("update-gadget-assets", "update gadget") 592 chg := s.state.NewChange("dummy", "...") 593 chg.AddTask(t) 594 595 s.state.Unlock() 596 597 s.settle(c) 598 599 s.state.Lock() 600 defer s.state.Unlock() 601 c.Assert(chg.IsReady(), Equals, true) 602 c.Check(chg.Err(), ErrorMatches, `(?s).*update gadget \(cannot run update gadget assets task on a classic system\).*`) 603 c.Check(t.Status(), Equals, state.ErrorStatus) 604 } 605 606 type mockUpdater struct{} 607 608 func (m *mockUpdater) Backup() error { return nil } 609 610 func (m *mockUpdater) Rollback() error { return nil } 611 612 func (m *mockUpdater) Update() error { return nil } 613 614 func (s *deviceMgrGadgetSuite) TestUpdateGadgetCallsToGadget(c *C) { 615 siCurrent := &snap.SideInfo{ 616 RealName: "foo-gadget", 617 Revision: snap.R(33), 618 SnapID: "foo-id", 619 } 620 si := &snap.SideInfo{ 621 RealName: "foo-gadget", 622 Revision: snap.R(34), 623 SnapID: "foo-id", 624 } 625 var gadgetCurrentYaml = ` 626 volumes: 627 pc: 628 bootloader: grub 629 structure: 630 - name: foo 631 size: 10M 632 type: bare 633 content: 634 - image: content.img 635 ` 636 var gadgetUpdateYaml = ` 637 volumes: 638 pc: 639 bootloader: grub 640 structure: 641 - name: foo 642 size: 10M 643 type: bare 644 content: 645 - image: content.img 646 update: 647 edition: 2 648 ` 649 snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, [][]string{ 650 {"meta/gadget.yaml", gadgetCurrentYaml}, 651 {"content.img", "some content"}, 652 }) 653 updateInfo := snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 654 {"meta/gadget.yaml", gadgetUpdateYaml}, 655 {"content.img", "updated content"}, 656 }) 657 658 expectedRollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 659 updaterForStructureCalls := 0 660 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, rootDir, rollbackDir string, _ gadget.ContentUpdateObserver) (gadget.Updater, error) { 661 updaterForStructureCalls++ 662 663 c.Assert(ps.Name, Equals, "foo") 664 c.Assert(rootDir, Equals, updateInfo.MountDir()) 665 c.Assert(filepath.Join(rootDir, "content.img"), testutil.FileEquals, "updated content") 666 c.Assert(strings.HasPrefix(rollbackDir, expectedRollbackDir), Equals, true) 667 c.Assert(osutil.IsDirectory(rollbackDir), Equals, true) 668 return &mockUpdater{}, nil 669 }) 670 defer restore() 671 672 s.state.Lock() 673 s.state.Set("seeded", true) 674 675 s.setupModelWithGadget(c, "foo-gadget") 676 677 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 678 SnapType: "gadget", 679 Sequence: []*snap.SideInfo{siCurrent}, 680 Current: siCurrent.Revision, 681 Active: true, 682 }) 683 684 t := s.state.NewTask("update-gadget-assets", "update gadget") 685 t.Set("snap-setup", &snapstate.SnapSetup{ 686 SideInfo: si, 687 Type: snap.TypeGadget, 688 }) 689 chg := s.state.NewChange("dummy", "...") 690 chg.AddTask(t) 691 692 s.state.Unlock() 693 694 s.settle(c) 695 696 s.state.Lock() 697 defer s.state.Unlock() 698 c.Assert(chg.IsReady(), Equals, true) 699 c.Check(t.Status(), Equals, state.DoneStatus) 700 c.Check(s.restartRequests, HasLen, 1) 701 c.Check(updaterForStructureCalls, Equals, 1) 702 } 703 704 func (s *deviceMgrGadgetSuite) TestCurrentAndUpdateInfo(c *C) { 705 siCurrent := &snap.SideInfo{ 706 RealName: "foo-gadget", 707 Revision: snap.R(33), 708 SnapID: "foo-id", 709 } 710 si := &snap.SideInfo{ 711 RealName: "foo-gadget", 712 Revision: snap.R(34), 713 SnapID: "foo-id", 714 } 715 716 s.state.Lock() 717 defer s.state.Unlock() 718 719 snapsup := &snapstate.SnapSetup{ 720 SideInfo: si, 721 Type: snap.TypeGadget, 722 } 723 724 model := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 725 "architecture": "amd64", 726 "kernel": "pc-kernel", 727 "gadget": "foo-gadget", 728 "base": "core18", 729 }) 730 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model} 731 732 current, err := devicestate.CurrentGadgetInfo(s.state, deviceCtx) 733 c.Assert(current, IsNil) 734 c.Check(err, IsNil) 735 736 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 737 SnapType: "gadget", 738 Sequence: []*snap.SideInfo{siCurrent}, 739 Current: siCurrent.Revision, 740 Active: true, 741 }) 742 743 // mock current first, but gadget.yaml is still missing 744 ci := snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, nil) 745 746 current, err = devicestate.CurrentGadgetInfo(s.state, deviceCtx) 747 748 c.Assert(current, IsNil) 749 c.Assert(err, ErrorMatches, "cannot read current gadget snap details: .*/33/meta/gadget.yaml: no such file or directory") 750 751 // drop gadget.yaml for current snap 752 ioutil.WriteFile(filepath.Join(ci.MountDir(), "meta/gadget.yaml"), []byte(gadgetYaml), 0644) 753 754 current, err = devicestate.CurrentGadgetInfo(s.state, deviceCtx) 755 c.Assert(err, IsNil) 756 c.Assert(current, DeepEquals, &gadget.GadgetData{ 757 Info: &gadget.Info{ 758 Volumes: map[string]*gadget.Volume{ 759 "pc": { 760 Bootloader: "grub", 761 Schema: "gpt", 762 }, 763 }, 764 }, 765 RootDir: ci.MountDir(), 766 }) 767 768 // pending update 769 update, err := devicestate.PendingGadgetInfo(snapsup, deviceCtx) 770 c.Assert(update, IsNil) 771 c.Assert(err, ErrorMatches, "cannot read candidate gadget snap details: cannot find installed snap .* .*/34/meta/snap.yaml") 772 773 ui := snaptest.MockSnapWithFiles(c, snapYaml, si, nil) 774 775 update, err = devicestate.PendingGadgetInfo(snapsup, deviceCtx) 776 c.Assert(update, IsNil) 777 c.Assert(err, ErrorMatches, "cannot read candidate snap gadget metadata: .*/34/meta/gadget.yaml: no such file or directory") 778 779 var updateGadgetYaml = ` 780 volumes: 781 pc: 782 bootloader: grub 783 id: 123 784 ` 785 786 // drop gadget.yaml for update snap 787 ioutil.WriteFile(filepath.Join(ui.MountDir(), "meta/gadget.yaml"), []byte(updateGadgetYaml), 0644) 788 789 update, err = devicestate.PendingGadgetInfo(snapsup, deviceCtx) 790 c.Assert(err, IsNil) 791 c.Assert(update, DeepEquals, &gadget.GadgetData{ 792 Info: &gadget.Info{ 793 Volumes: map[string]*gadget.Volume{ 794 "pc": { 795 Bootloader: "grub", 796 Schema: "gpt", 797 ID: "123", 798 }, 799 }, 800 }, 801 RootDir: ui.MountDir(), 802 }) 803 } 804 805 func (s *deviceMgrGadgetSuite) TestGadgetUpdateBlocksWhenOtherTasks(c *C) { 806 restore := release.MockOnClassic(true) 807 defer restore() 808 809 s.state.Lock() 810 defer s.state.Unlock() 811 812 tUpdate := s.state.NewTask("update-gadget-assets", "update gadget") 813 t1 := s.state.NewTask("other-task-1", "other 1") 814 t2 := s.state.NewTask("other-task-2", "other 2") 815 816 // no other running tasks, does not block 817 c.Assert(devicestate.GadgetUpdateBlocked(tUpdate, nil), Equals, false) 818 819 // list of running tasks actually contains ones that are in the 'running' state 820 t1.SetStatus(state.DoingStatus) 821 t2.SetStatus(state.UndoingStatus) 822 // block on any other running tasks 823 c.Assert(devicestate.GadgetUpdateBlocked(tUpdate, []*state.Task{t1, t2}), Equals, true) 824 } 825 826 func (s *deviceMgrGadgetSuite) TestGadgetUpdateBlocksOtherTasks(c *C) { 827 restore := release.MockOnClassic(true) 828 defer restore() 829 830 s.state.Lock() 831 defer s.state.Unlock() 832 833 tUpdate := s.state.NewTask("update-gadget-assets", "update gadget") 834 tUpdate.SetStatus(state.DoingStatus) 835 t1 := s.state.NewTask("other-task-1", "other 1") 836 t2 := s.state.NewTask("other-task-2", "other 2") 837 838 // block on any other running tasks 839 c.Assert(devicestate.GadgetUpdateBlocked(t1, []*state.Task{tUpdate}), Equals, true) 840 c.Assert(devicestate.GadgetUpdateBlocked(t2, []*state.Task{tUpdate}), Equals, true) 841 842 t2.SetStatus(state.UndoingStatus) 843 // update-gadget should be the only running task, for the sake of 844 // completeness pretend it's one of many running tasks 845 c.Assert(devicestate.GadgetUpdateBlocked(t1, []*state.Task{tUpdate, t2}), Equals, true) 846 847 // not blocking without gadget update task 848 c.Assert(devicestate.GadgetUpdateBlocked(t1, []*state.Task{t2}), Equals, false) 849 } 850 851 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreHybridFirstboot(c *C) { 852 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 853 return errors.New("unexpected call") 854 }) 855 defer restore() 856 857 // simulate first-boot/seeding, there is no existing snap state information 858 859 si := &snap.SideInfo{ 860 RealName: "foo-gadget", 861 Revision: snap.R(34), 862 SnapID: "foo-id", 863 } 864 snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 865 {"meta/gadget.yaml", hybridGadgetYaml}, 866 }) 867 868 s.state.Lock() 869 s.state.Set("seeded", true) 870 871 s.setupModelWithGadget(c, "foo-gadget") 872 873 t := s.state.NewTask("update-gadget-assets", "update gadget") 874 t.Set("snap-setup", &snapstate.SnapSetup{ 875 SideInfo: si, 876 Type: snap.TypeGadget, 877 }) 878 chg := s.state.NewChange("dummy", "...") 879 chg.AddTask(t) 880 881 s.state.Unlock() 882 883 s.settle(c) 884 885 s.state.Lock() 886 defer s.state.Unlock() 887 c.Assert(chg.IsReady(), Equals, true) 888 c.Check(chg.Err(), IsNil) 889 c.Check(t.Status(), Equals, state.DoneStatus) 890 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget") 891 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 892 c.Check(s.restartRequests, HasLen, 0) 893 } 894 895 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreHybridShouldWork(c *C) { 896 encryption := false 897 s.testUpdateGadgetOnCoreSimple(c, "", encryption, hybridGadgetYaml, "") 898 } 899 900 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreOldIsInvalidNowButShouldWork(c *C) { 901 encryption := false 902 // this is not gadget yaml that we should support, by the UC16/18 903 // rules it actually has two system-boot role partitions, 904 hybridGadgetYamlBroken := hybridGadgetYaml + ` 905 role: system-boot 906 ` 907 s.testUpdateGadgetOnCoreSimple(c, "", encryption, hybridGadgetYamlBroken, hybridGadgetYaml) 908 }