github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/snapstate/autorefresh_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017-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 snapstate_test
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/httputil"
    34  	"github.com/snapcore/snapd/logger"
    35  	"github.com/snapcore/snapd/overlord/auth"
    36  	"github.com/snapcore/snapd/overlord/configstate/config"
    37  	"github.com/snapcore/snapd/overlord/snapstate"
    38  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    39  	"github.com/snapcore/snapd/overlord/state"
    40  	"github.com/snapcore/snapd/release"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/store"
    43  	"github.com/snapcore/snapd/store/storetest"
    44  	"github.com/snapcore/snapd/timeutil"
    45  )
    46  
    47  type autoRefreshStore struct {
    48  	storetest.Store
    49  
    50  	ops []string
    51  
    52  	err error
    53  }
    54  
    55  func (r *autoRefreshStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) {
    56  	if assertQuery != nil {
    57  		panic("no assertion query support")
    58  	}
    59  	if !opts.IsAutoRefresh {
    60  		panic("AutoRefresh snap action did not set IsAutoRefresh flag")
    61  	}
    62  
    63  	if ctx == nil || !auth.IsEnsureContext(ctx) {
    64  		panic("Ensure marked context required")
    65  	}
    66  	if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 {
    67  		panic("expected in test one action for each current snaps, and at least one snap")
    68  	}
    69  	for _, a := range actions {
    70  		if a.Action != "refresh" {
    71  			panic("expected refresh actions")
    72  		}
    73  	}
    74  	r.ops = append(r.ops, "list-refresh")
    75  	return nil, nil, r.err
    76  }
    77  
    78  type autoRefreshTestSuite struct {
    79  	state *state.State
    80  
    81  	store *autoRefreshStore
    82  
    83  	restore func()
    84  }
    85  
    86  var _ = Suite(&autoRefreshTestSuite{})
    87  
    88  func (s *autoRefreshTestSuite) SetUpTest(c *C) {
    89  	dirs.SetRootDir(c.MkDir())
    90  
    91  	s.state = state.New(nil)
    92  
    93  	s.store = &autoRefreshStore{}
    94  
    95  	s.state.Lock()
    96  	defer s.state.Unlock()
    97  	snapstate.ReplaceStore(s.state, s.store)
    98  
    99  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   100  		Active: true,
   101  		Sequence: []*snap.SideInfo{
   102  			{RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"},
   103  		},
   104  		Current:  snap.R(5),
   105  		SnapType: "app",
   106  		UserID:   1,
   107  	})
   108  
   109  	snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil }
   110  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
   111  		return nil, nil
   112  	}
   113  	snapstate.IsOnMeteredConnection = func() (bool, error) { return false, nil }
   114  
   115  	s.state.Set("seeded", true)
   116  	s.state.Set("seed-time", time.Now())
   117  	s.state.Set("refresh-privacy-key", "privacy-key")
   118  	s.restore = snapstatetest.MockDeviceModel(DefaultModel())
   119  }
   120  
   121  func (s *autoRefreshTestSuite) TearDownTest(c *C) {
   122  	snapstate.CanAutoRefresh = nil
   123  	snapstate.AutoAliases = nil
   124  	s.restore()
   125  	dirs.SetRootDir("")
   126  }
   127  
   128  func (s *autoRefreshTestSuite) TestLastRefresh(c *C) {
   129  	// this does an immediate refresh
   130  
   131  	af := snapstate.NewAutoRefresh(s.state)
   132  	err := af.Ensure()
   133  	c.Check(err, IsNil)
   134  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   135  
   136  	var lastRefresh time.Time
   137  	s.state.Lock()
   138  	s.state.Get("last-refresh", &lastRefresh)
   139  	s.state.Unlock()
   140  	c.Check(lastRefresh.Year(), Equals, time.Now().Year())
   141  }
   142  
   143  func (s *autoRefreshTestSuite) TestLastRefreshRefreshManaged(c *C) {
   144  	snapstate.CanManageRefreshes = func(st *state.State) bool {
   145  		return true
   146  	}
   147  	defer func() { snapstate.CanManageRefreshes = nil }()
   148  
   149  	logbuf, restore := logger.MockLogger()
   150  	defer restore()
   151  
   152  	s.state.Lock()
   153  	defer s.state.Unlock()
   154  
   155  	for _, t := range []struct {
   156  		conf   string
   157  		legacy bool
   158  	}{
   159  		{"refresh.timer", false},
   160  		{"refresh.schedule", true},
   161  	} {
   162  		tr := config.NewTransaction(s.state)
   163  		tr.Set("core", t.conf, "managed")
   164  		tr.Commit()
   165  
   166  		af := snapstate.NewAutoRefresh(s.state)
   167  		s.state.Unlock()
   168  		err := af.Ensure()
   169  		s.state.Lock()
   170  		c.Check(err, IsNil)
   171  		c.Check(s.store.ops, HasLen, 0)
   172  
   173  		refreshScheduleStr, legacy, err := af.RefreshSchedule()
   174  		c.Check(refreshScheduleStr, Equals, "managed")
   175  		c.Check(legacy, Equals, t.legacy)
   176  		c.Check(err, IsNil)
   177  
   178  		c.Check(af.NextRefresh(), DeepEquals, time.Time{})
   179  
   180  		count := strings.Count(logbuf.String(),
   181  			": refresh is managed via the snapd-control interface\n")
   182  		c.Check(count, Equals, 1, Commentf("too many occurrences:\n%s", logbuf.String()))
   183  
   184  		// ensure clean config for the next run
   185  		s.state.Set("config", nil)
   186  		logbuf.Reset()
   187  	}
   188  }
   189  
   190  func (s *autoRefreshTestSuite) TestRefreshManagedTimerWins(c *C) {
   191  	snapstate.CanManageRefreshes = func(st *state.State) bool {
   192  		return true
   193  	}
   194  	defer func() { snapstate.CanManageRefreshes = nil }()
   195  
   196  	s.state.Lock()
   197  	defer s.state.Unlock()
   198  
   199  	tr := config.NewTransaction(s.state)
   200  	// the "refresh.timer" setting always takes precedence over
   201  	// refresh.schedule
   202  	tr.Set("core", "refresh.timer", "00:00-12:00")
   203  	tr.Set("core", "refresh.schedule", "managed")
   204  	tr.Commit()
   205  
   206  	af := snapstate.NewAutoRefresh(s.state)
   207  	s.state.Unlock()
   208  	err := af.Ensure()
   209  	s.state.Lock()
   210  	c.Check(err, IsNil)
   211  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   212  
   213  	refreshScheduleStr, legacy, err := af.RefreshSchedule()
   214  	c.Check(refreshScheduleStr, Equals, "00:00-12:00")
   215  	c.Check(legacy, Equals, false)
   216  	c.Check(err, IsNil)
   217  }
   218  
   219  func (s *autoRefreshTestSuite) TestRefreshManagedDenied(c *C) {
   220  	canManageCalled := false
   221  	snapstate.CanManageRefreshes = func(st *state.State) bool {
   222  		canManageCalled = true
   223  		// always deny
   224  		return false
   225  	}
   226  	defer func() { snapstate.CanManageRefreshes = nil }()
   227  
   228  	logbuf, restore := logger.MockLogger()
   229  	defer restore()
   230  
   231  	s.state.Lock()
   232  	defer s.state.Unlock()
   233  
   234  	for _, conf := range []string{"refresh.timer", "refresh.schedule"} {
   235  		tr := config.NewTransaction(s.state)
   236  		tr.Set("core", conf, "managed")
   237  		tr.Commit()
   238  
   239  		af := snapstate.NewAutoRefresh(s.state)
   240  		for i := 0; i < 2; i++ {
   241  			c.Logf("ensure iteration: %v", i)
   242  			s.state.Unlock()
   243  			err := af.Ensure()
   244  			s.state.Lock()
   245  			c.Check(err, IsNil)
   246  			c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   247  
   248  			refreshScheduleStr, _, err := af.RefreshSchedule()
   249  			c.Check(refreshScheduleStr, Equals, snapstate.DefaultRefreshSchedule)
   250  			c.Check(err, IsNil)
   251  			c.Check(canManageCalled, Equals, true)
   252  			count := strings.Count(logbuf.String(),
   253  				": managed refresh schedule denied, no properly configured snapd-control\n")
   254  			c.Check(count, Equals, 1, Commentf("too many occurrences:\n%s", logbuf.String()))
   255  
   256  			canManageCalled = false
   257  		}
   258  
   259  		// ensure clean config for the next run
   260  		s.state.Set("config", nil)
   261  		logbuf.Reset()
   262  		canManageCalled = false
   263  	}
   264  }
   265  
   266  func (s *autoRefreshTestSuite) TestLastRefreshNoRefreshNeeded(c *C) {
   267  	s.state.Lock()
   268  	s.state.Set("last-refresh", time.Now())
   269  	s.state.Unlock()
   270  
   271  	af := snapstate.NewAutoRefresh(s.state)
   272  	err := af.Ensure()
   273  	c.Check(err, IsNil)
   274  	c.Check(s.store.ops, HasLen, 0)
   275  }
   276  
   277  func (s *autoRefreshTestSuite) TestRefreshBackoff(c *C) {
   278  	s.store.err = fmt.Errorf("random store error")
   279  	af := snapstate.NewAutoRefresh(s.state)
   280  	err := af.Ensure()
   281  	c.Check(err, ErrorMatches, "random store error")
   282  	c.Check(s.store.ops, HasLen, 1)
   283  
   284  	// override next refresh to be here already
   285  	now := time.Now()
   286  	snapstate.MockNextRefresh(af, now)
   287  
   288  	// call ensure again, our back-off will prevent the store from
   289  	// being hit again
   290  	err = af.Ensure()
   291  	c.Check(err, IsNil)
   292  	c.Check(s.store.ops, HasLen, 1)
   293  
   294  	// nextRefresh unchanged
   295  	c.Check(af.NextRefresh().Equal(now), Equals, true)
   296  
   297  	// fake that the retryRefreshDelay is over
   298  	restore := snapstate.MockRefreshRetryDelay(1 * time.Millisecond)
   299  	defer restore()
   300  	time.Sleep(10 * time.Millisecond)
   301  
   302  	// ensure hits the store again
   303  	err = af.Ensure()
   304  	c.Check(err, ErrorMatches, "random store error")
   305  	c.Check(s.store.ops, HasLen, 2)
   306  
   307  	// nextRefresh now zero
   308  	c.Check(af.NextRefresh().IsZero(), Equals, true)
   309  	// set it to something in the future
   310  	snapstate.MockNextRefresh(af, time.Now().Add(time.Minute))
   311  
   312  	// nothing really happens yet: the previous autorefresh failed
   313  	// but it still counts as having tried to autorefresh
   314  	err = af.Ensure()
   315  	c.Check(err, IsNil)
   316  	c.Check(s.store.ops, HasLen, 2)
   317  
   318  	// pretend the time for next refresh is here
   319  	snapstate.MockNextRefresh(af, time.Now())
   320  	// including the wait for the retryRefreshDelay backoff
   321  	time.Sleep(10 * time.Millisecond)
   322  
   323  	// now yes it happens again
   324  	err = af.Ensure()
   325  	c.Check(err, ErrorMatches, "random store error")
   326  	c.Check(s.store.ops, HasLen, 3)
   327  	// and not *again* again
   328  	err = af.Ensure()
   329  	c.Check(err, IsNil)
   330  	c.Check(s.store.ops, HasLen, 3)
   331  
   332  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh", "list-refresh", "list-refresh"})
   333  }
   334  
   335  func (s *autoRefreshTestSuite) TestRefreshPersistentError(c *C) {
   336  	// fake that the retryRefreshDelay is over
   337  	restore := snapstate.MockRefreshRetryDelay(1 * time.Millisecond)
   338  	defer restore()
   339  
   340  	initialLastRefresh := time.Now().Add(-12 * time.Hour)
   341  	s.state.Lock()
   342  	s.state.Set("last-refresh", initialLastRefresh)
   343  	s.state.Unlock()
   344  
   345  	s.store.err = &httputil.PerstistentNetworkError{Err: fmt.Errorf("error")}
   346  	af := snapstate.NewAutoRefresh(s.state)
   347  	err := af.Ensure()
   348  	c.Check(err, ErrorMatches, "persistent network error: error")
   349  	c.Check(s.store.ops, HasLen, 1)
   350  
   351  	// last-refresh time remains untouched
   352  	var lastRefresh time.Time
   353  	s.state.Lock()
   354  	s.state.Get("last-refresh", &lastRefresh)
   355  	s.state.Unlock()
   356  	c.Check(lastRefresh.Format(time.RFC3339), Equals, initialLastRefresh.Format(time.RFC3339))
   357  
   358  	s.store.err = nil
   359  	time.Sleep(10 * time.Millisecond)
   360  
   361  	// call ensure again, refresh should be attempted again
   362  	err = af.Ensure()
   363  	c.Check(err, IsNil)
   364  	c.Check(s.store.ops, HasLen, 2)
   365  }
   366  
   367  func (s *autoRefreshTestSuite) TestDefaultScheduleIsRandomized(c *C) {
   368  	schedule, err := timeutil.ParseSchedule(snapstate.DefaultRefreshSchedule)
   369  	c.Assert(err, IsNil)
   370  
   371  	for _, sched := range schedule {
   372  		for _, span := range sched.ClockSpans {
   373  			c.Check(span.Start == span.End, Equals, false,
   374  				Commentf("clock span %v is a single time, expected an actual span", span))
   375  			c.Check(span.Spread, Equals, true,
   376  				Commentf("clock span %v is not randomized", span))
   377  		}
   378  	}
   379  }
   380  
   381  func (s *autoRefreshTestSuite) TestLastRefreshRefreshHold(c *C) {
   382  	s.state.Lock()
   383  	defer s.state.Unlock()
   384  
   385  	t0 := time.Now()
   386  	s.state.Set("last-refresh", t0.Add(-12*time.Hour))
   387  
   388  	holdTime := t0.Add(5 * time.Minute)
   389  	tr := config.NewTransaction(s.state)
   390  	tr.Set("core", "refresh.hold", holdTime)
   391  	tr.Commit()
   392  
   393  	af := snapstate.NewAutoRefresh(s.state)
   394  	s.state.Unlock()
   395  	err := af.Ensure()
   396  	s.state.Lock()
   397  	c.Check(err, IsNil)
   398  
   399  	// no refresh
   400  	c.Check(s.store.ops, HasLen, 0)
   401  
   402  	// hold still kept
   403  	tr = config.NewTransaction(s.state)
   404  	var t1 time.Time
   405  	err = tr.Get("core", "refresh.hold", &t1)
   406  	c.Assert(err, IsNil)
   407  	c.Check(t1.Equal(holdTime), Equals, true)
   408  }
   409  
   410  func (s *autoRefreshTestSuite) TestLastRefreshRefreshHoldExpired(c *C) {
   411  	s.state.Lock()
   412  	defer s.state.Unlock()
   413  
   414  	t0 := time.Now()
   415  	s.state.Set("last-refresh", t0.Add(-12*time.Hour))
   416  
   417  	holdTime := t0.Add(-5 * time.Minute)
   418  	tr := config.NewTransaction(s.state)
   419  	tr.Set("core", "refresh.hold", holdTime)
   420  	tr.Commit()
   421  
   422  	af := snapstate.NewAutoRefresh(s.state)
   423  	s.state.Unlock()
   424  	err := af.Ensure()
   425  	s.state.Lock()
   426  	c.Check(err, IsNil)
   427  
   428  	// refresh happened
   429  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   430  
   431  	var lastRefresh time.Time
   432  	s.state.Get("last-refresh", &lastRefresh)
   433  	c.Check(lastRefresh.Year(), Equals, time.Now().Year())
   434  
   435  	// hold was reset
   436  	tr = config.NewTransaction(s.state)
   437  	var t1 time.Time
   438  	err = tr.Get("core", "refresh.hold", &t1)
   439  	c.Assert(config.IsNoOption(err), Equals, true)
   440  }
   441  
   442  func (s *autoRefreshTestSuite) TestLastRefreshRefreshHoldExpiredReschedule(c *C) {
   443  	s.state.Lock()
   444  	defer s.state.Unlock()
   445  
   446  	t0 := time.Now()
   447  	s.state.Set("last-refresh", t0.Add(-12*time.Hour))
   448  
   449  	holdTime := t0.Add(-1 * time.Minute)
   450  	tr := config.NewTransaction(s.state)
   451  	tr.Set("core", "refresh.hold", holdTime)
   452  
   453  	nextRefresh := t0.Add(5 * time.Minute).Truncate(time.Minute)
   454  	schedule := fmt.Sprintf("%02d:%02d-%02d:59", nextRefresh.Hour(), nextRefresh.Minute(), nextRefresh.Hour())
   455  	tr.Set("core", "refresh.timer", schedule)
   456  	tr.Commit()
   457  
   458  	af := snapstate.NewAutoRefresh(s.state)
   459  	snapstate.MockLastRefreshSchedule(af, schedule)
   460  	snapstate.MockNextRefresh(af, holdTime.Add(-2*time.Minute))
   461  
   462  	s.state.Unlock()
   463  	err := af.Ensure()
   464  	s.state.Lock()
   465  	c.Check(err, IsNil)
   466  
   467  	// refresh did not happen yet
   468  	c.Check(s.store.ops, HasLen, 0)
   469  
   470  	// hold was reset
   471  	tr = config.NewTransaction(s.state)
   472  	var t1 time.Time
   473  	err = tr.Get("core", "refresh.hold", &t1)
   474  	c.Assert(config.IsNoOption(err), Equals, true)
   475  
   476  	// check next refresh
   477  	nextRefresh1 := af.NextRefresh()
   478  	c.Check(nextRefresh1.Before(nextRefresh), Equals, false)
   479  }
   480  
   481  func (s *autoRefreshTestSuite) TestEffectiveRefreshHold(c *C) {
   482  	s.state.Lock()
   483  	defer s.state.Unlock()
   484  
   485  	// assume no seed-time
   486  	s.state.Set("seed-time", nil)
   487  
   488  	af := snapstate.NewAutoRefresh(s.state)
   489  
   490  	t0, err := af.EffectiveRefreshHold()
   491  	c.Assert(err, IsNil)
   492  	c.Check(t0.IsZero(), Equals, true)
   493  
   494  	holdTime := time.Now()
   495  	tr := config.NewTransaction(s.state)
   496  	tr.Set("core", "refresh.hold", holdTime)
   497  	tr.Commit()
   498  
   499  	seedTime := holdTime.Add(-70 * 24 * time.Hour)
   500  	s.state.Set("seed-time", seedTime)
   501  
   502  	t1, err := af.EffectiveRefreshHold()
   503  	c.Assert(err, IsNil)
   504  	c.Check(t1.Equal(seedTime.Add(60*24*time.Hour)), Equals, true)
   505  
   506  	lastRefresh := holdTime.Add(-65 * 24 * time.Hour)
   507  	s.state.Set("last-refresh", lastRefresh)
   508  
   509  	t1, err = af.EffectiveRefreshHold()
   510  	c.Assert(err, IsNil)
   511  	c.Check(t1.Equal(lastRefresh.Add(60*24*time.Hour)), Equals, true)
   512  
   513  	s.state.Set("last-refresh", holdTime.Add(-6*time.Hour))
   514  	t1, err = af.EffectiveRefreshHold()
   515  	c.Assert(err, IsNil)
   516  	c.Check(t1.Equal(holdTime), Equals, true)
   517  }
   518  
   519  func (s *autoRefreshTestSuite) TestEnsureLastRefreshAnchor(c *C) {
   520  	s.state.Lock()
   521  	defer s.state.Unlock()
   522  	// set hold => no refreshes
   523  	t0 := time.Now()
   524  	holdTime := t0.Add(1 * time.Hour)
   525  	tr := config.NewTransaction(s.state)
   526  	tr.Set("core", "refresh.hold", holdTime)
   527  	tr.Commit()
   528  
   529  	// with seed-time
   530  	s.state.Set("seed-time", t0.Add(-1*time.Hour))
   531  
   532  	af := snapstate.NewAutoRefresh(s.state)
   533  	s.state.Unlock()
   534  	err := af.Ensure()
   535  	s.state.Lock()
   536  	c.Check(err, IsNil)
   537  	// no refresh
   538  	c.Check(s.store.ops, HasLen, 0)
   539  	lastRefresh, err := af.LastRefresh()
   540  	c.Assert(err, IsNil)
   541  	c.Check(lastRefresh.IsZero(), Equals, true)
   542  
   543  	// no seed-time
   544  	s.state.Set("seed-time", nil)
   545  
   546  	// fallback to time of executable
   547  	st, err := os.Stat("/proc/self/exe")
   548  	c.Assert(err, IsNil)
   549  	exeTime := st.ModTime()
   550  
   551  	af = snapstate.NewAutoRefresh(s.state)
   552  	s.state.Unlock()
   553  	err = af.Ensure()
   554  	s.state.Lock()
   555  	c.Check(err, IsNil)
   556  	// no refresh
   557  	c.Check(s.store.ops, HasLen, 0)
   558  	lastRefresh, err = af.LastRefresh()
   559  	c.Assert(err, IsNil)
   560  	c.Check(lastRefresh.Equal(exeTime), Equals, true)
   561  
   562  	// clear
   563  	s.state.Set("last-refresh", nil)
   564  	// use core last refresh time
   565  	coreCurrent := filepath.Join(dirs.SnapMountDir, "core", "current")
   566  	err = os.MkdirAll(coreCurrent, 0755)
   567  	c.Assert(err, IsNil)
   568  	st, err = os.Stat(coreCurrent)
   569  	c.Assert(err, IsNil)
   570  	coreRefreshed := st.ModTime()
   571  
   572  	af = snapstate.NewAutoRefresh(s.state)
   573  	s.state.Unlock()
   574  	err = af.Ensure()
   575  	s.state.Lock()
   576  	c.Check(err, IsNil)
   577  	// no refresh
   578  	c.Check(s.store.ops, HasLen, 0)
   579  	lastRefresh, err = af.LastRefresh()
   580  	c.Assert(err, IsNil)
   581  	c.Check(lastRefresh.Equal(coreRefreshed), Equals, true)
   582  }
   583  
   584  func (s *autoRefreshTestSuite) TestAtSeedPolicy(c *C) {
   585  	r := release.MockOnClassic(false)
   586  	defer r()
   587  
   588  	s.state.Lock()
   589  	defer s.state.Unlock()
   590  
   591  	af := snapstate.NewAutoRefresh(s.state)
   592  
   593  	// on core, does nothing
   594  	err := af.AtSeed()
   595  	c.Assert(err, IsNil)
   596  	c.Check(af.NextRefresh().IsZero(), Equals, true)
   597  	tr := config.NewTransaction(s.state)
   598  	var t1 time.Time
   599  	err = tr.Get("core", "refresh.hold", &t1)
   600  	c.Check(config.IsNoOption(err), Equals, true)
   601  
   602  	release.MockOnClassic(true)
   603  	now := time.Now()
   604  	// on classic it sets a refresh hold of 2h
   605  	err = af.AtSeed()
   606  	c.Assert(err, IsNil)
   607  	c.Check(af.NextRefresh().IsZero(), Equals, false)
   608  	tr = config.NewTransaction(s.state)
   609  	err = tr.Get("core", "refresh.hold", &t1)
   610  	c.Check(err, IsNil)
   611  	c.Check(t1.Before(now.Add(2*time.Hour)), Equals, false)
   612  	c.Check(t1.After(now.Add(2*time.Hour+5*time.Minute)), Equals, false)
   613  
   614  	// nop
   615  	err = af.AtSeed()
   616  	c.Assert(err, IsNil)
   617  	var t2 time.Time
   618  	tr = config.NewTransaction(s.state)
   619  	err = tr.Get("core", "refresh.hold", &t2)
   620  	c.Check(err, IsNil)
   621  	c.Check(t1.Equal(t2), Equals, true)
   622  }
   623  
   624  func (s *autoRefreshTestSuite) TestCanRefreshOnMetered(c *C) {
   625  	s.state.Lock()
   626  	defer s.state.Unlock()
   627  
   628  	can, err := snapstate.CanRefreshOnMeteredConnection(s.state)
   629  	c.Assert(can, Equals, true)
   630  	c.Assert(err, Equals, nil)
   631  
   632  	// enable holding refreshes when on metered connection
   633  	tr := config.NewTransaction(s.state)
   634  	err = tr.Set("core", "refresh.metered", "hold")
   635  	c.Assert(err, IsNil)
   636  	tr.Commit()
   637  
   638  	can, err = snapstate.CanRefreshOnMeteredConnection(s.state)
   639  	c.Assert(can, Equals, false)
   640  	c.Assert(err, Equals, nil)
   641  
   642  	// explicitly disable holding refreshes when on metered connection
   643  	tr = config.NewTransaction(s.state)
   644  	err = tr.Set("core", "refresh.metered", "")
   645  	c.Assert(err, IsNil)
   646  	tr.Commit()
   647  
   648  	can, err = snapstate.CanRefreshOnMeteredConnection(s.state)
   649  	c.Assert(can, Equals, true)
   650  	c.Assert(err, Equals, nil)
   651  }
   652  
   653  func (s *autoRefreshTestSuite) TestRefreshOnMeteredConnIsMetered(c *C) {
   654  	// pretend we're on metered connection
   655  	revert := snapstate.MockIsOnMeteredConnection(func() (bool, error) {
   656  		return true, nil
   657  	})
   658  	defer revert()
   659  
   660  	s.state.Lock()
   661  	defer s.state.Unlock()
   662  
   663  	tr := config.NewTransaction(s.state)
   664  	tr.Set("core", "refresh.metered", "hold")
   665  	tr.Commit()
   666  
   667  	af := snapstate.NewAutoRefresh(s.state)
   668  
   669  	s.state.Set("last-refresh", time.Now().Add(-5*24*time.Hour))
   670  	s.state.Unlock()
   671  	err := af.Ensure()
   672  	s.state.Lock()
   673  	c.Check(err, IsNil)
   674  	// no refresh
   675  	c.Check(s.store.ops, HasLen, 0)
   676  
   677  	c.Check(af.NextRefresh(), DeepEquals, time.Time{})
   678  
   679  	// last refresh over 60 days ago, new one is launched regardless of
   680  	// connection being metered
   681  	s.state.Set("last-refresh", time.Now().Add(-61*24*time.Hour))
   682  	s.state.Unlock()
   683  	err = af.Ensure()
   684  	s.state.Lock()
   685  	c.Check(err, IsNil)
   686  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   687  }
   688  
   689  func (s *autoRefreshTestSuite) TestRefreshOnMeteredConnNotMetered(c *C) {
   690  	// pretend we're on non-metered connection
   691  	revert := snapstate.MockIsOnMeteredConnection(func() (bool, error) {
   692  		return false, nil
   693  	})
   694  	defer revert()
   695  
   696  	s.state.Lock()
   697  	defer s.state.Unlock()
   698  
   699  	tr := config.NewTransaction(s.state)
   700  	tr.Set("core", "refresh.metered", "hold")
   701  	tr.Commit()
   702  
   703  	af := snapstate.NewAutoRefresh(s.state)
   704  
   705  	s.state.Set("last-refresh", time.Now().Add(-5*24*time.Hour))
   706  	s.state.Unlock()
   707  	err := af.Ensure()
   708  	s.state.Lock()
   709  	c.Check(err, IsNil)
   710  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   711  }
   712  
   713  func (s *autoRefreshTestSuite) TestInhibitRefreshWithinInhibitWindow(c *C) {
   714  	s.state.Lock()
   715  	defer s.state.Unlock()
   716  
   717  	si := &snap.SideInfo{RealName: "pkg", Revision: snap.R(1)}
   718  	info := &snap.Info{SideInfo: *si}
   719  	snapst := &snapstate.SnapState{
   720  		Sequence: []*snap.SideInfo{si},
   721  		Current:  si.Revision,
   722  	}
   723  	err := snapstate.InhibitRefresh(s.state, snapst, info, func(si *snap.Info) error {
   724  		return &snapstate.BusySnapError{SnapName: "pkg"}
   725  	})
   726  	c.Assert(err, ErrorMatches, `snap "pkg" has running apps or hooks`)
   727  
   728  	pending, _ := s.state.PendingWarnings()
   729  	c.Assert(pending, HasLen, 1)
   730  	c.Check(pending[0].String(), Equals, `snap "pkg" is currently in use. Its refresh will be postponed for up to 7 days to wait for the snap to no longer be in use.`)
   731  }
   732  
   733  func (s *autoRefreshTestSuite) TestInhibitRefreshWarnsAndRefreshesWhenOverdue(c *C) {
   734  	s.state.Lock()
   735  	defer s.state.Unlock()
   736  
   737  	instant := time.Now()
   738  	pastInstant := instant.Add(-snapstate.MaxInhibition * 2)
   739  
   740  	si := &snap.SideInfo{RealName: "pkg", Revision: snap.R(1)}
   741  	info := &snap.Info{SideInfo: *si}
   742  	snapst := &snapstate.SnapState{
   743  		Sequence:             []*snap.SideInfo{si},
   744  		Current:              si.Revision,
   745  		RefreshInhibitedTime: &pastInstant,
   746  	}
   747  	err := snapstate.InhibitRefresh(s.state, snapst, info, func(si *snap.Info) error {
   748  		return &snapstate.BusySnapError{SnapName: "pkg"}
   749  	})
   750  	c.Assert(err, IsNil)
   751  
   752  	pending, _ := s.state.PendingWarnings()
   753  	c.Assert(pending, HasLen, 1)
   754  	c.Check(pending[0].String(), Equals, `snap "pkg" has been running for the maximum allowable 7 days since its refresh was postponed. It will now be refreshed.`)
   755  }