github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/overlord/snapstate/snapstate_remove_test.go (about)

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