gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/snapshotstate/snapshotmgr_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  	"os"
    27  	"path/filepath"
    28  	"sort"
    29  	"time"
    30  
    31  	"gopkg.in/check.v1"
    32  	"gopkg.in/tomb.v2"
    33  
    34  	"github.com/snapcore/snapd/client"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/logger"
    37  	"github.com/snapcore/snapd/overlord"
    38  	"github.com/snapcore/snapd/overlord/snapshotstate"
    39  	"github.com/snapcore/snapd/overlord/snapshotstate/backend"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/testutil"
    43  )
    44  
    45  func (snapshotSuite) TestManager(c *check.C) {
    46  	st := state.New(nil)
    47  	st.Lock()
    48  	defer st.Unlock()
    49  	runner := state.NewTaskRunner(st)
    50  	mgr := snapshotstate.Manager(st, runner)
    51  	c.Assert(mgr, check.NotNil)
    52  	kinds := runner.KnownTaskKinds()
    53  	sort.Strings(kinds)
    54  	c.Check(kinds, check.DeepEquals, []string{
    55  		"check-snapshot",
    56  		"forget-snapshot",
    57  		"restore-snapshot",
    58  		"save-snapshot",
    59  	})
    60  }
    61  
    62  func mockDummySnapshot(c *check.C) (restore func()) {
    63  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
    64  	c.Assert(err, check.IsNil)
    65  
    66  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
    67  		c.Assert(f(&backend.Reader{
    68  			Snapshot: client.Snapshot{SetID: 1, Snap: "a-snap", SnapID: "a-id", Epoch: snap.Epoch{Read: []uint32{42}, Write: []uint32{17}}},
    69  			File:     shotfile,
    70  		}), check.IsNil)
    71  		return nil
    72  	}
    73  
    74  	restoreBackendIter := snapshotstate.MockBackendIter(fakeIter)
    75  
    76  	return func() {
    77  		shotfile.Close()
    78  		restoreBackendIter()
    79  	}
    80  }
    81  
    82  func (snapshotSuite) TestEnsureForgetsSnapshots(c *check.C) {
    83  	var removedSnapshot string
    84  	restoreOsRemove := snapshotstate.MockOsRemove(func(fileName string) error {
    85  		removedSnapshot = fileName
    86  		return nil
    87  	})
    88  	defer restoreOsRemove()
    89  
    90  	restore := mockDummySnapshot(c)
    91  	defer restore()
    92  
    93  	st := state.New(nil)
    94  	runner := state.NewTaskRunner(st)
    95  	mgr := snapshotstate.Manager(st, runner)
    96  	c.Assert(mgr, check.NotNil)
    97  
    98  	st.Lock()
    99  	defer st.Unlock()
   100  
   101  	st.Set("snapshots", map[uint64]interface{}{
   102  		1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"},
   103  		2: map[string]interface{}{"expiry-time": "2037-02-12T12:50:00Z"},
   104  	})
   105  
   106  	st.Unlock()
   107  	c.Assert(mgr.Ensure(), check.IsNil)
   108  	st.Lock()
   109  
   110  	// verify expired snapshots were removed
   111  	var expirations map[uint64]interface{}
   112  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   113  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
   114  		2: map[string]interface{}{"expiry-time": "2037-02-12T12:50:00Z"}})
   115  	c.Check(removedSnapshot, check.Matches, ".*/foo.zip")
   116  }
   117  
   118  func (snapshotSuite) TestEnsureForgetsSnapshotsRunsRegularly(c *check.C) {
   119  	var backendIterCalls int
   120  	shotfile, err := os.Create(filepath.Join(c.MkDir(), "foo.zip"))
   121  	c.Assert(err, check.IsNil)
   122  	fakeIter := func(_ context.Context, f func(*backend.Reader) error) error {
   123  		c.Assert(f(&backend.Reader{
   124  			Snapshot: client.Snapshot{SetID: 1, Snap: "a-snap", SnapID: "a-id", Epoch: snap.Epoch{Read: []uint32{42}, Write: []uint32{17}}},
   125  			File:     shotfile,
   126  		}), check.IsNil)
   127  		backendIterCalls++
   128  		return nil
   129  	}
   130  	restoreBackendIter := snapshotstate.MockBackendIter(fakeIter)
   131  	defer restoreBackendIter()
   132  
   133  	restoreOsRemove := snapshotstate.MockOsRemove(func(fileName string) error {
   134  		return nil
   135  	})
   136  	defer restoreOsRemove()
   137  
   138  	st := state.New(nil)
   139  	runner := state.NewTaskRunner(st)
   140  	mgr := snapshotstate.Manager(st, runner)
   141  	c.Assert(mgr, check.NotNil)
   142  
   143  	storeExpiredSnapshot := func() {
   144  		st.Lock()
   145  		// we need at least one snapshot set in the state for forgetExpiredSnapshots to do any work
   146  		st.Set("snapshots", map[uint64]interface{}{
   147  			1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"},
   148  		})
   149  		st.Unlock()
   150  	}
   151  
   152  	// consecutive runs of Ensure call the backend just once because of the snapshotExpirationLoopInterval
   153  	for i := 0; i < 3; i++ {
   154  		storeExpiredSnapshot()
   155  		c.Assert(mgr.Ensure(), check.IsNil)
   156  		c.Check(backendIterCalls, check.Equals, 1)
   157  	}
   158  
   159  	// pretend we haven't run for a while
   160  	t, err := time.Parse(time.RFC3339, "2002-03-11T11:24:00Z")
   161  	c.Assert(err, check.IsNil)
   162  	snapshotstate.SetLastForgetExpiredSnapshotTime(mgr, t)
   163  	c.Assert(mgr.Ensure(), check.IsNil)
   164  	c.Check(backendIterCalls, check.Equals, 2)
   165  
   166  	c.Assert(mgr.Ensure(), check.IsNil)
   167  	c.Check(backendIterCalls, check.Equals, 2)
   168  }
   169  
   170  func (snapshotSuite) testEnsureForgetSnapshotsConflict(c *check.C, snapshotOp string) {
   171  	removeCalled := 0
   172  	restoreOsRemove := snapshotstate.MockOsRemove(func(string) error {
   173  		removeCalled++
   174  		return nil
   175  	})
   176  	defer restoreOsRemove()
   177  
   178  	restore := mockDummySnapshot(c)
   179  	defer restore()
   180  
   181  	st := state.New(nil)
   182  	runner := state.NewTaskRunner(st)
   183  	mgr := snapshotstate.Manager(st, runner)
   184  	c.Assert(mgr, check.NotNil)
   185  
   186  	st.Lock()
   187  	defer st.Unlock()
   188  
   189  	st.Set("snapshots", map[uint64]interface{}{
   190  		1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"},
   191  	})
   192  
   193  	var tsk *state.Task
   194  
   195  	switch snapshotOp {
   196  	case "export-snapshot":
   197  		snapshotstate.SetSnapshotOpInProgress(st, 1, snapshotOp)
   198  	default:
   199  		chg := st.NewChange("snapshot-change", "...")
   200  		tsk = st.NewTask(snapshotOp, "...")
   201  		tsk.SetStatus(state.DoingStatus)
   202  		tsk.Set("snapshot-setup", map[string]int{"set-id": 1})
   203  		chg.AddTask(tsk)
   204  	}
   205  
   206  	st.Unlock()
   207  	c.Assert(mgr.Ensure(), check.IsNil)
   208  	st.Lock()
   209  
   210  	var expirations map[uint64]interface{}
   211  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   212  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
   213  		1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"},
   214  	})
   215  	c.Check(removeCalled, check.Equals, 0)
   216  
   217  	if tsk != nil {
   218  		// sanity check of the test setup: snapshot gets removed once conflict goes away
   219  		tsk.SetStatus(state.DoneStatus)
   220  	} else {
   221  		c.Check(snapshotstate.UnsetSnapshotOpInProgress(st, 1), check.Equals, snapshotOp)
   222  	}
   223  
   224  	// pretend we haven't run for a while
   225  	t, err := time.Parse(time.RFC3339, "2002-03-11T11:24:00Z")
   226  	c.Assert(err, check.IsNil)
   227  	snapshotstate.SetLastForgetExpiredSnapshotTime(mgr, t)
   228  
   229  	st.Unlock()
   230  	c.Assert(mgr.Ensure(), check.IsNil)
   231  	st.Lock()
   232  
   233  	expirations = nil
   234  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   235  	c.Check(removeCalled, check.Equals, 1)
   236  	c.Check(expirations, check.HasLen, 0)
   237  }
   238  
   239  func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithCheckSnapshot(c *check.C) {
   240  	s.testEnsureForgetSnapshotsConflict(c, "check-snapshot")
   241  }
   242  
   243  func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithRestoreSnapshot(c *check.C) {
   244  	s.testEnsureForgetSnapshotsConflict(c, "restore-snapshot")
   245  }
   246  
   247  func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithExportSnapshot(c *check.C) {
   248  	s.testEnsureForgetSnapshotsConflict(c, "export-snapshot")
   249  }
   250  
   251  func (snapshotSuite) TestFilename(c *check.C) {
   252  	si := &snap.Info{
   253  		SideInfo: snap.SideInfo{
   254  			RealName: "a-snap",
   255  			Revision: snap.R(-1),
   256  		},
   257  		Version: "1.33",
   258  	}
   259  	filename := snapshotstate.Filename(42, si)
   260  	c.Check(filepath.Dir(filename), check.Equals, dirs.SnapshotsDir)
   261  	c.Check(filepath.Base(filename), check.Equals, "42_a-snap_1.33_x1.zip")
   262  }
   263  
   264  func (snapshotSuite) TestDoSave(c *check.C) {
   265  	snapInfo := snap.Info{
   266  		SideInfo: snap.SideInfo{
   267  			RealName: "a-snap",
   268  			Revision: snap.R(-1),
   269  		},
   270  		Version: "1.33",
   271  	}
   272  	defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) {
   273  		c.Check(snapname, check.Equals, "a-snap")
   274  		return &snapInfo, nil
   275  	})()
   276  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
   277  		c.Check(snapname, check.Equals, "a-snap")
   278  		buf := json.RawMessage(`{"hello": "there"}`)
   279  		return &buf, nil
   280  	})()
   281  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   282  		c.Check(id, check.Equals, uint64(42))
   283  		c.Check(si, check.DeepEquals, &snapInfo)
   284  		c.Check(cfg, check.DeepEquals, map[string]interface{}{"hello": "there"})
   285  		c.Check(usernames, check.DeepEquals, []string{"a-user", "b-user"})
   286  		return nil, nil
   287  	})()
   288  
   289  	st := state.New(nil)
   290  	st.Lock()
   291  	task := st.NewTask("save-snapshot", "...")
   292  	task.Set("snapshot-setup", map[string]interface{}{
   293  		"set-id": 42,
   294  		"snap":   "a-snap",
   295  		"users":  []string{"a-user", "b-user"},
   296  	})
   297  	st.Unlock()
   298  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   299  	c.Assert(err, check.IsNil)
   300  }
   301  
   302  func (snapshotSuite) TestDoSaveFailsWithNoSnap(c *check.C) {
   303  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) {
   304  		return nil, errors.New("bzzt")
   305  	})()
   306  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })()
   307  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   308  		return nil, nil
   309  	})()
   310  
   311  	st := state.New(nil)
   312  	st.Lock()
   313  	task := st.NewTask("save-snapshot", "...")
   314  	task.Set("snapshot-setup", map[string]interface{}{
   315  		"set-id": 42,
   316  		"snap":   "a-snap",
   317  		"users":  []string{"a-user", "b-user"},
   318  	})
   319  	st.Unlock()
   320  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   321  	c.Assert(err, check.ErrorMatches, "bzzt")
   322  }
   323  
   324  func (snapshotSuite) TestDoSaveFailsWithNoSnapshot(c *check.C) {
   325  	snapInfo := snap.Info{
   326  		SideInfo: snap.SideInfo{
   327  			RealName: "a-snap",
   328  			Revision: snap.R(-1),
   329  		},
   330  		Version: "1.33",
   331  	}
   332  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   333  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })()
   334  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   335  		return nil, nil
   336  	})()
   337  
   338  	st := state.New(nil)
   339  	st.Lock()
   340  	task := st.NewTask("save-snapshot", "...")
   341  	// NOTE no task.Set("snapshot-setup", ...)
   342  	st.Unlock()
   343  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   344  	c.Assert(err, check.NotNil)
   345  	c.Assert(err.Error(), check.Equals, "internal error: task 1 (save-snapshot) is missing snapshot information")
   346  }
   347  
   348  func (snapshotSuite) TestDoSaveFailsBackendError(c *check.C) {
   349  	snapInfo := snap.Info{
   350  		SideInfo: snap.SideInfo{
   351  			RealName: "a-snap",
   352  			Revision: snap.R(-1),
   353  		},
   354  		Version: "1.33",
   355  	}
   356  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   357  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })()
   358  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   359  		return nil, errors.New("bzzt")
   360  	})()
   361  
   362  	st := state.New(nil)
   363  	st.Lock()
   364  	task := st.NewTask("save-snapshot", "...")
   365  	task.Set("snapshot-setup", map[string]interface{}{
   366  		"set-id": 42,
   367  		"snap":   "a-snap",
   368  		"users":  []string{"a-user", "b-user"},
   369  	})
   370  	st.Unlock()
   371  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   372  	c.Assert(err, check.ErrorMatches, "bzzt")
   373  }
   374  
   375  func (snapshotSuite) TestDoSaveFailsConfigError(c *check.C) {
   376  	snapInfo := snap.Info{
   377  		SideInfo: snap.SideInfo{
   378  			RealName: "a-snap",
   379  			Revision: snap.R(-1),
   380  		},
   381  		Version: "1.33",
   382  	}
   383  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   384  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   385  		return nil, errors.New("bzzt")
   386  	})()
   387  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   388  		return nil, nil
   389  	})()
   390  
   391  	st := state.New(nil)
   392  	st.Lock()
   393  	task := st.NewTask("save-snapshot", "...")
   394  	task.Set("snapshot-setup", map[string]interface{}{
   395  		"set-id": 42,
   396  		"snap":   "a-snap",
   397  		"users":  []string{"a-user", "b-user"},
   398  	})
   399  	st.Unlock()
   400  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   401  	c.Assert(err, check.ErrorMatches, "internal error: cannot obtain current snap config: bzzt")
   402  }
   403  
   404  func (snapshotSuite) TestDoSaveFailsBadConfig(c *check.C) {
   405  	snapInfo := snap.Info{
   406  		SideInfo: snap.SideInfo{
   407  			RealName: "a-snap",
   408  			Revision: snap.R(-1),
   409  		},
   410  		Version: "1.33",
   411  	}
   412  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   413  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   414  		// returns something that's not a JSON object
   415  		buf := json.RawMessage(`"hello-there"`)
   416  		return &buf, nil
   417  	})()
   418  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   419  		return nil, nil
   420  	})()
   421  
   422  	st := state.New(nil)
   423  	st.Lock()
   424  	task := st.NewTask("save-snapshot", "...")
   425  	task.Set("snapshot-setup", map[string]interface{}{
   426  		"set-id": 42,
   427  		"snap":   "a-snap",
   428  		"users":  []string{"a-user", "b-user"},
   429  	})
   430  	st.Unlock()
   431  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   432  	c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*")
   433  }
   434  
   435  func (snapshotSuite) TestDoSaveFailureRemovesStateEntry(c *check.C) {
   436  	st := state.New(nil)
   437  
   438  	snapInfo := snap.Info{
   439  		SideInfo: snap.SideInfo{
   440  			RealName: "a-snap",
   441  			Revision: snap.R(-1),
   442  		},
   443  		Version: "1.33",
   444  	}
   445  	defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) {
   446  		return &snapInfo, nil
   447  	})()
   448  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
   449  		return nil, nil
   450  	})()
   451  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   452  		var expirations map[uint64]interface{}
   453  		st.Lock()
   454  		defer st.Unlock()
   455  		// verify that prepareSave stored expiration in the state
   456  		c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   457  		c.Assert(expirations, check.HasLen, 1)
   458  		c.Check(expirations[42], check.NotNil)
   459  		return nil, errors.New("error")
   460  	})()
   461  
   462  	st.Lock()
   463  
   464  	task := st.NewTask("save-snapshot", "...")
   465  	task.Set("snapshot-setup", map[string]interface{}{
   466  		"set-id": 42,
   467  		"snap":   "a-snap",
   468  		"auto":   true,
   469  	})
   470  	st.Unlock()
   471  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   472  	c.Assert(err, check.ErrorMatches, "error")
   473  
   474  	st.Lock()
   475  	defer st.Unlock()
   476  
   477  	// verify that after backend.Save failure expiration was removed from the state
   478  	var expirations map[uint64]interface{}
   479  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   480  	c.Check(expirations, check.HasLen, 0)
   481  }
   482  
   483  type readerSuite struct {
   484  	task     *state.Task
   485  	calls    []string
   486  	restores []func()
   487  }
   488  
   489  var _ = check.Suite(&readerSuite{})
   490  
   491  func (rs *readerSuite) SetUpTest(c *check.C) {
   492  	st := state.New(nil)
   493  	st.Lock()
   494  	rs.task = st.NewTask("restore-snapshot", "...")
   495  	rs.task.Set("snapshot-setup", map[string]interface{}{
   496  		// interestingly restore doesn't use the set-id
   497  		"snap":     "a-snap",
   498  		"filename": "/some/1_file.zip",
   499  		"users":    []string{"a-user", "b-user"},
   500  	})
   501  	st.Unlock()
   502  
   503  	rs.calls = nil
   504  	rs.restores = []func(){
   505  		snapshotstate.MockOsRemove(func(string) error {
   506  			rs.calls = append(rs.calls, "remove")
   507  			return nil
   508  		}),
   509  		snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   510  			rs.calls = append(rs.calls, "get config")
   511  			return nil, nil
   512  		}),
   513  		snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error {
   514  			rs.calls = append(rs.calls, "set config")
   515  			return nil
   516  		}),
   517  		snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) {
   518  			rs.calls = append(rs.calls, "open")
   519  			return &backend.Reader{}, nil
   520  		}),
   521  		snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) {
   522  			rs.calls = append(rs.calls, "restore")
   523  			return &backend.RestoreState{}, nil
   524  		}),
   525  		snapshotstate.MockBackendCheck(func(*backend.Reader, context.Context, []string) error {
   526  			rs.calls = append(rs.calls, "check")
   527  			return nil
   528  		}),
   529  		snapshotstate.MockBackendRevert(func(*backend.RestoreState) {
   530  			rs.calls = append(rs.calls, "revert")
   531  		}),
   532  		snapshotstate.MockBackendCleanup(func(*backend.RestoreState) {
   533  			rs.calls = append(rs.calls, "cleanup")
   534  		}),
   535  	}
   536  }
   537  
   538  func (rs *readerSuite) TearDownTest(c *check.C) {
   539  	for _, restore := range rs.restores {
   540  		restore()
   541  	}
   542  }
   543  
   544  func (rs *readerSuite) TestDoRestore(c *check.C) {
   545  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
   546  		rs.calls = append(rs.calls, "get config")
   547  		c.Check(snapname, check.Equals, "a-snap")
   548  		buf := json.RawMessage(`{"old": "conf"}`)
   549  		return &buf, nil
   550  	})()
   551  	defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) {
   552  		rs.calls = append(rs.calls, "open")
   553  		// set id 0 tells backend.Open to use set id from the filename
   554  		c.Check(setID, check.Equals, uint64(0))
   555  		c.Check(filename, check.Equals, "/some/1_file.zip")
   556  		return &backend.Reader{
   557  			Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}},
   558  		}, nil
   559  	})()
   560  	defer snapshotstate.MockBackendRestore(func(_ *backend.Reader, _ context.Context, _ snap.Revision, users []string, _ backend.Logf) (*backend.RestoreState, error) {
   561  		rs.calls = append(rs.calls, "restore")
   562  		c.Check(users, check.DeepEquals, []string{"a-user", "b-user"})
   563  		return &backend.RestoreState{}, nil
   564  	})()
   565  	defer snapshotstate.MockConfigSetSnapConfig(func(_ *state.State, snapname string, conf *json.RawMessage) error {
   566  		rs.calls = append(rs.calls, "set config")
   567  		c.Check(snapname, check.Equals, "a-snap")
   568  		c.Check(string(*conf), check.Equals, `{"hello":"there"}`)
   569  		return nil
   570  	})()
   571  
   572  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   573  	c.Assert(err, check.IsNil)
   574  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config"})
   575  
   576  	st := rs.task.State()
   577  	st.Lock()
   578  	var v map[string]interface{}
   579  	rs.task.Get("restore-state", &v)
   580  	st.Unlock()
   581  	c.Check(v, check.DeepEquals, map[string]interface{}{"config": map[string]interface{}{"old": "conf"}})
   582  }
   583  
   584  func (rs *readerSuite) TestDoRestoreNoConfig(c *check.C) {
   585  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
   586  		rs.calls = append(rs.calls, "get config")
   587  		c.Check(snapname, check.Equals, "a-snap")
   588  		// simulate old config
   589  		raw := json.RawMessage(`{"foo": "bar"}`)
   590  		return &raw, nil
   591  	})()
   592  	defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) {
   593  		rs.calls = append(rs.calls, "open")
   594  		// set id 0 tells backend.Open to use set id from the filename
   595  		c.Check(setID, check.Equals, uint64(0))
   596  		c.Check(filename, check.Equals, "/some/1_file.zip")
   597  		return &backend.Reader{
   598  			// snapshot has no configuration to restore
   599  			Snapshot: client.Snapshot{Snap: "a-snap", Conf: nil},
   600  		}, nil
   601  	})()
   602  	defer snapshotstate.MockBackendRestore(func(_ *backend.Reader, _ context.Context, _ snap.Revision, users []string, _ backend.Logf) (*backend.RestoreState, error) {
   603  		rs.calls = append(rs.calls, "restore")
   604  		c.Check(users, check.DeepEquals, []string{"a-user", "b-user"})
   605  		return &backend.RestoreState{}, nil
   606  	})()
   607  	defer snapshotstate.MockConfigSetSnapConfig(func(_ *state.State, snapname string, conf *json.RawMessage) error {
   608  		rs.calls = append(rs.calls, "set config")
   609  		c.Check(snapname, check.Equals, "a-snap")
   610  		c.Check(conf, check.IsNil)
   611  		return nil
   612  	})()
   613  
   614  	st := rs.task.State()
   615  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   616  	c.Assert(err, check.IsNil)
   617  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config"})
   618  
   619  	st.Lock()
   620  	defer st.Unlock()
   621  	var v map[string]interface{}
   622  	rs.task.Get("restore-state", &v)
   623  	c.Check(v, check.DeepEquals, map[string]interface{}{"config": map[string]interface{}{"foo": "bar"}})
   624  }
   625  
   626  func (rs *readerSuite) TestDoRestoreFailsNoTaskSnapshot(c *check.C) {
   627  	rs.task.State().Lock()
   628  	rs.task.Clear("snapshot-setup")
   629  	rs.task.State().Unlock()
   630  
   631  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   632  	c.Assert(err, check.NotNil)
   633  	c.Assert(err.Error(), check.Equals, "internal error: task 1 (restore-snapshot) is missing snapshot information")
   634  	c.Check(rs.calls, check.HasLen, 0)
   635  }
   636  
   637  func (rs *readerSuite) TestDoRestoreFailsOnGetConfigError(c *check.C) {
   638  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   639  		rs.calls = append(rs.calls, "get config")
   640  		return nil, errors.New("bzzt")
   641  	})()
   642  
   643  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   644  	c.Assert(err, check.ErrorMatches, "internal error: cannot obtain current snap config: bzzt")
   645  	c.Check(rs.calls, check.DeepEquals, []string{"get config"})
   646  }
   647  
   648  func (rs *readerSuite) TestDoRestoreFailsOnBadConfig(c *check.C) {
   649  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   650  		rs.calls = append(rs.calls, "get config")
   651  		buf := json.RawMessage(`42`)
   652  		return &buf, nil
   653  	})()
   654  
   655  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   656  	c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*")
   657  	c.Check(rs.calls, check.DeepEquals, []string{"get config"})
   658  }
   659  
   660  func (rs *readerSuite) TestDoRestoreFailsOpenError(c *check.C) {
   661  	defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) {
   662  		rs.calls = append(rs.calls, "open")
   663  		return nil, errors.New("bzzt")
   664  	})()
   665  
   666  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   667  	c.Assert(err, check.ErrorMatches, "cannot open snapshot: bzzt")
   668  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open"})
   669  }
   670  
   671  func (rs *readerSuite) TestDoRestoreFailsUnserialisableSnapshotConfigError(c *check.C) {
   672  	defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) {
   673  		rs.calls = append(rs.calls, "open")
   674  		return &backend.Reader{
   675  			Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": func() {}}},
   676  		}, nil
   677  	})()
   678  
   679  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   680  	c.Assert(err, check.ErrorMatches, "cannot marshal saved config: json.*")
   681  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "revert"})
   682  }
   683  
   684  func (rs *readerSuite) TestDoRestoreFailsOnRestoreError(c *check.C) {
   685  	defer snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) {
   686  		rs.calls = append(rs.calls, "restore")
   687  		return nil, errors.New("bzzt")
   688  	})()
   689  
   690  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   691  	c.Assert(err, check.ErrorMatches, "bzzt")
   692  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore"})
   693  }
   694  
   695  func (rs *readerSuite) TestDoRestoreFailsAndRevertsOnSetConfigError(c *check.C) {
   696  	defer snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error {
   697  		rs.calls = append(rs.calls, "set config")
   698  		return errors.New("bzzt")
   699  	})()
   700  
   701  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   702  	c.Assert(err, check.ErrorMatches, "cannot set snap config: bzzt")
   703  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config", "revert"})
   704  }
   705  
   706  func (rs *readerSuite) TestUndoRestore(c *check.C) {
   707  	defer snapshotstate.MockConfigSetSnapConfig(func(st *state.State, snapName string, raw *json.RawMessage) error {
   708  		rs.calls = append(rs.calls, "set config")
   709  		c.Check(string(*raw), check.Equals, `{"foo":"bar"}`)
   710  		return nil
   711  	})()
   712  
   713  	st := rs.task.State()
   714  	st.Lock()
   715  	v := map[string]interface{}{"config": map[string]interface{}{"foo": "bar"}}
   716  	rs.task.Set("restore-state", &v)
   717  	st.Unlock()
   718  
   719  	err := snapshotstate.UndoRestore(rs.task, &tomb.Tomb{})
   720  	c.Assert(err, check.IsNil)
   721  	c.Check(rs.calls, check.DeepEquals, []string{"set config", "revert"})
   722  }
   723  
   724  func (rs *readerSuite) TestUndoRestoreNoConfig(c *check.C) {
   725  	defer snapshotstate.MockConfigSetSnapConfig(func(st *state.State, snapName string, raw *json.RawMessage) error {
   726  		rs.calls = append(rs.calls, "set config")
   727  		c.Check(raw, check.IsNil)
   728  		return nil
   729  	})()
   730  
   731  	st := rs.task.State()
   732  	st.Lock()
   733  	var v map[string]interface{}
   734  	rs.task.Set("restore-state", &v)
   735  	st.Unlock()
   736  
   737  	err := snapshotstate.UndoRestore(rs.task, &tomb.Tomb{})
   738  	c.Assert(err, check.IsNil)
   739  	c.Check(rs.calls, check.DeepEquals, []string{"set config", "revert"})
   740  }
   741  
   742  func (rs *readerSuite) TestCleanupRestore(c *check.C) {
   743  	st := rs.task.State()
   744  	st.Lock()
   745  	var v map[string]interface{}
   746  	rs.task.Set("restore-state", &v)
   747  	st.Unlock()
   748  
   749  	err := snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{})
   750  	c.Assert(err, check.IsNil)
   751  	c.Check(rs.calls, check.HasLen, 0)
   752  
   753  	st.Lock()
   754  	rs.task.SetStatus(state.DoneStatus)
   755  	st.Unlock()
   756  
   757  	err = snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{})
   758  	c.Assert(err, check.IsNil)
   759  	c.Check(rs.calls, check.DeepEquals, []string{"cleanup"})
   760  }
   761  
   762  func (rs *readerSuite) TestDoCheck(c *check.C) {
   763  	defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) {
   764  		rs.calls = append(rs.calls, "open")
   765  		c.Check(filename, check.Equals, "/some/1_file.zip")
   766  		// set id 0 tells backend.Open to use set id from the filename
   767  		c.Check(setID, check.Equals, uint64(0))
   768  		return &backend.Reader{
   769  			Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}},
   770  		}, nil
   771  	})()
   772  	defer snapshotstate.MockBackendCheck(func(_ *backend.Reader, _ context.Context, users []string) error {
   773  		rs.calls = append(rs.calls, "check")
   774  		c.Check(users, check.DeepEquals, []string{"a-user", "b-user"})
   775  		return nil
   776  	})()
   777  
   778  	err := snapshotstate.DoCheck(rs.task, &tomb.Tomb{})
   779  	c.Assert(err, check.IsNil)
   780  	c.Check(rs.calls, check.DeepEquals, []string{"open", "check"})
   781  }
   782  
   783  func (rs *readerSuite) TestDoRemove(c *check.C) {
   784  	defer snapshotstate.MockOsRemove(func(filename string) error {
   785  		c.Check(filename, check.Equals, "/some/1_file.zip")
   786  		rs.calls = append(rs.calls, "remove")
   787  		return nil
   788  	})()
   789  	err := snapshotstate.DoForget(rs.task, &tomb.Tomb{})
   790  	c.Assert(err, check.IsNil)
   791  	c.Check(rs.calls, check.DeepEquals, []string{"remove"})
   792  }
   793  
   794  func (rs *readerSuite) TestDoForgetRemovesAutomaticSnapshotExpiry(c *check.C) {
   795  	defer snapshotstate.MockOsRemove(func(filename string) error {
   796  		return nil
   797  	})()
   798  
   799  	st := state.New(nil)
   800  	st.Lock()
   801  	defer st.Unlock()
   802  
   803  	task := st.NewTask("forget-snapshot", "...")
   804  	task.Set("snapshot-setup", map[string]interface{}{
   805  		"set-id":   1,
   806  		"filename": "a-file",
   807  		"snap":     "a-snap",
   808  	})
   809  
   810  	st.Set("snapshots", map[uint64]interface{}{
   811  		1: map[string]interface{}{
   812  			"expiry-time": "2001-03-11T11:24:00Z",
   813  		},
   814  		2: map[string]interface{}{
   815  			"expiry-time": "2037-02-12T12:50:00Z",
   816  		},
   817  	})
   818  
   819  	st.Unlock()
   820  	c.Assert(snapshotstate.DoForget(task, &tomb.Tomb{}), check.IsNil)
   821  
   822  	st.Lock()
   823  	var expirations map[uint64]interface{}
   824  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   825  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
   826  		2: map[string]interface{}{
   827  			"expiry-time": "2037-02-12T12:50:00Z",
   828  		}})
   829  }
   830  
   831  func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartup(c *check.C) {
   832  	n := 0
   833  	restore := snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) {
   834  		n++
   835  		return 0, nil
   836  	})
   837  	defer restore()
   838  
   839  	o := overlord.Mock()
   840  	st := o.State()
   841  	mgr := snapshotstate.Manager(st, state.NewTaskRunner(st))
   842  	c.Assert(mgr, check.NotNil)
   843  	o.AddManager(mgr)
   844  	err := o.Settle(100 * time.Millisecond)
   845  	c.Assert(err, check.IsNil)
   846  
   847  	c.Check(n, check.Equals, 1)
   848  }
   849  
   850  func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartupErrorLogged(c *check.C) {
   851  	logbuf, restore := logger.MockLogger()
   852  	defer restore()
   853  
   854  	n := 0
   855  	restore = snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) {
   856  		n++
   857  		return 0, errors.New("some error")
   858  	})
   859  	defer restore()
   860  
   861  	o := overlord.Mock()
   862  	st := o.State()
   863  	mgr := snapshotstate.Manager(st, state.NewTaskRunner(st))
   864  	c.Assert(mgr, check.NotNil)
   865  	o.AddManager(mgr)
   866  	err := o.Settle(100 * time.Millisecond)
   867  	c.Assert(err, check.IsNil)
   868  
   869  	c.Check(n, check.Equals, 1)
   870  	c.Check(logbuf.String(), testutil.Contains, "cannot cleanup incomplete imports: some error\n")
   871  }