github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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.CheckSnapshotTaskConflict(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.CheckSnapshotTaskConflict(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.CheckSnapshotTaskConflict(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.CheckSnapshotTaskConflict(st, 42, "some-other-task"), check.IsNil)
   315  
   316  	// no change with that snapshot id
   317  	c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 43, "some-task"), check.IsNil)
   318  
   319  	// no non-ready change
   320  	tsk.SetStatus(state.DoneStatus)
   321  	c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task"), check.IsNil)
   322  }
   323  
   324  func (snapshotSuite) TestSaveChecksSnapnamesError(c *check.C) {
   325  	defer snapshotstate.MockSnapstateAll(func(*state.State) (map[string]*snapstate.SnapState, error) {
   326  		return nil, errors.New("bzzt")
   327  	})()
   328  
   329  	st := state.New(nil)
   330  	st.Lock()
   331  	defer st.Unlock()
   332  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   333  	c.Check(err, check.ErrorMatches, "bzzt")
   334  }
   335  
   336  func (snapshotSuite) createConflictingChange(c *check.C) (st *state.State, restore func()) {
   337  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   338  	c.Assert(err, check.IsNil)
   339  	shotfile.Close()
   340  
   341  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   342  		c.Assert(f(&backend.Reader{
   343  			Snapshot: client.Snapshot{SetID: 42, Snap: "foo"},
   344  			File:     shotfile,
   345  		}), check.IsNil)
   346  
   347  		return nil
   348  	}
   349  	restoreIter := snapshotstate.MockBackendIter(fakeIter)
   350  
   351  	o := overlord.Mock()
   352  	st = o.State()
   353  
   354  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   355  	c.Assert(err, check.IsNil)
   356  	o.AddManager(stmgr)
   357  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   358  	o.AddManager(shmgr)
   359  
   360  	st.Lock()
   361  	defer func() {
   362  		if c.Failed() {
   363  			// something went wrong
   364  			st.Unlock()
   365  		}
   366  	}()
   367  
   368  	snapstate.Set(st, "foo", &snapstate.SnapState{
   369  		Active: true,
   370  		Sequence: []*snap.SideInfo{
   371  			{RealName: "foo", Revision: snap.R(1)},
   372  		},
   373  		Current:  snap.R(1),
   374  		SnapType: "app",
   375  	})
   376  
   377  	r := snapstatetest.UseFallbackDeviceModel()
   378  	defer r()
   379  
   380  	chg := st.NewChange("rm foo", "...")
   381  	rmTasks, err := snapstate.Remove(st, "foo", snap.R(0), nil)
   382  	c.Assert(err, check.IsNil)
   383  	c.Assert(rmTasks, check.NotNil)
   384  	chg.AddAll(rmTasks)
   385  
   386  	return st, func() {
   387  		shotfile.Close()
   388  		st.Unlock()
   389  		restoreIter()
   390  	}
   391  }
   392  
   393  func (s snapshotSuite) TestSaveChecksSnapstateConflict(c *check.C) {
   394  	st, restore := s.createConflictingChange(c)
   395  	defer restore()
   396  
   397  	_, _, _, err := snapshotstate.Save(st, []string{"foo"}, nil)
   398  	c.Assert(err, check.NotNil)
   399  	c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{})
   400  }
   401  
   402  func (snapshotSuite) TestSaveConflictsWithSnapstate(c *check.C) {
   403  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   404  		return map[string]*snapstate.SnapState{
   405  			"foo": {Active: true},
   406  		}, nil
   407  	}
   408  
   409  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   410  
   411  	o := overlord.Mock()
   412  	st := o.State()
   413  
   414  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   415  	c.Assert(err, check.IsNil)
   416  	o.AddManager(stmgr)
   417  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   418  	o.AddManager(shmgr)
   419  
   420  	st.Lock()
   421  	defer st.Unlock()
   422  
   423  	snapstate.Set(st, "foo", &snapstate.SnapState{
   424  		Active: true,
   425  		Sequence: []*snap.SideInfo{
   426  			{RealName: "foo", Revision: snap.R(1)},
   427  		},
   428  		Current:  snap.R(1),
   429  		SnapType: "app",
   430  	})
   431  
   432  	chg := st.NewChange("snapshot-save", "...")
   433  	_, _, saveTasks, err := snapshotstate.Save(st, nil, nil)
   434  	c.Assert(err, check.IsNil)
   435  	chg.AddAll(saveTasks)
   436  
   437  	_, err = snapstate.Disable(st, "foo")
   438  	c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-save" change in progress`)
   439  }
   440  
   441  func (snapshotSuite) TestSaveChecksSnapstateConflictError(c *check.C) {
   442  	defer snapshotstate.MockSnapstateCheckChangeConflictMany(func(*state.State, []string, string) error {
   443  		return errors.New("bzzt")
   444  	})()
   445  
   446  	st := state.New(nil)
   447  	st.Lock()
   448  	defer st.Unlock()
   449  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   450  	c.Check(err, check.ErrorMatches, "bzzt")
   451  }
   452  
   453  func (snapshotSuite) TestSaveChecksSetIDError(c *check.C) {
   454  	st := state.New(nil)
   455  	st.Lock()
   456  	defer st.Unlock()
   457  
   458  	st.Set("last-snapshot-set-id", "3/4")
   459  
   460  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   461  	c.Check(err, check.ErrorMatches, ".* could not unmarshal .*")
   462  }
   463  
   464  func (snapshotSuite) TestSaveNoSnapsInState(c *check.C) {
   465  	st := state.New(nil)
   466  	st.Lock()
   467  	defer st.Unlock()
   468  
   469  	setID, saved, taskset, err := snapshotstate.Save(st, nil, nil)
   470  	c.Assert(err, check.IsNil)
   471  	c.Check(setID, check.Equals, uint64(1))
   472  	c.Check(saved, check.HasLen, 0)
   473  	c.Check(taskset.Tasks(), check.HasLen, 0)
   474  }
   475  
   476  func (snapshotSuite) TestSaveSomeSnaps(c *check.C) {
   477  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   478  		return map[string]*snapstate.SnapState{
   479  			"a-snap": {Active: true},
   480  			"b-snap": {},
   481  			"c-snap": {Active: true},
   482  		}, nil
   483  	}
   484  
   485  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   486  
   487  	st := state.New(nil)
   488  	st.Lock()
   489  	defer st.Unlock()
   490  
   491  	setID, saved, taskset, err := snapshotstate.Save(st, nil, nil)
   492  	c.Assert(err, check.IsNil)
   493  	c.Check(setID, check.Equals, uint64(1))
   494  	c.Check(saved, check.DeepEquals, []string{"a-snap", "c-snap"})
   495  	tasks := taskset.Tasks()
   496  	c.Assert(tasks, check.HasLen, 2)
   497  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
   498  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`)
   499  	c.Check(tasks[1].Kind(), check.Equals, "save-snapshot")
   500  	c.Check(tasks[1].Summary(), check.Equals, `Save data of snap "c-snap" in snapshot set #1`)
   501  }
   502  
   503  func (snapshotSuite) TestSaveOneSnap(c *check.C) {
   504  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   505  		// snapstate.All isn't called when a snap name is passed in
   506  		return nil, errors.New("bzzt")
   507  	}
   508  
   509  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   510  
   511  	st := state.New(nil)
   512  	st.Lock()
   513  	defer st.Unlock()
   514  
   515  	setID, saved, taskset, err := snapshotstate.Save(st, []string{"a-snap"}, []string{"a-user"})
   516  	c.Assert(err, check.IsNil)
   517  	c.Check(setID, check.Equals, uint64(1))
   518  	c.Check(saved, check.DeepEquals, []string{"a-snap"})
   519  	tasks := taskset.Tasks()
   520  	c.Assert(tasks, check.HasLen, 1)
   521  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
   522  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`)
   523  	var snapshot map[string]interface{}
   524  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
   525  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
   526  		"set-id":  1.,
   527  		"snap":    "a-snap",
   528  		"users":   []interface{}{"a-user"},
   529  		"current": "unset",
   530  	})
   531  }
   532  
   533  func (snapshotSuite) TestSaveIntegration(c *check.C) {
   534  	if os.Geteuid() == 0 {
   535  		c.Skip("this test cannot run as root (runuser will fail)")
   536  	}
   537  
   538  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
   539  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
   540  
   541  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
   542  		if username != "a-user" {
   543  			c.Fatalf("unexpected user %q", username)
   544  		}
   545  		return &user.User{
   546  			Uid:      fmt.Sprint(sys.Geteuid()),
   547  			Username: username,
   548  			HomeDir:  homedir,
   549  		}, nil
   550  	})()
   551  
   552  	o := overlord.Mock()
   553  	st := o.State()
   554  
   555  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   556  	c.Assert(err, check.IsNil)
   557  	o.AddManager(stmgr)
   558  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   559  	o.AddManager(shmgr)
   560  	o.AddManager(o.TaskRunner())
   561  
   562  	st.Lock()
   563  	defer st.Unlock()
   564  
   565  	snapshots := make(map[string]*client.Snapshot, 3)
   566  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
   567  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
   568  		snapstate.Set(st, name, &snapstate.SnapState{
   569  			Active:   true,
   570  			Sequence: []*snap.SideInfo{sideInfo},
   571  			Current:  sideInfo.Revision,
   572  			SnapType: "app",
   573  		})
   574  		snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
   575  
   576  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
   577  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil)
   578  
   579  		snapshots[name] = &client.Snapshot{
   580  			SetID:    1,
   581  			Snap:     name,
   582  			Version:  "v1",
   583  			Revision: sideInfo.Revision,
   584  			Epoch:    snap.E("0"),
   585  		}
   586  	}
   587  
   588  	setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"})
   589  	c.Assert(err, check.IsNil)
   590  	c.Check(setID, check.Equals, uint64(1))
   591  	c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
   592  
   593  	change := st.NewChange("save-snapshot", "...")
   594  	change.AddAll(taskset)
   595  
   596  	t0 := time.Now()
   597  
   598  	st.Unlock()
   599  	c.Assert(o.Settle(5*time.Second), check.IsNil)
   600  	st.Lock()
   601  	c.Check(change.Err(), check.IsNil)
   602  
   603  	tf := time.Now()
   604  	c.Assert(backend.Iter(context.TODO(), func(r *backend.Reader) error {
   605  		c.Check(r.Check(context.TODO(), nil), check.IsNil)
   606  
   607  		// check the unknowables, and zero them out
   608  		c.Check(r.Snapshot.Time.After(t0), check.Equals, true)
   609  		c.Check(r.Snapshot.Time.Before(tf), check.Equals, true)
   610  		c.Check(r.Snapshot.Size > 0, check.Equals, true)
   611  		c.Assert(r.Snapshot.SHA3_384, check.HasLen, 1)
   612  		c.Check(r.Snapshot.SHA3_384["user/a-user.tgz"], check.HasLen, 96)
   613  
   614  		r.Snapshot.Time = time.Time{}
   615  		r.Snapshot.Size = 0
   616  		r.Snapshot.SHA3_384 = nil
   617  
   618  		c.Check(&r.Snapshot, check.DeepEquals, snapshots[r.Snapshot.Snap])
   619  		return nil
   620  	}), check.IsNil)
   621  }
   622  
   623  func (snapshotSuite) TestSaveIntegrationFails(c *check.C) {
   624  	if os.Geteuid() == 0 {
   625  		c.Skip("this test cannot run as root (runuser will fail)")
   626  	}
   627  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
   628  	// sanity check: no files in snapshot dir
   629  	out, err := exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   630  	c.Assert(err, check.IsNil)
   631  	c.Check(string(out), check.Equals, "")
   632  
   633  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
   634  
   635  	// Mock "tar" so that the tars finish in the expected order.
   636  	// Locally .01s and .02s do the trick with count=1000;
   637  	// padded a lot bigger for slower systems.
   638  	mocktar := testutil.MockCommand(c, "tar", `
   639  case "$*" in
   640  */too-snap/*)
   641      sleep .5
   642      ;;
   643  */tri-snap/*)
   644      sleep 1
   645      ;;
   646  esac
   647  export LANG=C
   648  exec /bin/tar "$@"
   649  `)
   650  	defer mocktar.Restore()
   651  
   652  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
   653  		if username != "a-user" {
   654  			c.Fatalf("unexpected user %q", username)
   655  		}
   656  		return &user.User{
   657  			Uid:      fmt.Sprint(sys.Geteuid()),
   658  			Username: username,
   659  			HomeDir:  homedir,
   660  		}, nil
   661  	})()
   662  
   663  	o := overlord.Mock()
   664  	st := o.State()
   665  
   666  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   667  	c.Assert(err, check.IsNil)
   668  	o.AddManager(stmgr)
   669  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   670  	o.AddManager(shmgr)
   671  	o.AddManager(o.TaskRunner())
   672  
   673  	st.Lock()
   674  	defer st.Unlock()
   675  
   676  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
   677  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
   678  		snapstate.Set(st, name, &snapstate.SnapState{
   679  			Active:   true,
   680  			Sequence: []*snap.SideInfo{sideInfo},
   681  			Current:  sideInfo.Revision,
   682  			SnapType: "app",
   683  		})
   684  		snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
   685  
   686  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
   687  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common"), 0755), check.IsNil)
   688  		mode := os.FileMode(0755)
   689  		if i == 1 {
   690  			mode = 0
   691  		}
   692  		c.Assert(os.Mkdir(filepath.Join(homedir, "snap", name, "common", "common-"+name), mode), check.IsNil)
   693  	}
   694  
   695  	setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"})
   696  	c.Assert(err, check.IsNil)
   697  	c.Check(setID, check.Equals, uint64(1))
   698  	c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
   699  
   700  	change := st.NewChange("save-snapshot", "...")
   701  	change.AddAll(taskset)
   702  
   703  	st.Unlock()
   704  	c.Assert(o.Settle(5*time.Second), check.IsNil)
   705  	st.Lock()
   706  	c.Check(change.Err(), check.NotNil)
   707  	tasks := change.Tasks()
   708  	c.Assert(tasks, check.HasLen, 3)
   709  
   710  	// task 0 (for "one-snap") will have been undone
   711  	c.Check(tasks[0].Summary(), testutil.Contains, `"one-snap"`) // sanity check: task 0 is one-snap's
   712  	c.Check(tasks[0].Status(), check.Equals, state.UndoneStatus)
   713  
   714  	// task 1 (for "too-snap") will have errored
   715  	c.Check(tasks[1].Summary(), testutil.Contains, `"too-snap"`) // sanity check: task 1 is too-snap's
   716  	c.Check(tasks[1].Status(), check.Equals, state.ErrorStatus)
   717  	c.Check(strings.Join(tasks[1].Log(), "\n"), check.Matches, `\S+ ERROR cannot create archive: .* Permission denied .and \d+ more.`)
   718  
   719  	// task 2 (for "tri-snap") will have errored as well, hopefully, but it's a race (see the "tar" comment above)
   720  	c.Check(tasks[2].Summary(), testutil.Contains, `"tri-snap"`) // sanity check: task 2 is tri-snap's
   721  	c.Check(tasks[2].Status(), check.Equals, state.ErrorStatus, check.Commentf("if this ever fails, duplicate the fake tar sleeps please"))
   722  	// sometimes you'll get one, sometimes you'll get the other (depending on ordering of events)
   723  	c.Check(strings.Join(tasks[2].Log(), "\n"), check.Matches, `\S+ ERROR( tar failed:)? context canceled`)
   724  
   725  	// no zips left behind, not for errors, not for undos \o/
   726  	out, err = exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   727  	c.Assert(err, check.IsNil)
   728  	c.Check(string(out), check.Equals, "")
   729  }
   730  
   731  func (snapshotSuite) TestRestoreChecksIterError(c *check.C) {
   732  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
   733  		return errors.New("bzzt")
   734  	})()
   735  
   736  	st := state.New(nil)
   737  	st.Lock()
   738  	defer st.Unlock()
   739  
   740  	_, _, err := snapshotstate.Restore(st, 42, nil, nil)
   741  	c.Assert(err, check.ErrorMatches, "bzzt")
   742  }
   743  
   744  func (s snapshotSuite) TestRestoreChecksSnapstateConflicts(c *check.C) {
   745  	st, restore := s.createConflictingChange(c)
   746  	defer restore()
   747  
   748  	_, _, err := snapshotstate.Restore(st, 42, nil, nil)
   749  	c.Assert(err, check.NotNil)
   750  	c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{})
   751  
   752  }
   753  
   754  func (snapshotSuite) TestRestoreConflictsWithSnapstate(c *check.C) {
   755  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   756  	c.Assert(err, check.IsNil)
   757  	defer shotfile.Close()
   758  
   759  	sideInfo := &snap.SideInfo{RealName: "foo", Revision: snap.R(1)}
   760  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   761  		return map[string]*snapstate.SnapState{
   762  			"foo": {
   763  				Active:   true,
   764  				Sequence: []*snap.SideInfo{sideInfo},
   765  				Current:  sideInfo.Revision,
   766  			},
   767  		}, nil
   768  	}
   769  	snaptest.MockSnap(c, "{name: foo, version: v1}", sideInfo)
   770  
   771  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   772  
   773  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   774  		c.Assert(f(&backend.Reader{
   775  			Snapshot: client.Snapshot{SetID: 42, Snap: "foo"},
   776  			File:     shotfile,
   777  		}), check.IsNil)
   778  
   779  		return nil
   780  	}
   781  	defer snapshotstate.MockBackendIter(fakeIter)()
   782  
   783  	o := overlord.Mock()
   784  	st := o.State()
   785  
   786  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   787  	c.Assert(err, check.IsNil)
   788  	o.AddManager(stmgr)
   789  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   790  	o.AddManager(shmgr)
   791  
   792  	st.Lock()
   793  	defer st.Unlock()
   794  
   795  	snapstate.Set(st, "foo", &snapstate.SnapState{
   796  		Active: true,
   797  		Sequence: []*snap.SideInfo{
   798  			{RealName: "foo", Revision: snap.R(1)},
   799  		},
   800  		Current:  snap.R(1),
   801  		SnapType: "app",
   802  	})
   803  
   804  	chg := st.NewChange("snapshot-restore", "...")
   805  	_, restoreTasks, err := snapshotstate.Restore(st, 42, nil, nil)
   806  	c.Assert(err, check.IsNil)
   807  	chg.AddAll(restoreTasks)
   808  
   809  	_, err = snapstate.Disable(st, "foo")
   810  	c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-restore" change in progress`)
   811  }
   812  
   813  func (snapshotSuite) TestRestoreChecksForgetConflicts(c *check.C) {
   814  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   815  	c.Assert(err, check.IsNil)
   816  	defer shotfile.Close()
   817  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   818  		c.Assert(f(&backend.Reader{
   819  			// not wanted
   820  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
   821  			File:     shotfile,
   822  		}), check.IsNil)
   823  
   824  		return nil
   825  	}
   826  	defer snapshotstate.MockBackendIter(fakeIter)()
   827  
   828  	st := state.New(nil)
   829  	st.Lock()
   830  	defer st.Unlock()
   831  	chg := st.NewChange("forget-snapshot-change", "...")
   832  	tsk := st.NewTask("forget-snapshot", "...")
   833  	tsk.SetStatus(state.DoingStatus)
   834  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
   835  	chg.AddTask(tsk)
   836  
   837  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
   838  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
   839  }
   840  
   841  func (snapshotSuite) TestRestoreChecksChangesToSnapID(c *check.C) {
   842  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   843  	c.Assert(err, check.IsNil)
   844  	defer shotfile.Close()
   845  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   846  		return map[string]*snapstate.SnapState{
   847  			"a-snap": {
   848  				Active: true,
   849  				Sequence: []*snap.SideInfo{
   850  					{RealName: "a-snap", Revision: snap.R(1), SnapID: "1234567890"},
   851  				},
   852  				Current: snap.R(1),
   853  			},
   854  		}, nil
   855  	}
   856  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   857  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   858  		c.Assert(f(&backend.Reader{
   859  			// not wanted
   860  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap", SnapID: "0987654321"},
   861  			File:     shotfile,
   862  		}), check.IsNil)
   863  
   864  		return nil
   865  	}
   866  	defer snapshotstate.MockBackendIter(fakeIter)()
   867  
   868  	st := state.New(nil)
   869  	st.Lock()
   870  	defer st.Unlock()
   871  
   872  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
   873  	c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(ID 1234567…\) does not match snapshot \(ID 0987654…\)`)
   874  }
   875  
   876  func (snapshotSuite) TestRestoreChecksChangesToEpoch(c *check.C) {
   877  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   878  	c.Assert(err, check.IsNil)
   879  	defer shotfile.Close()
   880  
   881  	sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)}
   882  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   883  		return map[string]*snapstate.SnapState{
   884  			"a-snap": {
   885  				Active:   true,
   886  				Sequence: []*snap.SideInfo{sideInfo},
   887  				Current:  sideInfo.Revision,
   888  			},
   889  		}, nil
   890  	}
   891  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   892  	snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: 17}", sideInfo)
   893  
   894  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   895  		c.Assert(f(&backend.Reader{
   896  			// not wanted
   897  			Snapshot: client.Snapshot{
   898  				SetID: 42,
   899  				Snap:  "a-snap",
   900  				Epoch: snap.E("42"),
   901  			},
   902  			File: shotfile,
   903  		}), check.IsNil)
   904  
   905  		return nil
   906  	}
   907  	defer snapshotstate.MockBackendIter(fakeIter)()
   908  
   909  	st := state.New(nil)
   910  	st.Lock()
   911  	defer st.Unlock()
   912  
   913  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
   914  	c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(epoch 17\) cannot read snapshot data \(epoch 42\)`)
   915  }
   916  
   917  func (snapshotSuite) TestRestoreWorksWithCompatibleEpoch(c *check.C) {
   918  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   919  	c.Assert(err, check.IsNil)
   920  	defer shotfile.Close()
   921  
   922  	sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)}
   923  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   924  		return map[string]*snapstate.SnapState{
   925  			"a-snap": {
   926  				Active:   true,
   927  				Sequence: []*snap.SideInfo{sideInfo},
   928  				Current:  sideInfo.Revision,
   929  			},
   930  		}, nil
   931  	}
   932  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   933  	snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: {read: [17, 42], write: [42]}}", sideInfo)
   934  
   935  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   936  		c.Assert(f(&backend.Reader{
   937  			// not wanted
   938  			Snapshot: client.Snapshot{
   939  				SetID: 42,
   940  				Snap:  "a-snap",
   941  				Epoch: snap.E("17"),
   942  			},
   943  			File: shotfile,
   944  		}), check.IsNil)
   945  
   946  		return nil
   947  	}
   948  	defer snapshotstate.MockBackendIter(fakeIter)()
   949  
   950  	st := state.New(nil)
   951  	st.Lock()
   952  	defer st.Unlock()
   953  
   954  	found, taskset, err := snapshotstate.Restore(st, 42, nil, nil)
   955  	c.Assert(err, check.IsNil)
   956  	c.Check(found, check.DeepEquals, []string{"a-snap"})
   957  	tasks := taskset.Tasks()
   958  	c.Assert(tasks, check.HasLen, 1)
   959  	c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot")
   960  	c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`)
   961  	var snapshot map[string]interface{}
   962  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
   963  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
   964  		"set-id":   42.,
   965  		"snap":     "a-snap",
   966  		"filename": shotfile.Name(),
   967  		"current":  "1",
   968  	})
   969  }
   970  
   971  func (snapshotSuite) TestRestore(c *check.C) {
   972  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   973  	c.Assert(err, check.IsNil)
   974  	defer shotfile.Close()
   975  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   976  		c.Assert(f(&backend.Reader{
   977  			// not wanted
   978  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
   979  			File:     shotfile,
   980  		}), check.IsNil)
   981  
   982  		return nil
   983  	}
   984  	defer snapshotstate.MockBackendIter(fakeIter)()
   985  
   986  	st := state.New(nil)
   987  	st.Lock()
   988  	defer st.Unlock()
   989  
   990  	found, taskset, err := snapshotstate.Restore(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"})
   991  	c.Assert(err, check.IsNil)
   992  	c.Check(found, check.DeepEquals, []string{"a-snap"})
   993  	tasks := taskset.Tasks()
   994  	c.Assert(tasks, check.HasLen, 1)
   995  	c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot")
   996  	c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`)
   997  	var snapshot map[string]interface{}
   998  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
   999  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1000  		"set-id":   42.,
  1001  		"snap":     "a-snap",
  1002  		"filename": shotfile.Name(),
  1003  		"users":    []interface{}{"a-user"},
  1004  		"current":  "unset",
  1005  	})
  1006  }
  1007  
  1008  func (snapshotSuite) TestRestoreIntegration(c *check.C) {
  1009  	if os.Geteuid() == 0 {
  1010  		c.Skip("this test cannot run as root (runuser will fail)")
  1011  	}
  1012  
  1013  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
  1014  	homedirA := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
  1015  	homedirB := filepath.Join(dirs.GlobalRootDir, "home", "b-user")
  1016  
  1017  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
  1018  		if username != "a-user" && username != "b-user" {
  1019  			c.Fatalf("unexpected user %q", username)
  1020  			return nil, user.UnknownUserError(username)
  1021  		}
  1022  		return &user.User{
  1023  			Uid:      fmt.Sprint(sys.Geteuid()),
  1024  			Username: username,
  1025  			HomeDir:  filepath.Join(dirs.GlobalRootDir, "home", username),
  1026  		}, nil
  1027  
  1028  	})()
  1029  
  1030  	o := overlord.Mock()
  1031  	st := o.State()
  1032  
  1033  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
  1034  	c.Assert(err, check.IsNil)
  1035  	o.AddManager(stmgr)
  1036  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
  1037  	o.AddManager(shmgr)
  1038  	o.AddManager(o.TaskRunner())
  1039  
  1040  	st.Lock()
  1041  
  1042  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
  1043  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
  1044  		snapstate.Set(st, name, &snapstate.SnapState{
  1045  			Active:   true,
  1046  			Sequence: []*snap.SideInfo{sideInfo},
  1047  			Current:  sideInfo.Revision,
  1048  			SnapType: "app",
  1049  		})
  1050  		snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
  1051  
  1052  		for _, home := range []string{homedirA, homedirB} {
  1053  			c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
  1054  			c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, "common", "common-"+name), 0755), check.IsNil)
  1055  		}
  1056  
  1057  		_, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user", "b-user"})
  1058  		c.Assert(err, check.IsNil)
  1059  	}
  1060  
  1061  	// move the old away
  1062  	c.Assert(os.Rename(filepath.Join(homedirA, "snap"), filepath.Join(homedirA, "snap.old")), check.IsNil)
  1063  	// remove b-user's home
  1064  	c.Assert(os.RemoveAll(homedirB), check.IsNil)
  1065  
  1066  	found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user", "b-user"})
  1067  	c.Assert(err, check.IsNil)
  1068  	sort.Strings(found)
  1069  	c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
  1070  
  1071  	change := st.NewChange("restore-snapshot", "...")
  1072  	change.AddAll(taskset)
  1073  
  1074  	st.Unlock()
  1075  	c.Assert(o.Settle(5*time.Second), check.IsNil)
  1076  	st.Lock()
  1077  	c.Check(change.Err(), check.IsNil)
  1078  	defer st.Unlock()
  1079  
  1080  	// the three restores warn about the missing home (but no errors, no panics)
  1081  	for _, task := range change.Tasks() {
  1082  		c.Check(strings.Join(task.Log(), "\n"), check.Matches, `.* Skipping restore of "[^"]+/home/b-user/[^"]+" as "[^"]+/home/b-user" doesn't exist.`)
  1083  	}
  1084  
  1085  	// check it was all brought back \o/
  1086  	out, err := exec.Command("diff", "-rN", filepath.Join(homedirA, "snap"), filepath.Join("snap.old")).CombinedOutput()
  1087  	c.Assert(err, check.IsNil)
  1088  	c.Check(string(out), check.Equals, "")
  1089  }
  1090  
  1091  func (snapshotSuite) TestRestoreIntegrationFails(c *check.C) {
  1092  	if os.Geteuid() == 0 {
  1093  		c.Skip("this test cannot run as root (runuser will fail)")
  1094  	}
  1095  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
  1096  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
  1097  
  1098  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
  1099  		if username != "a-user" {
  1100  			c.Fatalf("unexpected user %q", username)
  1101  		}
  1102  		return &user.User{
  1103  			Uid:      fmt.Sprint(sys.Geteuid()),
  1104  			Username: username,
  1105  			HomeDir:  homedir,
  1106  		}, nil
  1107  	})()
  1108  
  1109  	o := overlord.Mock()
  1110  	st := o.State()
  1111  
  1112  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
  1113  	c.Assert(err, check.IsNil)
  1114  	o.AddManager(stmgr)
  1115  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
  1116  	o.AddManager(shmgr)
  1117  	o.AddManager(o.TaskRunner())
  1118  
  1119  	st.Lock()
  1120  
  1121  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
  1122  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
  1123  		snapstate.Set(st, name, &snapstate.SnapState{
  1124  			Active:   true,
  1125  			Sequence: []*snap.SideInfo{sideInfo},
  1126  			Current:  sideInfo.Revision,
  1127  			SnapType: "app",
  1128  		})
  1129  		snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: vv1}", name), sideInfo)
  1130  
  1131  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
  1132  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil)
  1133  
  1134  		_, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user"})
  1135  		c.Assert(err, check.IsNil)
  1136  	}
  1137  
  1138  	// move the old away
  1139  	c.Assert(os.Rename(filepath.Join(homedir, "snap"), filepath.Join(homedir, "snap.old")), check.IsNil)
  1140  	// but poison the well
  1141  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap"), 0755), check.IsNil)
  1142  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", "too-snap"), 0), check.IsNil)
  1143  
  1144  	found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user"})
  1145  	c.Assert(err, check.IsNil)
  1146  	sort.Strings(found)
  1147  	c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
  1148  
  1149  	change := st.NewChange("restore-snapshot", "...")
  1150  	change.AddAll(taskset)
  1151  
  1152  	st.Unlock()
  1153  	c.Assert(o.Settle(5*time.Second), check.IsNil)
  1154  	st.Lock()
  1155  	c.Check(change.Err(), check.NotNil)
  1156  	defer st.Unlock()
  1157  
  1158  	tasks := change.Tasks()
  1159  	c.Check(tasks, check.HasLen, 3)
  1160  	for _, task := range tasks {
  1161  		if strings.Contains(task.Summary(), `"too-snap"`) {
  1162  			// too-snap was set up to fail, should always fail with
  1163  			// 'permission denied' (see the mkdirall w/mode 0 above)
  1164  			c.Check(task.Status(), check.Equals, state.ErrorStatus)
  1165  			c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR mkdir \S+: permission denied`)
  1166  		} else {
  1167  			// the other two might fail (ErrorStatus) if they're
  1168  			// still running when too-snap fails, or they might have
  1169  			// finished and needed to be undone (UndoneStatus); it's
  1170  			// a race, but either is fine.
  1171  			if task.Status() == state.ErrorStatus {
  1172  				c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR.* context canceled`)
  1173  			} else {
  1174  				c.Check(task.Status(), check.Equals, state.UndoneStatus)
  1175  			}
  1176  		}
  1177  	}
  1178  
  1179  	// remove the poison
  1180  	c.Assert(os.Remove(filepath.Join(homedir, "snap", "too-snap")), check.IsNil)
  1181  
  1182  	// check that nothing else was put there
  1183  	out, err := exec.Command("find", filepath.Join(homedir, "snap")).CombinedOutput()
  1184  	c.Assert(err, check.IsNil)
  1185  	c.Check(strings.TrimSpace(string(out)), check.Equals, filepath.Join(homedir, "snap"))
  1186  }
  1187  
  1188  func (snapshotSuite) TestCheckChecksIterError(c *check.C) {
  1189  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
  1190  		return errors.New("bzzt")
  1191  	})()
  1192  
  1193  	st := state.New(nil)
  1194  	st.Lock()
  1195  	defer st.Unlock()
  1196  
  1197  	_, _, err := snapshotstate.Check(st, 42, nil, nil)
  1198  	c.Assert(err, check.ErrorMatches, "bzzt")
  1199  }
  1200  
  1201  func (s snapshotSuite) TestCheckDoesNotTriggerSnapstateConflict(c *check.C) {
  1202  	st, restore := s.createConflictingChange(c)
  1203  	defer restore()
  1204  
  1205  	_, _, err := snapshotstate.Check(st, 42, nil, nil)
  1206  	c.Assert(err, check.IsNil)
  1207  }
  1208  
  1209  func (snapshotSuite) TestCheckChecksForgetConflicts(c *check.C) {
  1210  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1211  	c.Assert(err, check.IsNil)
  1212  	defer shotfile.Close()
  1213  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1214  		c.Assert(f(&backend.Reader{
  1215  			// not wanted
  1216  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1217  			File:     shotfile,
  1218  		}), check.IsNil)
  1219  
  1220  		return nil
  1221  	}
  1222  	defer snapshotstate.MockBackendIter(fakeIter)()
  1223  
  1224  	st := state.New(nil)
  1225  	st.Lock()
  1226  	defer st.Unlock()
  1227  	chg := st.NewChange("forget-snapshot-change", "...")
  1228  	tsk := st.NewTask("forget-snapshot", "...")
  1229  	tsk.SetStatus(state.DoingStatus)
  1230  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1231  	chg.AddTask(tsk)
  1232  
  1233  	_, _, err = snapshotstate.Check(st, 42, nil, nil)
  1234  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1235  }
  1236  
  1237  func (snapshotSuite) TestCheck(c *check.C) {
  1238  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1239  	c.Assert(err, check.IsNil)
  1240  	defer shotfile.Close()
  1241  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1242  		c.Assert(f(&backend.Reader{
  1243  			// not wanted
  1244  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1245  			File:     shotfile,
  1246  		}), check.IsNil)
  1247  
  1248  		return nil
  1249  	}
  1250  	defer snapshotstate.MockBackendIter(fakeIter)()
  1251  
  1252  	st := state.New(nil)
  1253  	st.Lock()
  1254  	defer st.Unlock()
  1255  
  1256  	found, taskset, err := snapshotstate.Check(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"})
  1257  	c.Assert(err, check.IsNil)
  1258  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1259  	tasks := taskset.Tasks()
  1260  	c.Assert(tasks, check.HasLen, 1)
  1261  	c.Check(tasks[0].Kind(), check.Equals, "check-snapshot")
  1262  	c.Check(tasks[0].Summary(), check.Equals, `Check data of snap "a-snap" in snapshot set #42`)
  1263  	var snapshot map[string]interface{}
  1264  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1265  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1266  		"set-id":   42.,
  1267  		"snap":     "a-snap",
  1268  		"filename": shotfile.Name(),
  1269  		"users":    []interface{}{"a-user"},
  1270  		"current":  "unset",
  1271  	})
  1272  }
  1273  
  1274  func (snapshotSuite) TestForgetChecksIterError(c *check.C) {
  1275  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
  1276  		return errors.New("bzzt")
  1277  	})()
  1278  
  1279  	st := state.New(nil)
  1280  	st.Lock()
  1281  	defer st.Unlock()
  1282  
  1283  	_, _, err := snapshotstate.Forget(st, 42, nil)
  1284  	c.Assert(err, check.ErrorMatches, "bzzt")
  1285  }
  1286  
  1287  func (s snapshotSuite) TestForgetDoesNotTriggerSnapstateConflict(c *check.C) {
  1288  	st, restore := s.createConflictingChange(c)
  1289  	defer restore()
  1290  
  1291  	_, _, err := snapshotstate.Forget(st, 42, nil)
  1292  	c.Assert(err, check.IsNil)
  1293  }
  1294  
  1295  func (snapshotSuite) TestForgetChecksCheckConflicts(c *check.C) {
  1296  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1297  	c.Assert(err, check.IsNil)
  1298  	defer shotfile.Close()
  1299  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1300  		c.Assert(f(&backend.Reader{
  1301  			// not wanted
  1302  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1303  			File:     shotfile,
  1304  		}), check.IsNil)
  1305  
  1306  		return nil
  1307  	}
  1308  	defer snapshotstate.MockBackendIter(fakeIter)()
  1309  
  1310  	st := state.New(nil)
  1311  	st.Lock()
  1312  	defer st.Unlock()
  1313  	chg := st.NewChange("check-snapshot-change", "...")
  1314  	tsk := st.NewTask("check-snapshot", "...")
  1315  	tsk.SetStatus(state.DoingStatus)
  1316  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1317  	chg.AddTask(tsk)
  1318  
  1319  	_, _, err = snapshotstate.Forget(st, 42, nil)
  1320  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1321  }
  1322  
  1323  func (snapshotSuite) TestForgetChecksRestoreConflicts(c *check.C) {
  1324  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1325  	c.Assert(err, check.IsNil)
  1326  	defer shotfile.Close()
  1327  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1328  		c.Assert(f(&backend.Reader{
  1329  			// not wanted
  1330  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1331  			File:     shotfile,
  1332  		}), check.IsNil)
  1333  
  1334  		return nil
  1335  	}
  1336  	defer snapshotstate.MockBackendIter(fakeIter)()
  1337  
  1338  	st := state.New(nil)
  1339  	st.Lock()
  1340  	defer st.Unlock()
  1341  	chg := st.NewChange("restore-snapshot-change", "...")
  1342  	tsk := st.NewTask("restore-snapshot", "...")
  1343  	tsk.SetStatus(state.DoingStatus)
  1344  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1345  	chg.AddTask(tsk)
  1346  
  1347  	_, _, err = snapshotstate.Forget(st, 42, nil)
  1348  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1349  }
  1350  
  1351  func (snapshotSuite) TestForget(c *check.C) {
  1352  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1353  	c.Assert(err, check.IsNil)
  1354  	defer shotfile.Close()
  1355  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1356  		c.Assert(f(&backend.Reader{
  1357  			// not wanted
  1358  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1359  			File:     shotfile,
  1360  		}), check.IsNil)
  1361  
  1362  		return nil
  1363  	}
  1364  	defer snapshotstate.MockBackendIter(fakeIter)()
  1365  
  1366  	st := state.New(nil)
  1367  	st.Lock()
  1368  	defer st.Unlock()
  1369  
  1370  	found, taskset, err := snapshotstate.Forget(st, 42, []string{"a-snap", "b-snap"})
  1371  	c.Assert(err, check.IsNil)
  1372  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1373  	tasks := taskset.Tasks()
  1374  	c.Assert(tasks, check.HasLen, 1)
  1375  	c.Check(tasks[0].Kind(), check.Equals, "forget-snapshot")
  1376  	c.Check(tasks[0].Summary(), check.Equals, `Drop data of snap "a-snap" from snapshot set #42`)
  1377  	var snapshot map[string]interface{}
  1378  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1379  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1380  		"set-id":   42.,
  1381  		"snap":     "a-snap",
  1382  		"filename": shotfile.Name(),
  1383  		"current":  "unset",
  1384  	})
  1385  }
  1386  
  1387  func (snapshotSuite) TestSaveExpiration(c *check.C) {
  1388  	st := state.New(nil)
  1389  	st.Lock()
  1390  	defer st.Unlock()
  1391  
  1392  	var expirations map[uint64]interface{}
  1393  	tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z")
  1394  	c.Assert(err, check.IsNil)
  1395  	c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil)
  1396  
  1397  	tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z")
  1398  	c.Assert(err, check.IsNil)
  1399  	c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil)
  1400  
  1401  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
  1402  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
  1403  		12: map[string]interface{}{"expiry-time": "2019-03-11T11:24:00Z"},
  1404  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:50:00Z"},
  1405  	})
  1406  }
  1407  
  1408  func (snapshotSuite) TestRemoveSnapshotState(c *check.C) {
  1409  	st := state.New(nil)
  1410  	st.Lock()
  1411  	defer st.Unlock()
  1412  
  1413  	st.Set("snapshots", map[uint64]interface{}{
  1414  		12: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"},
  1415  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1416  		14: map[string]interface{}{"expiry-time": "2019-03-12T13:11:00Z"},
  1417  	})
  1418  
  1419  	snapshotstate.RemoveSnapshotState(st, 12, 14)
  1420  
  1421  	var snapshots map[uint64]interface{}
  1422  	c.Assert(st.Get("snapshots", &snapshots), check.IsNil)
  1423  	c.Check(snapshots, check.DeepEquals, map[uint64]interface{}{
  1424  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1425  	})
  1426  }
  1427  
  1428  func (snapshotSuite) TestExpiredSnapshotSets(c *check.C) {
  1429  	st := state.New(nil)
  1430  	st.Lock()
  1431  	defer st.Unlock()
  1432  
  1433  	tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z")
  1434  	c.Assert(err, check.IsNil)
  1435  	c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil)
  1436  
  1437  	tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z")
  1438  	c.Assert(err, check.IsNil)
  1439  	c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil)
  1440  
  1441  	tm, err = time.Parse(time.RFC3339, "2020-03-11T11:24:00Z")
  1442  	c.Assert(err, check.IsNil)
  1443  	expired, err := snapshotstate.ExpiredSnapshotSets(st, tm)
  1444  	c.Assert(err, check.IsNil)
  1445  	c.Check(expired, check.DeepEquals, map[uint64]bool{12: true, 13: true})
  1446  
  1447  	tm, err = time.Parse(time.RFC3339, "2019-03-01T11:24:00Z")
  1448  	c.Assert(err, check.IsNil)
  1449  	expired, err = snapshotstate.ExpiredSnapshotSets(st, tm)
  1450  	c.Assert(err, check.IsNil)
  1451  	c.Check(expired, check.DeepEquals, map[uint64]bool{13: true})
  1452  }
  1453  
  1454  func (snapshotSuite) TestAutomaticSnapshotDisabled(c *check.C) {
  1455  	st := state.New(nil)
  1456  	st.Lock()
  1457  	defer st.Unlock()
  1458  
  1459  	tr := config.NewTransaction(st)
  1460  	tr.Set("core", "snapshots.automatic.retention", "no")
  1461  	tr.Commit()
  1462  
  1463  	_, err := snapshotstate.AutomaticSnapshot(st, "foo")
  1464  	c.Assert(err, check.Equals, snapstate.ErrNothingToDo)
  1465  }
  1466  
  1467  func (snapshotSuite) TestAutomaticSnapshot(c *check.C) {
  1468  	st := state.New(nil)
  1469  	st.Lock()
  1470  	defer st.Unlock()
  1471  
  1472  	tr := config.NewTransaction(st)
  1473  	tr.Set("core", "snapshots.automatic.retention", "24h")
  1474  	tr.Commit()
  1475  
  1476  	ts, err := snapshotstate.AutomaticSnapshot(st, "foo")
  1477  	c.Assert(err, check.IsNil)
  1478  
  1479  	tasks := ts.Tasks()
  1480  	c.Assert(tasks, check.HasLen, 1)
  1481  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
  1482  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "foo" in automatic snapshot set #1`)
  1483  	var snapshot map[string]interface{}
  1484  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1485  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1486  		"set-id":  1.,
  1487  		"snap":    "foo",
  1488  		"current": "unset",
  1489  		"auto":    true,
  1490  	})
  1491  }
  1492  
  1493  func (snapshotSuite) TestAutomaticSnapshotDefaultClassic(c *check.C) {
  1494  	release.MockOnClassic(true)
  1495  
  1496  	st := state.New(nil)
  1497  	st.Lock()
  1498  	defer st.Unlock()
  1499  
  1500  	du, err := snapshotstate.AutomaticSnapshotExpiration(st)
  1501  	c.Assert(err, check.IsNil)
  1502  	c.Assert(du, check.Equals, snapshotstate.DefaultAutomaticSnapshotExpiration)
  1503  }
  1504  
  1505  func (snapshotSuite) TestAutomaticSnapshotDefaultUbuntuCore(c *check.C) {
  1506  	release.MockOnClassic(false)
  1507  
  1508  	st := state.New(nil)
  1509  	st.Lock()
  1510  	defer st.Unlock()
  1511  
  1512  	du, err := snapshotstate.AutomaticSnapshotExpiration(st)
  1513  	c.Assert(err, check.IsNil)
  1514  	c.Assert(du, check.Equals, time.Duration(0))
  1515  }
  1516  
  1517  func (snapshotSuite) TestListError(c *check.C) {
  1518  	restore := snapshotstate.MockBackendList(func(context.Context, uint64, []string) ([]client.SnapshotSet, error) {
  1519  		return nil, fmt.Errorf("boom")
  1520  	})
  1521  	defer restore()
  1522  
  1523  	st := state.New(nil)
  1524  	st.Lock()
  1525  	defer st.Unlock()
  1526  
  1527  	_, err := snapshotstate.List(context.TODO(), st, 0, nil)
  1528  	c.Assert(err, check.ErrorMatches, "boom")
  1529  }
  1530  
  1531  func (snapshotSuite) TestListSetsAutoFlag(c *check.C) {
  1532  	st := state.New(nil)
  1533  	st.Lock()
  1534  	defer st.Unlock()
  1535  
  1536  	st.Set("snapshots", map[uint64]interface{}{
  1537  		1: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"},
  1538  		2: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1539  	})
  1540  
  1541  	restore := snapshotstate.MockBackendList(func(ctx context.Context, setID uint64, snapNames []string) ([]client.SnapshotSet, error) {
  1542  		// three sets, first two are automatic (implied by expiration times in the state), the third isn't.
  1543  		return []client.SnapshotSet{
  1544  			{
  1545  				ID: 1,
  1546  				Snapshots: []*client.Snapshot{
  1547  					{
  1548  						Snap:  "foo",
  1549  						SetID: 1,
  1550  					},
  1551  					{
  1552  						Snap:  "bar",
  1553  						SetID: 1,
  1554  					},
  1555  				},
  1556  			},
  1557  			{
  1558  				ID: 2,
  1559  				Snapshots: []*client.Snapshot{
  1560  					{
  1561  						Snap:  "baz",
  1562  						SetID: 2,
  1563  					},
  1564  				},
  1565  			},
  1566  			{
  1567  				ID: 3,
  1568  				Snapshots: []*client.Snapshot{
  1569  					{
  1570  						Snap:  "baz",
  1571  						SetID: 3,
  1572  					},
  1573  				},
  1574  			},
  1575  		}, nil
  1576  	})
  1577  	defer restore()
  1578  
  1579  	sets, err := snapshotstate.List(context.TODO(), st, 0, nil)
  1580  	c.Assert(err, check.IsNil)
  1581  	c.Assert(sets, check.HasLen, 3)
  1582  
  1583  	for _, sset := range sets {
  1584  		switch sset.ID {
  1585  		case 1:
  1586  			c.Check(sset.Snapshots, check.HasLen, 2, check.Commentf("set #%d", sset.ID))
  1587  		default:
  1588  			c.Check(sset.Snapshots, check.HasLen, 1, check.Commentf("set #%d", sset.ID))
  1589  		}
  1590  
  1591  		switch sset.ID {
  1592  		case 1, 2:
  1593  			for _, snapshot := range sset.Snapshots {
  1594  				c.Check(snapshot.Auto, check.Equals, true)
  1595  			}
  1596  		default:
  1597  			for _, snapshot := range sset.Snapshots {
  1598  				c.Check(snapshot.Auto, check.Equals, false)
  1599  			}
  1600  		}
  1601  	}
  1602  }
  1603  
  1604  func (snapshotSuite) TestImportSnapshotHappy(c *check.C) {
  1605  	st := state.New(nil)
  1606  
  1607  	fakeSnapNames := []string{"baz", "bar", "foo"}
  1608  	fakeSnapshotData := "fake-import-data"
  1609  
  1610  	buf := bytes.NewBufferString(fakeSnapshotData)
  1611  	restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader) ([]string, error) {
  1612  		d, err := ioutil.ReadAll(r)
  1613  		c.Assert(err, check.IsNil)
  1614  		c.Check(fakeSnapshotData, check.Equals, string(d))
  1615  		return fakeSnapNames, nil
  1616  	})
  1617  	defer restore()
  1618  
  1619  	sid, names, err := snapshotstate.Import(context.TODO(), st, buf)
  1620  	c.Assert(err, check.IsNil)
  1621  	c.Check(sid, check.Equals, uint64(1))
  1622  	c.Check(names, check.DeepEquals, fakeSnapNames)
  1623  }
  1624  
  1625  func (snapshotSuite) TestImportSnapshotImportError(c *check.C) {
  1626  	st := state.New(nil)
  1627  
  1628  	restore := snapshotstate.MockBackendImport(func(ctx context.Context, id uint64, r io.Reader) ([]string, error) {
  1629  		return nil, errors.New("some-error")
  1630  	})
  1631  	defer restore()
  1632  
  1633  	r := bytes.NewBufferString("faked-import-data")
  1634  	sid, _, err := snapshotstate.Import(context.TODO(), st, r)
  1635  	c.Assert(err, check.NotNil)
  1636  	c.Assert(err.Error(), check.Equals, "some-error")
  1637  	c.Check(sid, check.Equals, uint64(0))
  1638  }
  1639  func (snapshotSuite) TestEstimateSnapshotSize(c *check.C) {
  1640  	st := state.New(nil)
  1641  	st.Lock()
  1642  	defer st.Unlock()
  1643  
  1644  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1645  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1646  		Active:   true,
  1647  		Sequence: []*snap.SideInfo{sideInfo},
  1648  		Current:  sideInfo.Revision,
  1649  	})
  1650  
  1651  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1652  		return 123, nil
  1653  	})()
  1654  
  1655  	sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1656  	c.Assert(err, check.IsNil)
  1657  	c.Check(sz, check.Equals, uint64(123))
  1658  }
  1659  
  1660  func (snapshotSuite) TestEstimateSnapshotSizeWithConfig(c *check.C) {
  1661  	st := state.New(nil)
  1662  	st.Lock()
  1663  	defer st.Unlock()
  1664  
  1665  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1666  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1667  		Active:   true,
  1668  		Sequence: []*snap.SideInfo{sideInfo},
  1669  		Current:  sideInfo.Revision,
  1670  	})
  1671  
  1672  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1673  		return 100, nil
  1674  	})()
  1675  
  1676  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
  1677  		c.Check(snapname, check.Equals, "some-snap")
  1678  		buf := json.RawMessage(`{"hello": "there"}`)
  1679  		return &buf, nil
  1680  	})()
  1681  
  1682  	sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1683  	c.Assert(err, check.IsNil)
  1684  	// size is 100 + 18
  1685  	c.Check(sz, check.Equals, uint64(118))
  1686  }
  1687  
  1688  func (snapshotSuite) TestEstimateSnapshotSizeError(c *check.C) {
  1689  	st := state.New(nil)
  1690  	st.Lock()
  1691  	defer st.Unlock()
  1692  
  1693  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1694  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1695  		Active:   true,
  1696  		Sequence: []*snap.SideInfo{sideInfo},
  1697  		Current:  sideInfo.Revision,
  1698  	})
  1699  
  1700  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1701  		return 0, fmt.Errorf("an error")
  1702  	})()
  1703  
  1704  	_, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1705  	c.Assert(err, check.ErrorMatches, `an error`)
  1706  }
  1707  
  1708  func (snapshotSuite) TestEstimateSnapshotSizeWithUsers(c *check.C) {
  1709  	st := state.New(nil)
  1710  	st.Lock()
  1711  	defer st.Unlock()
  1712  
  1713  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1714  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1715  		Active:   true,
  1716  		Sequence: []*snap.SideInfo{sideInfo},
  1717  		Current:  sideInfo.Revision,
  1718  	})
  1719  
  1720  	var gotUsers []string
  1721  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1722  		gotUsers = users
  1723  		return 0, nil
  1724  	})()
  1725  
  1726  	_, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", []string{"user1", "user2"})
  1727  	c.Assert(err, check.IsNil)
  1728  	c.Check(gotUsers, check.DeepEquals, []string{"user1", "user2"})
  1729  }