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