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