github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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, snapshotTaskKind 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  	chg := st.NewChange("snapshot-change", "...")
   194  	tsk := st.NewTask(snapshotTaskKind, "...")
   195  	tsk.SetStatus(state.DoingStatus)
   196  	tsk.Set("snapshot-setup", map[string]int{"set-id": 1})
   197  	chg.AddTask(tsk)
   198  
   199  	st.Unlock()
   200  	c.Assert(mgr.Ensure(), check.IsNil)
   201  	st.Lock()
   202  
   203  	var expirations map[uint64]interface{}
   204  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   205  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
   206  		1: map[string]interface{}{"expiry-time": "2001-03-11T11:24:00Z"},
   207  	})
   208  	c.Check(removeCalled, check.Equals, 0)
   209  
   210  	// sanity check of the test setup: snapshot gets removed once conflict goes away
   211  	tsk.SetStatus(state.DoneStatus)
   212  
   213  	// pretend we haven't run for a while
   214  	t, err := time.Parse(time.RFC3339, "2002-03-11T11:24:00Z")
   215  	c.Assert(err, check.IsNil)
   216  	snapshotstate.SetLastForgetExpiredSnapshotTime(mgr, t)
   217  
   218  	st.Unlock()
   219  	c.Assert(mgr.Ensure(), check.IsNil)
   220  	st.Lock()
   221  
   222  	expirations = nil
   223  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   224  	c.Check(removeCalled, check.Equals, 1)
   225  	c.Check(expirations, check.HasLen, 0)
   226  }
   227  
   228  func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithCheckSnapshot(c *check.C) {
   229  	s.testEnsureForgetSnapshotsConflict(c, "check-snapshot")
   230  }
   231  
   232  func (s *snapshotSuite) TestEnsureForgetSnapshotsConflictWithRestoreSnapshot(c *check.C) {
   233  	s.testEnsureForgetSnapshotsConflict(c, "restore-snapshot")
   234  }
   235  
   236  func (snapshotSuite) TestFilename(c *check.C) {
   237  	si := &snap.Info{
   238  		SideInfo: snap.SideInfo{
   239  			RealName: "a-snap",
   240  			Revision: snap.R(-1),
   241  		},
   242  		Version: "1.33",
   243  	}
   244  	filename := snapshotstate.Filename(42, si)
   245  	c.Check(filepath.Dir(filename), check.Equals, dirs.SnapshotsDir)
   246  	c.Check(filepath.Base(filename), check.Equals, "42_a-snap_1.33_x1.zip")
   247  }
   248  
   249  func (snapshotSuite) TestDoSave(c *check.C) {
   250  	snapInfo := snap.Info{
   251  		SideInfo: snap.SideInfo{
   252  			RealName: "a-snap",
   253  			Revision: snap.R(-1),
   254  		},
   255  		Version: "1.33",
   256  	}
   257  	defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) {
   258  		c.Check(snapname, check.Equals, "a-snap")
   259  		return &snapInfo, nil
   260  	})()
   261  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
   262  		c.Check(snapname, check.Equals, "a-snap")
   263  		buf := json.RawMessage(`{"hello": "there"}`)
   264  		return &buf, nil
   265  	})()
   266  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   267  		c.Check(id, check.Equals, uint64(42))
   268  		c.Check(si, check.DeepEquals, &snapInfo)
   269  		c.Check(cfg, check.DeepEquals, map[string]interface{}{"hello": "there"})
   270  		c.Check(usernames, check.DeepEquals, []string{"a-user", "b-user"})
   271  		return nil, nil
   272  	})()
   273  
   274  	st := state.New(nil)
   275  	st.Lock()
   276  	task := st.NewTask("save-snapshot", "...")
   277  	task.Set("snapshot-setup", map[string]interface{}{
   278  		"set-id": 42,
   279  		"snap":   "a-snap",
   280  		"users":  []string{"a-user", "b-user"},
   281  	})
   282  	st.Unlock()
   283  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   284  	c.Assert(err, check.IsNil)
   285  }
   286  
   287  func (snapshotSuite) TestDoSaveFailsWithNoSnap(c *check.C) {
   288  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) {
   289  		return nil, errors.New("bzzt")
   290  	})()
   291  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })()
   292  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   293  		return nil, nil
   294  	})()
   295  
   296  	st := state.New(nil)
   297  	st.Lock()
   298  	task := st.NewTask("save-snapshot", "...")
   299  	task.Set("snapshot-setup", map[string]interface{}{
   300  		"set-id": 42,
   301  		"snap":   "a-snap",
   302  		"users":  []string{"a-user", "b-user"},
   303  	})
   304  	st.Unlock()
   305  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   306  	c.Assert(err, check.ErrorMatches, "bzzt")
   307  }
   308  
   309  func (snapshotSuite) TestDoSaveFailsWithNoSnapshot(c *check.C) {
   310  	snapInfo := snap.Info{
   311  		SideInfo: snap.SideInfo{
   312  			RealName: "a-snap",
   313  			Revision: snap.R(-1),
   314  		},
   315  		Version: "1.33",
   316  	}
   317  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   318  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })()
   319  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   320  		return nil, nil
   321  	})()
   322  
   323  	st := state.New(nil)
   324  	st.Lock()
   325  	task := st.NewTask("save-snapshot", "...")
   326  	// NOTE no task.Set("snapshot-setup", ...)
   327  	st.Unlock()
   328  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   329  	c.Assert(err, check.NotNil)
   330  	c.Assert(err.Error(), check.Equals, "internal error: task 1 (save-snapshot) is missing snapshot information")
   331  }
   332  
   333  func (snapshotSuite) TestDoSaveFailsBackendError(c *check.C) {
   334  	snapInfo := snap.Info{
   335  		SideInfo: snap.SideInfo{
   336  			RealName: "a-snap",
   337  			Revision: snap.R(-1),
   338  		},
   339  		Version: "1.33",
   340  	}
   341  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   342  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) { return nil, nil })()
   343  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   344  		return nil, errors.New("bzzt")
   345  	})()
   346  
   347  	st := state.New(nil)
   348  	st.Lock()
   349  	task := st.NewTask("save-snapshot", "...")
   350  	task.Set("snapshot-setup", map[string]interface{}{
   351  		"set-id": 42,
   352  		"snap":   "a-snap",
   353  		"users":  []string{"a-user", "b-user"},
   354  	})
   355  	st.Unlock()
   356  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   357  	c.Assert(err, check.ErrorMatches, "bzzt")
   358  }
   359  
   360  func (snapshotSuite) TestDoSaveFailsConfigError(c *check.C) {
   361  	snapInfo := snap.Info{
   362  		SideInfo: snap.SideInfo{
   363  			RealName: "a-snap",
   364  			Revision: snap.R(-1),
   365  		},
   366  		Version: "1.33",
   367  	}
   368  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   369  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   370  		return nil, errors.New("bzzt")
   371  	})()
   372  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   373  		return nil, nil
   374  	})()
   375  
   376  	st := state.New(nil)
   377  	st.Lock()
   378  	task := st.NewTask("save-snapshot", "...")
   379  	task.Set("snapshot-setup", map[string]interface{}{
   380  		"set-id": 42,
   381  		"snap":   "a-snap",
   382  		"users":  []string{"a-user", "b-user"},
   383  	})
   384  	st.Unlock()
   385  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   386  	c.Assert(err, check.ErrorMatches, "bzzt")
   387  }
   388  
   389  func (snapshotSuite) TestDoSaveFailsBadConfig(c *check.C) {
   390  	snapInfo := snap.Info{
   391  		SideInfo: snap.SideInfo{
   392  			RealName: "a-snap",
   393  			Revision: snap.R(-1),
   394  		},
   395  		Version: "1.33",
   396  	}
   397  	defer snapshotstate.MockSnapstateCurrentInfo(func(*state.State, string) (*snap.Info, error) { return &snapInfo, nil })()
   398  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   399  		// returns something that's not a JSON object
   400  		buf := json.RawMessage(`"hello-there"`)
   401  		return &buf, nil
   402  	})()
   403  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   404  		return nil, nil
   405  	})()
   406  
   407  	st := state.New(nil)
   408  	st.Lock()
   409  	task := st.NewTask("save-snapshot", "...")
   410  	task.Set("snapshot-setup", map[string]interface{}{
   411  		"set-id": 42,
   412  		"snap":   "a-snap",
   413  		"users":  []string{"a-user", "b-user"},
   414  	})
   415  	st.Unlock()
   416  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   417  	c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*")
   418  }
   419  
   420  func (snapshotSuite) TestDoSaveFailureRemovesStateEntry(c *check.C) {
   421  	st := state.New(nil)
   422  
   423  	snapInfo := snap.Info{
   424  		SideInfo: snap.SideInfo{
   425  			RealName: "a-snap",
   426  			Revision: snap.R(-1),
   427  		},
   428  		Version: "1.33",
   429  	}
   430  	defer snapshotstate.MockSnapstateCurrentInfo(func(_ *state.State, snapname string) (*snap.Info, error) {
   431  		return &snapInfo, nil
   432  	})()
   433  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
   434  		return nil, nil
   435  	})()
   436  	defer snapshotstate.MockBackendSave(func(_ context.Context, id uint64, si *snap.Info, cfg map[string]interface{}, usernames []string) (*client.Snapshot, error) {
   437  		var expirations map[uint64]interface{}
   438  		st.Lock()
   439  		defer st.Unlock()
   440  		// verify that prepareSave stored expiration in the state
   441  		c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   442  		c.Assert(expirations, check.HasLen, 1)
   443  		c.Check(expirations[42], check.NotNil)
   444  		return nil, errors.New("error")
   445  	})()
   446  
   447  	st.Lock()
   448  
   449  	task := st.NewTask("save-snapshot", "...")
   450  	task.Set("snapshot-setup", map[string]interface{}{
   451  		"set-id": 42,
   452  		"snap":   "a-snap",
   453  		"auto":   true,
   454  	})
   455  	st.Unlock()
   456  	err := snapshotstate.DoSave(task, &tomb.Tomb{})
   457  	c.Assert(err, check.ErrorMatches, "error")
   458  
   459  	st.Lock()
   460  	defer st.Unlock()
   461  
   462  	// verify that after backend.Save failure expiration was removed from the state
   463  	var expirations map[uint64]interface{}
   464  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   465  	c.Check(expirations, check.HasLen, 0)
   466  }
   467  
   468  type readerSuite struct {
   469  	task     *state.Task
   470  	calls    []string
   471  	restores []func()
   472  }
   473  
   474  var _ = check.Suite(&readerSuite{})
   475  
   476  func (rs *readerSuite) SetUpTest(c *check.C) {
   477  	st := state.New(nil)
   478  	st.Lock()
   479  	rs.task = st.NewTask("restore-snapshot", "...")
   480  	rs.task.Set("snapshot-setup", map[string]interface{}{
   481  		// interestingly restore doesn't use the set-id
   482  		"snap":     "a-snap",
   483  		"filename": "/some/1_file.zip",
   484  		"users":    []string{"a-user", "b-user"},
   485  	})
   486  	st.Unlock()
   487  
   488  	rs.calls = nil
   489  	rs.restores = []func(){
   490  		snapshotstate.MockOsRemove(func(string) error {
   491  			rs.calls = append(rs.calls, "remove")
   492  			return nil
   493  		}),
   494  		snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   495  			rs.calls = append(rs.calls, "get config")
   496  			return nil, nil
   497  		}),
   498  		snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error {
   499  			rs.calls = append(rs.calls, "set config")
   500  			return nil
   501  		}),
   502  		snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) {
   503  			rs.calls = append(rs.calls, "open")
   504  			return &backend.Reader{}, nil
   505  		}),
   506  		snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) {
   507  			rs.calls = append(rs.calls, "restore")
   508  			return &backend.RestoreState{}, nil
   509  		}),
   510  		snapshotstate.MockBackendCheck(func(*backend.Reader, context.Context, []string) error {
   511  			rs.calls = append(rs.calls, "check")
   512  			return nil
   513  		}),
   514  		snapshotstate.MockBackendRevert(func(*backend.RestoreState) {
   515  			rs.calls = append(rs.calls, "revert")
   516  		}),
   517  		snapshotstate.MockBackendCleanup(func(*backend.RestoreState) {
   518  			rs.calls = append(rs.calls, "cleanup")
   519  		}),
   520  	}
   521  }
   522  
   523  func (rs *readerSuite) TearDownTest(c *check.C) {
   524  	for _, restore := range rs.restores {
   525  		restore()
   526  	}
   527  }
   528  
   529  func (rs *readerSuite) TestDoRestore(c *check.C) {
   530  	defer snapshotstate.MockConfigGetSnapConfig(func(_ *state.State, snapname string) (*json.RawMessage, error) {
   531  		rs.calls = append(rs.calls, "get config")
   532  		c.Check(snapname, check.Equals, "a-snap")
   533  		buf := json.RawMessage(`{"old": "conf"}`)
   534  		return &buf, nil
   535  	})()
   536  	defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) {
   537  		rs.calls = append(rs.calls, "open")
   538  		// set id 0 tells backend.Open to use set id from the filename
   539  		c.Check(setID, check.Equals, uint64(0))
   540  		c.Check(filename, check.Equals, "/some/1_file.zip")
   541  		return &backend.Reader{
   542  			Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}},
   543  		}, nil
   544  	})()
   545  	defer snapshotstate.MockBackendRestore(func(_ *backend.Reader, _ context.Context, _ snap.Revision, users []string, _ backend.Logf) (*backend.RestoreState, error) {
   546  		rs.calls = append(rs.calls, "restore")
   547  		c.Check(users, check.DeepEquals, []string{"a-user", "b-user"})
   548  		return &backend.RestoreState{}, nil
   549  	})()
   550  	defer snapshotstate.MockConfigSetSnapConfig(func(_ *state.State, snapname string, conf *json.RawMessage) error {
   551  		rs.calls = append(rs.calls, "set config")
   552  		c.Check(snapname, check.Equals, "a-snap")
   553  		c.Check(string(*conf), check.Equals, `{"hello":"there"}`)
   554  		return nil
   555  	})()
   556  
   557  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   558  	c.Assert(err, check.IsNil)
   559  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config"})
   560  
   561  	st := rs.task.State()
   562  	st.Lock()
   563  	var v map[string]interface{}
   564  	rs.task.Get("restore-state", &v)
   565  	st.Unlock()
   566  	c.Check(v, check.DeepEquals, map[string]interface{}{"config": map[string]interface{}{"old": "conf"}})
   567  }
   568  
   569  func (rs *readerSuite) TestDoRestoreFailsNoTaskSnapshot(c *check.C) {
   570  	rs.task.State().Lock()
   571  	rs.task.Clear("snapshot-setup")
   572  	rs.task.State().Unlock()
   573  
   574  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   575  	c.Assert(err, check.NotNil)
   576  	c.Assert(err.Error(), check.Equals, "internal error: task 1 (restore-snapshot) is missing snapshot information")
   577  	c.Check(rs.calls, check.HasLen, 0)
   578  }
   579  
   580  func (rs *readerSuite) TestDoRestoreFailsOnGetConfigError(c *check.C) {
   581  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   582  		rs.calls = append(rs.calls, "get config")
   583  		return nil, errors.New("bzzt")
   584  	})()
   585  
   586  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   587  	c.Assert(err, check.ErrorMatches, "internal error: cannot obtain current snap config for snapshot restore: bzzt")
   588  	c.Check(rs.calls, check.DeepEquals, []string{"get config"})
   589  }
   590  
   591  func (rs *readerSuite) TestDoRestoreFailsOnBadConfig(c *check.C) {
   592  	defer snapshotstate.MockConfigGetSnapConfig(func(*state.State, string) (*json.RawMessage, error) {
   593  		rs.calls = append(rs.calls, "get config")
   594  		buf := json.RawMessage(`42`)
   595  		return &buf, nil
   596  	})()
   597  
   598  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   599  	c.Assert(err, check.ErrorMatches, ".* cannot unmarshal .*")
   600  	c.Check(rs.calls, check.DeepEquals, []string{"get config"})
   601  }
   602  
   603  func (rs *readerSuite) TestDoRestoreFailsOpenError(c *check.C) {
   604  	defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) {
   605  		rs.calls = append(rs.calls, "open")
   606  		return nil, errors.New("bzzt")
   607  	})()
   608  
   609  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   610  	c.Assert(err, check.ErrorMatches, "cannot open snapshot: bzzt")
   611  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open"})
   612  }
   613  
   614  func (rs *readerSuite) TestDoRestoreFailsUnserialisableSnapshotConfigError(c *check.C) {
   615  	defer snapshotstate.MockBackendOpen(func(string, uint64) (*backend.Reader, error) {
   616  		rs.calls = append(rs.calls, "open")
   617  		return &backend.Reader{
   618  			Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": func() {}}},
   619  		}, nil
   620  	})()
   621  
   622  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   623  	c.Assert(err, check.ErrorMatches, "cannot marshal saved config: json.*")
   624  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "revert"})
   625  }
   626  
   627  func (rs *readerSuite) TestDoRestoreFailsOnRestoreError(c *check.C) {
   628  	defer snapshotstate.MockBackendRestore(func(*backend.Reader, context.Context, snap.Revision, []string, backend.Logf) (*backend.RestoreState, error) {
   629  		rs.calls = append(rs.calls, "restore")
   630  		return nil, errors.New("bzzt")
   631  	})()
   632  
   633  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   634  	c.Assert(err, check.ErrorMatches, "bzzt")
   635  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore"})
   636  }
   637  
   638  func (rs *readerSuite) TestDoRestoreFailsAndRevertsOnSetConfigError(c *check.C) {
   639  	defer snapshotstate.MockConfigSetSnapConfig(func(*state.State, string, *json.RawMessage) error {
   640  		rs.calls = append(rs.calls, "set config")
   641  		return errors.New("bzzt")
   642  	})()
   643  
   644  	err := snapshotstate.DoRestore(rs.task, &tomb.Tomb{})
   645  	c.Assert(err, check.ErrorMatches, "cannot set snap config: bzzt")
   646  	c.Check(rs.calls, check.DeepEquals, []string{"get config", "open", "restore", "set config", "revert"})
   647  }
   648  
   649  func (rs *readerSuite) TestUndoRestore(c *check.C) {
   650  	st := rs.task.State()
   651  	st.Lock()
   652  	var v map[string]interface{}
   653  	rs.task.Set("restore-state", &v)
   654  	st.Unlock()
   655  
   656  	err := snapshotstate.UndoRestore(rs.task, &tomb.Tomb{})
   657  	c.Assert(err, check.IsNil)
   658  	c.Check(rs.calls, check.DeepEquals, []string{"set config", "revert"})
   659  }
   660  
   661  func (rs *readerSuite) TestCleanupRestore(c *check.C) {
   662  	st := rs.task.State()
   663  	st.Lock()
   664  	var v map[string]interface{}
   665  	rs.task.Set("restore-state", &v)
   666  	st.Unlock()
   667  
   668  	err := snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{})
   669  	c.Assert(err, check.IsNil)
   670  	c.Check(rs.calls, check.HasLen, 0)
   671  
   672  	st.Lock()
   673  	rs.task.SetStatus(state.DoneStatus)
   674  	st.Unlock()
   675  
   676  	err = snapshotstate.CleanupRestore(rs.task, &tomb.Tomb{})
   677  	c.Assert(err, check.IsNil)
   678  	c.Check(rs.calls, check.DeepEquals, []string{"cleanup"})
   679  }
   680  
   681  func (rs *readerSuite) TestDoCheck(c *check.C) {
   682  	defer snapshotstate.MockBackendOpen(func(filename string, setID uint64) (*backend.Reader, error) {
   683  		rs.calls = append(rs.calls, "open")
   684  		c.Check(filename, check.Equals, "/some/1_file.zip")
   685  		// set id 0 tells backend.Open to use set id from the filename
   686  		c.Check(setID, check.Equals, uint64(0))
   687  		return &backend.Reader{
   688  			Snapshot: client.Snapshot{Conf: map[string]interface{}{"hello": "there"}},
   689  		}, nil
   690  	})()
   691  	defer snapshotstate.MockBackendCheck(func(_ *backend.Reader, _ context.Context, users []string) error {
   692  		rs.calls = append(rs.calls, "check")
   693  		c.Check(users, check.DeepEquals, []string{"a-user", "b-user"})
   694  		return nil
   695  	})()
   696  
   697  	err := snapshotstate.DoCheck(rs.task, &tomb.Tomb{})
   698  	c.Assert(err, check.IsNil)
   699  	c.Check(rs.calls, check.DeepEquals, []string{"open", "check"})
   700  }
   701  
   702  func (rs *readerSuite) TestDoRemove(c *check.C) {
   703  	defer snapshotstate.MockOsRemove(func(filename string) error {
   704  		c.Check(filename, check.Equals, "/some/1_file.zip")
   705  		rs.calls = append(rs.calls, "remove")
   706  		return nil
   707  	})()
   708  	err := snapshotstate.DoForget(rs.task, &tomb.Tomb{})
   709  	c.Assert(err, check.IsNil)
   710  	c.Check(rs.calls, check.DeepEquals, []string{"remove"})
   711  }
   712  
   713  func (rs *readerSuite) TestDoForgetRemovesAutomaticSnapshotExpiry(c *check.C) {
   714  	defer snapshotstate.MockOsRemove(func(filename string) error {
   715  		return nil
   716  	})()
   717  
   718  	st := state.New(nil)
   719  	st.Lock()
   720  	defer st.Unlock()
   721  
   722  	task := st.NewTask("forget-snapshot", "...")
   723  	task.Set("snapshot-setup", map[string]interface{}{
   724  		"set-id":   1,
   725  		"filename": "a-file",
   726  		"snap":     "a-snap",
   727  	})
   728  
   729  	st.Set("snapshots", map[uint64]interface{}{
   730  		1: map[string]interface{}{
   731  			"expiry-time": "2001-03-11T11:24:00Z",
   732  		},
   733  		2: map[string]interface{}{
   734  			"expiry-time": "2037-02-12T12:50:00Z",
   735  		},
   736  	})
   737  
   738  	st.Unlock()
   739  	c.Assert(snapshotstate.DoForget(task, &tomb.Tomb{}), check.IsNil)
   740  
   741  	st.Lock()
   742  	var expirations map[uint64]interface{}
   743  	c.Assert(st.Get("snapshots", &expirations), check.IsNil)
   744  	c.Check(expirations, check.DeepEquals, map[uint64]interface{}{
   745  		2: map[string]interface{}{
   746  			"expiry-time": "2037-02-12T12:50:00Z",
   747  		}})
   748  }
   749  
   750  func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartup(c *check.C) {
   751  	n := 0
   752  	restore := snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) {
   753  		n++
   754  		return 0, nil
   755  	})
   756  	defer restore()
   757  
   758  	o := overlord.Mock()
   759  	st := o.State()
   760  	mgr := snapshotstate.Manager(st, state.NewTaskRunner(st))
   761  	c.Assert(mgr, check.NotNil)
   762  	o.AddManager(mgr)
   763  	err := o.Settle(100 * time.Millisecond)
   764  	c.Assert(err, check.IsNil)
   765  
   766  	c.Check(n, check.Equals, 1)
   767  }
   768  
   769  func (snapshotSuite) TestManagerRunCleanupAbandondedImportsAtStartupErrorLogged(c *check.C) {
   770  	logbuf, restore := logger.MockLogger()
   771  	defer restore()
   772  
   773  	n := 0
   774  	restore = snapshotstate.MockBackenCleanupAbandondedImports(func() (int, error) {
   775  		n++
   776  		return 0, errors.New("some error")
   777  	})
   778  	defer restore()
   779  
   780  	o := overlord.Mock()
   781  	st := o.State()
   782  	mgr := snapshotstate.Manager(st, state.NewTaskRunner(st))
   783  	c.Assert(mgr, check.NotNil)
   784  	o.AddManager(mgr)
   785  	err := o.Settle(100 * time.Millisecond)
   786  	c.Assert(err, check.IsNil)
   787  
   788  	c.Check(n, check.Equals, 1)
   789  	c.Check(logbuf.String(), testutil.Contains, "cannot cleanup incomplete imports: some error\n")
   790  }