github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/snapstate_remove_test.go (about)

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