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