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