github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/devicestate/devicestate_gadget_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package devicestate_test 21 22 import ( 23 "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/dirs" 33 "github.com/snapcore/snapd/gadget" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/overlord/auth" 36 "github.com/snapcore/snapd/overlord/devicestate" 37 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 38 "github.com/snapcore/snapd/overlord/snapstate" 39 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 40 "github.com/snapcore/snapd/overlord/state" 41 "github.com/snapcore/snapd/release" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/snaptest" 44 "github.com/snapcore/snapd/testutil" 45 ) 46 47 type deviceMgrGadgetSuite struct { 48 deviceMgrBaseSuite 49 } 50 51 var _ = Suite(&deviceMgrGadgetSuite{}) 52 53 var snapYaml = ` 54 name: foo-gadget 55 type: gadget 56 ` 57 58 var gadgetYaml = ` 59 volumes: 60 pc: 61 bootloader: grub 62 ` 63 64 var uc20gadgetYaml = ` 65 volumes: 66 pc: 67 bootloader: grub 68 structure: 69 - name: ubuntu-seed 70 role: system-seed 71 type: 21686148-6449-6E6F-744E-656564454649 72 size: 20M 73 - name: ubuntu-boot 74 role: system-boot 75 type: 21686148-6449-6E6F-744E-656564454649 76 size: 10M 77 - name: ubuntu-data 78 role: system-data 79 type: 21686148-6449-6E6F-744E-656564454649 80 size: 50M 81 ` 82 83 func (s *deviceMgrGadgetSuite) setupModelWithGadget(c *C, gadget string) { 84 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 85 "architecture": "amd64", 86 "kernel": "pc-kernel", 87 "gadget": gadget, 88 "base": "core18", 89 }) 90 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 91 Brand: "canonical", 92 Model: "pc-model", 93 Serial: "serial", 94 }) 95 } 96 97 func (s *deviceMgrGadgetSuite) setupUC20ModelWithGadget(c *C, gadget string) { 98 s.makeModelAssertionInState(c, "canonical", "pc20-model", map[string]interface{}{ 99 "display-name": "UC20 pc model", 100 "architecture": "amd64", 101 "base": "core20", 102 // enough to have a grade set 103 "grade": "dangerous", 104 "snaps": []interface{}{ 105 map[string]interface{}{ 106 "name": "pc-kernel", 107 "id": "pckernelidididididididididididid", 108 "type": "kernel", 109 "default-channel": "20", 110 }, 111 map[string]interface{}{ 112 "name": gadget, 113 "id": "pcididididididididididididididid", 114 "type": "gadget", 115 "default-channel": "20", 116 }}, 117 }) 118 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 119 Brand: "canonical", 120 Model: "pc20-model", 121 Serial: "serial", 122 }) 123 } 124 125 func (s *deviceMgrGadgetSuite) setupGadgetUpdate(c *C, modelGrade string) (chg *state.Change, tsk *state.Task) { 126 siCurrent := &snap.SideInfo{ 127 RealName: "foo-gadget", 128 Revision: snap.R(33), 129 SnapID: "foo-id", 130 } 131 si := &snap.SideInfo{ 132 RealName: "foo-gadget", 133 Revision: snap.R(34), 134 SnapID: "foo-id", 135 } 136 gadgetYamlContent := gadgetYaml 137 if modelGrade != "" { 138 gadgetYamlContent = uc20gadgetYaml 139 } 140 snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, [][]string{ 141 {"meta/gadget.yaml", gadgetYamlContent}, 142 }) 143 snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 144 {"meta/gadget.yaml", gadgetYamlContent}, 145 }) 146 147 s.state.Lock() 148 defer s.state.Unlock() 149 150 if modelGrade == "" { 151 s.setupModelWithGadget(c, "foo-gadget") 152 } else { 153 s.setupUC20ModelWithGadget(c, "foo-gadget") 154 } 155 156 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 157 SnapType: "gadget", 158 Sequence: []*snap.SideInfo{siCurrent}, 159 Current: siCurrent.Revision, 160 Active: true, 161 }) 162 163 tsk = s.state.NewTask("update-gadget-assets", "update gadget") 164 tsk.Set("snap-setup", &snapstate.SnapSetup{ 165 SideInfo: si, 166 Type: snap.TypeGadget, 167 }) 168 chg = s.state.NewChange("dummy", "...") 169 chg.AddTask(tsk) 170 171 return chg, tsk 172 } 173 174 func (s *deviceMgrGadgetSuite) testUpdateGadgetOnCoreSimple(c *C, grade string) { 175 var updateCalled bool 176 var passedRollbackDir string 177 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, observer gadget.ContentUpdateObserver) error { 178 updateCalled = true 179 passedRollbackDir = path 180 st, err := os.Stat(path) 181 c.Assert(err, IsNil) 182 m := st.Mode() 183 c.Assert(m.IsDir(), Equals, true) 184 c.Check(m.Perm(), Equals, os.FileMode(0750)) 185 if grade == "" { 186 // non UC20 model 187 c.Check(observer, IsNil) 188 } else { 189 c.Check(observer, NotNil) 190 // expecting a very specific observer 191 trustedUpdateObserver, ok := observer.(*boot.TrustedAssetsUpdateObserver) 192 c.Assert(ok, Equals, true, Commentf("unexpected type: %T", observer)) 193 c.Assert(trustedUpdateObserver, NotNil) 194 } 195 return nil 196 }) 197 defer restore() 198 199 chg, t := s.setupGadgetUpdate(c, grade) 200 201 // procure modeenv 202 if grade != "" { 203 // state after mark-seeded ran 204 modeenv := boot.Modeenv{ 205 Mode: "run", 206 RecoverySystem: "", 207 } 208 err := modeenv.WriteTo("") 209 c.Assert(err, IsNil) 210 } 211 devicestate.SetBootOkRan(s.mgr, true) 212 213 s.state.Lock() 214 s.state.Set("seeded", true) 215 s.state.Unlock() 216 217 s.settle(c) 218 219 s.state.Lock() 220 defer s.state.Unlock() 221 c.Assert(chg.IsReady(), Equals, true) 222 c.Check(chg.Err(), IsNil) 223 c.Check(t.Status(), Equals, state.DoneStatus) 224 c.Check(updateCalled, Equals, true) 225 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 226 c.Check(rollbackDir, Equals, passedRollbackDir) 227 // should have been removed right after update 228 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 229 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystem}) 230 231 } 232 233 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreSimple(c *C) { 234 // unset grade 235 s.testUpdateGadgetOnCoreSimple(c, "") 236 } 237 238 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnUC20CoreSimple(c *C) { 239 s.testUpdateGadgetOnCoreSimple(c, "dangerous") 240 } 241 242 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreNoUpdateNeeded(c *C) { 243 var called bool 244 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 245 called = true 246 return gadget.ErrNoUpdate 247 }) 248 defer restore() 249 250 chg, t := s.setupGadgetUpdate(c, "") 251 252 s.se.Ensure() 253 s.se.Wait() 254 255 s.state.Lock() 256 defer s.state.Unlock() 257 c.Assert(chg.IsReady(), Equals, true) 258 c.Check(chg.Err(), IsNil) 259 c.Check(t.Status(), Equals, state.DoneStatus) 260 c.Check(t.Log(), HasLen, 1) 261 c.Check(t.Log()[0], Matches, ".* INFO No gadget assets update needed") 262 c.Check(called, Equals, true) 263 c.Check(s.restartRequests, HasLen, 0) 264 } 265 266 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreRollbackDirCreateFailed(c *C) { 267 if os.Geteuid() == 0 { 268 c.Skip("this test cannot run as root (permissions are not honored)") 269 } 270 271 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 272 return errors.New("unexpected call") 273 }) 274 defer restore() 275 276 chg, t := s.setupGadgetUpdate(c, "") 277 278 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 279 err := os.MkdirAll(dirs.SnapRollbackDir, 0000) 280 c.Assert(err, IsNil) 281 282 s.state.Lock() 283 s.state.Set("seeded", true) 284 s.state.Unlock() 285 286 s.settle(c) 287 288 s.state.Lock() 289 defer s.state.Unlock() 290 c.Assert(chg.IsReady(), Equals, true) 291 c.Check(chg.Err(), ErrorMatches, `(?s).*cannot prepare update rollback directory: .*`) 292 c.Check(t.Status(), Equals, state.ErrorStatus) 293 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 294 c.Check(s.restartRequests, HasLen, 0) 295 } 296 297 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreUpdateFailed(c *C) { 298 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 299 return errors.New("gadget exploded") 300 }) 301 defer restore() 302 chg, t := s.setupGadgetUpdate(c, "") 303 304 s.state.Lock() 305 s.state.Set("seeded", true) 306 s.state.Unlock() 307 308 s.settle(c) 309 310 s.state.Lock() 311 defer s.state.Unlock() 312 c.Assert(chg.IsReady(), Equals, true) 313 c.Check(chg.Err(), ErrorMatches, `(?s).*update gadget \(gadget exploded\).*`) 314 c.Check(t.Status(), Equals, state.ErrorStatus) 315 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 316 // update rollback left for inspection 317 c.Check(osutil.IsDirectory(rollbackDir), Equals, true) 318 c.Check(s.restartRequests, HasLen, 0) 319 } 320 321 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreNotDuringFirstboot(c *C) { 322 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 323 return errors.New("unexpected call") 324 }) 325 defer restore() 326 327 // simulate first-boot/seeding, there is no existing snap state information 328 329 si := &snap.SideInfo{ 330 RealName: "foo-gadget", 331 Revision: snap.R(34), 332 SnapID: "foo-id", 333 } 334 snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 335 {"meta/gadget.yaml", gadgetYaml}, 336 }) 337 338 s.state.Lock() 339 s.state.Set("seeded", true) 340 341 s.setupModelWithGadget(c, "foo-gadget") 342 343 t := s.state.NewTask("update-gadget-assets", "update gadget") 344 t.Set("snap-setup", &snapstate.SnapSetup{ 345 SideInfo: si, 346 Type: snap.TypeGadget, 347 }) 348 chg := s.state.NewChange("dummy", "...") 349 chg.AddTask(t) 350 351 s.state.Unlock() 352 353 s.settle(c) 354 355 s.state.Lock() 356 defer s.state.Unlock() 357 c.Assert(chg.IsReady(), Equals, true) 358 c.Check(chg.Err(), IsNil) 359 c.Check(t.Status(), Equals, state.DoneStatus) 360 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget") 361 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 362 c.Check(s.restartRequests, HasLen, 0) 363 } 364 365 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreBadGadgetYaml(c *C) { 366 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 367 return errors.New("unexpected call") 368 }) 369 defer restore() 370 siCurrent := &snap.SideInfo{ 371 RealName: "foo-gadget", 372 Revision: snap.R(33), 373 SnapID: "foo-id", 374 } 375 si := &snap.SideInfo{ 376 RealName: "foo-gadget", 377 Revision: snap.R(34), 378 SnapID: "foo-id", 379 } 380 snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, [][]string{ 381 {"meta/gadget.yaml", gadgetYaml}, 382 }) 383 // invalid gadget.yaml data 384 snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 385 {"meta/gadget.yaml", "foobar"}, 386 }) 387 388 s.state.Lock() 389 s.state.Set("seeded", true) 390 391 s.setupModelWithGadget(c, "foo-gadget") 392 393 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 394 SnapType: "gadget", 395 Sequence: []*snap.SideInfo{siCurrent}, 396 Current: siCurrent.Revision, 397 Active: true, 398 }) 399 400 t := s.state.NewTask("update-gadget-assets", "update gadget") 401 t.Set("snap-setup", &snapstate.SnapSetup{ 402 SideInfo: si, 403 Type: snap.TypeGadget, 404 }) 405 chg := s.state.NewChange("dummy", "...") 406 chg.AddTask(t) 407 408 s.state.Unlock() 409 410 s.settle(c) 411 412 s.state.Lock() 413 defer s.state.Unlock() 414 c.Assert(chg.IsReady(), Equals, true) 415 c.Check(chg.Err(), ErrorMatches, `(?s).*update gadget \(cannot read candidate snap gadget metadata: .*\).*`) 416 c.Check(t.Status(), Equals, state.ErrorStatus) 417 rollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget") 418 c.Check(osutil.IsDirectory(rollbackDir), Equals, false) 419 c.Check(s.restartRequests, HasLen, 0) 420 } 421 422 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnCoreParanoidChecks(c *C) { 423 restore := devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 424 return errors.New("unexpected call") 425 }) 426 defer restore() 427 siCurrent := &snap.SideInfo{ 428 RealName: "foo-gadget", 429 Revision: snap.R(33), 430 SnapID: "foo-id", 431 } 432 si := &snap.SideInfo{ 433 RealName: "foo-gadget-unexpected", 434 Revision: snap.R(34), 435 SnapID: "foo-id", 436 } 437 438 s.state.Lock() 439 440 s.state.Set("seeded", true) 441 442 s.setupModelWithGadget(c, "foo-gadget") 443 444 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 445 SnapType: "gadget", 446 Sequence: []*snap.SideInfo{siCurrent}, 447 Current: siCurrent.Revision, 448 Active: true, 449 }) 450 451 t := s.state.NewTask("update-gadget-assets", "update gadget") 452 t.Set("snap-setup", &snapstate.SnapSetup{ 453 SideInfo: si, 454 Type: snap.TypeGadget, 455 }) 456 chg := s.state.NewChange("dummy", "...") 457 chg.AddTask(t) 458 459 s.state.Unlock() 460 461 s.settle(c) 462 463 s.state.Lock() 464 defer s.state.Unlock() 465 c.Assert(chg.IsReady(), Equals, true) 466 c.Assert(chg.Err(), ErrorMatches, `(?s).*\(cannot apply gadget assets update from non-model gadget snap "foo-gadget-unexpected", expected "foo-gadget" snap\)`) 467 c.Check(t.Status(), Equals, state.ErrorStatus) 468 c.Check(s.restartRequests, HasLen, 0) 469 } 470 471 func (s *deviceMgrGadgetSuite) TestUpdateGadgetOnClassicErrorsOut(c *C) { 472 restore := release.MockOnClassic(true) 473 defer restore() 474 475 restore = devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error { 476 return errors.New("unexpected call") 477 }) 478 defer restore() 479 480 s.state.Lock() 481 482 s.state.Set("seeded", true) 483 484 t := s.state.NewTask("update-gadget-assets", "update gadget") 485 chg := s.state.NewChange("dummy", "...") 486 chg.AddTask(t) 487 488 s.state.Unlock() 489 490 s.settle(c) 491 492 s.state.Lock() 493 defer s.state.Unlock() 494 c.Assert(chg.IsReady(), Equals, true) 495 c.Check(chg.Err(), ErrorMatches, `(?s).*update gadget \(cannot run update gadget assets task on a classic system\).*`) 496 c.Check(t.Status(), Equals, state.ErrorStatus) 497 } 498 499 type mockUpdater struct{} 500 501 func (m *mockUpdater) Backup() error { return nil } 502 503 func (m *mockUpdater) Rollback() error { return nil } 504 505 func (m *mockUpdater) Update() error { return nil } 506 507 func (s *deviceMgrGadgetSuite) TestUpdateGadgetCallsToGadget(c *C) { 508 siCurrent := &snap.SideInfo{ 509 RealName: "foo-gadget", 510 Revision: snap.R(33), 511 SnapID: "foo-id", 512 } 513 si := &snap.SideInfo{ 514 RealName: "foo-gadget", 515 Revision: snap.R(34), 516 SnapID: "foo-id", 517 } 518 var gadgetCurrentYaml = ` 519 volumes: 520 pc: 521 bootloader: grub 522 structure: 523 - name: foo 524 size: 10M 525 type: bare 526 content: 527 - image: content.img 528 ` 529 var gadgetUpdateYaml = ` 530 volumes: 531 pc: 532 bootloader: grub 533 structure: 534 - name: foo 535 size: 10M 536 type: bare 537 content: 538 - image: content.img 539 update: 540 edition: 2 541 ` 542 snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, [][]string{ 543 {"meta/gadget.yaml", gadgetCurrentYaml}, 544 {"content.img", "some content"}, 545 }) 546 updateInfo := snaptest.MockSnapWithFiles(c, snapYaml, si, [][]string{ 547 {"meta/gadget.yaml", gadgetUpdateYaml}, 548 {"content.img", "updated content"}, 549 }) 550 551 expectedRollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34") 552 updaterForStructureCalls := 0 553 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, rootDir, rollbackDir string, _ gadget.ContentUpdateObserver) (gadget.Updater, error) { 554 updaterForStructureCalls++ 555 556 c.Assert(ps.Name, Equals, "foo") 557 c.Assert(rootDir, Equals, updateInfo.MountDir()) 558 c.Assert(filepath.Join(rootDir, "content.img"), testutil.FileEquals, "updated content") 559 c.Assert(strings.HasPrefix(rollbackDir, expectedRollbackDir), Equals, true) 560 c.Assert(osutil.IsDirectory(rollbackDir), Equals, true) 561 return &mockUpdater{}, nil 562 }) 563 defer restore() 564 565 s.state.Lock() 566 s.state.Set("seeded", true) 567 568 s.setupModelWithGadget(c, "foo-gadget") 569 570 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 571 SnapType: "gadget", 572 Sequence: []*snap.SideInfo{siCurrent}, 573 Current: siCurrent.Revision, 574 Active: true, 575 }) 576 577 t := s.state.NewTask("update-gadget-assets", "update gadget") 578 t.Set("snap-setup", &snapstate.SnapSetup{ 579 SideInfo: si, 580 Type: snap.TypeGadget, 581 }) 582 chg := s.state.NewChange("dummy", "...") 583 chg.AddTask(t) 584 585 s.state.Unlock() 586 587 s.settle(c) 588 589 s.state.Lock() 590 defer s.state.Unlock() 591 c.Assert(chg.IsReady(), Equals, true) 592 c.Check(t.Status(), Equals, state.DoneStatus) 593 c.Check(s.restartRequests, HasLen, 1) 594 c.Check(updaterForStructureCalls, Equals, 1) 595 } 596 597 func (s *deviceMgrGadgetSuite) TestCurrentAndUpdateInfo(c *C) { 598 siCurrent := &snap.SideInfo{ 599 RealName: "foo-gadget", 600 Revision: snap.R(33), 601 SnapID: "foo-id", 602 } 603 si := &snap.SideInfo{ 604 RealName: "foo-gadget", 605 Revision: snap.R(34), 606 SnapID: "foo-id", 607 } 608 609 s.state.Lock() 610 defer s.state.Unlock() 611 612 snapsup := &snapstate.SnapSetup{ 613 SideInfo: si, 614 Type: snap.TypeGadget, 615 } 616 617 model := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 618 "architecture": "amd64", 619 "kernel": "pc-kernel", 620 "gadget": "foo-gadget", 621 "base": "core18", 622 }) 623 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model} 624 625 current, err := devicestate.CurrentGadgetInfo(s.state, deviceCtx) 626 c.Assert(current, IsNil) 627 c.Check(err, IsNil) 628 629 snapstate.Set(s.state, "foo-gadget", &snapstate.SnapState{ 630 SnapType: "gadget", 631 Sequence: []*snap.SideInfo{siCurrent}, 632 Current: siCurrent.Revision, 633 Active: true, 634 }) 635 636 // mock current first, but gadget.yaml is still missing 637 ci := snaptest.MockSnapWithFiles(c, snapYaml, siCurrent, nil) 638 639 current, err = devicestate.CurrentGadgetInfo(s.state, deviceCtx) 640 641 c.Assert(current, IsNil) 642 c.Assert(err, ErrorMatches, "cannot read current gadget snap details: .*/33/meta/gadget.yaml: no such file or directory") 643 644 // drop gadget.yaml for current snap 645 ioutil.WriteFile(filepath.Join(ci.MountDir(), "meta/gadget.yaml"), []byte(gadgetYaml), 0644) 646 647 current, err = devicestate.CurrentGadgetInfo(s.state, deviceCtx) 648 c.Assert(err, IsNil) 649 c.Assert(current, DeepEquals, &gadget.GadgetData{ 650 Info: &gadget.Info{ 651 Volumes: map[string]gadget.Volume{ 652 "pc": { 653 Bootloader: "grub", 654 }, 655 }, 656 }, 657 RootDir: ci.MountDir(), 658 }) 659 660 // pending update 661 update, err := devicestate.PendingGadgetInfo(snapsup, deviceCtx) 662 c.Assert(update, IsNil) 663 c.Assert(err, ErrorMatches, "cannot read candidate gadget snap details: cannot find installed snap .* .*/34/meta/snap.yaml") 664 665 ui := snaptest.MockSnapWithFiles(c, snapYaml, si, nil) 666 667 update, err = devicestate.PendingGadgetInfo(snapsup, deviceCtx) 668 c.Assert(update, IsNil) 669 c.Assert(err, ErrorMatches, "cannot read candidate snap gadget metadata: .*/34/meta/gadget.yaml: no such file or directory") 670 671 var updateGadgetYaml = ` 672 volumes: 673 pc: 674 bootloader: grub 675 id: 123 676 ` 677 678 // drop gadget.yaml for update snap 679 ioutil.WriteFile(filepath.Join(ui.MountDir(), "meta/gadget.yaml"), []byte(updateGadgetYaml), 0644) 680 681 update, err = devicestate.PendingGadgetInfo(snapsup, deviceCtx) 682 c.Assert(err, IsNil) 683 c.Assert(update, DeepEquals, &gadget.GadgetData{ 684 Info: &gadget.Info{ 685 Volumes: map[string]gadget.Volume{ 686 "pc": { 687 Bootloader: "grub", 688 ID: "123", 689 }, 690 }, 691 }, 692 RootDir: ui.MountDir(), 693 }) 694 } 695 696 func (s *deviceMgrGadgetSuite) TestGadgetUpdateBlocksWhenOtherTasks(c *C) { 697 restore := release.MockOnClassic(true) 698 defer restore() 699 700 s.state.Lock() 701 defer s.state.Unlock() 702 703 tUpdate := s.state.NewTask("update-gadget-assets", "update gadget") 704 t1 := s.state.NewTask("other-task-1", "other 1") 705 t2 := s.state.NewTask("other-task-2", "other 2") 706 707 // no other running tasks, does not block 708 c.Assert(devicestate.GadgetUpdateBlocked(tUpdate, nil), Equals, false) 709 710 // list of running tasks actually contains ones that are in the 'running' state 711 t1.SetStatus(state.DoingStatus) 712 t2.SetStatus(state.UndoingStatus) 713 // block on any other running tasks 714 c.Assert(devicestate.GadgetUpdateBlocked(tUpdate, []*state.Task{t1, t2}), Equals, true) 715 } 716 717 func (s *deviceMgrGadgetSuite) TestGadgetUpdateBlocksOtherTasks(c *C) { 718 restore := release.MockOnClassic(true) 719 defer restore() 720 721 s.state.Lock() 722 defer s.state.Unlock() 723 724 tUpdate := s.state.NewTask("update-gadget-assets", "update gadget") 725 tUpdate.SetStatus(state.DoingStatus) 726 t1 := s.state.NewTask("other-task-1", "other 1") 727 t2 := s.state.NewTask("other-task-2", "other 2") 728 729 // block on any other running tasks 730 c.Assert(devicestate.GadgetUpdateBlocked(t1, []*state.Task{tUpdate}), Equals, true) 731 c.Assert(devicestate.GadgetUpdateBlocked(t2, []*state.Task{tUpdate}), Equals, true) 732 733 t2.SetStatus(state.UndoingStatus) 734 // update-gadget should be the only running task, for the sake of 735 // completeness pretend it's one of many running tasks 736 c.Assert(devicestate.GadgetUpdateBlocked(t1, []*state.Task{tUpdate, t2}), Equals, true) 737 738 // not blocking without gadget update task 739 c.Assert(devicestate.GadgetUpdateBlocked(t1, []*state.Task{t2}), Equals, false) 740 }