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