github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/snapstate/snapstate_remove_test.go (about)

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