github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/snapstate_remove_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 snapstate_test 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/dirs" 30 "github.com/snapcore/snapd/osutil" 31 "github.com/snapcore/snapd/overlord/configstate/config" 32 "github.com/snapcore/snapd/overlord/snapstate" 33 "github.com/snapcore/snapd/overlord/state" 34 "github.com/snapcore/snapd/snap" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 func (s *snapmgrTestSuite) TestRemoveTasks(c *C) { 39 s.state.Lock() 40 defer s.state.Unlock() 41 42 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 43 Active: true, 44 Sequence: []*snap.SideInfo{ 45 {RealName: "foo", Revision: snap.R(11)}, 46 }, 47 Current: snap.R(11), 48 SnapType: "app", 49 }) 50 51 ts, err := snapstate.Remove(s.state, "foo", snap.R(0), nil) 52 c.Assert(err, IsNil) 53 54 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 55 verifyRemoveTasks(c, ts) 56 } 57 58 func (s *snapmgrTestSuite) TestRemoveTasksAutoSnapshotDisabled(c *C) { 59 snapstate.AutomaticSnapshot = func(st *state.State, instanceName string) (ts *state.TaskSet, err error) { 60 return nil, snapstate.ErrNothingToDo 61 } 62 63 s.state.Lock() 64 defer s.state.Unlock() 65 66 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 67 Active: true, 68 Sequence: []*snap.SideInfo{ 69 {RealName: "foo", Revision: snap.R(11)}, 70 }, 71 Current: snap.R(11), 72 SnapType: "app", 73 }) 74 75 ts, err := snapstate.Remove(s.state, "foo", snap.R(0), nil) 76 c.Assert(err, IsNil) 77 78 c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{ 79 "stop-snap-services", 80 "run-hook[remove]", 81 "auto-disconnect", 82 "remove-aliases", 83 "unlink-snap", 84 "remove-profiles", 85 "clear-snap", 86 "discard-snap", 87 }) 88 } 89 90 func (s *snapmgrTestSuite) TestRemoveTasksAutoSnapshotDisabledByPurgeFlag(c *C) { 91 s.state.Lock() 92 defer s.state.Unlock() 93 94 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 95 Active: true, 96 Sequence: []*snap.SideInfo{ 97 {RealName: "foo", Revision: snap.R(11)}, 98 }, 99 Current: snap.R(11), 100 SnapType: "app", 101 }) 102 103 ts, err := snapstate.Remove(s.state, "foo", snap.R(0), &snapstate.RemoveFlags{Purge: true}) 104 c.Assert(err, IsNil) 105 106 c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{ 107 "stop-snap-services", 108 "run-hook[remove]", 109 "auto-disconnect", 110 "remove-aliases", 111 "unlink-snap", 112 "remove-profiles", 113 "clear-snap", 114 "discard-snap", 115 }) 116 } 117 118 func (s *snapmgrTestSuite) TestRemoveHookNotExecutedIfNotLastRevison(c *C) { 119 s.state.Lock() 120 defer s.state.Unlock() 121 122 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 123 Active: true, 124 Sequence: []*snap.SideInfo{ 125 {RealName: "foo", Revision: snap.R(11)}, 126 {RealName: "foo", Revision: snap.R(12)}, 127 }, 128 Current: snap.R(12), 129 }) 130 131 ts, err := snapstate.Remove(s.state, "foo", snap.R(11), nil) 132 c.Assert(err, IsNil) 133 134 runHooks := tasksWithKind(ts, "run-hook") 135 // no 'remove' hook task 136 c.Assert(runHooks, HasLen, 0) 137 } 138 139 func (s *snapmgrTestSuite) TestRemoveConflict(c *C) { 140 s.state.Lock() 141 defer s.state.Unlock() 142 143 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 144 Active: true, 145 Sequence: []*snap.SideInfo{{RealName: "some-snap", Revision: snap.R(11)}}, 146 Current: snap.R(11), 147 }) 148 149 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 150 c.Assert(err, IsNil) 151 // need a change to make the tasks visible 152 s.state.NewChange("remove", "...").AddAll(ts) 153 154 _, err = snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 155 c.Assert(err, ErrorMatches, `snap "some-snap" has "remove" change in progress`) 156 } 157 158 func (s *snapmgrTestSuite) testRemoveDiskSpaceCheck(c *C, featureFlag, automaticSnapshot bool) error { 159 s.state.Lock() 160 defer s.state.Unlock() 161 162 restore := snapstate.MockOsutilCheckFreeSpace(func(string, uint64) error { 163 // osutil.CheckFreeSpace shouldn't be hit if either featureFlag 164 // or automaticSnapshot is false. If both are true then we return disk 165 // space error which should result in snapstate.InsufficientSpaceError 166 // on remove(). 167 return &osutil.NotEnoughDiskSpaceError{} 168 }) 169 defer restore() 170 171 var automaticSnapshotCalled bool 172 snapstate.AutomaticSnapshot = func(st *state.State, instanceName string) (ts *state.TaskSet, err error) { 173 automaticSnapshotCalled = true 174 if automaticSnapshot { 175 t := s.state.NewTask("foo", "") 176 ts = state.NewTaskSet(t) 177 return ts, nil 178 } 179 // ErrNothingToDo is returned if automatic snapshots are disabled 180 return nil, snapstate.ErrNothingToDo 181 } 182 183 tr := config.NewTransaction(s.state) 184 tr.Set("core", "experimental.check-disk-space-remove", featureFlag) 185 tr.Commit() 186 187 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 188 Active: true, 189 Sequence: []*snap.SideInfo{{RealName: "some-snap", Revision: snap.R(11)}}, 190 Current: snap.R(11), 191 SnapType: "app", 192 }) 193 194 _, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 195 c.Assert(automaticSnapshotCalled, Equals, true) 196 return err 197 } 198 199 func (s *snapmgrTestSuite) TestRemoveDiskSpaceCheckDoesNothingWhenNoSnapshot(c *C) { 200 featureFlag := true 201 snapshot := false 202 err := s.testRemoveDiskSpaceCheck(c, featureFlag, snapshot) 203 c.Assert(err, IsNil) 204 } 205 206 func (s *snapmgrTestSuite) TestRemoveDiskSpaceCheckDisabledByFeatureFlag(c *C) { 207 featureFlag := false 208 snapshot := true 209 err := s.testRemoveDiskSpaceCheck(c, featureFlag, snapshot) 210 c.Assert(err, IsNil) 211 } 212 213 func (s *snapmgrTestSuite) TestRemoveDiskSpaceForSnapshotError(c *C) { 214 featureFlag := true 215 snapshot := true 216 // both the snapshot and disk check feature are enabled, so we should hit 217 // the disk check (which fails). 218 err := s.testRemoveDiskSpaceCheck(c, featureFlag, snapshot) 219 c.Assert(err, NotNil) 220 221 diskSpaceErr := err.(*snapstate.InsufficientSpaceError) 222 c.Assert(diskSpaceErr, ErrorMatches, `cannot create automatic snapshot when removing last revision of the snap: insufficient space.*`) 223 c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 224 c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"some-snap"}) 225 c.Check(diskSpaceErr.ChangeKind, Equals, "remove") 226 } 227 228 func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) { 229 c.Assert(snapstate.KeepAuxStoreInfo("some-snap-id", nil), IsNil) 230 c.Check(snapstate.AuxStoreInfoFilename("some-snap-id"), testutil.FilePresent) 231 si := snap.SideInfo{ 232 SnapID: "some-snap-id", 233 RealName: "some-snap", 234 Revision: snap.R(7), 235 } 236 237 s.state.Lock() 238 defer s.state.Unlock() 239 240 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 241 Active: true, 242 Sequence: []*snap.SideInfo{&si}, 243 Current: si.Revision, 244 SnapType: "app", 245 }) 246 247 chg := s.state.NewChange("remove", "remove a snap") 248 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 249 c.Assert(err, IsNil) 250 chg.AddAll(ts) 251 252 s.state.Unlock() 253 defer s.se.Stop() 254 s.settle(c) 255 s.state.Lock() 256 257 expected := fakeOps{ 258 { 259 op: "auto-disconnect:Doing", 260 name: "some-snap", 261 revno: snap.R(7), 262 }, 263 { 264 op: "remove-snap-aliases", 265 name: "some-snap", 266 }, 267 { 268 op: "unlink-snap", 269 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 270 }, 271 { 272 op: "remove-profiles:Doing", 273 name: "some-snap", 274 revno: snap.R(7), 275 }, 276 { 277 op: "remove-snap-data", 278 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 279 }, 280 { 281 op: "remove-snap-common-data", 282 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 283 }, 284 { 285 op: "remove-snap-data-dir", 286 name: "some-snap", 287 path: filepath.Join(dirs.SnapDataDir, "some-snap"), 288 }, 289 { 290 op: "remove-snap-files", 291 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 292 stype: "app", 293 }, 294 { 295 op: "discard-namespace", 296 name: "some-snap", 297 }, 298 { 299 op: "remove-inhibit-lock", 300 name: "some-snap", 301 }, 302 { 303 op: "remove-snap-dir", 304 name: "some-snap", 305 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 306 }, 307 } 308 // start with an easier-to-read error if this fails: 309 c.Check(len(s.fakeBackend.ops), Equals, len(expected)) 310 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 311 c.Check(s.fakeBackend.ops, DeepEquals, expected) 312 313 // verify snapSetup info 314 tasks := ts.Tasks() 315 for _, t := range tasks { 316 if t.Kind() == "run-hook" { 317 continue 318 } 319 if t.Kind() == "save-snapshot" { 320 continue 321 } 322 snapsup, err := snapstate.TaskSnapSetup(t) 323 c.Assert(err, IsNil) 324 325 var expSnapSetup *snapstate.SnapSetup 326 switch t.Kind() { 327 case "discard-conns": 328 expSnapSetup = &snapstate.SnapSetup{ 329 SideInfo: &snap.SideInfo{ 330 RealName: "some-snap", 331 }, 332 } 333 case "clear-snap", "discard-snap": 334 expSnapSetup = &snapstate.SnapSetup{ 335 SideInfo: &snap.SideInfo{ 336 RealName: "some-snap", 337 SnapID: "some-snap-id", 338 Revision: snap.R(7), 339 }, 340 Type: snap.TypeApp, 341 } 342 default: 343 expSnapSetup = &snapstate.SnapSetup{ 344 SideInfo: &snap.SideInfo{ 345 RealName: "some-snap", 346 SnapID: "some-snap-id", 347 Revision: snap.R(7), 348 }, 349 Type: snap.TypeApp, 350 PlugsOnly: true, 351 } 352 353 } 354 355 c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind())) 356 } 357 358 // verify snaps in the system state 359 var snapst snapstate.SnapState 360 err = snapstate.Get(s.state, "some-snap", &snapst) 361 c.Assert(err, Equals, state.ErrNoState) 362 c.Check(snapstate.AuxStoreInfoFilename("some-snap-id"), testutil.FileAbsent) 363 364 } 365 366 func (s *snapmgrTestSuite) TestParallelInstanceRemoveRunThrough(c *C) { 367 si := snap.SideInfo{ 368 RealName: "some-snap", 369 Revision: snap.R(7), 370 } 371 372 s.state.Lock() 373 defer s.state.Unlock() 374 375 // pretend we have both a regular snap and a parallel instance 376 snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{ 377 Active: true, 378 Sequence: []*snap.SideInfo{&si}, 379 Current: si.Revision, 380 SnapType: "app", 381 InstanceKey: "instance", 382 }) 383 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 384 Active: true, 385 Sequence: []*snap.SideInfo{&si}, 386 Current: si.Revision, 387 SnapType: "app", 388 }) 389 390 chg := s.state.NewChange("remove", "remove a snap") 391 ts, err := snapstate.Remove(s.state, "some-snap_instance", snap.R(0), nil) 392 c.Assert(err, IsNil) 393 chg.AddAll(ts) 394 395 s.state.Unlock() 396 s.settle(c) 397 s.state.Lock() 398 399 expected := fakeOps{ 400 { 401 op: "auto-disconnect:Doing", 402 name: "some-snap_instance", 403 revno: snap.R(7), 404 }, 405 { 406 op: "remove-snap-aliases", 407 name: "some-snap_instance", 408 }, 409 { 410 op: "unlink-snap", 411 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 412 }, 413 { 414 op: "remove-profiles:Doing", 415 name: "some-snap_instance", 416 revno: snap.R(7), 417 }, 418 { 419 op: "remove-snap-data", 420 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 421 }, 422 { 423 op: "remove-snap-common-data", 424 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 425 }, 426 { 427 op: "remove-snap-data-dir", 428 name: "some-snap_instance", 429 path: filepath.Join(dirs.SnapDataDir, "some-snap"), 430 otherInstances: true, 431 }, 432 { 433 op: "remove-snap-files", 434 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 435 stype: "app", 436 }, 437 { 438 op: "discard-namespace", 439 name: "some-snap_instance", 440 }, 441 { 442 op: "remove-inhibit-lock", 443 name: "some-snap_instance", 444 }, 445 { 446 op: "remove-snap-dir", 447 name: "some-snap_instance", 448 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 449 otherInstances: true, 450 }, 451 } 452 // start with an easier-to-read error if this fails: 453 c.Check(len(s.fakeBackend.ops), Equals, len(expected)) 454 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 455 c.Check(s.fakeBackend.ops, DeepEquals, expected) 456 457 // verify snapSetup info 458 tasks := ts.Tasks() 459 for _, t := range tasks { 460 if t.Kind() == "run-hook" { 461 continue 462 } 463 if t.Kind() == "save-snapshot" { 464 continue 465 } 466 snapsup, err := snapstate.TaskSnapSetup(t) 467 c.Assert(err, IsNil) 468 469 var expSnapSetup *snapstate.SnapSetup 470 switch t.Kind() { 471 case "discard-conns": 472 expSnapSetup = &snapstate.SnapSetup{ 473 SideInfo: &snap.SideInfo{ 474 RealName: "some-snap", 475 }, 476 InstanceKey: "instance", 477 } 478 case "clear-snap", "discard-snap": 479 expSnapSetup = &snapstate.SnapSetup{ 480 SideInfo: &snap.SideInfo{ 481 RealName: "some-snap", 482 Revision: snap.R(7), 483 }, 484 InstanceKey: "instance", 485 Type: snap.TypeApp, 486 } 487 default: 488 expSnapSetup = &snapstate.SnapSetup{ 489 SideInfo: &snap.SideInfo{ 490 RealName: "some-snap", 491 Revision: snap.R(7), 492 }, 493 Type: snap.TypeApp, 494 PlugsOnly: true, 495 InstanceKey: "instance", 496 } 497 498 } 499 500 c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind())) 501 } 502 503 // verify snaps in the system state 504 var snapst snapstate.SnapState 505 err = snapstate.Get(s.state, "some-snap_instance", &snapst) 506 c.Assert(err, Equals, state.ErrNoState) 507 508 // the non-instance snap is still there 509 err = snapstate.Get(s.state, "some-snap", &snapst) 510 c.Assert(err, IsNil) 511 } 512 513 func (s *snapmgrTestSuite) TestParallelInstanceRemoveRunThroughOtherInstances(c *C) { 514 si := snap.SideInfo{ 515 RealName: "some-snap", 516 Revision: snap.R(7), 517 } 518 519 s.state.Lock() 520 defer s.state.Unlock() 521 522 // pretend we have both a regular snap and a parallel instance 523 snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{ 524 Active: true, 525 Sequence: []*snap.SideInfo{&si}, 526 Current: si.Revision, 527 SnapType: "app", 528 InstanceKey: "instance", 529 }) 530 snapstate.Set(s.state, "some-snap_other", &snapstate.SnapState{ 531 Active: true, 532 Sequence: []*snap.SideInfo{&si}, 533 Current: si.Revision, 534 SnapType: "app", 535 InstanceKey: "other", 536 }) 537 538 chg := s.state.NewChange("remove", "remove a snap") 539 ts, err := snapstate.Remove(s.state, "some-snap_instance", snap.R(0), nil) 540 c.Assert(err, IsNil) 541 chg.AddAll(ts) 542 543 s.state.Unlock() 544 s.settle(c) 545 s.state.Lock() 546 547 expected := fakeOps{ 548 { 549 op: "auto-disconnect:Doing", 550 name: "some-snap_instance", 551 revno: snap.R(7), 552 }, 553 { 554 op: "remove-snap-aliases", 555 name: "some-snap_instance", 556 }, 557 { 558 op: "unlink-snap", 559 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 560 }, 561 { 562 op: "remove-profiles:Doing", 563 name: "some-snap_instance", 564 revno: snap.R(7), 565 }, 566 { 567 op: "remove-snap-data", 568 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 569 }, 570 { 571 op: "remove-snap-common-data", 572 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 573 }, 574 { 575 op: "remove-snap-data-dir", 576 name: "some-snap_instance", 577 path: filepath.Join(dirs.SnapDataDir, "some-snap"), 578 otherInstances: true, 579 }, 580 { 581 op: "remove-snap-files", 582 path: filepath.Join(dirs.SnapMountDir, "some-snap_instance/7"), 583 stype: "app", 584 }, 585 { 586 op: "discard-namespace", 587 name: "some-snap_instance", 588 }, 589 { 590 op: "remove-inhibit-lock", 591 name: "some-snap_instance", 592 }, 593 { 594 op: "remove-snap-dir", 595 name: "some-snap_instance", 596 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 597 otherInstances: true, 598 }, 599 } 600 // start with an easier-to-read error if this fails: 601 c.Check(len(s.fakeBackend.ops), Equals, len(expected)) 602 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 603 c.Check(s.fakeBackend.ops, DeepEquals, expected) 604 605 // verify snaps in the system state 606 var snapst snapstate.SnapState 607 err = snapstate.Get(s.state, "some-snap_instance", &snapst) 608 c.Assert(err, Equals, state.ErrNoState) 609 610 // the other instance is still there 611 err = snapstate.Get(s.state, "some-snap_other", &snapst) 612 c.Assert(err, IsNil) 613 } 614 615 func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsRunThrough(c *C) { 616 si3 := snap.SideInfo{ 617 SnapID: "some-snap-id", 618 RealName: "some-snap", 619 Revision: snap.R(3), 620 } 621 622 si5 := snap.SideInfo{ 623 SnapID: "some-snap-id", 624 RealName: "some-snap", 625 Revision: snap.R(5), 626 } 627 628 si7 := snap.SideInfo{ 629 SnapID: "some-snap-id", 630 RealName: "some-snap", 631 Revision: snap.R(7), 632 } 633 634 s.state.Lock() 635 defer s.state.Unlock() 636 637 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 638 Active: true, 639 Sequence: []*snap.SideInfo{&si5, &si3, &si7}, 640 Current: si7.Revision, 641 SnapType: "app", 642 }) 643 644 chg := s.state.NewChange("remove", "remove a snap") 645 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 646 c.Assert(err, IsNil) 647 chg.AddAll(ts) 648 649 s.state.Unlock() 650 defer s.se.Stop() 651 s.settle(c) 652 s.state.Lock() 653 654 expected := fakeOps{ 655 { 656 op: "auto-disconnect:Doing", 657 name: "some-snap", 658 revno: snap.R(7), 659 }, 660 { 661 op: "remove-snap-aliases", 662 name: "some-snap", 663 }, 664 { 665 op: "unlink-snap", 666 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 667 }, 668 { 669 op: "remove-profiles:Doing", 670 name: "some-snap", 671 revno: snap.R(7), 672 }, 673 { 674 op: "remove-snap-data", 675 path: filepath.Join(dirs.SnapMountDir, "some-snap/3"), 676 }, 677 { 678 op: "remove-snap-files", 679 path: filepath.Join(dirs.SnapMountDir, "some-snap/3"), 680 stype: "app", 681 }, 682 { 683 op: "remove-snap-data", 684 path: filepath.Join(dirs.SnapMountDir, "some-snap/5"), 685 }, 686 { 687 op: "remove-snap-files", 688 path: filepath.Join(dirs.SnapMountDir, "some-snap/5"), 689 stype: "app", 690 }, 691 { 692 op: "remove-snap-data", 693 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 694 }, 695 { 696 op: "remove-snap-common-data", 697 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 698 }, 699 { 700 op: "remove-snap-data-dir", 701 name: "some-snap", 702 path: filepath.Join(dirs.SnapDataDir, "some-snap"), 703 }, 704 { 705 op: "remove-snap-files", 706 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 707 stype: "app", 708 }, 709 { 710 op: "discard-namespace", 711 name: "some-snap", 712 }, 713 { 714 op: "remove-inhibit-lock", 715 name: "some-snap", 716 }, 717 { 718 op: "remove-snap-dir", 719 name: "some-snap", 720 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 721 }, 722 } 723 // start with an easier-to-read error if this fails: 724 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 725 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 726 727 // verify snapSetup info 728 tasks := ts.Tasks() 729 revnos := []snap.Revision{{N: 3}, {N: 5}, {N: 7}} 730 whichRevno := 0 731 for _, t := range tasks { 732 if t.Kind() == "run-hook" { 733 continue 734 } 735 if t.Kind() == "save-snapshot" { 736 continue 737 } 738 snapsup, err := snapstate.TaskSnapSetup(t) 739 c.Assert(err, IsNil) 740 741 var expSnapSetup *snapstate.SnapSetup 742 switch t.Kind() { 743 case "discard-conns": 744 expSnapSetup = &snapstate.SnapSetup{ 745 SideInfo: &snap.SideInfo{ 746 SnapID: "some-snap-id", 747 RealName: "some-snap", 748 }, 749 } 750 case "clear-snap", "discard-snap": 751 expSnapSetup = &snapstate.SnapSetup{ 752 SideInfo: &snap.SideInfo{ 753 SnapID: "some-snap-id", 754 RealName: "some-snap", 755 Revision: revnos[whichRevno], 756 }, 757 Type: snap.TypeApp, 758 } 759 default: 760 expSnapSetup = &snapstate.SnapSetup{ 761 SideInfo: &snap.SideInfo{ 762 SnapID: "some-snap-id", 763 RealName: "some-snap", 764 Revision: snap.R(7), 765 }, 766 Type: snap.TypeApp, 767 PlugsOnly: true, 768 } 769 770 } 771 772 c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind())) 773 774 if t.Kind() == "discard-snap" { 775 whichRevno++ 776 } 777 } 778 779 // verify snaps in the system state 780 var snapst snapstate.SnapState 781 err = snapstate.Get(s.state, "some-snap", &snapst) 782 c.Assert(err, Equals, state.ErrNoState) 783 } 784 785 func (s *snapmgrTestSuite) TestRemoveOneRevisionRunThrough(c *C) { 786 si3 := snap.SideInfo{ 787 RealName: "some-snap", 788 Revision: snap.R(3), 789 } 790 791 si5 := snap.SideInfo{ 792 RealName: "some-snap", 793 Revision: snap.R(5), 794 } 795 796 si7 := snap.SideInfo{ 797 RealName: "some-snap", 798 Revision: snap.R(7), 799 } 800 801 s.state.Lock() 802 defer s.state.Unlock() 803 804 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 805 Active: true, 806 Sequence: []*snap.SideInfo{&si5, &si3, &si7}, 807 Current: si7.Revision, 808 SnapType: "app", 809 }) 810 811 chg := s.state.NewChange("remove", "remove a snap") 812 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(3), nil) 813 c.Assert(err, IsNil) 814 chg.AddAll(ts) 815 816 s.state.Unlock() 817 defer s.se.Stop() 818 s.settle(c) 819 s.state.Lock() 820 821 c.Check(len(s.fakeBackend.ops), Equals, 2) 822 expected := fakeOps{ 823 { 824 op: "remove-snap-data", 825 path: filepath.Join(dirs.SnapMountDir, "some-snap/3"), 826 }, 827 { 828 op: "remove-snap-files", 829 path: filepath.Join(dirs.SnapMountDir, "some-snap/3"), 830 stype: "app", 831 }, 832 } 833 // start with an easier-to-read error if this fails: 834 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 835 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 836 837 // verify snapSetup info 838 tasks := ts.Tasks() 839 for _, t := range tasks { 840 if t.Kind() == "save-snapshot" { 841 continue 842 } 843 snapsup, err := snapstate.TaskSnapSetup(t) 844 c.Assert(err, IsNil) 845 846 expSnapSetup := &snapstate.SnapSetup{ 847 SideInfo: &snap.SideInfo{ 848 RealName: "some-snap", 849 Revision: snap.R(3), 850 }, 851 Type: snap.TypeApp, 852 } 853 854 c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind())) 855 } 856 857 // verify snaps in the system state 858 var snapst snapstate.SnapState 859 err = snapstate.Get(s.state, "some-snap", &snapst) 860 c.Assert(err, IsNil) 861 c.Check(snapst.Sequence, HasLen, 2) 862 } 863 864 func (s *snapmgrTestSuite) TestRemoveLastRevisionRunThrough(c *C) { 865 si := snap.SideInfo{ 866 RealName: "some-snap", 867 Revision: snap.R(2), 868 } 869 870 s.state.Lock() 871 defer s.state.Unlock() 872 873 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 874 Active: false, 875 Sequence: []*snap.SideInfo{&si}, 876 Current: si.Revision, 877 SnapType: "app", 878 }) 879 880 chg := s.state.NewChange("remove", "remove a snap") 881 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(2), nil) 882 c.Assert(err, IsNil) 883 chg.AddAll(ts) 884 885 s.state.Unlock() 886 defer s.se.Stop() 887 s.settle(c) 888 s.state.Lock() 889 890 c.Check(len(s.fakeBackend.ops), Equals, 8) 891 expected := fakeOps{ 892 { 893 op: "auto-disconnect:Doing", 894 name: "some-snap", 895 revno: snap.R(2), 896 }, 897 { 898 op: "remove-snap-data", 899 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 900 }, 901 { 902 op: "remove-snap-common-data", 903 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 904 }, 905 { 906 op: "remove-snap-data-dir", 907 name: "some-snap", 908 path: filepath.Join(dirs.SnapDataDir, "some-snap"), 909 }, 910 { 911 op: "remove-snap-files", 912 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 913 stype: "app", 914 }, 915 { 916 op: "discard-namespace", 917 name: "some-snap", 918 }, 919 { 920 op: "remove-inhibit-lock", 921 name: "some-snap", 922 }, 923 { 924 op: "remove-snap-dir", 925 name: "some-snap", 926 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 927 }, 928 } 929 // start with an easier-to-read error if this fails: 930 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 931 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 932 933 // verify snapSetup info 934 tasks := ts.Tasks() 935 for _, t := range tasks { 936 if t.Kind() == "run-hook" { 937 continue 938 } 939 if t.Kind() == "save-snapshot" { 940 continue 941 } 942 snapsup, err := snapstate.TaskSnapSetup(t) 943 c.Assert(err, IsNil) 944 945 expSnapSetup := &snapstate.SnapSetup{ 946 SideInfo: &snap.SideInfo{ 947 RealName: "some-snap", 948 }, 949 } 950 if t.Kind() != "discard-conns" { 951 expSnapSetup.SideInfo.Revision = snap.R(2) 952 expSnapSetup.Type = snap.TypeApp 953 } 954 if t.Kind() == "auto-disconnect" { 955 expSnapSetup.PlugsOnly = true 956 expSnapSetup.Type = snap.TypeApp 957 } 958 959 c.Check(snapsup, DeepEquals, expSnapSetup, Commentf(t.Kind())) 960 } 961 962 // verify snaps in the system state 963 var snapst snapstate.SnapState 964 err = snapstate.Get(s.state, "some-snap", &snapst) 965 c.Assert(err, Equals, state.ErrNoState) 966 } 967 968 func (s *snapmgrTestSuite) TestRemoveCurrentActiveRevisionRefused(c *C) { 969 si := snap.SideInfo{ 970 RealName: "some-snap", 971 Revision: snap.R(2), 972 } 973 974 s.state.Lock() 975 defer s.state.Unlock() 976 977 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 978 Active: true, 979 Sequence: []*snap.SideInfo{&si}, 980 Current: si.Revision, 981 SnapType: "app", 982 }) 983 984 _, err := snapstate.Remove(s.state, "some-snap", snap.R(2), nil) 985 986 c.Check(err, ErrorMatches, `cannot remove active revision 2 of snap "some-snap"`) 987 } 988 989 func (s *snapmgrTestSuite) TestRemoveCurrentRevisionOfSeveralRefused(c *C) { 990 si := snap.SideInfo{ 991 RealName: "some-snap", 992 Revision: snap.R(2), 993 } 994 995 s.state.Lock() 996 defer s.state.Unlock() 997 998 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 999 Active: true, 1000 Sequence: []*snap.SideInfo{&si, &si}, 1001 Current: si.Revision, 1002 SnapType: "app", 1003 }) 1004 1005 _, err := snapstate.Remove(s.state, "some-snap", snap.R(2), nil) 1006 c.Assert(err, NotNil) 1007 c.Check(err.Error(), Equals, `cannot remove active revision 2 of snap "some-snap" (revert first?)`) 1008 } 1009 1010 func (s *snapmgrTestSuite) TestRemoveMissingRevisionRefused(c *C) { 1011 si := snap.SideInfo{ 1012 RealName: "some-snap", 1013 Revision: snap.R(2), 1014 } 1015 1016 s.state.Lock() 1017 defer s.state.Unlock() 1018 1019 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1020 Active: true, 1021 Sequence: []*snap.SideInfo{&si}, 1022 Current: si.Revision, 1023 SnapType: "app", 1024 }) 1025 1026 _, err := snapstate.Remove(s.state, "some-snap", snap.R(1), nil) 1027 1028 c.Check(err, ErrorMatches, `revision 1 of snap "some-snap" is not installed`) 1029 } 1030 1031 func (s *snapmgrTestSuite) TestRemoveRefused(c *C) { 1032 si := snap.SideInfo{ 1033 RealName: "brand-gadget", 1034 Revision: snap.R(7), 1035 } 1036 1037 s.state.Lock() 1038 defer s.state.Unlock() 1039 1040 snapstate.Set(s.state, "brand-gadget", &snapstate.SnapState{ 1041 Active: true, 1042 Sequence: []*snap.SideInfo{&si}, 1043 Current: si.Revision, 1044 SnapType: "gadget", 1045 }) 1046 1047 _, err := snapstate.Remove(s.state, "brand-gadget", snap.R(0), nil) 1048 1049 c.Check(err, ErrorMatches, `snap "brand-gadget" is not removable: snap is used by the model`) 1050 } 1051 1052 func (s *snapmgrTestSuite) TestRemoveRefusedLastRevision(c *C) { 1053 si := snap.SideInfo{ 1054 RealName: "brand-gadget", 1055 Revision: snap.R(7), 1056 } 1057 1058 s.state.Lock() 1059 defer s.state.Unlock() 1060 1061 snapstate.Set(s.state, "brand-gadget", &snapstate.SnapState{ 1062 Active: false, 1063 Sequence: []*snap.SideInfo{&si}, 1064 Current: si.Revision, 1065 SnapType: "gadget", 1066 }) 1067 1068 _, err := snapstate.Remove(s.state, "brand-gadget", snap.R(7), nil) 1069 1070 c.Check(err, ErrorMatches, `snap "brand-gadget" is not removable: snap is used by the model`) 1071 } 1072 1073 func (s *snapmgrTestSuite) TestRemoveDeletesConfigOnLastRevision(c *C) { 1074 si := snap.SideInfo{ 1075 RealName: "some-snap", 1076 Revision: snap.R(7), 1077 } 1078 1079 s.state.Lock() 1080 defer s.state.Unlock() 1081 1082 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1083 Active: true, 1084 Sequence: []*snap.SideInfo{&si}, 1085 Current: si.Revision, 1086 SnapType: "app", 1087 }) 1088 1089 snapstate.Set(s.state, "another-snap", &snapstate.SnapState{ 1090 Active: true, 1091 Sequence: []*snap.SideInfo{&si}, 1092 Current: si.Revision, 1093 SnapType: "app", 1094 }) 1095 1096 tr := config.NewTransaction(s.state) 1097 tr.Set("some-snap", "foo", "bar") 1098 tr.Commit() 1099 1100 // a config for some other snap to verify its not accidentally destroyed 1101 tr = config.NewTransaction(s.state) 1102 tr.Set("another-snap", "bar", "baz") 1103 tr.Commit() 1104 1105 var res string 1106 tr = config.NewTransaction(s.state) 1107 c.Assert(tr.Get("some-snap", "foo", &res), IsNil) 1108 c.Assert(tr.Get("another-snap", "bar", &res), IsNil) 1109 1110 chg := s.state.NewChange("remove", "remove a snap") 1111 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 1112 c.Assert(err, IsNil) 1113 chg.AddAll(ts) 1114 1115 s.state.Unlock() 1116 defer s.se.Stop() 1117 s.settle(c) 1118 s.state.Lock() 1119 1120 // verify snaps in the system state 1121 var snapst snapstate.SnapState 1122 err = snapstate.Get(s.state, "some-snap", &snapst) 1123 c.Assert(err, Equals, state.ErrNoState) 1124 1125 tr = config.NewTransaction(s.state) 1126 err = tr.Get("some-snap", "foo", &res) 1127 c.Assert(err, NotNil) 1128 c.Assert(err, ErrorMatches, `snap "some-snap" has no "foo" configuration option`) 1129 1130 // and another snap has its config intact 1131 c.Assert(tr.Get("another-snap", "bar", &res), IsNil) 1132 c.Assert(res, Equals, "baz") 1133 } 1134 1135 func (s *snapmgrTestSuite) TestRemoveDoesntDeleteConfigIfNotLastRevision(c *C) { 1136 si1 := snap.SideInfo{ 1137 RealName: "some-snap", 1138 Revision: snap.R(7), 1139 } 1140 si2 := snap.SideInfo{ 1141 RealName: "some-snap", 1142 Revision: snap.R(8), 1143 } 1144 1145 s.state.Lock() 1146 defer s.state.Unlock() 1147 1148 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1149 Active: true, 1150 Sequence: []*snap.SideInfo{&si1, &si2}, 1151 Current: si2.Revision, 1152 SnapType: "app", 1153 }) 1154 1155 tr := config.NewTransaction(s.state) 1156 tr.Set("some-snap", "foo", "bar") 1157 tr.Commit() 1158 1159 var res string 1160 tr = config.NewTransaction(s.state) 1161 c.Assert(tr.Get("some-snap", "foo", &res), IsNil) 1162 1163 chg := s.state.NewChange("remove", "remove a snap") 1164 ts, err := snapstate.Remove(s.state, "some-snap", si1.Revision, nil) 1165 c.Assert(err, IsNil) 1166 chg.AddAll(ts) 1167 1168 s.state.Unlock() 1169 defer s.se.Stop() 1170 s.settle(c) 1171 s.state.Lock() 1172 1173 // verify snaps in the system state 1174 var snapst snapstate.SnapState 1175 err = snapstate.Get(s.state, "some-snap", &snapst) 1176 c.Assert(err, IsNil) 1177 1178 tr = config.NewTransaction(s.state) 1179 c.Assert(tr.Get("some-snap", "foo", &res), IsNil) 1180 c.Assert(res, Equals, "bar") 1181 } 1182 1183 func (s *snapmgrTestSuite) TestRemoveMany(c *C) { 1184 s.state.Lock() 1185 defer s.state.Unlock() 1186 1187 snapstate.Set(s.state, "one", &snapstate.SnapState{ 1188 Active: true, 1189 Sequence: []*snap.SideInfo{ 1190 {RealName: "one", SnapID: "one-id", Revision: snap.R(1)}, 1191 }, 1192 Current: snap.R(1), 1193 }) 1194 snapstate.Set(s.state, "two", &snapstate.SnapState{ 1195 Active: true, 1196 Sequence: []*snap.SideInfo{ 1197 {RealName: "two", SnapID: "two-id", Revision: snap.R(1)}, 1198 }, 1199 Current: snap.R(1), 1200 }) 1201 1202 removed, tts, err := snapstate.RemoveMany(s.state, []string{"one", "two"}) 1203 c.Assert(err, IsNil) 1204 c.Assert(tts, HasLen, 2) 1205 c.Check(removed, DeepEquals, []string{"one", "two"}) 1206 1207 c.Assert(s.state.TaskCount(), Equals, 8*2) 1208 for i, ts := range tts { 1209 c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{ 1210 "stop-snap-services", 1211 "run-hook[remove]", 1212 "auto-disconnect", 1213 "remove-aliases", 1214 "unlink-snap", 1215 "remove-profiles", 1216 "clear-snap", 1217 "discard-snap", 1218 }) 1219 verifyStopReason(c, ts, "remove") 1220 // check that tasksets are in separate lanes 1221 for _, t := range ts.Tasks() { 1222 c.Assert(t.Lanes(), DeepEquals, []int{i + 1}) 1223 } 1224 1225 } 1226 } 1227 1228 func (s *snapmgrTestSuite) testRemoveManyDiskSpaceCheck(c *C, featureFlag, automaticSnapshot, freeSpaceCheckFail bool) error { 1229 s.state.Lock() 1230 defer s.state.Unlock() 1231 1232 var checkFreeSpaceCall, snapshotSizeCall int 1233 1234 // restored by TearDownTest 1235 snapstate.EstimateSnapshotSize = func(st *state.State, instanceName string, users []string) (uint64, error) { 1236 snapshotSizeCall++ 1237 // expect two snapshot size estimations 1238 switch instanceName { 1239 case "one": 1240 return 10, nil 1241 case "two": 1242 return 20, nil 1243 default: 1244 c.Fatalf("unexpected snap: %s", instanceName) 1245 } 1246 return 1, nil 1247 } 1248 1249 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, required uint64) error { 1250 checkFreeSpaceCall++ 1251 // required size is the sum of snapshot sizes of test snaps 1252 c.Check(required, Equals, snapstate.SafetyMarginDiskSpace(30)) 1253 if freeSpaceCheckFail { 1254 return &osutil.NotEnoughDiskSpaceError{} 1255 } 1256 return nil 1257 }) 1258 defer restore() 1259 1260 var automaticSnapshotCalled bool 1261 snapstate.AutomaticSnapshot = func(st *state.State, instanceName string) (ts *state.TaskSet, err error) { 1262 automaticSnapshotCalled = true 1263 if automaticSnapshot { 1264 t := s.state.NewTask("foo", "") 1265 ts = state.NewTaskSet(t) 1266 return ts, nil 1267 } 1268 // ErrNothingToDo is returned if automatic snapshots are disabled 1269 return nil, snapstate.ErrNothingToDo 1270 } 1271 1272 tr := config.NewTransaction(s.state) 1273 tr.Set("core", "experimental.check-disk-space-remove", featureFlag) 1274 tr.Commit() 1275 1276 snapstate.Set(s.state, "one", &snapstate.SnapState{ 1277 Active: true, 1278 SnapType: "app", 1279 Sequence: []*snap.SideInfo{ 1280 {RealName: "one", SnapID: "one-id", Revision: snap.R(1)}, 1281 }, 1282 Current: snap.R(1), 1283 }) 1284 snapstate.Set(s.state, "two", &snapstate.SnapState{ 1285 Active: true, 1286 SnapType: "app", 1287 Sequence: []*snap.SideInfo{ 1288 {RealName: "two", SnapID: "two-id", Revision: snap.R(1)}, 1289 }, 1290 Current: snap.R(1), 1291 }) 1292 1293 _, _, err := snapstate.RemoveMany(s.state, []string{"one", "two"}) 1294 if featureFlag && automaticSnapshot { 1295 c.Check(snapshotSizeCall, Equals, 2) 1296 c.Check(checkFreeSpaceCall, Equals, 1) 1297 } else { 1298 c.Check(checkFreeSpaceCall, Equals, 0) 1299 c.Check(snapshotSizeCall, Equals, 0) 1300 } 1301 c.Check(automaticSnapshotCalled, Equals, true) 1302 1303 return err 1304 } 1305 1306 func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceError(c *C) { 1307 featureFlag := true 1308 automaticSnapshot := true 1309 freeSpaceCheckFail := true 1310 err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail) 1311 1312 diskSpaceErr := err.(*snapstate.InsufficientSpaceError) 1313 c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 1314 c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"one", "two"}) 1315 c.Check(diskSpaceErr.ChangeKind, Equals, "remove") 1316 } 1317 1318 func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceCheckDisabled(c *C) { 1319 featureFlag := false 1320 automaticSnapshot := true 1321 freeSpaceCheckFail := true 1322 err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail) 1323 c.Assert(err, IsNil) 1324 } 1325 1326 func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceSnapshotDisabled(c *C) { 1327 featureFlag := true 1328 automaticSnapshot := false 1329 freeSpaceCheckFail := true 1330 err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail) 1331 c.Assert(err, IsNil) 1332 } 1333 1334 func (s *snapmgrTestSuite) TestRemoveManyDiskSpaceCheckPasses(c *C) { 1335 featureFlag := true 1336 automaticSnapshot := true 1337 freeSpaceCheckFail := false 1338 err := s.testRemoveManyDiskSpaceCheck(c, featureFlag, automaticSnapshot, freeSpaceCheckFail) 1339 c.Check(err, IsNil) 1340 } 1341 1342 type snapdBackend struct { 1343 fakeSnappyBackend 1344 } 1345 1346 func (f *snapdBackend) RemoveSnapData(info *snap.Info) error { 1347 dir := snap.DataDir(info.SnapName(), info.Revision) 1348 if err := os.Remove(dir); err != nil { 1349 return fmt.Errorf("unexpected error: %v", err) 1350 } 1351 return f.fakeSnappyBackend.RemoveSnapData(info) 1352 } 1353 1354 func (f *snapdBackend) RemoveSnapCommonData(info *snap.Info) error { 1355 dir := snap.CommonDataDir(info.SnapName()) 1356 if err := os.Remove(dir); err != nil { 1357 return fmt.Errorf("unexpected error: %v", err) 1358 } 1359 return f.fakeSnappyBackend.RemoveSnapCommonData(info) 1360 } 1361 1362 func isUndone(c *C, tasks []*state.Task, kind string, numExpected int) { 1363 var count int 1364 for _, t := range tasks { 1365 if t.Kind() == kind { 1366 c.Assert(t.Status(), Equals, state.UndoneStatus) 1367 count++ 1368 } 1369 } 1370 c.Assert(count, Equals, numExpected) 1371 } 1372 1373 func injectError(c *C, chg *state.Change, beforeTaskKind string, snapRev snap.Revision) { 1374 var found bool 1375 for _, t := range chg.Tasks() { 1376 if t.Kind() != beforeTaskKind { 1377 continue 1378 } 1379 sup, err := snapstate.TaskSnapSetup(t) 1380 c.Assert(err, IsNil) 1381 if sup.Revision() != snapRev { 1382 continue 1383 } 1384 prev := t.WaitTasks()[0] 1385 terr := chg.State().NewTask("error-trigger", "provoking undo") 1386 t.WaitFor(terr) 1387 terr.WaitFor(prev) 1388 chg.AddTask(terr) 1389 found = true 1390 break 1391 } 1392 c.Assert(found, Equals, true) 1393 } 1394 1395 func makeTestSnaps(c *C, st *state.State) { 1396 si1 := snap.SideInfo{ 1397 SnapID: "some-snap-id", 1398 RealName: "some-snap", 1399 Revision: snap.R(1), 1400 } 1401 1402 si2 := snap.SideInfo{ 1403 SnapID: "some-snap-id", 1404 RealName: "some-snap", 1405 Revision: snap.R(2), 1406 } 1407 1408 snapstate.Set(st, "some-snap", &snapstate.SnapState{ 1409 Active: true, 1410 Sequence: []*snap.SideInfo{&si1, &si2}, 1411 Current: si1.Revision, 1412 SnapType: "app", 1413 }) 1414 1415 c.Assert(os.MkdirAll(snap.DataDir("some-snap", si1.Revision), 0755), IsNil) 1416 c.Assert(os.MkdirAll(snap.DataDir("some-snap", si2.Revision), 0755), IsNil) 1417 c.Assert(os.MkdirAll(snap.CommonDataDir("some-snap"), 0755), IsNil) 1418 } 1419 1420 func (s *snapmgrTestSuite) TestRemoveManyUndoRestoresCurrent(c *C) { 1421 b := &snapdBackend{} 1422 snapstate.SetSnapManagerBackend(s.snapmgr, b) 1423 AddForeignTaskHandlers(s.o.TaskRunner(), b) 1424 1425 s.state.Lock() 1426 defer s.state.Unlock() 1427 makeTestSnaps(c, s.state) 1428 1429 chg := s.state.NewChange("remove", "remove a snap") 1430 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 1431 c.Assert(err, IsNil) 1432 chg.AddAll(ts) 1433 1434 // inject an error before clear-snap of revision 1 (current), after 1435 // discard-snap for revision 2, that means data and snap rev 1 1436 // are still present. 1437 injectError(c, chg, "clear-snap", snap.Revision{N: 1}) 1438 1439 s.state.Unlock() 1440 defer s.se.Stop() 1441 s.settle(c) 1442 s.state.Lock() 1443 1444 c.Assert(chg.Status(), Equals, state.ErrorStatus) 1445 isUndone(c, chg.Tasks(), "unlink-snap", 1) 1446 1447 var snapst snapstate.SnapState 1448 c.Assert(snapstate.Get(s.state, "some-snap", &snapst), IsNil) 1449 c.Check(snapst.Active, Equals, true) 1450 c.Check(snapst.Current, Equals, snap.Revision{N: 1}) 1451 c.Assert(snapst.Sequence, HasLen, 1) 1452 c.Check(snapst.Sequence[0].Revision, Equals, snap.Revision{N: 1}) 1453 1454 expected := fakeOps{ 1455 { 1456 op: "auto-disconnect:Doing", 1457 name: "some-snap", 1458 revno: snap.R(1), 1459 }, 1460 { 1461 op: "remove-snap-aliases", 1462 name: "some-snap", 1463 }, 1464 { 1465 op: "unlink-snap", 1466 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 1467 }, 1468 { 1469 op: "remove-profiles:Doing", 1470 name: "some-snap", 1471 revno: snap.R(1), 1472 }, 1473 { 1474 op: "remove-snap-data", 1475 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 1476 }, 1477 { 1478 op: "remove-snap-files", 1479 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 1480 stype: "app", 1481 }, 1482 { 1483 op: "remove-profiles:Undoing", 1484 name: "some-snap", 1485 revno: snap.R(1), 1486 }, 1487 { 1488 op: "link-snap", 1489 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 1490 }, 1491 { 1492 op: "update-aliases", 1493 }, 1494 } 1495 // start with an easier-to-read error if this fails: 1496 c.Check(len(b.ops), Equals, len(expected)) 1497 c.Assert(b.ops.Ops(), DeepEquals, expected.Ops()) 1498 c.Check(b.ops, DeepEquals, expected) 1499 } 1500 1501 func (s *snapmgrTestSuite) TestRemoveManyUndoLeavesInactiveSnapAfterDataIsLost(c *C) { 1502 b := &snapdBackend{} 1503 snapstate.SetSnapManagerBackend(s.snapmgr, b) 1504 AddForeignTaskHandlers(s.o.TaskRunner(), b) 1505 1506 s.state.Lock() 1507 defer s.state.Unlock() 1508 makeTestSnaps(c, s.state) 1509 1510 chg := s.state.NewChange("remove", "remove a snap") 1511 ts, err := snapstate.Remove(s.state, "some-snap", snap.R(0), nil) 1512 c.Assert(err, IsNil) 1513 chg.AddAll(ts) 1514 1515 // inject an error after removing data of both revisions (which includes 1516 // current rev 1), before discarding the snap completely. 1517 injectError(c, chg, "discard-snap", snap.Revision{N: 1}) 1518 1519 s.state.Unlock() 1520 defer s.se.Stop() 1521 s.settle(c) 1522 s.state.Lock() 1523 1524 c.Assert(chg.Status(), Equals, state.ErrorStatus) 1525 isUndone(c, chg.Tasks(), "unlink-snap", 1) 1526 1527 var snapst snapstate.SnapState 1528 c.Assert(snapstate.Get(s.state, "some-snap", &snapst), IsNil) 1529 1530 // revision 1 is still present but not active, since the error happened 1531 // after its data was removed. 1532 c.Check(snapst.Active, Equals, false) 1533 c.Check(snapst.Current, Equals, snap.Revision{N: 1}) 1534 c.Assert(snapst.Sequence, HasLen, 1) 1535 c.Check(snapst.Sequence[0].Revision, Equals, snap.Revision{N: 1}) 1536 1537 expected := fakeOps{ 1538 { 1539 op: "auto-disconnect:Doing", 1540 name: "some-snap", 1541 revno: snap.R(1), 1542 }, 1543 { 1544 op: "remove-snap-aliases", 1545 name: "some-snap", 1546 }, 1547 { 1548 op: "unlink-snap", 1549 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 1550 }, 1551 { 1552 op: "remove-profiles:Doing", 1553 name: "some-snap", 1554 revno: snap.R(1), 1555 }, 1556 { 1557 op: "remove-snap-data", 1558 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 1559 }, 1560 { 1561 op: "remove-snap-files", 1562 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 1563 stype: "app", 1564 }, 1565 { 1566 op: "remove-snap-data", 1567 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 1568 }, 1569 { 1570 op: "remove-snap-common-data", 1571 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 1572 }, 1573 { 1574 op: "remove-snap-data-dir", 1575 name: "some-snap", 1576 path: filepath.Join(dirs.SnapDataDir, "some-snap"), 1577 }, 1578 { 1579 op: "remove-profiles:Undoing", 1580 name: "some-snap", 1581 revno: snap.R(1), 1582 }, 1583 { 1584 op: "update-aliases", 1585 }, 1586 } 1587 1588 // start with an easier-to-read error if this fails: 1589 c.Check(len(b.ops), Equals, len(expected)) 1590 c.Assert(b.ops.Ops(), DeepEquals, expected.Ops()) 1591 c.Check(b.ops, DeepEquals, expected) 1592 }