gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/snapshotstate/snapshotstate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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 snapshotstate_test
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"encoding/json"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"os/exec"
    32  	"os/user"
    33  	"path/filepath"
    34  	"sort"
    35  	"strings"
    36  	"testing"
    37  	"time"
    38  
    39  	"gopkg.in/check.v1"
    40  
    41  	"github.com/snapcore/snapd/client"
    42  	"github.com/snapcore/snapd/dirs"
    43  	"github.com/snapcore/snapd/osutil/sys"
    44  	"github.com/snapcore/snapd/overlord"
    45  	"github.com/snapcore/snapd/overlord/configstate/config"
    46  	"github.com/snapcore/snapd/overlord/snapshotstate"
    47  	"github.com/snapcore/snapd/overlord/snapshotstate/backend"
    48  	"github.com/snapcore/snapd/overlord/snapstate"
    49  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    50  	"github.com/snapcore/snapd/overlord/state"
    51  	"github.com/snapcore/snapd/release"
    52  	"github.com/snapcore/snapd/snap"
    53  	"github.com/snapcore/snapd/snap/snaptest"
    54  	"github.com/snapcore/snapd/testutil"
    55  )
    56  
    57  type snapshotSuite struct{}
    58  
    59  var _ = check.Suite(&snapshotSuite{})
    60  
    61  // tie gocheck into testing
    62  func TestSnapshot(t *testing.T) { check.TestingT(t) }
    63  
    64  func (snapshotSuite) SetUpTest(c *check.C) {
    65  	dirs.SetRootDir(c.MkDir())
    66  	os.MkdirAll(dirs.SnapshotsDir, os.ModePerm)
    67  }
    68  
    69  func (snapshotSuite) TearDownTest(c *check.C) {
    70  	dirs.SetRootDir("/")
    71  }
    72  
    73  func (snapshotSuite) TestNewSnapshotSetID(c *check.C) {
    74  	st := state.New(nil)
    75  	st.Lock()
    76  	defer st.Unlock()
    77  
    78  	// Disk last set id unset, state set id unset, use 1
    79  	sid, err := snapshotstate.NewSnapshotSetID(st)
    80  	c.Assert(err, check.IsNil)
    81  	c.Check(sid, check.Equals, uint64(1))
    82  
    83  	var stateSetID uint64
    84  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
    85  	c.Check(stateSetID, check.Equals, uint64(1))
    86  
    87  	c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, "9_some-snap-1.zip"), []byte{}, 0644), check.IsNil)
    88  
    89  	// Disk last set id 9 > state set id 1, use 9++ = 10
    90  	sid, err = snapshotstate.NewSnapshotSetID(st)
    91  	c.Assert(err, check.IsNil)
    92  	c.Check(sid, check.Equals, uint64(10))
    93  
    94  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
    95  	c.Check(stateSetID, check.Equals, uint64(10))
    96  
    97  	// Disk last set id 9 < state set id 10, use 10++ = 11
    98  	sid, err = snapshotstate.NewSnapshotSetID(st)
    99  	c.Assert(err, check.IsNil)
   100  	c.Check(sid, check.Equals, uint64(11))
   101  
   102  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
   103  	c.Check(stateSetID, check.Equals, uint64(11))
   104  
   105  	c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, "88_some-snap-1.zip"), []byte{}, 0644), check.IsNil)
   106  
   107  	// Disk last set id 88 > state set id 11, use 88++ = 89
   108  	sid, err = snapshotstate.NewSnapshotSetID(st)
   109  	c.Assert(err, check.IsNil)
   110  	c.Check(sid, check.Equals, uint64(89))
   111  
   112  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
   113  	c.Check(stateSetID, check.Equals, uint64(89))
   114  }
   115  
   116  func (snapshotSuite) TestAllActiveSnapNames(c *check.C) {
   117  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   118  		return map[string]*snapstate.SnapState{
   119  			"a-snap": {Active: true},
   120  			"b-snap": {},
   121  			"c-snap": {Active: true},
   122  		}, nil
   123  	}
   124  
   125  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   126  
   127  	// loop to check sortedness
   128  	for i := 0; i < 100; i++ {
   129  		names, err := snapshotstate.AllActiveSnapNames(nil)
   130  		c.Assert(err, check.IsNil)
   131  		c.Check(names, check.DeepEquals, []string{"a-snap", "c-snap"})
   132  	}
   133  }
   134  
   135  func (snapshotSuite) TestAllActiveSnapNamesError(c *check.C) {
   136  	errBad := errors.New("bad")
   137  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   138  		return nil, errBad
   139  	}
   140  
   141  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   142  
   143  	names, err := snapshotstate.AllActiveSnapNames(nil)
   144  	c.Check(err, check.Equals, errBad)
   145  	c.Check(names, check.IsNil)
   146  }
   147  
   148  func (snapshotSuite) TestSnapSummariesInSnapshotSet(c *check.C) {
   149  	shotfileA, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   150  	c.Assert(err, check.IsNil)
   151  	defer shotfileA.Close()
   152  	shotfileB, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   153  	c.Assert(err, check.IsNil)
   154  	defer shotfileB.Close()
   155  
   156  	setID := uint64(42)
   157  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   158  		c.Assert(f(&backend.Reader{
   159  			// wanted
   160  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap", SnapID: "a-id", Epoch: snap.Epoch{Read: []uint32{42}, Write: []uint32{17}}},
   161  			File:     shotfileA,
   162  		}), check.IsNil)
   163  		c.Assert(f(&backend.Reader{
   164  			// not wanted (bad set id)
   165  			Snapshot: client.Snapshot{SetID: setID + 1, Snap: "a-snap", SnapID: "a-id"},
   166  			File:     shotfileA,
   167  		}), check.IsNil)
   168  		c.Assert(f(&backend.Reader{
   169  			// wanted
   170  			Snapshot: client.Snapshot{SetID: setID, Snap: "b-snap", SnapID: "b-id"},
   171  			File:     shotfileB,
   172  		}), check.IsNil)
   173  		return nil
   174  	}
   175  	defer snapshotstate.MockBackendIter(fakeIter)()
   176  
   177  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil)
   178  	c.Assert(err, check.IsNil)
   179  	c.Assert(summaries.AsMaps(), check.DeepEquals, []map[string]string{
   180  		{"snap": "a-snap", "snapID": "a-id", "filename": shotfileA.Name(), "epoch": `{"read":[42],"write":[17]}`},
   181  		{"snap": "b-snap", "snapID": "b-id", "filename": shotfileB.Name(), "epoch": "0"},
   182  	})
   183  }
   184  
   185  func (snapshotSuite) TestSnapSummariesInSnapshotSetSnaps(c *check.C) {
   186  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   187  	c.Assert(err, check.IsNil)
   188  	defer shotfile.Close()
   189  	setID := uint64(42)
   190  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   191  		c.Assert(f(&backend.Reader{
   192  			// wanted
   193  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap", SnapID: "a-id"},
   194  			File:     shotfile,
   195  		}), check.IsNil)
   196  		c.Assert(f(&backend.Reader{
   197  			// not wanted (bad set id)
   198  			Snapshot: client.Snapshot{SetID: setID + 1, Snap: "a-snap", SnapID: "a-id"},
   199  			File:     shotfile,
   200  		}), check.IsNil)
   201  		c.Assert(f(&backend.Reader{
   202  			// not wanted (bad snap name)
   203  			Snapshot: client.Snapshot{SetID: setID, Snap: "c-snap", SnapID: "c-id"},
   204  			File:     shotfile,
   205  		}), check.IsNil)
   206  		return nil
   207  	}
   208  	defer snapshotstate.MockBackendIter(fakeIter)()
   209  
   210  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, []string{"a-snap"})
   211  	c.Assert(err, check.IsNil)
   212  	c.Check(summaries.AsMaps(), check.DeepEquals, []map[string]string{
   213  		{"snap": "a-snap", "snapID": "a-id", "filename": shotfile.Name(), "epoch": "0"},
   214  	})
   215  }
   216  
   217  func (snapshotSuite) TestSnapSummariesInSnapshotSetErrors(c *check.C) {
   218  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   219  	c.Assert(err, check.IsNil)
   220  	defer shotfile.Close()
   221  	setID := uint64(42)
   222  	errBad := errors.New("bad")
   223  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   224  		c.Assert(f(&backend.Reader{
   225  			// wanted
   226  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap"},
   227  			File:     shotfile,
   228  		}), check.IsNil)
   229  
   230  		return errBad
   231  	}
   232  	defer snapshotstate.MockBackendIter(fakeIter)()
   233  
   234  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil)
   235  	c.Assert(err, check.Equals, errBad)
   236  	c.Check(summaries, check.IsNil)
   237  }
   238  
   239  func (snapshotSuite) TestSnapSummariesInSnapshotSetNotFound(c *check.C) {
   240  	setID := uint64(42)
   241  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   242  	c.Assert(err, check.IsNil)
   243  	defer shotfile.Close()
   244  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   245  		c.Assert(f(&backend.Reader{
   246  			// not wanted
   247  			Snapshot: client.Snapshot{SetID: setID - 1, Snap: "a-snap"},
   248  			File:     shotfile,
   249  		}), check.IsNil)
   250  
   251  		return nil
   252  	}
   253  	defer snapshotstate.MockBackendIter(fakeIter)()
   254  
   255  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil)
   256  	c.Assert(err, check.Equals, client.ErrSnapshotSetNotFound)
   257  	c.Check(summaries, check.IsNil)
   258  }
   259  
   260  func (snapshotSuite) TestSnapSummariesInSnapshotSetEmptyNotFound(c *check.C) {
   261  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { return nil }
   262  	defer snapshotstate.MockBackendIter(fakeIter)()
   263  
   264  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(42, nil)
   265  	c.Assert(err, check.Equals, client.ErrSnapshotSetNotFound)
   266  	c.Check(summaries, check.IsNil)
   267  }
   268  
   269  func (snapshotSuite) TestSnapSummariesInSnapshotSetSnapNotFound(c *check.C) {
   270  	setID := uint64(42)
   271  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   272  	c.Assert(err, check.IsNil)
   273  	defer shotfile.Close()
   274  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   275  		c.Assert(f(&backend.Reader{
   276  			// not wanted
   277  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap"},
   278  			File:     shotfile,
   279  		}), check.IsNil)
   280  
   281  		return nil
   282  	}
   283  	defer snapshotstate.MockBackendIter(fakeIter)()
   284  
   285  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, []string{"b-snap"})
   286  	c.Assert(err, check.Equals, client.ErrSnapshotSnapsNotFound)
   287  	c.Check(summaries, check.IsNil)
   288  }
   289  
   290  func (snapshotSuite) TestCheckConflict(c *check.C) {
   291  	st := state.New(nil)
   292  	st.Lock()
   293  	defer st.Unlock()
   294  	chg := st.NewChange("some-change", "...")
   295  	tsk := st.NewTask("some-task", "...")
   296  	tsk.SetStatus(state.DoingStatus)
   297  	chg.AddTask(tsk)
   298  
   299  	// no snapshot state
   300  	err := snapshotstate.CheckSnapshotConflict(st, 42, "some-task")
   301  	c.Assert(err, check.ErrorMatches, "internal error: task 1 .some-task. is missing snapshot information")
   302  
   303  	// wrong snapshot state
   304  	tsk.Set("snapshot-setup", "hello")
   305  	err = snapshotstate.CheckSnapshotConflict(st, 42, "some-task")
   306  	c.Assert(err, check.ErrorMatches, "internal error.* could not unmarshal.*")
   307  
   308  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
   309  
   310  	err = snapshotstate.CheckSnapshotConflict(st, 42, "some-task")
   311  	c.Assert(err, check.ErrorMatches, "cannot operate on snapshot set #42 while change \"1\" is in progress")
   312  
   313  	// no change with that label
   314  	c.Assert(snapshotstate.CheckSnapshotConflict(st, 42, "some-other-task"), check.IsNil)
   315  
   316  	// no change with that snapshot id
   317  	c.Assert(snapshotstate.CheckSnapshotConflict(st, 43, "some-task"), check.IsNil)
   318  
   319  	// no non-ready change
   320  	tsk.SetStatus(state.DoneStatus)
   321  	c.Assert(snapshotstate.CheckSnapshotConflict(st, 42, "some-task"), check.IsNil)
   322  }
   323  
   324  func (snapshotSuite) TestCheckConflictSnapshotOpInProgress(c *check.C) {
   325  	st := state.New(nil)
   326  	st.Lock()
   327  	defer st.Unlock()
   328  
   329  	snapshotstate.SetSnapshotOpInProgress(st, 1, "foo-op")
   330  	snapshotstate.SetSnapshotOpInProgress(st, 2, "bar-op")
   331  
   332  	c.Assert(snapshotstate.CheckSnapshotConflict(st, 1, "foo-op"), check.ErrorMatches, `cannot operate on snapshot set #1 while operation foo-op is in progress`)
   333  	// unrelated set-id doesn't conflict
   334  	c.Assert(snapshotstate.CheckSnapshotConflict(st, 3, "foo-op"), check.IsNil)
   335  	c.Assert(snapshotstate.CheckSnapshotConflict(st, 3, "bar-op"), check.IsNil)
   336  	// non-conflicting op
   337  	c.Assert(snapshotstate.CheckSnapshotConflict(st, 1, "safe-op"), check.IsNil)
   338  }
   339  
   340  func (snapshotSuite) TestSaveChecksSnapnamesError(c *check.C) {
   341  	defer snapshotstate.MockSnapstateAll(func(*state.State) (map[string]*snapstate.SnapState, error) {
   342  		return nil, errors.New("bzzt")
   343  	})()
   344  
   345  	st := state.New(nil)
   346  	st.Lock()
   347  	defer st.Unlock()
   348  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   349  	c.Check(err, check.ErrorMatches, "bzzt")
   350  }
   351  
   352  func (snapshotSuite) createConflictingChange(c *check.C) (st *state.State, restore func()) {
   353  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   354  	c.Assert(err, check.IsNil)
   355  	shotfile.Close()
   356  
   357  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   358  		c.Assert(f(&backend.Reader{
   359  			Snapshot: client.Snapshot{SetID: 42, Snap: "foo"},
   360  			File:     shotfile,
   361  		}), check.IsNil)
   362  
   363  		return nil
   364  	}
   365  	restoreIter := snapshotstate.MockBackendIter(fakeIter)
   366  
   367  	o := overlord.Mock()
   368  	st = o.State()
   369  
   370  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   371  	c.Assert(err, check.IsNil)
   372  	o.AddManager(stmgr)
   373  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   374  	o.AddManager(shmgr)
   375  
   376  	st.Lock()
   377  	defer func() {
   378  		if c.Failed() {
   379  			// something went wrong
   380  			st.Unlock()
   381  		}
   382  	}()
   383  
   384  	snapstate.Set(st, "foo", &snapstate.SnapState{
   385  		Active: true,
   386  		Sequence: []*snap.SideInfo{
   387  			{RealName: "foo", Revision: snap.R(1)},
   388  		},
   389  		Current:  snap.R(1),
   390  		SnapType: "app",
   391  	})
   392  
   393  	r := snapstatetest.UseFallbackDeviceModel()
   394  	defer r()
   395  
   396  	chg := st.NewChange("rm foo", "...")
   397  	rmTasks, err := snapstate.Remove(st, "foo", snap.R(0), nil)
   398  	c.Assert(err, check.IsNil)
   399  	c.Assert(rmTasks, check.NotNil)
   400  	chg.AddAll(rmTasks)
   401  
   402  	return st, func() {
   403  		shotfile.Close()
   404  		st.Unlock()
   405  		restoreIter()
   406  	}
   407  }
   408  
   409  func (s snapshotSuite) TestSaveChecksSnapstateConflict(c *check.C) {
   410  	st, restore := s.createConflictingChange(c)
   411  	defer restore()
   412  
   413  	_, _, _, err := snapshotstate.Save(st, []string{"foo"}, nil)
   414  	c.Assert(err, check.NotNil)
   415  	c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{})
   416  }
   417  
   418  func (snapshotSuite) TestSaveConflictsWithSnapstate(c *check.C) {
   419  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   420  		return map[string]*snapstate.SnapState{
   421  			"foo": {Active: true},
   422  		}, nil
   423  	}
   424  
   425  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   426  
   427  	o := overlord.Mock()
   428  	st := o.State()
   429  
   430  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   431  	c.Assert(err, check.IsNil)
   432  	o.AddManager(stmgr)
   433  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   434  	o.AddManager(shmgr)
   435  
   436  	st.Lock()
   437  	defer st.Unlock()
   438  
   439  	snapstate.Set(st, "foo", &snapstate.SnapState{
   440  		Active: true,
   441  		Sequence: []*snap.SideInfo{
   442  			{RealName: "foo", Revision: snap.R(1)},
   443  		},
   444  		Current:  snap.R(1),
   445  		SnapType: "app",
   446  	})
   447  
   448  	chg := st.NewChange("snapshot-save", "...")
   449  	_, _, saveTasks, err := snapshotstate.Save(st, nil, nil)
   450  	c.Assert(err, check.IsNil)
   451  	chg.AddAll(saveTasks)
   452  
   453  	_, err = snapstate.Disable(st, "foo")
   454  	c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-save" change in progress`)
   455  }
   456  
   457  func (snapshotSuite) TestSaveChecksSnapstateConflictError(c *check.C) {
   458  	defer snapshotstate.MockSnapstateCheckChangeConflictMany(func(*state.State, []string, string) error {
   459  		return errors.New("bzzt")
   460  	})()
   461  
   462  	st := state.New(nil)
   463  	st.Lock()
   464  	defer st.Unlock()
   465  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   466  	c.Check(err, check.ErrorMatches, "bzzt")
   467  }
   468  
   469  func (snapshotSuite) TestSaveChecksSetIDError(c *check.C) {
   470  	st := state.New(nil)
   471  	st.Lock()
   472  	defer st.Unlock()
   473  
   474  	st.Set("last-snapshot-set-id", "3/4")
   475  
   476  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   477  	c.Check(err, check.ErrorMatches, ".* could not unmarshal .*")
   478  }
   479  
   480  func (snapshotSuite) TestSaveNoSnapsInState(c *check.C) {
   481  	st := state.New(nil)
   482  	st.Lock()
   483  	defer st.Unlock()
   484  
   485  	setID, saved, taskset, err := snapshotstate.Save(st, nil, nil)
   486  	c.Assert(err, check.IsNil)
   487  	c.Check(setID, check.Equals, uint64(1))
   488  	c.Check(saved, check.HasLen, 0)
   489  	c.Check(taskset.Tasks(), check.HasLen, 0)
   490  }
   491  
   492  func (snapshotSuite) TestSaveSomeSnaps(c *check.C) {
   493  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   494  		return map[string]*snapstate.SnapState{
   495  			"a-snap": {Active: true},
   496  			"b-snap": {},
   497  			"c-snap": {Active: true},
   498  		}, nil
   499  	}
   500  
   501  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   502  
   503  	st := state.New(nil)
   504  	st.Lock()
   505  	defer st.Unlock()
   506  
   507  	setID, saved, taskset, err := snapshotstate.Save(st, nil, nil)
   508  	c.Assert(err, check.IsNil)
   509  	c.Check(setID, check.Equals, uint64(1))
   510  	c.Check(saved, check.DeepEquals, []string{"a-snap", "c-snap"})
   511  	tasks := taskset.Tasks()
   512  	c.Assert(tasks, check.HasLen, 2)
   513  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
   514  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`)
   515  	c.Check(tasks[1].Kind(), check.Equals, "save-snapshot")
   516  	c.Check(tasks[1].Summary(), check.Equals, `Save data of snap "c-snap" in snapshot set #1`)
   517  }
   518  
   519  func (snapshotSuite) TestSaveOneSnap(c *check.C) {
   520  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   521  		// snapstate.All isn't called when a snap name is passed in
   522  		return nil, errors.New("bzzt")
   523  	}
   524  
   525  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   526  
   527  	st := state.New(nil)
   528  	st.Lock()
   529  	defer st.Unlock()
   530  
   531  	setID, saved, taskset, err := snapshotstate.Save(st, []string{"a-snap"}, []string{"a-user"})
   532  	c.Assert(err, check.IsNil)
   533  	c.Check(setID, check.Equals, uint64(1))
   534  	c.Check(saved, check.DeepEquals, []string{"a-snap"})
   535  	tasks := taskset.Tasks()
   536  	c.Assert(tasks, check.HasLen, 1)
   537  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
   538  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`)
   539  	var snapshot map[string]interface{}
   540  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
   541  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
   542  		"set-id":  1.,
   543  		"snap":    "a-snap",
   544  		"users":   []interface{}{"a-user"},
   545  		"current": "unset",
   546  	})
   547  }
   548  
   549  func (snapshotSuite) TestSaveIntegration(c *check.C) {
   550  	if os.Geteuid() == 0 {
   551  		c.Skip("this test cannot run as root (runuser will fail)")
   552  	}
   553  
   554  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
   555  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
   556  
   557  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
   558  		if username != "a-user" {
   559  			c.Fatalf("unexpected user %q", username)
   560  		}
   561  		return &user.User{
   562  			Uid:      fmt.Sprint(sys.Geteuid()),
   563  			Username: username,
   564  			HomeDir:  homedir,
   565  		}, nil
   566  	})()
   567  
   568  	o := overlord.Mock()
   569  	st := o.State()
   570  
   571  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   572  	c.Assert(err, check.IsNil)
   573  	o.AddManager(stmgr)
   574  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   575  	o.AddManager(shmgr)
   576  	o.AddManager(o.TaskRunner())
   577  
   578  	st.Lock()
   579  	defer st.Unlock()
   580  
   581  	snapshots := make(map[string]*client.Snapshot, 3)
   582  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
   583  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
   584  		snapstate.Set(st, name, &snapstate.SnapState{
   585  			Active:   true,
   586  			Sequence: []*snap.SideInfo{sideInfo},
   587  			Current:  sideInfo.Revision,
   588  			SnapType: "app",
   589  		})
   590  		snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
   591  
   592  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
   593  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil)
   594  
   595  		snapshots[name] = &client.Snapshot{
   596  			SetID:    1,
   597  			Snap:     name,
   598  			Version:  "v1",
   599  			Revision: sideInfo.Revision,
   600  			Epoch:    snap.E("0"),
   601  		}
   602  	}
   603  
   604  	setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"})
   605  	c.Assert(err, check.IsNil)
   606  	c.Check(setID, check.Equals, uint64(1))
   607  	c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
   608  
   609  	change := st.NewChange("save-snapshot", "...")
   610  	change.AddAll(taskset)
   611  
   612  	t0 := time.Now()
   613  
   614  	st.Unlock()
   615  	c.Assert(o.Settle(5*time.Second), check.IsNil)
   616  	st.Lock()
   617  	c.Check(change.Err(), check.IsNil)
   618  
   619  	tf := time.Now()
   620  	c.Assert(backend.Iter(context.TODO(), func(r *backend.Reader) error {
   621  		c.Check(r.Check(context.TODO(), nil), check.IsNil)
   622  
   623  		// check the unknowables, and zero them out
   624  		c.Check(r.Snapshot.Time.After(t0), check.Equals, true)
   625  		c.Check(r.Snapshot.Time.Before(tf), check.Equals, true)
   626  		c.Check(r.Snapshot.Size > 0, check.Equals, true)
   627  		c.Assert(r.Snapshot.SHA3_384, check.HasLen, 1)
   628  		c.Check(r.Snapshot.SHA3_384["user/a-user.tgz"], check.HasLen, 96)
   629  
   630  		r.Snapshot.Time = time.Time{}
   631  		r.Snapshot.Size = 0
   632  		r.Snapshot.SHA3_384 = nil
   633  
   634  		c.Check(&r.Snapshot, check.DeepEquals, snapshots[r.Snapshot.Snap])
   635  		return nil
   636  	}), check.IsNil)
   637  }
   638  
   639  func (snapshotSuite) TestSaveIntegrationFails(c *check.C) {
   640  	if os.Geteuid() == 0 {
   641  		c.Skip("this test cannot run as root (runuser will fail)")
   642  	}
   643  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
   644  	// sanity check: no files in snapshot dir
   645  	out, err := exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   646  	c.Assert(err, check.IsNil)
   647  	c.Check(string(out), check.Equals, "")
   648  
   649  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
   650  
   651  	// Mock "tar" so that the tars finish in the expected order.
   652  	// Locally .01s and .02s do the trick with count=1000;
   653  	// padded a lot bigger for slower systems.
   654  	mocktar := testutil.MockCommand(c, "tar", `
   655  case "$*" in
   656  */too-snap/*)
   657      sleep .5
   658      ;;
   659  */tri-snap/*)
   660      sleep 1
   661      ;;
   662  esac
   663  export LANG=C
   664  exec /bin/tar "$@"
   665  `)
   666  	defer mocktar.Restore()
   667  
   668  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
   669  		if username != "a-user" {
   670  			c.Fatalf("unexpected user %q", username)
   671  		}
   672  		return &user.User{
   673  			Uid:      fmt.Sprint(sys.Geteuid()),
   674  			Username: username,
   675  			HomeDir:  homedir,
   676  		}, nil
   677  	})()
   678  
   679  	o := overlord.Mock()
   680  	st := o.State()
   681  
   682  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   683  	c.Assert(err, check.IsNil)
   684  	o.AddManager(stmgr)
   685  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   686  	o.AddManager(shmgr)
   687  	o.AddManager(o.TaskRunner())
   688  
   689  	st.Lock()
   690  	defer st.Unlock()
   691  
   692  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
   693  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
   694  		snapstate.Set(st, name, &snapstate.SnapState{
   695  			Active:   true,
   696  			Sequence: []*snap.SideInfo{sideInfo},
   697  			Current:  sideInfo.Revision,
   698  			SnapType: "app",
   699  		})
   700  		snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
   701  
   702  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
   703  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common"), 0755), check.IsNil)
   704  		mode := os.FileMode(0755)
   705  		if i == 1 {
   706  			mode = 0
   707  		}
   708  		c.Assert(os.Mkdir(filepath.Join(homedir, "snap", name, "common", "common-"+name), mode), check.IsNil)
   709  	}
   710  
   711  	setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"})
   712  	c.Assert(err, check.IsNil)
   713  	c.Check(setID, check.Equals, uint64(1))
   714  	c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
   715  
   716  	change := st.NewChange("save-snapshot", "...")
   717  	change.AddAll(taskset)
   718  
   719  	st.Unlock()
   720  	c.Assert(o.Settle(5*time.Second), check.IsNil)
   721  	st.Lock()
   722  	c.Check(change.Err(), check.NotNil)
   723  	tasks := change.Tasks()
   724  	c.Assert(tasks, check.HasLen, 3)
   725  
   726  	// task 0 (for "one-snap") will have been undone
   727  	c.Check(tasks[0].Summary(), testutil.Contains, `"one-snap"`) // sanity check: task 0 is one-snap's
   728  	c.Check(tasks[0].Status(), check.Equals, state.UndoneStatus)
   729  
   730  	// task 1 (for "too-snap") will have errored
   731  	c.Check(tasks[1].Summary(), testutil.Contains, `"too-snap"`) // sanity check: task 1 is too-snap's
   732  	c.Check(tasks[1].Status(), check.Equals, state.ErrorStatus)
   733  	c.Check(strings.Join(tasks[1].Log(), "\n"), check.Matches, `(?ms)\S+ ERROR cannot create archive:
   734  /bin/tar: common/common-too-snap: .* Permission denied
   735  /bin/tar: Exiting with failure status due to previous errors`)
   736  
   737  	// task 2 (for "tri-snap") will have errored as well, hopefully, but it's a race (see the "tar" comment above)
   738  	c.Check(tasks[2].Summary(), testutil.Contains, `"tri-snap"`) // sanity check: task 2 is tri-snap's
   739  	c.Check(tasks[2].Status(), check.Equals, state.ErrorStatus, check.Commentf("if this ever fails, duplicate the fake tar sleeps please"))
   740  	// sometimes you'll get one, sometimes you'll get the other (depending on ordering of events)
   741  	c.Check(strings.Join(tasks[2].Log(), "\n"), check.Matches, `\S+ ERROR( tar failed:)? context canceled`)
   742  
   743  	// no zips left behind, not for errors, not for undos \o/
   744  	out, err = exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   745  	c.Assert(err, check.IsNil)
   746  	c.Check(string(out), check.Equals, "")
   747  }
   748  
   749  func (snapshotSuite) testSaveIntegrationTarFails(c *check.C, tarLogLines int, expectedErr string) {
   750  	if os.Geteuid() == 0 {
   751  		c.Skip("this test cannot run as root (runuser will fail)")
   752  	}
   753  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
   754  	// sanity check: no files in snapshot dir
   755  	out, err := exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   756  	c.Assert(err, check.IsNil)
   757  	c.Check(string(out), check.Equals, "")
   758  
   759  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
   760  	// mock tar so that it outputs a desired number of lines
   761  	tarFailScript := fmt.Sprintf(`
   762  export LANG=C
   763  for c in $(seq %d); do echo "log line $c" >&2 ; done
   764  exec /bin/tar "$@"
   765  `, tarLogLines)
   766  	if tarLogLines == 1 {
   767  		tarFailScript = "echo nope >&2 ; exit 1"
   768  	} else if tarLogLines == 0 {
   769  		tarFailScript = "exit 1"
   770  	}
   771  	mocktar := testutil.MockCommand(c, "tar", tarFailScript)
   772  	defer mocktar.Restore()
   773  
   774  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
   775  		if username != "a-user" {
   776  			c.Fatalf("unexpected user %q", username)
   777  		}
   778  		return &user.User{
   779  			Uid:      fmt.Sprint(sys.Geteuid()),
   780  			Username: username,
   781  			HomeDir:  homedir,
   782  		}, nil
   783  	})()
   784  
   785  	o := overlord.Mock()
   786  	st := o.State()
   787  
   788  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   789  	c.Assert(err, check.IsNil)
   790  	o.AddManager(stmgr)
   791  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   792  	o.AddManager(shmgr)
   793  	o.AddManager(o.TaskRunner())
   794  
   795  	st.Lock()
   796  	defer st.Unlock()
   797  
   798  	sideInfo := &snap.SideInfo{RealName: "tar-fail-snap", Revision: snap.R(1)}
   799  	snapstate.Set(st, "tar-fail-snap", &snapstate.SnapState{
   800  		Active:   true,
   801  		Sequence: []*snap.SideInfo{sideInfo},
   802  		Current:  sideInfo.Revision,
   803  		SnapType: "app",
   804  	})
   805  	snaptest.MockSnap(c, "name: tar-fail-snap\nversion: v1", sideInfo)
   806  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap/tar-fail-snap/1/canary-tar-fail-snap"), 0755), check.IsNil)
   807  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap/tar-fail-snap/common"), 0755), check.IsNil)
   808  	// these dir permissions (000) make tar unhappy
   809  	c.Assert(os.Mkdir(filepath.Join(homedir, "snap/tar-fail-snap/common/common-tar-fail-snap"), 00), check.IsNil)
   810  
   811  	setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"})
   812  	c.Assert(err, check.IsNil)
   813  	c.Check(setID, check.Equals, uint64(1))
   814  	c.Check(saved, check.DeepEquals, []string{"tar-fail-snap"})
   815  
   816  	change := st.NewChange("save-snapshot", "...")
   817  	change.AddAll(taskset)
   818  
   819  	st.Unlock()
   820  	c.Assert(o.Settle(testutil.HostScaledTimeout(5*time.Second)), check.IsNil)
   821  	st.Lock()
   822  	c.Check(change.Err(), check.NotNil)
   823  	tasks := change.Tasks()
   824  	c.Assert(tasks, check.HasLen, 1)
   825  
   826  	// task 1 (for "too-snap") will have errored
   827  	c.Check(tasks[0].Summary(), testutil.Contains, `"tar-fail-snap"`) // sanity check: task 1 is too-snap's
   828  	c.Check(tasks[0].Status(), check.Equals, state.ErrorStatus)
   829  	c.Check(strings.Join(tasks[0].Log(), "\n"), check.Matches, expectedErr)
   830  
   831  	// no zips left behind, not for errors, not for undos \o/
   832  	out, err = exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   833  	c.Assert(err, check.IsNil)
   834  	c.Check(string(out), check.Equals, "")
   835  }
   836  
   837  func (s *snapshotSuite) TestSaveIntegrationTarFailsManyLines(c *check.C) {
   838  	// cutoff at 5 lines, 3 lines of log + 2 lines from tar
   839  	s.testSaveIntegrationTarFails(c, 3, `(?ms)\S+ ERROR cannot create archive:
   840  log line 1
   841  log line 2
   842  log line 3
   843  /bin/tar: common/common-tar-fail-snap: .* Permission denied
   844  /bin/tar: Exiting with failure status due to previous errors`)
   845  }
   846  
   847  func (s *snapshotSuite) TestSaveIntegrationTarFailsTrimmedLines(c *check.C) {
   848  	s.testSaveIntegrationTarFails(c, 10, `(?ms)\S+ ERROR cannot create archive \(showing last 5 lines out of 12\):
   849  log line 8
   850  log line 9
   851  log line 10
   852  /bin/tar: common/common-tar-fail-snap: .* Permission denied
   853  /bin/tar: Exiting with failure status due to previous errors`)
   854  }
   855  
   856  func (s *snapshotSuite) TestSaveIntegrationTarFailsSingleLine(c *check.C) {
   857  	s.testSaveIntegrationTarFails(c, 1, `(?ms)\S+ ERROR cannot create archive:
   858  nope`)
   859  }
   860  
   861  func (s *snapshotSuite) TestSaveIntegrationTarFailsNoLines(c *check.C) {
   862  	s.testSaveIntegrationTarFails(c, 0, `(?ms)\S+ ERROR tar failed: exit status 1`)
   863  }
   864  
   865  func (snapshotSuite) TestRestoreChecksIterError(c *check.C) {
   866  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
   867  		return errors.New("bzzt")
   868  	})()
   869  
   870  	st := state.New(nil)
   871  	st.Lock()
   872  	defer st.Unlock()
   873  
   874  	_, _, err := snapshotstate.Restore(st, 42, nil, nil)
   875  	c.Assert(err, check.ErrorMatches, "bzzt")
   876  }
   877  
   878  func (s snapshotSuite) TestRestoreChecksSnapstateConflicts(c *check.C) {
   879  	st, restore := s.createConflictingChange(c)
   880  	defer restore()
   881  
   882  	_, _, err := snapshotstate.Restore(st, 42, nil, nil)
   883  	c.Assert(err, check.NotNil)
   884  	c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{})
   885  
   886  }
   887  
   888  func (snapshotSuite) TestRestoreConflictsWithSnapstate(c *check.C) {
   889  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   890  	c.Assert(err, check.IsNil)
   891  	defer shotfile.Close()
   892  
   893  	sideInfo := &snap.SideInfo{RealName: "foo", Revision: snap.R(1)}
   894  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   895  		return map[string]*snapstate.SnapState{
   896  			"foo": {
   897  				Active:   true,
   898  				Sequence: []*snap.SideInfo{sideInfo},
   899  				Current:  sideInfo.Revision,
   900  			},
   901  		}, nil
   902  	}
   903  	snaptest.MockSnap(c, "{name: foo, version: v1}", sideInfo)
   904  
   905  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   906  
   907  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   908  		c.Assert(f(&backend.Reader{
   909  			Snapshot: client.Snapshot{SetID: 42, Snap: "foo"},
   910  			File:     shotfile,
   911  		}), check.IsNil)
   912  
   913  		return nil
   914  	}
   915  	defer snapshotstate.MockBackendIter(fakeIter)()
   916  
   917  	o := overlord.Mock()
   918  	st := o.State()
   919  
   920  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   921  	c.Assert(err, check.IsNil)
   922  	o.AddManager(stmgr)
   923  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   924  	o.AddManager(shmgr)
   925  
   926  	st.Lock()
   927  	defer st.Unlock()
   928  
   929  	snapstate.Set(st, "foo", &snapstate.SnapState{
   930  		Active: true,
   931  		Sequence: []*snap.SideInfo{
   932  			{RealName: "foo", Revision: snap.R(1)},
   933  		},
   934  		Current:  snap.R(1),
   935  		SnapType: "app",
   936  	})
   937  
   938  	chg := st.NewChange("snapshot-restore", "...")
   939  	_, restoreTasks, err := snapshotstate.Restore(st, 42, nil, nil)
   940  	c.Assert(err, check.IsNil)
   941  	chg.AddAll(restoreTasks)
   942  
   943  	_, err = snapstate.Disable(st, "foo")
   944  	c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-restore" change in progress`)
   945  }
   946  
   947  func (snapshotSuite) TestRestoreChecksForgetConflicts(c *check.C) {
   948  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   949  	c.Assert(err, check.IsNil)
   950  	defer shotfile.Close()
   951  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   952  		c.Assert(f(&backend.Reader{
   953  			// not wanted
   954  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
   955  			File:     shotfile,
   956  		}), check.IsNil)
   957  
   958  		return nil
   959  	}
   960  	defer snapshotstate.MockBackendIter(fakeIter)()
   961  
   962  	st := state.New(nil)
   963  	st.Lock()
   964  	defer st.Unlock()
   965  	chg := st.NewChange("forget-snapshot-change", "...")
   966  	tsk := st.NewTask("forget-snapshot", "...")
   967  	tsk.SetStatus(state.DoingStatus)
   968  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
   969  	chg.AddTask(tsk)
   970  
   971  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
   972  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
   973  }
   974  
   975  func (snapshotSuite) TestRestoreChecksChangesToSnapID(c *check.C) {
   976  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   977  	c.Assert(err, check.IsNil)
   978  	defer shotfile.Close()
   979  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   980  		return map[string]*snapstate.SnapState{
   981  			"a-snap": {
   982  				Active: true,
   983  				Sequence: []*snap.SideInfo{
   984  					{RealName: "a-snap", Revision: snap.R(1), SnapID: "1234567890"},
   985  				},
   986  				Current: snap.R(1),
   987  			},
   988  		}, nil
   989  	}
   990  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   991  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   992  		c.Assert(f(&backend.Reader{
   993  			// not wanted
   994  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap", SnapID: "0987654321"},
   995  			File:     shotfile,
   996  		}), check.IsNil)
   997  
   998  		return nil
   999  	}
  1000  	defer snapshotstate.MockBackendIter(fakeIter)()
  1001  
  1002  	st := state.New(nil)
  1003  	st.Lock()
  1004  	defer st.Unlock()
  1005  
  1006  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
  1007  	c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(ID 1234567…\) does not match snapshot \(ID 0987654…\)`)
  1008  }
  1009  
  1010  func (snapshotSuite) TestRestoreChecksChangesToEpoch(c *check.C) {
  1011  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1012  	c.Assert(err, check.IsNil)
  1013  	defer shotfile.Close()
  1014  
  1015  	sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)}
  1016  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
  1017  		return map[string]*snapstate.SnapState{
  1018  			"a-snap": {
  1019  				Active:   true,
  1020  				Sequence: []*snap.SideInfo{sideInfo},
  1021  				Current:  sideInfo.Revision,
  1022  			},
  1023  		}, nil
  1024  	}
  1025  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
  1026  	snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: 17}", sideInfo)
  1027  
  1028  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1029  		c.Assert(f(&backend.Reader{
  1030  			// not wanted
  1031  			Snapshot: client.Snapshot{
  1032  				SetID: 42,
  1033  				Snap:  "a-snap",
  1034  				Epoch: snap.E("42"),
  1035  			},
  1036  			File: shotfile,
  1037  		}), check.IsNil)
  1038  
  1039  		return nil
  1040  	}
  1041  	defer snapshotstate.MockBackendIter(fakeIter)()
  1042  
  1043  	st := state.New(nil)
  1044  	st.Lock()
  1045  	defer st.Unlock()
  1046  
  1047  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
  1048  	c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(epoch 17\) cannot read snapshot data \(epoch 42\)`)
  1049  }
  1050  
  1051  func (snapshotSuite) TestRestoreWorksWithCompatibleEpoch(c *check.C) {
  1052  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1053  	c.Assert(err, check.IsNil)
  1054  	defer shotfile.Close()
  1055  
  1056  	sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)}
  1057  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
  1058  		return map[string]*snapstate.SnapState{
  1059  			"a-snap": {
  1060  				Active:   true,
  1061  				Sequence: []*snap.SideInfo{sideInfo},
  1062  				Current:  sideInfo.Revision,
  1063  			},
  1064  		}, nil
  1065  	}
  1066  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
  1067  	snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: {read: [17, 42], write: [42]}}", sideInfo)
  1068  
  1069  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1070  		c.Assert(f(&backend.Reader{
  1071  			// not wanted
  1072  			Snapshot: client.Snapshot{
  1073  				SetID: 42,
  1074  				Snap:  "a-snap",
  1075  				Epoch: snap.E("17"),
  1076  			},
  1077  			File: shotfile,
  1078  		}), check.IsNil)
  1079  
  1080  		return nil
  1081  	}
  1082  	defer snapshotstate.MockBackendIter(fakeIter)()
  1083  
  1084  	st := state.New(nil)
  1085  	st.Lock()
  1086  	defer st.Unlock()
  1087  
  1088  	found, taskset, err := snapshotstate.Restore(st, 42, nil, nil)
  1089  	c.Assert(err, check.IsNil)
  1090  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1091  	tasks := taskset.Tasks()
  1092  	c.Assert(tasks, check.HasLen, 1)
  1093  	c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot")
  1094  	c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`)
  1095  	var snapshot map[string]interface{}
  1096  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1097  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1098  		"set-id":   42.,
  1099  		"snap":     "a-snap",
  1100  		"filename": shotfile.Name(),
  1101  		"current":  "1",
  1102  	})
  1103  }
  1104  
  1105  func (snapshotSuite) TestRestore(c *check.C) {
  1106  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1107  	c.Assert(err, check.IsNil)
  1108  	defer shotfile.Close()
  1109  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1110  		c.Assert(f(&backend.Reader{
  1111  			// not wanted
  1112  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1113  			File:     shotfile,
  1114  		}), check.IsNil)
  1115  
  1116  		return nil
  1117  	}
  1118  	defer snapshotstate.MockBackendIter(fakeIter)()
  1119  
  1120  	st := state.New(nil)
  1121  	st.Lock()
  1122  	defer st.Unlock()
  1123  
  1124  	found, taskset, err := snapshotstate.Restore(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"})
  1125  	c.Assert(err, check.IsNil)
  1126  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1127  	tasks := taskset.Tasks()
  1128  	c.Assert(tasks, check.HasLen, 1)
  1129  	c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot")
  1130  	c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`)
  1131  	var snapshot map[string]interface{}
  1132  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1133  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1134  		"set-id":   42.,
  1135  		"snap":     "a-snap",
  1136  		"filename": shotfile.Name(),
  1137  		"users":    []interface{}{"a-user"},
  1138  		"current":  "unset",
  1139  	})
  1140  }
  1141  
  1142  func (snapshotSuite) TestRestoreIntegration(c *check.C) {
  1143  	if os.Geteuid() == 0 {
  1144  		c.Skip("this test cannot run as root (runuser will fail)")
  1145  	}
  1146  
  1147  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
  1148  	homedirA := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
  1149  	homedirB := filepath.Join(dirs.GlobalRootDir, "home", "b-user")
  1150  
  1151  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
  1152  		if username != "a-user" && username != "b-user" {
  1153  			c.Fatalf("unexpected user %q", username)
  1154  			return nil, user.UnknownUserError(username)
  1155  		}
  1156  		return &user.User{
  1157  			Uid:      fmt.Sprint(sys.Geteuid()),
  1158  			Username: username,
  1159  			HomeDir:  filepath.Join(dirs.GlobalRootDir, "home", username),
  1160  		}, nil
  1161  
  1162  	})()
  1163  
  1164  	o := overlord.Mock()
  1165  	st := o.State()
  1166  
  1167  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
  1168  	c.Assert(err, check.IsNil)
  1169  	o.AddManager(stmgr)
  1170  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
  1171  	o.AddManager(shmgr)
  1172  	o.AddManager(o.TaskRunner())
  1173  
  1174  	st.Lock()
  1175  
  1176  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
  1177  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
  1178  		snapstate.Set(st, name, &snapstate.SnapState{
  1179  			Active:   true,
  1180  			Sequence: []*snap.SideInfo{sideInfo},
  1181  			Current:  sideInfo.Revision,
  1182  			SnapType: "app",
  1183  		})
  1184  		snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
  1185  
  1186  		for _, home := range []string{homedirA, homedirB} {
  1187  			c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
  1188  			c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, "common", "common-"+name), 0755), check.IsNil)
  1189  		}
  1190  
  1191  		_, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user", "b-user"})
  1192  		c.Assert(err, check.IsNil)
  1193  	}
  1194  
  1195  	// move the old away
  1196  	c.Assert(os.Rename(filepath.Join(homedirA, "snap"), filepath.Join(homedirA, "snap.old")), check.IsNil)
  1197  	// remove b-user's home
  1198  	c.Assert(os.RemoveAll(homedirB), check.IsNil)
  1199  
  1200  	found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user", "b-user"})
  1201  	c.Assert(err, check.IsNil)
  1202  	sort.Strings(found)
  1203  	c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
  1204  
  1205  	change := st.NewChange("restore-snapshot", "...")
  1206  	change.AddAll(taskset)
  1207  
  1208  	st.Unlock()
  1209  	c.Assert(o.Settle(5*time.Second), check.IsNil)
  1210  	st.Lock()
  1211  	c.Check(change.Err(), check.IsNil)
  1212  	defer st.Unlock()
  1213  
  1214  	// the three restores warn about the missing home (but no errors, no panics)
  1215  	for _, task := range change.Tasks() {
  1216  		c.Check(strings.Join(task.Log(), "\n"), check.Matches, `.* Skipping restore of "[^"]+/home/b-user/[^"]+" as "[^"]+/home/b-user" doesn't exist.`)
  1217  	}
  1218  
  1219  	// check it was all brought back \o/
  1220  	out, err := exec.Command("diff", "-rN", filepath.Join(homedirA, "snap"), filepath.Join("snap.old")).CombinedOutput()
  1221  	c.Assert(err, check.IsNil)
  1222  	c.Check(string(out), check.Equals, "")
  1223  }
  1224  
  1225  func (snapshotSuite) TestRestoreIntegrationFails(c *check.C) {
  1226  	if os.Geteuid() == 0 {
  1227  		c.Skip("this test cannot run as root (runuser will fail)")
  1228  	}
  1229  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
  1230  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
  1231  
  1232  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
  1233  		if username != "a-user" {
  1234  			c.Fatalf("unexpected user %q", username)
  1235  		}
  1236  		return &user.User{
  1237  			Uid:      fmt.Sprint(sys.Geteuid()),
  1238  			Username: username,
  1239  			HomeDir:  homedir,
  1240  		}, nil
  1241  	})()
  1242  
  1243  	o := overlord.Mock()
  1244  	st := o.State()
  1245  
  1246  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
  1247  	c.Assert(err, check.IsNil)
  1248  	o.AddManager(stmgr)
  1249  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
  1250  	o.AddManager(shmgr)
  1251  	o.AddManager(o.TaskRunner())
  1252  
  1253  	st.Lock()
  1254  
  1255  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
  1256  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
  1257  		snapstate.Set(st, name, &snapstate.SnapState{
  1258  			Active:   true,
  1259  			Sequence: []*snap.SideInfo{sideInfo},
  1260  			Current:  sideInfo.Revision,
  1261  			SnapType: "app",
  1262  		})
  1263  		snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: vv1}", name), sideInfo)
  1264  
  1265  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
  1266  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil)
  1267  
  1268  		_, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user"})
  1269  		c.Assert(err, check.IsNil)
  1270  	}
  1271  
  1272  	// move the old away
  1273  	c.Assert(os.Rename(filepath.Join(homedir, "snap"), filepath.Join(homedir, "snap.old")), check.IsNil)
  1274  	// but poison the well
  1275  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap"), 0755), check.IsNil)
  1276  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", "too-snap"), 0), check.IsNil)
  1277  
  1278  	found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user"})
  1279  	c.Assert(err, check.IsNil)
  1280  	sort.Strings(found)
  1281  	c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
  1282  
  1283  	change := st.NewChange("restore-snapshot", "...")
  1284  	change.AddAll(taskset)
  1285  
  1286  	st.Unlock()
  1287  	c.Assert(o.Settle(5*time.Second), check.IsNil)
  1288  	st.Lock()
  1289  	c.Check(change.Err(), check.NotNil)
  1290  	defer st.Unlock()
  1291  
  1292  	tasks := change.Tasks()
  1293  	c.Check(tasks, check.HasLen, 3)
  1294  	for _, task := range tasks {
  1295  		if strings.Contains(task.Summary(), `"too-snap"`) {
  1296  			// too-snap was set up to fail, should always fail with
  1297  			// 'permission denied' (see the mkdirall w/mode 0 above)
  1298  			c.Check(task.Status(), check.Equals, state.ErrorStatus)
  1299  			c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR mkdir \S+: permission denied`)
  1300  		} else {
  1301  			// the other two might fail (ErrorStatus) if they're
  1302  			// still running when too-snap fails, or they might have
  1303  			// finished and needed to be undone (UndoneStatus); it's
  1304  			// a race, but either is fine.
  1305  			if task.Status() == state.ErrorStatus {
  1306  				c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR.* context canceled`)
  1307  			} else {
  1308  				c.Check(task.Status(), check.Equals, state.UndoneStatus)
  1309  			}
  1310  		}
  1311  	}
  1312  
  1313  	// remove the poison
  1314  	c.Assert(os.Remove(filepath.Join(homedir, "snap", "too-snap")), check.IsNil)
  1315  
  1316  	// check that nothing else was put there
  1317  	out, err := exec.Command("find", filepath.Join(homedir, "snap")).CombinedOutput()
  1318  	c.Assert(err, check.IsNil)
  1319  	c.Check(strings.TrimSpace(string(out)), check.Equals, filepath.Join(homedir, "snap"))
  1320  }
  1321  
  1322  func (snapshotSuite) TestCheckChecksIterError(c *check.C) {
  1323  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
  1324  		return errors.New("bzzt")
  1325  	})()
  1326  
  1327  	st := state.New(nil)
  1328  	st.Lock()
  1329  	defer st.Unlock()
  1330  
  1331  	_, _, err := snapshotstate.Check(st, 42, nil, nil)
  1332  	c.Assert(err, check.ErrorMatches, "bzzt")
  1333  }
  1334  
  1335  func (s snapshotSuite) TestCheckDoesNotTriggerSnapstateConflict(c *check.C) {
  1336  	st, restore := s.createConflictingChange(c)
  1337  	defer restore()
  1338  
  1339  	_, _, err := snapshotstate.Check(st, 42, nil, nil)
  1340  	c.Assert(err, check.IsNil)
  1341  }
  1342  
  1343  func (snapshotSuite) TestCheckChecksForgetConflicts(c *check.C) {
  1344  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1345  	c.Assert(err, check.IsNil)
  1346  	defer shotfile.Close()
  1347  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1348  		c.Assert(f(&backend.Reader{
  1349  			// not wanted
  1350  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1351  			File:     shotfile,
  1352  		}), check.IsNil)
  1353  
  1354  		return nil
  1355  	}
  1356  	defer snapshotstate.MockBackendIter(fakeIter)()
  1357  
  1358  	st := state.New(nil)
  1359  	st.Lock()
  1360  	defer st.Unlock()
  1361  	chg := st.NewChange("forget-snapshot-change", "...")
  1362  	tsk := st.NewTask("forget-snapshot", "...")
  1363  	tsk.SetStatus(state.DoingStatus)
  1364  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1365  	chg.AddTask(tsk)
  1366  
  1367  	_, _, err = snapshotstate.Check(st, 42, nil, nil)
  1368  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1369  }
  1370  
  1371  func (snapshotSuite) TestCheck(c *check.C) {
  1372  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1373  	c.Assert(err, check.IsNil)
  1374  	defer shotfile.Close()
  1375  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1376  		c.Assert(f(&backend.Reader{
  1377  			// not wanted
  1378  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1379  			File:     shotfile,
  1380  		}), check.IsNil)
  1381  
  1382  		return nil
  1383  	}
  1384  	defer snapshotstate.MockBackendIter(fakeIter)()
  1385  
  1386  	st := state.New(nil)
  1387  	st.Lock()
  1388  	defer st.Unlock()
  1389  
  1390  	found, taskset, err := snapshotstate.Check(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"})
  1391  	c.Assert(err, check.IsNil)
  1392  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1393  	tasks := taskset.Tasks()
  1394  	c.Assert(tasks, check.HasLen, 1)
  1395  	c.Check(tasks[0].Kind(), check.Equals, "check-snapshot")
  1396  	c.Check(tasks[0].Summary(), check.Equals, `Check data of snap "a-snap" in snapshot set #42`)
  1397  	var snapshot map[string]interface{}
  1398  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1399  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1400  		"set-id":   42.,
  1401  		"snap":     "a-snap",
  1402  		"filename": shotfile.Name(),
  1403  		"users":    []interface{}{"a-user"},
  1404  		"current":  "unset",
  1405  	})
  1406  }
  1407  
  1408  func (snapshotSuite) TestForgetChecksIterError(c *check.C) {
  1409  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
  1410  		return errors.New("bzzt")
  1411  	})()
  1412  
  1413  	st := state.New(nil)
  1414  	st.Lock()
  1415  	defer st.Unlock()
  1416  
  1417  	_, _, err := snapshotstate.Forget(st, 42, nil)
  1418  	c.Assert(err, check.ErrorMatches, "bzzt")
  1419  }
  1420  
  1421  func (s snapshotSuite) TestForgetDoesNotTriggerSnapstateConflict(c *check.C) {
  1422  	st, restore := s.createConflictingChange(c)
  1423  	defer restore()
  1424  
  1425  	_, _, err := snapshotstate.Forget(st, 42, nil)
  1426  	c.Assert(err, check.IsNil)
  1427  }
  1428  
  1429  func (snapshotSuite) TestForgetChecksCheckConflicts(c *check.C) {
  1430  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1431  	c.Assert(err, check.IsNil)
  1432  	defer shotfile.Close()
  1433  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1434  		c.Assert(f(&backend.Reader{
  1435  			// not wanted
  1436  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1437  			File:     shotfile,
  1438  		}), check.IsNil)
  1439  
  1440  		return nil
  1441  	}
  1442  	defer snapshotstate.MockBackendIter(fakeIter)()
  1443  
  1444  	st := state.New(nil)
  1445  	st.Lock()
  1446  	defer st.Unlock()
  1447  	chg := st.NewChange("check-snapshot-change", "...")
  1448  	tsk := st.NewTask("check-snapshot", "...")
  1449  	tsk.SetStatus(state.DoingStatus)
  1450  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1451  	chg.AddTask(tsk)
  1452  
  1453  	_, _, err = snapshotstate.Forget(st, 42, nil)
  1454  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1455  }
  1456  
  1457  func (snapshotSuite) TestForgetChecksExportConflicts(c *check.C) {
  1458  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1459  	c.Assert(err, check.IsNil)
  1460  	defer shotfile.Close()
  1461  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1462  		c.Assert(f(&backend.Reader{
  1463  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1464  			File:     shotfile,
  1465  		}), check.IsNil)
  1466  
  1467  		return nil
  1468  	}
  1469  	defer snapshotstate.MockBackendIter(fakeIter)()
  1470  
  1471  	st := state.New(nil)
  1472  	st.Lock()
  1473  	defer st.Unlock()
  1474  
  1475  	snapshotstate.SetSnapshotOpInProgress(st, 42, "export-snapshot")
  1476  
  1477  	_, _, err = snapshotstate.Forget(st, 42, nil)
  1478  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while operation export-snapshot is in progress`)
  1479  }
  1480  
  1481  func (snapshotSuite) TestForgetChecksRestoreConflicts(c *check.C) {
  1482  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1483  	c.Assert(err, check.IsNil)
  1484  	defer shotfile.Close()
  1485  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1486  		c.Assert(f(&backend.Reader{
  1487  			// not wanted
  1488  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1489  			File:     shotfile,
  1490  		}), check.IsNil)
  1491  
  1492  		return nil
  1493  	}
  1494  	defer snapshotstate.MockBackendIter(fakeIter)()
  1495  
  1496  	st := state.New(nil)
  1497  	st.Lock()
  1498  	defer st.Unlock()
  1499  	chg := st.NewChange("restore-snapshot-change", "...")
  1500  	tsk := st.NewTask("restore-snapshot", "...")
  1501  	tsk.SetStatus(state.DoingStatus)
  1502  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1503  	chg.AddTask(tsk)
  1504  
  1505  	_, _, err = snapshotstate.Forget(st, 42, nil)
  1506  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1507  }
  1508  
  1509  func (snapshotSuite) TestForget(c *check.C) {
  1510  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1511  	c.Assert(err, check.IsNil)
  1512  	defer shotfile.Close()
  1513  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1514  		c.Assert(f(&backend.Reader{
  1515  			// not wanted
  1516  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1517  			File:     shotfile,
  1518  		}), check.IsNil)
  1519  
  1520  		return nil
  1521  	}
  1522  	defer snapshotstate.MockBackendIter(fakeIter)()
  1523  
  1524  	st := state.New(nil)
  1525  	st.Lock()
  1526  	defer st.Unlock()
  1527  
  1528  	found, taskset, err := snapshotstate.Forget(st, 42, []string{"a-snap", "b-snap"})
  1529  	c.Assert(err, check.IsNil)
  1530  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1531  	tasks := taskset.Tasks()
  1532  	c.Assert(tasks, check.HasLen, 1)
  1533  	c.Check(tasks[0].Kind(), check.Equals, "forget-snapshot")
  1534  	c.Check(tasks[0].Summary(), check.Equals, `Drop data of snap "a-snap" from snapshot set #42`)
  1535  	var snapshot map[string]interface{}
  1536  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1537  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1538  		"set-id":   42.,
  1539  		"snap":     "a-snap",
  1540  		"filename": shotfile.Name(),
  1541  		"current":  "unset",
  1542  	})
  1543  }
  1544  
  1545  func (snapshotSuite) TestSaveExpiration(c *check.C) {
  1546  	st := state.New(nil)
  1547  	st.Lock()
  1548  	defer st.Unlock()
  1549  
  1550  	var expirations map[uint64]interface{}
  1551  	tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z")
  1552  	c.Assert(err, check.IsNil)
  1553  	c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil)
  1554  
  1555  	tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z")
  1556  	c.Assert(err, check.IsNil)
  1557  	c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil)
  1558  
  1559  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
  1560  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
  1561  		12: map[string]interface{}{"expiry-time": "2019-03-11T11:24:00Z"},
  1562  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:50:00Z"},
  1563  	})
  1564  }
  1565  
  1566  func (snapshotSuite) TestRemoveSnapshotState(c *check.C) {
  1567  	st := state.New(nil)
  1568  	st.Lock()
  1569  	defer st.Unlock()
  1570  
  1571  	st.Set("snapshots", map[uint64]interface{}{
  1572  		12: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"},
  1573  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1574  		14: map[string]interface{}{"expiry-time": "2019-03-12T13:11:00Z"},
  1575  	})
  1576  
  1577  	snapshotstate.RemoveSnapshotState(st, 12, 14)
  1578  
  1579  	var snapshots map[uint64]interface{}
  1580  	c.Assert(st.Get("snapshots", &snapshots), check.IsNil)
  1581  	c.Check(snapshots, check.DeepEquals, map[uint64]interface{}{
  1582  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1583  	})
  1584  }
  1585  
  1586  func (snapshotSuite) TestExpiredSnapshotSets(c *check.C) {
  1587  	st := state.New(nil)
  1588  	st.Lock()
  1589  	defer st.Unlock()
  1590  
  1591  	tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z")
  1592  	c.Assert(err, check.IsNil)
  1593  	c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil)
  1594  
  1595  	tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z")
  1596  	c.Assert(err, check.IsNil)
  1597  	c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil)
  1598  
  1599  	tm, err = time.Parse(time.RFC3339, "2020-03-11T11:24:00Z")
  1600  	c.Assert(err, check.IsNil)
  1601  	expired, err := snapshotstate.ExpiredSnapshotSets(st, tm)
  1602  	c.Assert(err, check.IsNil)
  1603  	c.Check(expired, check.DeepEquals, map[uint64]bool{12: true, 13: true})
  1604  
  1605  	tm, err = time.Parse(time.RFC3339, "2019-03-01T11:24:00Z")
  1606  	c.Assert(err, check.IsNil)
  1607  	expired, err = snapshotstate.ExpiredSnapshotSets(st, tm)
  1608  	c.Assert(err, check.IsNil)
  1609  	c.Check(expired, check.DeepEquals, map[uint64]bool{13: true})
  1610  }
  1611  
  1612  func (snapshotSuite) TestAutomaticSnapshotDisabled(c *check.C) {
  1613  	st := state.New(nil)
  1614  	st.Lock()
  1615  	defer st.Unlock()
  1616  
  1617  	tr := config.NewTransaction(st)
  1618  	tr.Set("core", "snapshots.automatic.retention", "no")
  1619  	tr.Commit()
  1620  
  1621  	_, err := snapshotstate.AutomaticSnapshot(st, "foo")
  1622  	c.Assert(err, check.Equals, snapstate.ErrNothingToDo)
  1623  }
  1624  
  1625  func (snapshotSuite) TestAutomaticSnapshot(c *check.C) {
  1626  	st := state.New(nil)
  1627  	st.Lock()
  1628  	defer st.Unlock()
  1629  
  1630  	tr := config.NewTransaction(st)
  1631  	tr.Set("core", "snapshots.automatic.retention", "24h")
  1632  	tr.Commit()
  1633  
  1634  	ts, err := snapshotstate.AutomaticSnapshot(st, "foo")
  1635  	c.Assert(err, check.IsNil)
  1636  
  1637  	tasks := ts.Tasks()
  1638  	c.Assert(tasks, check.HasLen, 1)
  1639  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
  1640  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "foo" in automatic snapshot set #1`)
  1641  	var snapshot map[string]interface{}
  1642  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1643  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1644  		"set-id":  1.,
  1645  		"snap":    "foo",
  1646  		"current": "unset",
  1647  		"auto":    true,
  1648  	})
  1649  }
  1650  
  1651  func (snapshotSuite) TestAutomaticSnapshotDefaultClassic(c *check.C) {
  1652  	release.MockOnClassic(true)
  1653  
  1654  	st := state.New(nil)
  1655  	st.Lock()
  1656  	defer st.Unlock()
  1657  
  1658  	du, err := snapshotstate.AutomaticSnapshotExpiration(st)
  1659  	c.Assert(err, check.IsNil)
  1660  	c.Assert(du, check.Equals, snapshotstate.DefaultAutomaticSnapshotExpiration)
  1661  }
  1662  
  1663  func (snapshotSuite) TestAutomaticSnapshotDefaultUbuntuCore(c *check.C) {
  1664  	release.MockOnClassic(false)
  1665  
  1666  	st := state.New(nil)
  1667  	st.Lock()
  1668  	defer st.Unlock()
  1669  
  1670  	du, err := snapshotstate.AutomaticSnapshotExpiration(st)
  1671  	c.Assert(err, check.IsNil)
  1672  	c.Assert(du, check.Equals, time.Duration(0))
  1673  }
  1674  
  1675  func (snapshotSuite) TestListError(c *check.C) {
  1676  	restore := snapshotstate.MockBackendList(func(context.Context, uint64, []string) ([]client.SnapshotSet, error) {
  1677  		return nil, fmt.Errorf("boom")
  1678  	})
  1679  	defer restore()
  1680  
  1681  	st := state.New(nil)
  1682  	st.Lock()
  1683  	defer st.Unlock()
  1684  
  1685  	_, err := snapshotstate.List(context.TODO(), st, 0, nil)
  1686  	c.Assert(err, check.ErrorMatches, "boom")
  1687  }
  1688  
  1689  func (snapshotSuite) TestListSetsAutoFlag(c *check.C) {
  1690  	st := state.New(nil)
  1691  	st.Lock()
  1692  	defer st.Unlock()
  1693  
  1694  	st.Set("snapshots", map[uint64]interface{}{
  1695  		1: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"},
  1696  		2: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1697  	})
  1698  
  1699  	restore := snapshotstate.MockBackendList(func(ctx context.Context, setID uint64, snapNames []string) ([]client.SnapshotSet, error) {
  1700  		// three sets, first two are automatic (implied by expiration times in the state), the third isn't.
  1701  		return []client.SnapshotSet{
  1702  			{
  1703  				ID: 1,
  1704  				Snapshots: []*client.Snapshot{
  1705  					{
  1706  						Snap:  "foo",
  1707  						SetID: 1,
  1708  					},
  1709  					{
  1710  						Snap:  "bar",
  1711  						SetID: 1,
  1712  					},
  1713  				},
  1714  			},
  1715  			{
  1716  				ID: 2,
  1717  				Snapshots: []*client.Snapshot{
  1718  					{
  1719  						Snap:  "baz",
  1720  						SetID: 2,
  1721  					},
  1722  				},
  1723  			},
  1724  			{
  1725  				ID: 3,
  1726  				Snapshots: []*client.Snapshot{
  1727  					{
  1728  						Snap:  "baz",
  1729  						SetID: 3,
  1730  					},
  1731  				},
  1732  			},
  1733  		}, nil
  1734  	})
  1735  	defer restore()
  1736  
  1737  	sets, err := snapshotstate.List(context.TODO(), st, 0, nil)
  1738  	c.Assert(err, check.IsNil)
  1739  	c.Assert(sets, check.HasLen, 3)
  1740  
  1741  	for _, sset := range sets {
  1742  		switch sset.ID {
  1743  		case 1:
  1744  			c.Check(sset.Snapshots, check.HasLen, 2, check.Commentf("set #%d", sset.ID))
  1745  		default:
  1746  			c.Check(sset.Snapshots, check.HasLen, 1, check.Commentf("set #%d", sset.ID))
  1747  		}
  1748  
  1749  		switch sset.ID {
  1750  		case 1, 2:
  1751  			for _, snapshot := range sset.Snapshots {
  1752  				c.Check(snapshot.Auto, check.Equals, true)
  1753  			}
  1754  		default:
  1755  			for _, snapshot := range sset.Snapshots {
  1756  				c.Check(snapshot.Auto, check.Equals, false)
  1757  			}
  1758  		}
  1759  	}
  1760  }
  1761  
  1762  func (snapshotSuite) TestImportSnapshotHappy(c *check.C) {
  1763  	st := state.New(nil)
  1764  
  1765  	fakeSnapNames := []string{"baz", "bar", "foo"}
  1766  	fakeSnapshotData := "fake-import-data"
  1767  
  1768  	buf := bytes.NewBufferString(fakeSnapshotData)
  1769  	restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) {
  1770  		d, err := ioutil.ReadAll(r)
  1771  		c.Assert(err, check.IsNil)
  1772  		c.Check(fakeSnapshotData, check.Equals, string(d))
  1773  		return fakeSnapNames, nil
  1774  	})
  1775  	defer restore()
  1776  
  1777  	sid, names, err := snapshotstate.Import(context.TODO(), st, buf)
  1778  	c.Assert(err, check.IsNil)
  1779  	c.Check(sid, check.Equals, uint64(1))
  1780  	c.Check(names, check.DeepEquals, fakeSnapNames)
  1781  }
  1782  
  1783  func (snapshotSuite) TestImportSnapshotImportError(c *check.C) {
  1784  	st := state.New(nil)
  1785  
  1786  	restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) {
  1787  		return nil, errors.New("some-error")
  1788  	})
  1789  	defer restore()
  1790  
  1791  	r := bytes.NewBufferString("faked-import-data")
  1792  	sid, _, err := snapshotstate.Import(context.TODO(), st, r)
  1793  	c.Assert(err, check.NotNil)
  1794  	c.Assert(err.Error(), check.Equals, "some-error")
  1795  	c.Check(sid, check.Equals, uint64(0))
  1796  }
  1797  
  1798  func (snapshotSuite) TestImportSnapshotDuplicate(c *check.C) {
  1799  	st := state.New(nil)
  1800  
  1801  	restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) {
  1802  		return nil, backend.DuplicatedSnapshotImportError{SetID: 3, SnapNames: []string{"foo-snap"}}
  1803  	})
  1804  	defer restore()
  1805  
  1806  	st.Lock()
  1807  	st.Set("snapshots", map[uint64]interface{}{
  1808  		2: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"},
  1809  		3: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1810  	})
  1811  	st.Unlock()
  1812  
  1813  	sid, snapNames, err := snapshotstate.Import(context.TODO(), st, bytes.NewBufferString(""))
  1814  	c.Assert(err, check.IsNil)
  1815  	c.Check(sid, check.Equals, uint64(3))
  1816  	c.Check(snapNames, check.DeepEquals, []string{"foo-snap"})
  1817  
  1818  	st.Lock()
  1819  	defer st.Unlock()
  1820  	// expiry-time has been removed for snapshot set 3
  1821  	var snapshots map[uint64]interface{}
  1822  	c.Assert(st.Get("snapshots", &snapshots), check.IsNil)
  1823  	c.Check(snapshots, check.DeepEquals, map[uint64]interface{}{
  1824  		2: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"},
  1825  	})
  1826  }
  1827  
  1828  func (snapshotSuite) TestEstimateSnapshotSize(c *check.C) {
  1829  	st := state.New(nil)
  1830  	st.Lock()
  1831  	defer st.Unlock()
  1832  
  1833  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1834  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1835  		Active:   true,
  1836  		Sequence: []*snap.SideInfo{sideInfo},
  1837  		Current:  sideInfo.Revision,
  1838  	})
  1839  
  1840  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1841  		return 123, nil
  1842  	})()
  1843  
  1844  	sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1845  	c.Assert(err, check.IsNil)
  1846  	c.Check(sz, check.Equals, uint64(123))
  1847  }
  1848  
  1849  func (snapshotSuite) TestEstimateSnapshotSizeWithConfig(c *check.C) {
  1850  	st := state.New(nil)
  1851  	st.Lock()
  1852  	defer st.Unlock()
  1853  
  1854  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1855  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1856  		Active:   true,
  1857  		Sequence: []*snap.SideInfo{sideInfo},
  1858  		Current:  sideInfo.Revision,
  1859  	})
  1860  
  1861  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1862  		return 100, nil
  1863  	})()
  1864  
  1865  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
  1866  		c.Check(snapname, check.Equals, "some-snap")
  1867  		buf := json.RawMessage(`{"hello": "there"}`)
  1868  		return &buf, nil
  1869  	})()
  1870  
  1871  	sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1872  	c.Assert(err, check.IsNil)
  1873  	// size is 100 + 18
  1874  	c.Check(sz, check.Equals, uint64(118))
  1875  }
  1876  
  1877  func (snapshotSuite) TestEstimateSnapshotSizeError(c *check.C) {
  1878  	st := state.New(nil)
  1879  	st.Lock()
  1880  	defer st.Unlock()
  1881  
  1882  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1883  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1884  		Active:   true,
  1885  		Sequence: []*snap.SideInfo{sideInfo},
  1886  		Current:  sideInfo.Revision,
  1887  	})
  1888  
  1889  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1890  		return 0, fmt.Errorf("an error")
  1891  	})()
  1892  
  1893  	_, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1894  	c.Assert(err, check.ErrorMatches, `an error`)
  1895  }
  1896  
  1897  func (snapshotSuite) TestEstimateSnapshotSizeWithUsers(c *check.C) {
  1898  	st := state.New(nil)
  1899  	st.Lock()
  1900  	defer st.Unlock()
  1901  
  1902  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1903  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1904  		Active:   true,
  1905  		Sequence: []*snap.SideInfo{sideInfo},
  1906  		Current:  sideInfo.Revision,
  1907  	})
  1908  
  1909  	var gotUsers []string
  1910  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1911  		gotUsers = users
  1912  		return 0, nil
  1913  	})()
  1914  
  1915  	_, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", []string{"user1", "user2"})
  1916  	c.Assert(err, check.IsNil)
  1917  	c.Check(gotUsers, check.DeepEquals, []string{"user1", "user2"})
  1918  }
  1919  
  1920  func (snapshotSuite) TestExportSnapshotConflictsWithForget(c *check.C) {
  1921  	st := state.New(nil)
  1922  	st.Lock()
  1923  	defer st.Unlock()
  1924  
  1925  	chg := st.NewChange("forget-snapshot-change", "...")
  1926  	tsk := st.NewTask("forget-snapshot", "...")
  1927  	tsk.SetStatus(state.DoingStatus)
  1928  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1929  	chg.AddTask(tsk)
  1930  
  1931  	_, err := snapshotstate.Export(context.TODO(), st, 42)
  1932  	c.Assert(err, check.NotNil)
  1933  	c.Assert(err.Error(), check.Equals, `cannot operate on snapshot set #42 while change "1" is in progress`)
  1934  }
  1935  
  1936  func (snapshotSuite) TestImportSnapshotDuplicatedNoConflict(c *check.C) {
  1937  	buf := &bytes.Buffer{}
  1938  	var importCalls int
  1939  	restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) {
  1940  		importCalls++
  1941  		c.Check(id, check.Equals, uint64(1))
  1942  		return nil, backend.DuplicatedSnapshotImportError{SetID: 42, SnapNames: []string{"foo-snap"}}
  1943  	})
  1944  	defer restore()
  1945  
  1946  	st := state.New(nil)
  1947  	setID, snaps, err := snapshotstate.Import(context.TODO(), st, buf)
  1948  	c.Check(importCalls, check.Equals, 1)
  1949  	c.Assert(err, check.IsNil)
  1950  	c.Check(setID, check.Equals, uint64(42))
  1951  	c.Check(snaps, check.DeepEquals, []string{"foo-snap"})
  1952  }
  1953  
  1954  func (snapshotSuite) TestImportSnapshotConflictsWithForget(c *check.C) {
  1955  	buf := &bytes.Buffer{}
  1956  	var importCalls int
  1957  	restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader, flags *backend.ImportFlags) ([]string, error) {
  1958  		importCalls++
  1959  		switch importCalls {
  1960  		case 1:
  1961  			c.Assert(flags, check.IsNil)
  1962  		case 2:
  1963  			c.Assert(flags, check.NotNil)
  1964  			c.Assert(flags.NoDuplicatedImportCheck, check.Equals, true)
  1965  			return []string{"foo"}, nil
  1966  		default:
  1967  			c.Fatal("unexpected number call to Import")
  1968  		}
  1969  		// DuplicatedSnapshotImportError is the only case where we can encounter
  1970  		// conflict on import (trying to reuse existing snapshot).
  1971  		return nil, backend.DuplicatedSnapshotImportError{SetID: 42, SnapNames: []string{"not-relevant-because-of-retry"}}
  1972  	})
  1973  	defer restore()
  1974  
  1975  	st := state.New(nil)
  1976  	st.Lock()
  1977  	defer st.Unlock()
  1978  
  1979  	// conflicting change
  1980  	chg := st.NewChange("forget-snapshot-change", "...")
  1981  	tsk := st.NewTask("forget-snapshot", "...")
  1982  	tsk.SetStatus(state.DoingStatus)
  1983  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1984  	chg.AddTask(tsk)
  1985  
  1986  	st.Unlock()
  1987  	setID, snaps, err := snapshotstate.Import(context.TODO(), st, buf)
  1988  	st.Lock()
  1989  	c.Check(importCalls, check.Equals, 2)
  1990  	c.Assert(err, check.IsNil)
  1991  	c.Check(setID, check.Equals, uint64(1))
  1992  	c.Check(snaps, check.DeepEquals, []string{"foo"})
  1993  }
  1994  
  1995  func (snapshotSuite) TestExportSnapshotSetsOpInProgress(c *check.C) {
  1996  	restore := snapshotstate.MockBackendNewSnapshotExport(func(ctx context.Context, setID uint64) (se *backend.SnapshotExport, err error) {
  1997  		return nil, nil
  1998  	})
  1999  	defer restore()
  2000  
  2001  	st := state.New(nil)
  2002  	st.Lock()
  2003  	defer st.Unlock()
  2004  
  2005  	_, err := snapshotstate.Export(context.TODO(), st, 42)
  2006  	c.Assert(err, check.IsNil)
  2007  
  2008  	ops := st.Cached("snapshot-ops")
  2009  	c.Assert(ops, check.DeepEquals, map[uint64]string{
  2010  		uint64(42): "export-snapshot",
  2011  	})
  2012  }
  2013  
  2014  func (snapshotSuite) TestSetSnapshotOpInProgress(c *check.C) {
  2015  	st := state.New(nil)
  2016  	st.Lock()
  2017  	defer st.Unlock()
  2018  
  2019  	c.Assert(snapshotstate.UnsetSnapshotOpInProgress(st, 9999), check.Equals, "")
  2020  
  2021  	snapshotstate.SetSnapshotOpInProgress(st, 1, "foo-op")
  2022  	snapshotstate.SetSnapshotOpInProgress(st, 2, "bar-op")
  2023  
  2024  	val := st.Cached("snapshot-ops")
  2025  	c.Check(val, check.DeepEquals, map[uint64]string{
  2026  		uint64(1): "foo-op",
  2027  		uint64(2): "bar-op",
  2028  	})
  2029  
  2030  	c.Check(snapshotstate.UnsetSnapshotOpInProgress(st, 1), check.Equals, "foo-op")
  2031  
  2032  	val = st.Cached("snapshot-ops")
  2033  	c.Check(val, check.DeepEquals, map[uint64]string{
  2034  		uint64(2): "bar-op",
  2035  	})
  2036  
  2037  	c.Check(snapshotstate.UnsetSnapshotOpInProgress(st, 2), check.Equals, "bar-op")
  2038  
  2039  	val = st.Cached("snapshot-ops")
  2040  	c.Check(val, check.HasLen, 0)
  2041  }