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