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