github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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  	"context"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"io/ioutil"
    28  	"os"
    29  	"os/exec"
    30  	"os/user"
    31  	"path/filepath"
    32  	"sort"
    33  	"strings"
    34  	"testing"
    35  	"time"
    36  
    37  	"gopkg.in/check.v1"
    38  
    39  	"github.com/snapcore/snapd/client"
    40  	"github.com/snapcore/snapd/dirs"
    41  	"github.com/snapcore/snapd/osutil/sys"
    42  	"github.com/snapcore/snapd/overlord"
    43  	"github.com/snapcore/snapd/overlord/configstate/config"
    44  	"github.com/snapcore/snapd/overlord/snapshotstate"
    45  	"github.com/snapcore/snapd/overlord/snapshotstate/backend"
    46  	"github.com/snapcore/snapd/overlord/snapstate"
    47  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    48  	"github.com/snapcore/snapd/overlord/state"
    49  	"github.com/snapcore/snapd/release"
    50  	"github.com/snapcore/snapd/snap"
    51  	"github.com/snapcore/snapd/snap/snaptest"
    52  	"github.com/snapcore/snapd/testutil"
    53  )
    54  
    55  type snapshotSuite struct{}
    56  
    57  var _ = check.Suite(&snapshotSuite{})
    58  
    59  // tie gocheck into testing
    60  func TestSnapshot(t *testing.T) { check.TestingT(t) }
    61  
    62  func (snapshotSuite) SetUpTest(c *check.C) {
    63  	dirs.SetRootDir(c.MkDir())
    64  	os.MkdirAll(dirs.SnapshotsDir, os.ModePerm)
    65  }
    66  
    67  func (snapshotSuite) TearDownTest(c *check.C) {
    68  	dirs.SetRootDir("/")
    69  }
    70  
    71  func (snapshotSuite) TestNewSnapshotSetID(c *check.C) {
    72  	st := state.New(nil)
    73  	st.Lock()
    74  	defer st.Unlock()
    75  
    76  	// Disk last set id unset, state set id unset, use 1
    77  	sid, err := snapshotstate.NewSnapshotSetID(st)
    78  	c.Assert(err, check.IsNil)
    79  	c.Check(sid, check.Equals, uint64(1))
    80  
    81  	var stateSetID uint64
    82  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
    83  	c.Check(stateSetID, check.Equals, uint64(1))
    84  
    85  	c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, "9_some-snap-1.zip"), []byte{}, 0644), check.IsNil)
    86  
    87  	// Disk last set id 9 > state set id 1, use 9++ = 10
    88  	sid, err = snapshotstate.NewSnapshotSetID(st)
    89  	c.Assert(err, check.IsNil)
    90  	c.Check(sid, check.Equals, uint64(10))
    91  
    92  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
    93  	c.Check(stateSetID, check.Equals, uint64(10))
    94  
    95  	// Disk last set id 9 < state set id 10, use 10++ = 11
    96  	sid, err = snapshotstate.NewSnapshotSetID(st)
    97  	c.Assert(err, check.IsNil)
    98  	c.Check(sid, check.Equals, uint64(11))
    99  
   100  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
   101  	c.Check(stateSetID, check.Equals, uint64(11))
   102  
   103  	c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, "88_some-snap-1.zip"), []byte{}, 0644), check.IsNil)
   104  
   105  	// Disk last set id 88 > state set id 11, use 88++ = 89
   106  	sid, err = snapshotstate.NewSnapshotSetID(st)
   107  	c.Assert(err, check.IsNil)
   108  	c.Check(sid, check.Equals, uint64(89))
   109  
   110  	c.Assert(st.Get("last-snapshot-set-id", &stateSetID), check.IsNil)
   111  	c.Check(stateSetID, check.Equals, uint64(89))
   112  }
   113  
   114  func (snapshotSuite) TestAllActiveSnapNames(c *check.C) {
   115  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   116  		return map[string]*snapstate.SnapState{
   117  			"a-snap": {Active: true},
   118  			"b-snap": {},
   119  			"c-snap": {Active: true},
   120  		}, nil
   121  	}
   122  
   123  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   124  
   125  	// loop to check sortedness
   126  	for i := 0; i < 100; i++ {
   127  		names, err := snapshotstate.AllActiveSnapNames(nil)
   128  		c.Assert(err, check.IsNil)
   129  		c.Check(names, check.DeepEquals, []string{"a-snap", "c-snap"})
   130  	}
   131  }
   132  
   133  func (snapshotSuite) TestAllActiveSnapNamesError(c *check.C) {
   134  	errBad := errors.New("bad")
   135  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   136  		return nil, errBad
   137  	}
   138  
   139  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   140  
   141  	names, err := snapshotstate.AllActiveSnapNames(nil)
   142  	c.Check(err, check.Equals, errBad)
   143  	c.Check(names, check.IsNil)
   144  }
   145  
   146  func (snapshotSuite) TestSnapSummariesInSnapshotSet(c *check.C) {
   147  	shotfileA, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   148  	c.Assert(err, check.IsNil)
   149  	defer shotfileA.Close()
   150  	shotfileB, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   151  	c.Assert(err, check.IsNil)
   152  	defer shotfileB.Close()
   153  
   154  	setID := uint64(42)
   155  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   156  		c.Assert(f(&backend.Reader{
   157  			// wanted
   158  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap", SnapID: "a-id", Epoch: snap.Epoch{Read: []uint32{42}, Write: []uint32{17}}},
   159  			File:     shotfileA,
   160  		}), check.IsNil)
   161  		c.Assert(f(&backend.Reader{
   162  			// not wanted (bad set id)
   163  			Snapshot: client.Snapshot{SetID: setID + 1, Snap: "a-snap", SnapID: "a-id"},
   164  			File:     shotfileA,
   165  		}), check.IsNil)
   166  		c.Assert(f(&backend.Reader{
   167  			// wanted
   168  			Snapshot: client.Snapshot{SetID: setID, Snap: "b-snap", SnapID: "b-id"},
   169  			File:     shotfileB,
   170  		}), check.IsNil)
   171  		return nil
   172  	}
   173  	defer snapshotstate.MockBackendIter(fakeIter)()
   174  
   175  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil)
   176  	c.Assert(err, check.IsNil)
   177  	c.Assert(summaries.AsMaps(), check.DeepEquals, []map[string]string{
   178  		{"snap": "a-snap", "snapID": "a-id", "filename": shotfileA.Name(), "epoch": `{"read":[42],"write":[17]}`},
   179  		{"snap": "b-snap", "snapID": "b-id", "filename": shotfileB.Name(), "epoch": "0"},
   180  	})
   181  }
   182  
   183  func (snapshotSuite) TestSnapSummariesInSnapshotSetSnaps(c *check.C) {
   184  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   185  	c.Assert(err, check.IsNil)
   186  	defer shotfile.Close()
   187  	setID := uint64(42)
   188  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   189  		c.Assert(f(&backend.Reader{
   190  			// wanted
   191  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap", SnapID: "a-id"},
   192  			File:     shotfile,
   193  		}), check.IsNil)
   194  		c.Assert(f(&backend.Reader{
   195  			// not wanted (bad set id)
   196  			Snapshot: client.Snapshot{SetID: setID + 1, Snap: "a-snap", SnapID: "a-id"},
   197  			File:     shotfile,
   198  		}), check.IsNil)
   199  		c.Assert(f(&backend.Reader{
   200  			// not wanted (bad snap name)
   201  			Snapshot: client.Snapshot{SetID: setID, Snap: "c-snap", SnapID: "c-id"},
   202  			File:     shotfile,
   203  		}), check.IsNil)
   204  		return nil
   205  	}
   206  	defer snapshotstate.MockBackendIter(fakeIter)()
   207  
   208  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, []string{"a-snap"})
   209  	c.Assert(err, check.IsNil)
   210  	c.Check(summaries.AsMaps(), check.DeepEquals, []map[string]string{
   211  		{"snap": "a-snap", "snapID": "a-id", "filename": shotfile.Name(), "epoch": "0"},
   212  	})
   213  }
   214  
   215  func (snapshotSuite) TestSnapSummariesInSnapshotSetErrors(c *check.C) {
   216  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   217  	c.Assert(err, check.IsNil)
   218  	defer shotfile.Close()
   219  	setID := uint64(42)
   220  	errBad := errors.New("bad")
   221  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   222  		c.Assert(f(&backend.Reader{
   223  			// wanted
   224  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap"},
   225  			File:     shotfile,
   226  		}), check.IsNil)
   227  
   228  		return errBad
   229  	}
   230  	defer snapshotstate.MockBackendIter(fakeIter)()
   231  
   232  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil)
   233  	c.Assert(err, check.Equals, errBad)
   234  	c.Check(summaries, check.IsNil)
   235  }
   236  
   237  func (snapshotSuite) TestSnapSummariesInSnapshotSetNotFound(c *check.C) {
   238  	setID := uint64(42)
   239  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   240  	c.Assert(err, check.IsNil)
   241  	defer shotfile.Close()
   242  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   243  		c.Assert(f(&backend.Reader{
   244  			// not wanted
   245  			Snapshot: client.Snapshot{SetID: setID - 1, Snap: "a-snap"},
   246  			File:     shotfile,
   247  		}), check.IsNil)
   248  
   249  		return nil
   250  	}
   251  	defer snapshotstate.MockBackendIter(fakeIter)()
   252  
   253  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, nil)
   254  	c.Assert(err, check.Equals, client.ErrSnapshotSetNotFound)
   255  	c.Check(summaries, check.IsNil)
   256  }
   257  
   258  func (snapshotSuite) TestSnapSummariesInSnapshotSetEmptyNotFound(c *check.C) {
   259  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error { return nil }
   260  	defer snapshotstate.MockBackendIter(fakeIter)()
   261  
   262  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(42, nil)
   263  	c.Assert(err, check.Equals, client.ErrSnapshotSetNotFound)
   264  	c.Check(summaries, check.IsNil)
   265  }
   266  
   267  func (snapshotSuite) TestSnapSummariesInSnapshotSetSnapNotFound(c *check.C) {
   268  	setID := uint64(42)
   269  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   270  	c.Assert(err, check.IsNil)
   271  	defer shotfile.Close()
   272  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   273  		c.Assert(f(&backend.Reader{
   274  			// not wanted
   275  			Snapshot: client.Snapshot{SetID: setID, Snap: "a-snap"},
   276  			File:     shotfile,
   277  		}), check.IsNil)
   278  
   279  		return nil
   280  	}
   281  	defer snapshotstate.MockBackendIter(fakeIter)()
   282  
   283  	summaries, err := snapshotstate.SnapSummariesInSnapshotSet(setID, []string{"b-snap"})
   284  	c.Assert(err, check.Equals, client.ErrSnapshotSnapsNotFound)
   285  	c.Check(summaries, check.IsNil)
   286  }
   287  
   288  func (snapshotSuite) TestCheckConflict(c *check.C) {
   289  	st := state.New(nil)
   290  	st.Lock()
   291  	defer st.Unlock()
   292  	chg := st.NewChange("some-change", "...")
   293  	tsk := st.NewTask("some-task", "...")
   294  	tsk.SetStatus(state.DoingStatus)
   295  	chg.AddTask(tsk)
   296  
   297  	// no snapshot state
   298  	err := snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task")
   299  	c.Assert(err, check.ErrorMatches, "internal error: task 1 .some-task. is missing snapshot information")
   300  
   301  	// wrong snapshot state
   302  	tsk.Set("snapshot-setup", "hello")
   303  	err = snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task")
   304  	c.Assert(err, check.ErrorMatches, "internal error.* could not unmarshal.*")
   305  
   306  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
   307  
   308  	err = snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task")
   309  	c.Assert(err, check.ErrorMatches, "cannot operate on snapshot set #42 while change \"1\" is in progress")
   310  
   311  	// no change with that label
   312  	c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-other-task"), check.IsNil)
   313  
   314  	// no change with that snapshot id
   315  	c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 43, "some-task"), check.IsNil)
   316  
   317  	// no non-ready change
   318  	tsk.SetStatus(state.DoneStatus)
   319  	c.Assert(snapshotstate.CheckSnapshotTaskConflict(st, 42, "some-task"), check.IsNil)
   320  }
   321  
   322  func (snapshotSuite) TestSaveChecksSnapnamesError(c *check.C) {
   323  	defer snapshotstate.MockSnapstateAll(func(*state.State) (map[string]*snapstate.SnapState, error) {
   324  		return nil, errors.New("bzzt")
   325  	})()
   326  
   327  	st := state.New(nil)
   328  	st.Lock()
   329  	defer st.Unlock()
   330  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   331  	c.Check(err, check.ErrorMatches, "bzzt")
   332  }
   333  
   334  func (snapshotSuite) createConflictingChange(c *check.C) (st *state.State, restore func()) {
   335  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   336  	c.Assert(err, check.IsNil)
   337  	shotfile.Close()
   338  
   339  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   340  		c.Assert(f(&backend.Reader{
   341  			Snapshot: client.Snapshot{SetID: 42, Snap: "foo"},
   342  			File:     shotfile,
   343  		}), check.IsNil)
   344  
   345  		return nil
   346  	}
   347  	restoreIter := snapshotstate.MockBackendIter(fakeIter)
   348  
   349  	o := overlord.Mock()
   350  	st = o.State()
   351  
   352  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   353  	c.Assert(err, check.IsNil)
   354  	o.AddManager(stmgr)
   355  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   356  	o.AddManager(shmgr)
   357  
   358  	st.Lock()
   359  	defer func() {
   360  		if c.Failed() {
   361  			// something went wrong
   362  			st.Unlock()
   363  		}
   364  	}()
   365  
   366  	snapstate.Set(st, "foo", &snapstate.SnapState{
   367  		Active: true,
   368  		Sequence: []*snap.SideInfo{
   369  			{RealName: "foo", Revision: snap.R(1)},
   370  		},
   371  		Current:  snap.R(1),
   372  		SnapType: "app",
   373  	})
   374  
   375  	r := snapstatetest.UseFallbackDeviceModel()
   376  	defer r()
   377  
   378  	chg := st.NewChange("rm foo", "...")
   379  	rmTasks, err := snapstate.Remove(st, "foo", snap.R(0), nil)
   380  	c.Assert(err, check.IsNil)
   381  	c.Assert(rmTasks, check.NotNil)
   382  	chg.AddAll(rmTasks)
   383  
   384  	return st, func() {
   385  		shotfile.Close()
   386  		st.Unlock()
   387  		restoreIter()
   388  	}
   389  }
   390  
   391  func (s snapshotSuite) TestSaveChecksSnapstateConflict(c *check.C) {
   392  	st, restore := s.createConflictingChange(c)
   393  	defer restore()
   394  
   395  	_, _, _, err := snapshotstate.Save(st, []string{"foo"}, nil)
   396  	c.Assert(err, check.NotNil)
   397  	c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{})
   398  }
   399  
   400  func (snapshotSuite) TestSaveConflictsWithSnapstate(c *check.C) {
   401  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   402  		return map[string]*snapstate.SnapState{
   403  			"foo": {Active: true},
   404  		}, nil
   405  	}
   406  
   407  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   408  
   409  	o := overlord.Mock()
   410  	st := o.State()
   411  
   412  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   413  	c.Assert(err, check.IsNil)
   414  	o.AddManager(stmgr)
   415  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   416  	o.AddManager(shmgr)
   417  
   418  	st.Lock()
   419  	defer st.Unlock()
   420  
   421  	snapstate.Set(st, "foo", &snapstate.SnapState{
   422  		Active: true,
   423  		Sequence: []*snap.SideInfo{
   424  			{RealName: "foo", Revision: snap.R(1)},
   425  		},
   426  		Current:  snap.R(1),
   427  		SnapType: "app",
   428  	})
   429  
   430  	chg := st.NewChange("snapshot-save", "...")
   431  	_, _, saveTasks, err := snapshotstate.Save(st, nil, nil)
   432  	c.Assert(err, check.IsNil)
   433  	chg.AddAll(saveTasks)
   434  
   435  	_, err = snapstate.Disable(st, "foo")
   436  	c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-save" change in progress`)
   437  }
   438  
   439  func (snapshotSuite) TestSaveChecksSnapstateConflictError(c *check.C) {
   440  	defer snapshotstate.MockSnapstateCheckChangeConflictMany(func(*state.State, []string, string) error {
   441  		return errors.New("bzzt")
   442  	})()
   443  
   444  	st := state.New(nil)
   445  	st.Lock()
   446  	defer st.Unlock()
   447  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   448  	c.Check(err, check.ErrorMatches, "bzzt")
   449  }
   450  
   451  func (snapshotSuite) TestSaveChecksSetIDError(c *check.C) {
   452  	st := state.New(nil)
   453  	st.Lock()
   454  	defer st.Unlock()
   455  
   456  	st.Set("last-snapshot-set-id", "3/4")
   457  
   458  	_, _, _, err := snapshotstate.Save(st, nil, nil)
   459  	c.Check(err, check.ErrorMatches, ".* could not unmarshal .*")
   460  }
   461  
   462  func (snapshotSuite) TestSaveNoSnapsInState(c *check.C) {
   463  	st := state.New(nil)
   464  	st.Lock()
   465  	defer st.Unlock()
   466  
   467  	setID, saved, taskset, err := snapshotstate.Save(st, nil, nil)
   468  	c.Assert(err, check.IsNil)
   469  	c.Check(setID, check.Equals, uint64(1))
   470  	c.Check(saved, check.HasLen, 0)
   471  	c.Check(taskset.Tasks(), check.HasLen, 0)
   472  }
   473  
   474  func (snapshotSuite) TestSaveSomeSnaps(c *check.C) {
   475  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   476  		return map[string]*snapstate.SnapState{
   477  			"a-snap": {Active: true},
   478  			"b-snap": {},
   479  			"c-snap": {Active: true},
   480  		}, nil
   481  	}
   482  
   483  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   484  
   485  	st := state.New(nil)
   486  	st.Lock()
   487  	defer st.Unlock()
   488  
   489  	setID, saved, taskset, err := snapshotstate.Save(st, nil, nil)
   490  	c.Assert(err, check.IsNil)
   491  	c.Check(setID, check.Equals, uint64(1))
   492  	c.Check(saved, check.DeepEquals, []string{"a-snap", "c-snap"})
   493  	tasks := taskset.Tasks()
   494  	c.Assert(tasks, check.HasLen, 2)
   495  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
   496  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`)
   497  	c.Check(tasks[1].Kind(), check.Equals, "save-snapshot")
   498  	c.Check(tasks[1].Summary(), check.Equals, `Save data of snap "c-snap" in snapshot set #1`)
   499  }
   500  
   501  func (snapshotSuite) TestSaveOneSnap(c *check.C) {
   502  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   503  		// snapstate.All isn't called when a snap name is passed in
   504  		return nil, errors.New("bzzt")
   505  	}
   506  
   507  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   508  
   509  	st := state.New(nil)
   510  	st.Lock()
   511  	defer st.Unlock()
   512  
   513  	setID, saved, taskset, err := snapshotstate.Save(st, []string{"a-snap"}, []string{"a-user"})
   514  	c.Assert(err, check.IsNil)
   515  	c.Check(setID, check.Equals, uint64(1))
   516  	c.Check(saved, check.DeepEquals, []string{"a-snap"})
   517  	tasks := taskset.Tasks()
   518  	c.Assert(tasks, check.HasLen, 1)
   519  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
   520  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "a-snap" in snapshot set #1`)
   521  	var snapshot map[string]interface{}
   522  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
   523  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
   524  		"set-id":  1.,
   525  		"snap":    "a-snap",
   526  		"users":   []interface{}{"a-user"},
   527  		"current": "unset",
   528  	})
   529  }
   530  
   531  func (snapshotSuite) TestSaveIntegration(c *check.C) {
   532  	if os.Geteuid() == 0 {
   533  		c.Skip("this test cannot run as root (runuser will fail)")
   534  	}
   535  
   536  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
   537  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
   538  
   539  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
   540  		if username != "a-user" {
   541  			c.Fatalf("unexpected user %q", username)
   542  		}
   543  		return &user.User{
   544  			Uid:      fmt.Sprint(sys.Geteuid()),
   545  			Username: username,
   546  			HomeDir:  homedir,
   547  		}, nil
   548  	})()
   549  
   550  	o := overlord.Mock()
   551  	st := o.State()
   552  
   553  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   554  	c.Assert(err, check.IsNil)
   555  	o.AddManager(stmgr)
   556  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   557  	o.AddManager(shmgr)
   558  	o.AddManager(o.TaskRunner())
   559  
   560  	st.Lock()
   561  	defer st.Unlock()
   562  
   563  	snapshots := make(map[string]*client.Snapshot, 3)
   564  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
   565  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
   566  		snapstate.Set(st, name, &snapstate.SnapState{
   567  			Active:   true,
   568  			Sequence: []*snap.SideInfo{sideInfo},
   569  			Current:  sideInfo.Revision,
   570  			SnapType: "app",
   571  		})
   572  		snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
   573  
   574  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
   575  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil)
   576  
   577  		snapshots[name] = &client.Snapshot{
   578  			SetID:    1,
   579  			Snap:     name,
   580  			Version:  "v1",
   581  			Revision: sideInfo.Revision,
   582  			Epoch:    snap.E("0"),
   583  		}
   584  	}
   585  
   586  	setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"})
   587  	c.Assert(err, check.IsNil)
   588  	c.Check(setID, check.Equals, uint64(1))
   589  	c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
   590  
   591  	change := st.NewChange("save-snapshot", "...")
   592  	change.AddAll(taskset)
   593  
   594  	t0 := time.Now()
   595  
   596  	st.Unlock()
   597  	c.Assert(o.Settle(5*time.Second), check.IsNil)
   598  	st.Lock()
   599  	c.Check(change.Err(), check.IsNil)
   600  
   601  	tf := time.Now()
   602  	c.Assert(backend.Iter(context.TODO(), func(r *backend.Reader) error {
   603  		c.Check(r.Check(context.TODO(), nil), check.IsNil)
   604  
   605  		// check the unknowables, and zero them out
   606  		c.Check(r.Snapshot.Time.After(t0), check.Equals, true)
   607  		c.Check(r.Snapshot.Time.Before(tf), check.Equals, true)
   608  		c.Check(r.Snapshot.Size > 0, check.Equals, true)
   609  		c.Assert(r.Snapshot.SHA3_384, check.HasLen, 1)
   610  		c.Check(r.Snapshot.SHA3_384["user/a-user.tgz"], check.HasLen, 96)
   611  
   612  		r.Snapshot.Time = time.Time{}
   613  		r.Snapshot.Size = 0
   614  		r.Snapshot.SHA3_384 = nil
   615  
   616  		c.Check(&r.Snapshot, check.DeepEquals, snapshots[r.Snapshot.Snap])
   617  		return nil
   618  	}), check.IsNil)
   619  }
   620  
   621  func (snapshotSuite) TestSaveIntegrationFails(c *check.C) {
   622  	if os.Geteuid() == 0 {
   623  		c.Skip("this test cannot run as root (runuser will fail)")
   624  	}
   625  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
   626  	// sanity check: no files in snapshot dir
   627  	out, err := exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   628  	c.Assert(err, check.IsNil)
   629  	c.Check(string(out), check.Equals, "")
   630  
   631  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
   632  
   633  	// Mock "tar" so that the tars finish in the expected order.
   634  	// Locally .01s and .02s do the trick with count=1000;
   635  	// padded a lot bigger for slower systems.
   636  	mocktar := testutil.MockCommand(c, "tar", `
   637  case "$*" in
   638  */too-snap/*)
   639      sleep .5
   640      ;;
   641  */tri-snap/*)
   642      sleep 1
   643      ;;
   644  esac
   645  export LANG=C
   646  exec /bin/tar "$@"
   647  `)
   648  	defer mocktar.Restore()
   649  
   650  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
   651  		if username != "a-user" {
   652  			c.Fatalf("unexpected user %q", username)
   653  		}
   654  		return &user.User{
   655  			Uid:      fmt.Sprint(sys.Geteuid()),
   656  			Username: username,
   657  			HomeDir:  homedir,
   658  		}, nil
   659  	})()
   660  
   661  	o := overlord.Mock()
   662  	st := o.State()
   663  
   664  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   665  	c.Assert(err, check.IsNil)
   666  	o.AddManager(stmgr)
   667  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   668  	o.AddManager(shmgr)
   669  	o.AddManager(o.TaskRunner())
   670  
   671  	st.Lock()
   672  	defer st.Unlock()
   673  
   674  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
   675  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
   676  		snapstate.Set(st, name, &snapstate.SnapState{
   677  			Active:   true,
   678  			Sequence: []*snap.SideInfo{sideInfo},
   679  			Current:  sideInfo.Revision,
   680  			SnapType: "app",
   681  		})
   682  		snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
   683  
   684  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
   685  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common"), 0755), check.IsNil)
   686  		mode := os.FileMode(0755)
   687  		if i == 1 {
   688  			mode = 0
   689  		}
   690  		c.Assert(os.Mkdir(filepath.Join(homedir, "snap", name, "common", "common-"+name), mode), check.IsNil)
   691  	}
   692  
   693  	setID, saved, taskset, err := snapshotstate.Save(st, nil, []string{"a-user"})
   694  	c.Assert(err, check.IsNil)
   695  	c.Check(setID, check.Equals, uint64(1))
   696  	c.Check(saved, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
   697  
   698  	change := st.NewChange("save-snapshot", "...")
   699  	change.AddAll(taskset)
   700  
   701  	st.Unlock()
   702  	c.Assert(o.Settle(5*time.Second), check.IsNil)
   703  	st.Lock()
   704  	c.Check(change.Err(), check.NotNil)
   705  	tasks := change.Tasks()
   706  	c.Assert(tasks, check.HasLen, 3)
   707  
   708  	// task 0 (for "one-snap") will have been undone
   709  	c.Check(tasks[0].Summary(), testutil.Contains, `"one-snap"`) // sanity check: task 0 is one-snap's
   710  	c.Check(tasks[0].Status(), check.Equals, state.UndoneStatus)
   711  
   712  	// task 1 (for "too-snap") will have errored
   713  	c.Check(tasks[1].Summary(), testutil.Contains, `"too-snap"`) // sanity check: task 1 is too-snap's
   714  	c.Check(tasks[1].Status(), check.Equals, state.ErrorStatus)
   715  	c.Check(strings.Join(tasks[1].Log(), "\n"), check.Matches, `\S+ ERROR cannot create archive: .* Permission denied .and \d+ more.`)
   716  
   717  	// task 2 (for "tri-snap") will have errored as well, hopefully, but it's a race (see the "tar" comment above)
   718  	c.Check(tasks[2].Summary(), testutil.Contains, `"tri-snap"`) // sanity check: task 2 is tri-snap's
   719  	c.Check(tasks[2].Status(), check.Equals, state.ErrorStatus, check.Commentf("if this ever fails, duplicate the fake tar sleeps please"))
   720  	// sometimes you'll get one, sometimes you'll get the other (depending on ordering of events)
   721  	c.Check(strings.Join(tasks[2].Log(), "\n"), check.Matches, `\S+ ERROR( tar failed:)? context canceled`)
   722  
   723  	// no zips left behind, not for errors, not for undos \o/
   724  	out, err = exec.Command("find", dirs.SnapshotsDir, "-type", "f").CombinedOutput()
   725  	c.Assert(err, check.IsNil)
   726  	c.Check(string(out), check.Equals, "")
   727  }
   728  
   729  func (snapshotSuite) TestRestoreChecksIterError(c *check.C) {
   730  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
   731  		return errors.New("bzzt")
   732  	})()
   733  
   734  	st := state.New(nil)
   735  	st.Lock()
   736  	defer st.Unlock()
   737  
   738  	_, _, err := snapshotstate.Restore(st, 42, nil, nil)
   739  	c.Assert(err, check.ErrorMatches, "bzzt")
   740  }
   741  
   742  func (s snapshotSuite) TestRestoreChecksSnapstateConflicts(c *check.C) {
   743  	st, restore := s.createConflictingChange(c)
   744  	defer restore()
   745  
   746  	_, _, err := snapshotstate.Restore(st, 42, nil, nil)
   747  	c.Assert(err, check.NotNil)
   748  	c.Check(err, check.FitsTypeOf, &snapstate.ChangeConflictError{})
   749  
   750  }
   751  
   752  func (snapshotSuite) TestRestoreConflictsWithSnapstate(c *check.C) {
   753  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   754  	c.Assert(err, check.IsNil)
   755  	defer shotfile.Close()
   756  
   757  	sideInfo := &snap.SideInfo{RealName: "foo", Revision: snap.R(1)}
   758  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   759  		return map[string]*snapstate.SnapState{
   760  			"foo": {
   761  				Active:   true,
   762  				Sequence: []*snap.SideInfo{sideInfo},
   763  				Current:  sideInfo.Revision,
   764  			},
   765  		}, nil
   766  	}
   767  	snaptest.MockSnap(c, "{name: foo, version: v1}", sideInfo)
   768  
   769  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   770  
   771  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   772  		c.Assert(f(&backend.Reader{
   773  			Snapshot: client.Snapshot{SetID: 42, Snap: "foo"},
   774  			File:     shotfile,
   775  		}), check.IsNil)
   776  
   777  		return nil
   778  	}
   779  	defer snapshotstate.MockBackendIter(fakeIter)()
   780  
   781  	o := overlord.Mock()
   782  	st := o.State()
   783  
   784  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
   785  	c.Assert(err, check.IsNil)
   786  	o.AddManager(stmgr)
   787  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
   788  	o.AddManager(shmgr)
   789  
   790  	st.Lock()
   791  	defer st.Unlock()
   792  
   793  	snapstate.Set(st, "foo", &snapstate.SnapState{
   794  		Active: true,
   795  		Sequence: []*snap.SideInfo{
   796  			{RealName: "foo", Revision: snap.R(1)},
   797  		},
   798  		Current:  snap.R(1),
   799  		SnapType: "app",
   800  	})
   801  
   802  	chg := st.NewChange("snapshot-restore", "...")
   803  	_, restoreTasks, err := snapshotstate.Restore(st, 42, nil, nil)
   804  	c.Assert(err, check.IsNil)
   805  	chg.AddAll(restoreTasks)
   806  
   807  	_, err = snapstate.Disable(st, "foo")
   808  	c.Assert(err, check.ErrorMatches, `snap "foo" has "snapshot-restore" change in progress`)
   809  }
   810  
   811  func (snapshotSuite) TestRestoreChecksForgetConflicts(c *check.C) {
   812  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   813  	c.Assert(err, check.IsNil)
   814  	defer shotfile.Close()
   815  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   816  		c.Assert(f(&backend.Reader{
   817  			// not wanted
   818  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
   819  			File:     shotfile,
   820  		}), check.IsNil)
   821  
   822  		return nil
   823  	}
   824  	defer snapshotstate.MockBackendIter(fakeIter)()
   825  
   826  	st := state.New(nil)
   827  	st.Lock()
   828  	defer st.Unlock()
   829  	chg := st.NewChange("forget-snapshot-change", "...")
   830  	tsk := st.NewTask("forget-snapshot", "...")
   831  	tsk.SetStatus(state.DoingStatus)
   832  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
   833  	chg.AddTask(tsk)
   834  
   835  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
   836  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
   837  }
   838  
   839  func (snapshotSuite) TestRestoreChecksChangesToSnapID(c *check.C) {
   840  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   841  	c.Assert(err, check.IsNil)
   842  	defer shotfile.Close()
   843  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   844  		return map[string]*snapstate.SnapState{
   845  			"a-snap": {
   846  				Active: true,
   847  				Sequence: []*snap.SideInfo{
   848  					{RealName: "a-snap", Revision: snap.R(1), SnapID: "1234567890"},
   849  				},
   850  				Current: snap.R(1),
   851  			},
   852  		}, nil
   853  	}
   854  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   855  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   856  		c.Assert(f(&backend.Reader{
   857  			// not wanted
   858  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap", SnapID: "0987654321"},
   859  			File:     shotfile,
   860  		}), check.IsNil)
   861  
   862  		return nil
   863  	}
   864  	defer snapshotstate.MockBackendIter(fakeIter)()
   865  
   866  	st := state.New(nil)
   867  	st.Lock()
   868  	defer st.Unlock()
   869  
   870  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
   871  	c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(ID 1234567…\) does not match snapshot \(ID 0987654…\)`)
   872  }
   873  
   874  func (snapshotSuite) TestRestoreChecksChangesToEpoch(c *check.C) {
   875  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   876  	c.Assert(err, check.IsNil)
   877  	defer shotfile.Close()
   878  
   879  	sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)}
   880  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   881  		return map[string]*snapstate.SnapState{
   882  			"a-snap": {
   883  				Active:   true,
   884  				Sequence: []*snap.SideInfo{sideInfo},
   885  				Current:  sideInfo.Revision,
   886  			},
   887  		}, nil
   888  	}
   889  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   890  	snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: 17}", sideInfo)
   891  
   892  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   893  		c.Assert(f(&backend.Reader{
   894  			// not wanted
   895  			Snapshot: client.Snapshot{
   896  				SetID: 42,
   897  				Snap:  "a-snap",
   898  				Epoch: snap.E("42"),
   899  			},
   900  			File: shotfile,
   901  		}), check.IsNil)
   902  
   903  		return nil
   904  	}
   905  	defer snapshotstate.MockBackendIter(fakeIter)()
   906  
   907  	st := state.New(nil)
   908  	st.Lock()
   909  	defer st.Unlock()
   910  
   911  	_, _, err = snapshotstate.Restore(st, 42, nil, nil)
   912  	c.Assert(err, check.ErrorMatches, `cannot restore snapshot for "a-snap": current snap \(epoch 17\) cannot read snapshot data \(epoch 42\)`)
   913  }
   914  
   915  func (snapshotSuite) TestRestoreWorksWithCompatibleEpoch(c *check.C) {
   916  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   917  	c.Assert(err, check.IsNil)
   918  	defer shotfile.Close()
   919  
   920  	sideInfo := &snap.SideInfo{RealName: "a-snap", Revision: snap.R(1)}
   921  	fakeSnapstateAll := func(*state.State) (map[string]*snapstate.SnapState, error) {
   922  		return map[string]*snapstate.SnapState{
   923  			"a-snap": {
   924  				Active:   true,
   925  				Sequence: []*snap.SideInfo{sideInfo},
   926  				Current:  sideInfo.Revision,
   927  			},
   928  		}, nil
   929  	}
   930  	defer snapshotstate.MockSnapstateAll(fakeSnapstateAll)()
   931  	snaptest.MockSnap(c, "{name: a-snap, version: v1, epoch: {read: [17, 42], write: [42]}}", sideInfo)
   932  
   933  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   934  		c.Assert(f(&backend.Reader{
   935  			// not wanted
   936  			Snapshot: client.Snapshot{
   937  				SetID: 42,
   938  				Snap:  "a-snap",
   939  				Epoch: snap.E("17"),
   940  			},
   941  			File: shotfile,
   942  		}), check.IsNil)
   943  
   944  		return nil
   945  	}
   946  	defer snapshotstate.MockBackendIter(fakeIter)()
   947  
   948  	st := state.New(nil)
   949  	st.Lock()
   950  	defer st.Unlock()
   951  
   952  	found, taskset, err := snapshotstate.Restore(st, 42, nil, nil)
   953  	c.Assert(err, check.IsNil)
   954  	c.Check(found, check.DeepEquals, []string{"a-snap"})
   955  	tasks := taskset.Tasks()
   956  	c.Assert(tasks, check.HasLen, 1)
   957  	c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot")
   958  	c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`)
   959  	var snapshot map[string]interface{}
   960  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
   961  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
   962  		"set-id":   42.,
   963  		"snap":     "a-snap",
   964  		"filename": shotfile.Name(),
   965  		"current":  "1",
   966  	})
   967  }
   968  
   969  func (snapshotSuite) TestRestore(c *check.C) {
   970  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
   971  	c.Assert(err, check.IsNil)
   972  	defer shotfile.Close()
   973  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   974  		c.Assert(f(&backend.Reader{
   975  			// not wanted
   976  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
   977  			File:     shotfile,
   978  		}), check.IsNil)
   979  
   980  		return nil
   981  	}
   982  	defer snapshotstate.MockBackendIter(fakeIter)()
   983  
   984  	st := state.New(nil)
   985  	st.Lock()
   986  	defer st.Unlock()
   987  
   988  	found, taskset, err := snapshotstate.Restore(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"})
   989  	c.Assert(err, check.IsNil)
   990  	c.Check(found, check.DeepEquals, []string{"a-snap"})
   991  	tasks := taskset.Tasks()
   992  	c.Assert(tasks, check.HasLen, 1)
   993  	c.Check(tasks[0].Kind(), check.Equals, "restore-snapshot")
   994  	c.Check(tasks[0].Summary(), check.Equals, `Restore data of snap "a-snap" from snapshot set #42`)
   995  	var snapshot map[string]interface{}
   996  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
   997  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
   998  		"set-id":   42.,
   999  		"snap":     "a-snap",
  1000  		"filename": shotfile.Name(),
  1001  		"users":    []interface{}{"a-user"},
  1002  		"current":  "unset",
  1003  	})
  1004  }
  1005  
  1006  func (snapshotSuite) TestRestoreIntegration(c *check.C) {
  1007  	if os.Geteuid() == 0 {
  1008  		c.Skip("this test cannot run as root (runuser will fail)")
  1009  	}
  1010  
  1011  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
  1012  	homedirA := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
  1013  	homedirB := filepath.Join(dirs.GlobalRootDir, "home", "b-user")
  1014  
  1015  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
  1016  		if username != "a-user" && username != "b-user" {
  1017  			c.Fatalf("unexpected user %q", username)
  1018  			return nil, user.UnknownUserError(username)
  1019  		}
  1020  		return &user.User{
  1021  			Uid:      fmt.Sprint(sys.Geteuid()),
  1022  			Username: username,
  1023  			HomeDir:  filepath.Join(dirs.GlobalRootDir, "home", username),
  1024  		}, nil
  1025  
  1026  	})()
  1027  
  1028  	o := overlord.Mock()
  1029  	st := o.State()
  1030  
  1031  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
  1032  	c.Assert(err, check.IsNil)
  1033  	o.AddManager(stmgr)
  1034  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
  1035  	o.AddManager(shmgr)
  1036  	o.AddManager(o.TaskRunner())
  1037  
  1038  	st.Lock()
  1039  	defer st.Unlock()
  1040  
  1041  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
  1042  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
  1043  		snapstate.Set(st, name, &snapstate.SnapState{
  1044  			Active:   true,
  1045  			Sequence: []*snap.SideInfo{sideInfo},
  1046  			Current:  sideInfo.Revision,
  1047  			SnapType: "app",
  1048  		})
  1049  		snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: v1}", name), sideInfo)
  1050  
  1051  		for _, home := range []string{homedirA, homedirB} {
  1052  			c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
  1053  			c.Assert(os.MkdirAll(filepath.Join(home, "snap", name, "common", "common-"+name), 0755), check.IsNil)
  1054  		}
  1055  
  1056  		_, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user", "b-user"}, nil)
  1057  		c.Assert(err, check.IsNil)
  1058  	}
  1059  
  1060  	// move the old away
  1061  	c.Assert(os.Rename(filepath.Join(homedirA, "snap"), filepath.Join(homedirA, "snap.old")), check.IsNil)
  1062  	// remove b-user's home
  1063  	c.Assert(os.RemoveAll(homedirB), check.IsNil)
  1064  
  1065  	found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user", "b-user"})
  1066  	c.Assert(err, check.IsNil)
  1067  	sort.Strings(found)
  1068  	c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
  1069  
  1070  	change := st.NewChange("restore-snapshot", "...")
  1071  	change.AddAll(taskset)
  1072  
  1073  	st.Unlock()
  1074  	c.Assert(o.Settle(5*time.Second), check.IsNil)
  1075  	st.Lock()
  1076  	c.Check(change.Err(), check.IsNil)
  1077  
  1078  	// the three restores warn about the missing home (but no errors, no panics)
  1079  	for _, task := range change.Tasks() {
  1080  		c.Check(strings.Join(task.Log(), "\n"), check.Matches, `.* Skipping restore of "[^"]+/home/b-user/[^"]+" as "[^"]+/home/b-user" doesn't exist.`)
  1081  	}
  1082  
  1083  	// check it was all brought back \o/
  1084  	out, err := exec.Command("diff", "-rN", filepath.Join(homedirA, "snap"), filepath.Join("snap.old")).CombinedOutput()
  1085  	c.Assert(err, check.IsNil)
  1086  	c.Check(string(out), check.Equals, "")
  1087  }
  1088  
  1089  func (snapshotSuite) TestRestoreIntegrationFails(c *check.C) {
  1090  	if os.Geteuid() == 0 {
  1091  		c.Skip("this test cannot run as root (runuser will fail)")
  1092  	}
  1093  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, 0755), check.IsNil)
  1094  	homedir := filepath.Join(dirs.GlobalRootDir, "home", "a-user")
  1095  
  1096  	defer backend.MockUserLookup(func(username string) (*user.User, error) {
  1097  		if username != "a-user" {
  1098  			c.Fatalf("unexpected user %q", username)
  1099  		}
  1100  		return &user.User{
  1101  			Uid:      fmt.Sprint(sys.Geteuid()),
  1102  			Username: username,
  1103  			HomeDir:  homedir,
  1104  		}, nil
  1105  	})()
  1106  
  1107  	o := overlord.Mock()
  1108  	st := o.State()
  1109  
  1110  	stmgr, err := snapstate.Manager(st, o.TaskRunner())
  1111  	c.Assert(err, check.IsNil)
  1112  	o.AddManager(stmgr)
  1113  	shmgr := snapshotstate.Manager(st, o.TaskRunner())
  1114  	o.AddManager(shmgr)
  1115  	o.AddManager(o.TaskRunner())
  1116  
  1117  	st.Lock()
  1118  	defer st.Unlock()
  1119  
  1120  	for i, name := range []string{"one-snap", "too-snap", "tri-snap"} {
  1121  		sideInfo := &snap.SideInfo{RealName: name, Revision: snap.R(i + 1)}
  1122  		snapstate.Set(st, name, &snapstate.SnapState{
  1123  			Active:   true,
  1124  			Sequence: []*snap.SideInfo{sideInfo},
  1125  			Current:  sideInfo.Revision,
  1126  			SnapType: "app",
  1127  		})
  1128  		snapInfo := snaptest.MockSnap(c, fmt.Sprintf("{name: %s, version: vv1}", name), sideInfo)
  1129  
  1130  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, fmt.Sprint(i+1), "canary-"+name), 0755), check.IsNil)
  1131  		c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", name, "common", "common-"+name), 0755), check.IsNil)
  1132  
  1133  		_, err := backend.Save(context.TODO(), 42, snapInfo, nil, []string{"a-user"}, nil)
  1134  		c.Assert(err, check.IsNil)
  1135  	}
  1136  
  1137  	// move the old away
  1138  	c.Assert(os.Rename(filepath.Join(homedir, "snap"), filepath.Join(homedir, "snap.old")), check.IsNil)
  1139  	// but poison the well
  1140  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap"), 0755), check.IsNil)
  1141  	c.Assert(os.MkdirAll(filepath.Join(homedir, "snap", "too-snap"), 0), check.IsNil)
  1142  
  1143  	found, taskset, err := snapshotstate.Restore(st, 42, nil, []string{"a-user"})
  1144  	c.Assert(err, check.IsNil)
  1145  	sort.Strings(found)
  1146  	c.Check(found, check.DeepEquals, []string{"one-snap", "too-snap", "tri-snap"})
  1147  
  1148  	change := st.NewChange("restore-snapshot", "...")
  1149  	change.AddAll(taskset)
  1150  
  1151  	st.Unlock()
  1152  	c.Assert(o.Settle(5*time.Second), check.IsNil)
  1153  	st.Lock()
  1154  	c.Check(change.Err(), check.NotNil)
  1155  
  1156  	tasks := change.Tasks()
  1157  	c.Check(tasks, check.HasLen, 3)
  1158  	for _, task := range tasks {
  1159  		if strings.Contains(task.Summary(), `"too-snap"`) {
  1160  			// too-snap was set up to fail, should always fail with
  1161  			// 'permission denied' (see the mkdirall w/mode 0 above)
  1162  			c.Check(task.Status(), check.Equals, state.ErrorStatus)
  1163  			c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR mkdir \S+: permission denied`)
  1164  		} else {
  1165  			// the other two might fail (ErrorStatus) if they're
  1166  			// still running when too-snap fails, or they might have
  1167  			// finished and needed to be undone (UndoneStatus); it's
  1168  			// a race, but either is fine.
  1169  			if task.Status() == state.ErrorStatus {
  1170  				c.Check(strings.Join(task.Log(), "\n"), check.Matches, `\S+ ERROR.* context canceled`)
  1171  			} else {
  1172  				c.Check(task.Status(), check.Equals, state.UndoneStatus)
  1173  			}
  1174  		}
  1175  	}
  1176  
  1177  	// remove the poison
  1178  	c.Assert(os.Remove(filepath.Join(homedir, "snap", "too-snap")), check.IsNil)
  1179  
  1180  	// check that nothing else was put there
  1181  	out, err := exec.Command("find", filepath.Join(homedir, "snap")).CombinedOutput()
  1182  	c.Assert(err, check.IsNil)
  1183  	c.Check(strings.TrimSpace(string(out)), check.Equals, filepath.Join(homedir, "snap"))
  1184  }
  1185  
  1186  func (snapshotSuite) TestCheckChecksIterError(c *check.C) {
  1187  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
  1188  		return errors.New("bzzt")
  1189  	})()
  1190  
  1191  	st := state.New(nil)
  1192  	st.Lock()
  1193  	defer st.Unlock()
  1194  
  1195  	_, _, err := snapshotstate.Check(st, 42, nil, nil)
  1196  	c.Assert(err, check.ErrorMatches, "bzzt")
  1197  }
  1198  
  1199  func (s snapshotSuite) TestCheckDoesNotTriggerSnapstateConflict(c *check.C) {
  1200  	st, restore := s.createConflictingChange(c)
  1201  	defer restore()
  1202  
  1203  	_, _, err := snapshotstate.Check(st, 42, nil, nil)
  1204  	c.Assert(err, check.IsNil)
  1205  }
  1206  
  1207  func (snapshotSuite) TestCheckChecksForgetConflicts(c *check.C) {
  1208  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1209  	c.Assert(err, check.IsNil)
  1210  	defer shotfile.Close()
  1211  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1212  		c.Assert(f(&backend.Reader{
  1213  			// not wanted
  1214  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1215  			File:     shotfile,
  1216  		}), check.IsNil)
  1217  
  1218  		return nil
  1219  	}
  1220  	defer snapshotstate.MockBackendIter(fakeIter)()
  1221  
  1222  	st := state.New(nil)
  1223  	st.Lock()
  1224  	defer st.Unlock()
  1225  	chg := st.NewChange("forget-snapshot-change", "...")
  1226  	tsk := st.NewTask("forget-snapshot", "...")
  1227  	tsk.SetStatus(state.DoingStatus)
  1228  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1229  	chg.AddTask(tsk)
  1230  
  1231  	_, _, err = snapshotstate.Check(st, 42, nil, nil)
  1232  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1233  }
  1234  
  1235  func (snapshotSuite) TestCheck(c *check.C) {
  1236  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1237  	c.Assert(err, check.IsNil)
  1238  	defer shotfile.Close()
  1239  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1240  		c.Assert(f(&backend.Reader{
  1241  			// not wanted
  1242  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1243  			File:     shotfile,
  1244  		}), check.IsNil)
  1245  
  1246  		return nil
  1247  	}
  1248  	defer snapshotstate.MockBackendIter(fakeIter)()
  1249  
  1250  	st := state.New(nil)
  1251  	st.Lock()
  1252  	defer st.Unlock()
  1253  
  1254  	found, taskset, err := snapshotstate.Check(st, 42, []string{"a-snap", "b-snap"}, []string{"a-user"})
  1255  	c.Assert(err, check.IsNil)
  1256  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1257  	tasks := taskset.Tasks()
  1258  	c.Assert(tasks, check.HasLen, 1)
  1259  	c.Check(tasks[0].Kind(), check.Equals, "check-snapshot")
  1260  	c.Check(tasks[0].Summary(), check.Equals, `Check data of snap "a-snap" in snapshot set #42`)
  1261  	var snapshot map[string]interface{}
  1262  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1263  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1264  		"set-id":   42.,
  1265  		"snap":     "a-snap",
  1266  		"filename": shotfile.Name(),
  1267  		"users":    []interface{}{"a-user"},
  1268  		"current":  "unset",
  1269  	})
  1270  }
  1271  
  1272  func (snapshotSuite) TestForgetChecksIterError(c *check.C) {
  1273  	defer snapshotstate.MockBackendIter(func(context.Context, func(*backend.Reader) error) error {
  1274  		return errors.New("bzzt")
  1275  	})()
  1276  
  1277  	st := state.New(nil)
  1278  	st.Lock()
  1279  	defer st.Unlock()
  1280  
  1281  	_, _, err := snapshotstate.Forget(st, 42, nil)
  1282  	c.Assert(err, check.ErrorMatches, "bzzt")
  1283  }
  1284  
  1285  func (s snapshotSuite) TestForgetDoesNotTriggerSnapstateConflict(c *check.C) {
  1286  	st, restore := s.createConflictingChange(c)
  1287  	defer restore()
  1288  
  1289  	_, _, err := snapshotstate.Forget(st, 42, nil)
  1290  	c.Assert(err, check.IsNil)
  1291  }
  1292  
  1293  func (snapshotSuite) TestForgetChecksCheckConflicts(c *check.C) {
  1294  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1295  	c.Assert(err, check.IsNil)
  1296  	defer shotfile.Close()
  1297  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1298  		c.Assert(f(&backend.Reader{
  1299  			// not wanted
  1300  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1301  			File:     shotfile,
  1302  		}), check.IsNil)
  1303  
  1304  		return nil
  1305  	}
  1306  	defer snapshotstate.MockBackendIter(fakeIter)()
  1307  
  1308  	st := state.New(nil)
  1309  	st.Lock()
  1310  	defer st.Unlock()
  1311  	chg := st.NewChange("check-snapshot-change", "...")
  1312  	tsk := st.NewTask("check-snapshot", "...")
  1313  	tsk.SetStatus(state.DoingStatus)
  1314  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1315  	chg.AddTask(tsk)
  1316  
  1317  	_, _, err = snapshotstate.Forget(st, 42, nil)
  1318  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1319  }
  1320  
  1321  func (snapshotSuite) TestForgetChecksRestoreConflicts(c *check.C) {
  1322  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1323  	c.Assert(err, check.IsNil)
  1324  	defer shotfile.Close()
  1325  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1326  		c.Assert(f(&backend.Reader{
  1327  			// not wanted
  1328  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1329  			File:     shotfile,
  1330  		}), check.IsNil)
  1331  
  1332  		return nil
  1333  	}
  1334  	defer snapshotstate.MockBackendIter(fakeIter)()
  1335  
  1336  	st := state.New(nil)
  1337  	st.Lock()
  1338  	defer st.Unlock()
  1339  	chg := st.NewChange("restore-snapshot-change", "...")
  1340  	tsk := st.NewTask("restore-snapshot", "...")
  1341  	tsk.SetStatus(state.DoingStatus)
  1342  	tsk.Set("snapshot-setup", map[string]int{"set-id": 42})
  1343  	chg.AddTask(tsk)
  1344  
  1345  	_, _, err = snapshotstate.Forget(st, 42, nil)
  1346  	c.Assert(err, check.ErrorMatches, `cannot operate on snapshot set #42 while change \"1\" is in progress`)
  1347  }
  1348  
  1349  func (snapshotSuite) TestForget(c *check.C) {
  1350  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "yadda.zip"))
  1351  	c.Assert(err, check.IsNil)
  1352  	defer shotfile.Close()
  1353  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
  1354  		c.Assert(f(&backend.Reader{
  1355  			// not wanted
  1356  			Snapshot: client.Snapshot{SetID: 42, Snap: "a-snap"},
  1357  			File:     shotfile,
  1358  		}), check.IsNil)
  1359  
  1360  		return nil
  1361  	}
  1362  	defer snapshotstate.MockBackendIter(fakeIter)()
  1363  
  1364  	st := state.New(nil)
  1365  	st.Lock()
  1366  	defer st.Unlock()
  1367  
  1368  	found, taskset, err := snapshotstate.Forget(st, 42, []string{"a-snap", "b-snap"})
  1369  	c.Assert(err, check.IsNil)
  1370  	c.Check(found, check.DeepEquals, []string{"a-snap"})
  1371  	tasks := taskset.Tasks()
  1372  	c.Assert(tasks, check.HasLen, 1)
  1373  	c.Check(tasks[0].Kind(), check.Equals, "forget-snapshot")
  1374  	c.Check(tasks[0].Summary(), check.Equals, `Drop data of snap "a-snap" from snapshot set #42`)
  1375  	var snapshot map[string]interface{}
  1376  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1377  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1378  		"set-id":   42.,
  1379  		"snap":     "a-snap",
  1380  		"filename": shotfile.Name(),
  1381  		"current":  "unset",
  1382  	})
  1383  }
  1384  
  1385  func (snapshotSuite) TestSaveExpiration(c *check.C) {
  1386  	st := state.New(nil)
  1387  	st.Lock()
  1388  	defer st.Unlock()
  1389  
  1390  	var expirations map[uint64]interface{}
  1391  	tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z")
  1392  	c.Assert(err, check.IsNil)
  1393  	c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil)
  1394  
  1395  	tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z")
  1396  	c.Assert(err, check.IsNil)
  1397  	c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil)
  1398  
  1399  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
  1400  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
  1401  		12: map[string]interface{}{"expiry-time": "2019-03-11T11:24:00Z"},
  1402  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:50:00Z"},
  1403  	})
  1404  }
  1405  
  1406  func (snapshotSuite) TestRemoveSnapshotState(c *check.C) {
  1407  	st := state.New(nil)
  1408  	st.Lock()
  1409  	defer st.Unlock()
  1410  
  1411  	st.Set("snapshots", map[uint64]interface{}{
  1412  		12: map[string]interface{}{"expiry-time": "2019-01-11T11:11:00Z"},
  1413  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1414  		14: map[string]interface{}{"expiry-time": "2019-03-12T13:11:00Z"},
  1415  	})
  1416  
  1417  	snapshotstate.RemoveSnapshotState(st, 12, 14)
  1418  
  1419  	var snapshots map[uint64]interface{}
  1420  	c.Assert(st.Get("snapshots", &snapshots), check.IsNil)
  1421  	c.Check(snapshots, check.DeepEquals, map[uint64]interface{}{
  1422  		13: map[string]interface{}{"expiry-time": "2019-02-12T12:11:00Z"},
  1423  	})
  1424  }
  1425  
  1426  func (snapshotSuite) TestExpiredSnapshotSets(c *check.C) {
  1427  	st := state.New(nil)
  1428  	st.Lock()
  1429  	defer st.Unlock()
  1430  
  1431  	tm, err := time.Parse(time.RFC3339, "2019-03-11T11:24:00Z")
  1432  	c.Assert(err, check.IsNil)
  1433  	c.Assert(snapshotstate.SaveExpiration(st, 12, tm), check.IsNil)
  1434  
  1435  	tm, err = time.Parse(time.RFC3339, "2019-02-12T12:50:00Z")
  1436  	c.Assert(err, check.IsNil)
  1437  	c.Assert(snapshotstate.SaveExpiration(st, 13, tm), check.IsNil)
  1438  
  1439  	tm, err = time.Parse(time.RFC3339, "2020-03-11T11:24:00Z")
  1440  	c.Assert(err, check.IsNil)
  1441  	expired, err := snapshotstate.ExpiredSnapshotSets(st, tm)
  1442  	c.Assert(err, check.IsNil)
  1443  	c.Check(expired, check.DeepEquals, map[uint64]bool{12: true, 13: true})
  1444  
  1445  	tm, err = time.Parse(time.RFC3339, "2019-03-01T11:24:00Z")
  1446  	c.Assert(err, check.IsNil)
  1447  	expired, err = snapshotstate.ExpiredSnapshotSets(st, tm)
  1448  	c.Assert(err, check.IsNil)
  1449  	c.Check(expired, check.DeepEquals, map[uint64]bool{13: true})
  1450  }
  1451  
  1452  func (snapshotSuite) TestAutomaticSnapshotDisabled(c *check.C) {
  1453  	st := state.New(nil)
  1454  	st.Lock()
  1455  	defer st.Unlock()
  1456  
  1457  	tr := config.NewTransaction(st)
  1458  	tr.Set("core", "snapshots.automatic.retention", "no")
  1459  	tr.Commit()
  1460  
  1461  	_, err := snapshotstate.AutomaticSnapshot(st, "foo")
  1462  	c.Assert(err, check.Equals, snapstate.ErrNothingToDo)
  1463  }
  1464  
  1465  func (snapshotSuite) TestAutomaticSnapshot(c *check.C) {
  1466  	st := state.New(nil)
  1467  	st.Lock()
  1468  	defer st.Unlock()
  1469  
  1470  	tr := config.NewTransaction(st)
  1471  	tr.Set("core", "snapshots.automatic.retention", "24h")
  1472  	tr.Commit()
  1473  
  1474  	ts, err := snapshotstate.AutomaticSnapshot(st, "foo")
  1475  	c.Assert(err, check.IsNil)
  1476  
  1477  	tasks := ts.Tasks()
  1478  	c.Assert(tasks, check.HasLen, 1)
  1479  	c.Check(tasks[0].Kind(), check.Equals, "save-snapshot")
  1480  	c.Check(tasks[0].Summary(), check.Equals, `Save data of snap "foo" in automatic snapshot set #1`)
  1481  	var snapshot map[string]interface{}
  1482  	c.Check(tasks[0].Get("snapshot-setup", &snapshot), check.IsNil)
  1483  	c.Check(snapshot, check.DeepEquals, map[string]interface{}{
  1484  		"set-id":  1.,
  1485  		"snap":    "foo",
  1486  		"current": "unset",
  1487  		"auto":    true,
  1488  	})
  1489  }
  1490  
  1491  func (snapshotSuite) TestAutomaticSnapshotDefaultClassic(c *check.C) {
  1492  	release.MockOnClassic(true)
  1493  
  1494  	st := state.New(nil)
  1495  	st.Lock()
  1496  	defer st.Unlock()
  1497  
  1498  	du, err := snapshotstate.AutomaticSnapshotExpiration(st)
  1499  	c.Assert(err, check.IsNil)
  1500  	c.Assert(du, check.Equals, snapshotstate.DefaultAutomaticSnapshotExpiration)
  1501  }
  1502  
  1503  func (snapshotSuite) TestAutomaticSnapshotDefaultUbuntuCore(c *check.C) {
  1504  	release.MockOnClassic(false)
  1505  
  1506  	st := state.New(nil)
  1507  	st.Lock()
  1508  	defer st.Unlock()
  1509  
  1510  	du, err := snapshotstate.AutomaticSnapshotExpiration(st)
  1511  	c.Assert(err, check.IsNil)
  1512  	c.Assert(du, check.Equals, time.Duration(0))
  1513  }
  1514  
  1515  func (snapshotSuite) TestEstimateSnapshotSize(c *check.C) {
  1516  	st := state.New(nil)
  1517  	st.Lock()
  1518  	defer st.Unlock()
  1519  
  1520  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1521  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1522  		Active:   true,
  1523  		Sequence: []*snap.SideInfo{sideInfo},
  1524  		Current:  sideInfo.Revision,
  1525  	})
  1526  
  1527  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1528  		return 123, nil
  1529  	})()
  1530  
  1531  	sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1532  	c.Assert(err, check.IsNil)
  1533  	c.Check(sz, check.Equals, uint64(123))
  1534  }
  1535  
  1536  func (snapshotSuite) TestEstimateSnapshotSizeWithConfig(c *check.C) {
  1537  	st := state.New(nil)
  1538  	st.Lock()
  1539  	defer st.Unlock()
  1540  
  1541  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1542  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1543  		Active:   true,
  1544  		Sequence: []*snap.SideInfo{sideInfo},
  1545  		Current:  sideInfo.Revision,
  1546  	})
  1547  
  1548  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1549  		return 100, nil
  1550  	})()
  1551  
  1552  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
  1553  		c.Check(snapname, check.Equals, "some-snap")
  1554  		buf := json.RawMessage(`{"hello": "there"}`)
  1555  		return &buf, nil
  1556  	})()
  1557  
  1558  	sz, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1559  	c.Assert(err, check.IsNil)
  1560  	// size is 100 + 18
  1561  	c.Check(sz, check.Equals, uint64(118))
  1562  }
  1563  
  1564  func (snapshotSuite) TestEstimateSnapshotSizeError(c *check.C) {
  1565  	st := state.New(nil)
  1566  	st.Lock()
  1567  	defer st.Unlock()
  1568  
  1569  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1570  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1571  		Active:   true,
  1572  		Sequence: []*snap.SideInfo{sideInfo},
  1573  		Current:  sideInfo.Revision,
  1574  	})
  1575  
  1576  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1577  		return 0, fmt.Errorf("an error")
  1578  	})()
  1579  
  1580  	_, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", nil)
  1581  	c.Assert(err, check.ErrorMatches, `an error`)
  1582  }
  1583  
  1584  func (snapshotSuite) TestEstimateSnapshotSizeWithUsers(c *check.C) {
  1585  	st := state.New(nil)
  1586  	st.Lock()
  1587  	defer st.Unlock()
  1588  
  1589  	sideInfo := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(2)}
  1590  	snapstate.Set(st, "some-snap", &snapstate.SnapState{
  1591  		Active:   true,
  1592  		Sequence: []*snap.SideInfo{sideInfo},
  1593  		Current:  sideInfo.Revision,
  1594  	})
  1595  
  1596  	var gotUsers []string
  1597  	defer snapshotstate.MockBackendEstimateSnapshotSize(func(info *snap.Info, users []string) (uint64, error) {
  1598  		gotUsers = users
  1599  		return 0, nil
  1600  	})()
  1601  
  1602  	_, err := snapshotstate.EstimateSnapshotSize(st, "some-snap", []string{"user1", "user2"})
  1603  	c.Assert(err, check.IsNil)
  1604  	c.Check(gotUsers, check.DeepEquals, []string{"user1", "user2"})
  1605  }