github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/overlord/snapstate/autorefresh_gating_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/interfaces"
    32  	"github.com/snapcore/snapd/interfaces/builtin"
    33  	"github.com/snapcore/snapd/logger"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/overlord/auth"
    36  	"github.com/snapcore/snapd/overlord/configstate/config"
    37  	"github.com/snapcore/snapd/overlord/hookstate"
    38  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    39  	"github.com/snapcore/snapd/overlord/snapstate"
    40  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    41  	"github.com/snapcore/snapd/overlord/state"
    42  	"github.com/snapcore/snapd/release"
    43  	"github.com/snapcore/snapd/snap"
    44  	"github.com/snapcore/snapd/snap/snaptest"
    45  	"github.com/snapcore/snapd/store"
    46  	"github.com/snapcore/snapd/testutil"
    47  
    48  	. "gopkg.in/check.v1"
    49  )
    50  
    51  type autoRefreshGatingStore struct {
    52  	*fakeStore
    53  	refreshedSnaps []*snap.Info
    54  }
    55  
    56  type autorefreshGatingSuite struct {
    57  	testutil.BaseTest
    58  	state *state.State
    59  	repo  *interfaces.Repository
    60  	store *autoRefreshGatingStore
    61  }
    62  
    63  var _ = Suite(&autorefreshGatingSuite{})
    64  
    65  func (s *autorefreshGatingSuite) SetUpTest(c *C) {
    66  	s.BaseTest.SetUpTest(c)
    67  	dirs.SetRootDir(c.MkDir())
    68  	s.AddCleanup(func() {
    69  		dirs.SetRootDir("/")
    70  	})
    71  	s.state = state.New(nil)
    72  
    73  	s.repo = interfaces.NewRepository()
    74  	for _, iface := range builtin.Interfaces() {
    75  		c.Assert(s.repo.AddInterface(iface), IsNil)
    76  	}
    77  
    78  	s.state.Lock()
    79  	defer s.state.Unlock()
    80  	ifacerepo.Replace(s.state, s.repo)
    81  
    82  	s.store = &autoRefreshGatingStore{fakeStore: &fakeStore{}}
    83  	snapstate.ReplaceStore(s.state, s.store)
    84  	s.state.Set("refresh-privacy-key", "privacy-key")
    85  }
    86  
    87  func (r *autoRefreshGatingStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) {
    88  	if assertQuery != nil {
    89  		panic("no assertion query support")
    90  	}
    91  	if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 {
    92  		panic("expected in test one action for each current snaps, and at least one snap")
    93  	}
    94  	for _, a := range actions {
    95  		if a.Action != "refresh" {
    96  			panic("expected refresh actions")
    97  		}
    98  	}
    99  
   100  	res := []store.SnapActionResult{}
   101  	for _, rs := range r.refreshedSnaps {
   102  		res = append(res, store.SnapActionResult{Info: rs})
   103  	}
   104  
   105  	return res, nil, nil
   106  }
   107  
   108  func mockInstalledSnap(c *C, st *state.State, snapYaml string, hasHook bool) *snap.Info {
   109  	snapInfo := snaptest.MockSnap(c, string(snapYaml), &snap.SideInfo{
   110  		Revision: snap.R(1),
   111  	})
   112  
   113  	snapName := snapInfo.SnapName()
   114  	si := &snap.SideInfo{RealName: snapName, SnapID: "id", Revision: snap.R(1)}
   115  	snapstate.Set(st, snapName, &snapstate.SnapState{
   116  		Active:   true,
   117  		Sequence: []*snap.SideInfo{si},
   118  		Current:  si.Revision,
   119  		SnapType: string(snapInfo.Type()),
   120  	})
   121  
   122  	if hasHook {
   123  		c.Assert(os.MkdirAll(snapInfo.HooksDir(), 0775), IsNil)
   124  		err := ioutil.WriteFile(filepath.Join(snapInfo.HooksDir(), "gate-auto-refresh"), nil, 0755)
   125  		c.Assert(err, IsNil)
   126  	}
   127  	return snapInfo
   128  }
   129  
   130  func mockLastRefreshed(c *C, st *state.State, refreshedTime string, snaps ...string) {
   131  	refreshed, err := time.Parse(time.RFC3339, refreshedTime)
   132  	c.Assert(err, IsNil)
   133  	for _, snapName := range snaps {
   134  		var snapst snapstate.SnapState
   135  		c.Assert(snapstate.Get(st, snapName, &snapst), IsNil)
   136  		snapst.LastRefreshTime = &refreshed
   137  		snapstate.Set(st, snapName, &snapst)
   138  	}
   139  }
   140  
   141  const baseSnapAyaml = `name: base-snap-a
   142  type: base
   143  `
   144  
   145  const snapAyaml = `name: snap-a
   146  type: app
   147  base: base-snap-a
   148  `
   149  
   150  const baseSnapByaml = `name: base-snap-b
   151  type: base
   152  `
   153  
   154  const snapByaml = `name: snap-b
   155  type: app
   156  base: base-snap-b
   157  version: 1
   158  `
   159  
   160  const kernelYaml = `name: kernel
   161  type: kernel
   162  version: 1
   163  `
   164  
   165  const gadget1Yaml = `name: gadget
   166  type: gadget
   167  version: 1
   168  `
   169  
   170  const snapCyaml = `name: snap-c
   171  type: app
   172  version: 1
   173  `
   174  
   175  const snapDyaml = `name: snap-d
   176  type: app
   177  version: 1
   178  slots:
   179      slot: desktop
   180  `
   181  
   182  const snapEyaml = `name: snap-e
   183  type: app
   184  version: 1
   185  base: other-base
   186  plugs:
   187      plug: desktop
   188  `
   189  
   190  const snapFyaml = `name: snap-f
   191  type: app
   192  version: 1
   193  plugs:
   194      plug: desktop
   195  `
   196  
   197  const snapGyaml = `name: snap-g
   198  type: app
   199  version: 1
   200  base: other-base
   201  plugs:
   202      desktop:
   203      mir:
   204  `
   205  
   206  const coreYaml = `name: core
   207  type: os
   208  version: 1
   209  slots:
   210      desktop:
   211      mir:
   212  `
   213  
   214  const core18Yaml = `name: core18
   215  type: os
   216  version: 1
   217  `
   218  
   219  const snapdYaml = `name: snapd
   220  version: 1
   221  type: snapd
   222  slots:
   223      desktop:
   224  `
   225  
   226  func (s *autorefreshGatingSuite) TestHoldDurationLeft(c *C) {
   227  	now, err := time.Parse(time.RFC3339, "2021-06-03T10:00:00Z")
   228  	c.Assert(err, IsNil)
   229  	maxPostponement := time.Hour * 24 * 90
   230  
   231  	for i, tc := range []struct {
   232  		lastRefresh, firstHeld string
   233  		maxDuration            string
   234  		expected               string
   235  	}{
   236  		{
   237  			"2021-05-03T10:00:00Z", // last refreshed (1 month ago)
   238  			"2021-06-03T10:00:00Z", // first held now
   239  			"48h", // max duration
   240  			"48h", // expected
   241  		},
   242  		{
   243  			"2021-05-03T10:00:00Z", // last refreshed (1 month ago)
   244  			"2021-06-02T10:00:00Z", // first held (1 day ago)
   245  			"48h", // max duration
   246  			"24h", // expected
   247  		},
   248  		{
   249  			"2021-05-03T10:00:00Z", // last refreshed (1 month ago)
   250  			"2021-06-01T10:00:00Z", // first held (2 days ago)
   251  			"48h", // max duration
   252  			"00h", // expected
   253  		},
   254  		{
   255  			"2021-03-08T10:00:00Z", // last refreshed (almost 3 months ago)
   256  			"2021-06-01T10:00:00Z", // first held
   257  			"2160h",                // max duration (90 days)
   258  			"72h",                  // expected
   259  		},
   260  		{
   261  			"2021-03-04T10:00:00Z", // last refreshed
   262  			"2021-06-01T10:00:00Z", // first held (2 days ago)
   263  			"2160h",                // max duration (90 days)
   264  			"-24h",                 // expected (refresh is 1 day overdue)
   265  		},
   266  		{
   267  			"2021-06-01T10:00:00Z", // last refreshed (2 days ago)
   268  			"2021-06-03T10:00:00Z", // first held now
   269  			"2160h",                // max duration (90 days)
   270  			"2112h",                // expected (max minus 2 days)
   271  		},
   272  	} {
   273  		lastRefresh, err := time.Parse(time.RFC3339, tc.lastRefresh)
   274  		c.Assert(err, IsNil)
   275  		firstHeld, err := time.Parse(time.RFC3339, tc.firstHeld)
   276  		c.Assert(err, IsNil)
   277  		maxDuration, err := time.ParseDuration(tc.maxDuration)
   278  		c.Assert(err, IsNil)
   279  		expected, err := time.ParseDuration(tc.expected)
   280  		c.Assert(err, IsNil)
   281  
   282  		left := snapstate.HoldDurationLeft(now, lastRefresh, firstHeld, maxDuration, maxPostponement)
   283  		c.Check(left, Equals, expected, Commentf("case #%d", i))
   284  	}
   285  }
   286  
   287  func (s *autorefreshGatingSuite) TestLastRefreshedHelper(c *C) {
   288  	st := s.state
   289  	st.Lock()
   290  	defer st.Unlock()
   291  
   292  	inf := mockInstalledSnap(c, st, snapAyaml, false)
   293  	stat, err := os.Stat(inf.MountFile())
   294  	c.Assert(err, IsNil)
   295  
   296  	refreshed, err := snapstate.LastRefreshed(st, "snap-a")
   297  	c.Assert(err, IsNil)
   298  	c.Check(refreshed, DeepEquals, stat.ModTime())
   299  
   300  	t, err := time.Parse(time.RFC3339, "2021-01-01T10:00:00Z")
   301  	c.Assert(err, IsNil)
   302  
   303  	var snapst snapstate.SnapState
   304  	c.Assert(snapstate.Get(st, "snap-a", &snapst), IsNil)
   305  	snapst.LastRefreshTime = &t
   306  	snapstate.Set(st, "snap-a", &snapst)
   307  
   308  	refreshed, err = snapstate.LastRefreshed(st, "snap-a")
   309  	c.Assert(err, IsNil)
   310  	c.Check(refreshed, DeepEquals, t)
   311  }
   312  
   313  func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) {
   314  	st := s.state
   315  	st.Lock()
   316  	defer st.Unlock()
   317  
   318  	restore := snapstate.MockTimeNow(func() time.Time {
   319  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   320  		c.Assert(err, IsNil)
   321  		return t
   322  	})
   323  	defer restore()
   324  
   325  	mockInstalledSnap(c, st, snapAyaml, false)
   326  	mockInstalledSnap(c, st, snapByaml, false)
   327  	mockInstalledSnap(c, st, snapCyaml, false)
   328  	mockInstalledSnap(c, st, snapDyaml, false)
   329  	mockInstalledSnap(c, st, snapEyaml, false)
   330  	mockInstalledSnap(c, st, snapFyaml, false)
   331  
   332  	mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f")
   333  
   334  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
   335  	// this could be merged with the above HoldRefresh call, but it's fine if
   336  	// done separately too.
   337  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-e"), IsNil)
   338  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-e"), IsNil)
   339  	c.Assert(snapstate.HoldRefresh(st, "snap-f", 0, "snap-f"), IsNil)
   340  
   341  	var gating map[string]map[string]*snapstate.HoldState
   342  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   343  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   344  		"snap-b": {
   345  			// holding of other snaps for maxOtherHoldDuration (48h)
   346  			"snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   347  		},
   348  		"snap-c": {
   349  			"snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   350  		},
   351  		"snap-e": {
   352  			"snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   353  			"snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   354  		},
   355  		"snap-f": {
   356  			// holding self set for maxPostponement minus 1 day due to last refresh.
   357  			"snap-f": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-07T10:00:00Z"),
   358  		},
   359  	})
   360  }
   361  
   362  func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) {
   363  	st := s.state
   364  	st.Lock()
   365  	defer st.Unlock()
   366  
   367  	lastRefreshed := "2021-05-09T10:00:00Z"
   368  	now := "2021-05-10T10:00:00Z"
   369  	restore := snapstate.MockTimeNow(func() time.Time {
   370  		t, err := time.Parse(time.RFC3339, now)
   371  		c.Assert(err, IsNil)
   372  		return t
   373  	})
   374  	defer restore()
   375  
   376  	mockInstalledSnap(c, st, snapAyaml, false)
   377  	mockInstalledSnap(c, st, snapByaml, false)
   378  	// snap-a was last refreshed yesterday
   379  	mockLastRefreshed(c, st, lastRefreshed, "snap-a")
   380  
   381  	// hold it for just a bit (10h) initially
   382  	hold := time.Hour * 10
   383  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   384  	var gating map[string]map[string]*snapstate.HoldState
   385  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   386  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   387  		"snap-a": {
   388  			"snap-b": snapstate.MockHoldState(now, "2021-05-10T20:00:00Z"),
   389  		},
   390  	})
   391  
   392  	// holding for a shorter time is fine too
   393  	hold = time.Hour * 5
   394  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   395  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   396  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   397  		"snap-a": {
   398  			"snap-b": snapstate.MockHoldState(now, "2021-05-10T15:00:00Z"),
   399  		},
   400  	})
   401  
   402  	oldNow := now
   403  
   404  	// a refresh on next day
   405  	now = "2021-05-11T08:00:00Z"
   406  
   407  	// default hold time requested
   408  	hold = 0
   409  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   410  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   411  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   412  		"snap-a": {
   413  			// maximum for holding other snaps, but taking into consideration
   414  			// firstHeld time = "2021-05-10T10:00:00".
   415  			"snap-b": snapstate.MockHoldState(oldNow, "2021-05-12T10:00:00Z"),
   416  		},
   417  	})
   418  }
   419  
   420  func (s *autorefreshGatingSuite) TestHoldRefreshHelperCloseToMaxPostponement(c *C) {
   421  	st := s.state
   422  	st.Lock()
   423  	defer st.Unlock()
   424  
   425  	lastRefreshedStr := "2021-01-01T10:00:00Z"
   426  	lastRefreshed, err := time.Parse(time.RFC3339, lastRefreshedStr)
   427  	c.Assert(err, IsNil)
   428  	// we are 1 day before maxPostponent
   429  	now := lastRefreshed.Add(89 * time.Hour * 24)
   430  
   431  	restore := snapstate.MockTimeNow(func() time.Time { return now })
   432  	defer restore()
   433  
   434  	mockInstalledSnap(c, st, snapAyaml, false)
   435  	mockInstalledSnap(c, st, snapByaml, false)
   436  	mockLastRefreshed(c, st, lastRefreshedStr, "snap-a")
   437  
   438  	// request default hold time
   439  	var hold time.Duration
   440  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   441  
   442  	var gating map[string]map[string]*snapstate.HoldState
   443  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   444  	c.Assert(gating, HasLen, 1)
   445  	c.Check(gating["snap-a"]["snap-b"].HoldUntil.String(), DeepEquals, lastRefreshed.Add(90*time.Hour*24).String())
   446  }
   447  
   448  func (s *autorefreshGatingSuite) TestHoldRefreshExplicitHoldTime(c *C) {
   449  	st := s.state
   450  	st.Lock()
   451  	defer st.Unlock()
   452  
   453  	now := "2021-05-10T10:00:00Z"
   454  	restore := snapstate.MockTimeNow(func() time.Time {
   455  		t, err := time.Parse(time.RFC3339, now)
   456  		c.Assert(err, IsNil)
   457  		return t
   458  	})
   459  	defer restore()
   460  
   461  	mockInstalledSnap(c, st, snapAyaml, false)
   462  	mockInstalledSnap(c, st, snapByaml, false)
   463  
   464  	hold := time.Hour * 24 * 3
   465  	// holding self for 3 days
   466  	c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), IsNil)
   467  
   468  	// snap-b holds snap-a for 1 day
   469  	hold = time.Hour * 24
   470  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   471  
   472  	var gating map[string]map[string]*snapstate.HoldState
   473  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   474  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   475  		"snap-a": {
   476  			"snap-a": snapstate.MockHoldState(now, "2021-05-13T10:00:00Z"),
   477  			"snap-b": snapstate.MockHoldState(now, "2021-05-11T10:00:00Z"),
   478  		},
   479  	})
   480  }
   481  
   482  func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) {
   483  	st := s.state
   484  	st.Lock()
   485  	defer st.Unlock()
   486  
   487  	now := "2021-05-10T10:00:00Z"
   488  	restore := snapstate.MockTimeNow(func() time.Time {
   489  		t, err := time.Parse(time.RFC3339, now)
   490  		c.Assert(err, IsNil)
   491  		return t
   492  	})
   493  	defer restore()
   494  
   495  	mockInstalledSnap(c, st, snapAyaml, false)
   496  	mockInstalledSnap(c, st, snapByaml, false)
   497  	// snap-b was refreshed a few days ago
   498  	mockLastRefreshed(c, st, "2021-05-01T10:00:00Z", "snap-b")
   499  
   500  	// holding itself
   501  	hold := time.Hour * 24 * 96
   502  	c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-a" of 2304h0m0s by snap "snap-a" exceeds maximum holding time`)
   503  
   504  	// holding other snap
   505  	hold = time.Hour * 49
   506  	err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-b")
   507  	c.Check(err, ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`)
   508  	herr, ok := err.(*snapstate.HoldError)
   509  	c.Assert(ok, Equals, true)
   510  	c.Check(herr.SnapsInError, DeepEquals, map[string]snapstate.HoldDurationError{
   511  		"snap-b": {
   512  			Err:          fmt.Errorf(`requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`),
   513  			DurationLeft: 48 * time.Hour,
   514  		},
   515  	})
   516  
   517  	// hold for maximum allowed for other snaps
   518  	hold = time.Hour * 48
   519  	c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), IsNil)
   520  	// 2 days passed since it was first held
   521  	now = "2021-05-12T10:00:00Z"
   522  	hold = time.Minute * 2
   523  	c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-a" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
   524  
   525  	// refreshed long time ago (> maxPostponement)
   526  	mockLastRefreshed(c, st, "2021-01-01T10:00:00Z", "snap-b")
   527  	hold = time.Hour * 2
   528  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
   529  	c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
   530  }
   531  
   532  func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) {
   533  	st := s.state
   534  	st.Lock()
   535  	defer st.Unlock()
   536  
   537  	mockInstalledSnap(c, st, snapAyaml, false)
   538  	mockInstalledSnap(c, st, snapByaml, false)
   539  	mockInstalledSnap(c, st, snapCyaml, false)
   540  	mockInstalledSnap(c, st, snapDyaml, false)
   541  
   542  	mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-b", "snap-c", "snap-d")
   543  
   544  	restore := snapstate.MockTimeNow(func() time.Time {
   545  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   546  		c.Assert(err, IsNil)
   547  		return t
   548  	})
   549  	defer restore()
   550  
   551  	// nothing is held initially
   552  	held, err := snapstate.HeldSnaps(st)
   553  	c.Assert(err, IsNil)
   554  	c.Check(held, IsNil)
   555  
   556  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
   557  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil)
   558  	// holding self
   559  	c.Assert(snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d"), IsNil)
   560  
   561  	held, err = snapstate.HeldSnaps(st)
   562  	c.Assert(err, IsNil)
   563  	c.Check(held, DeepEquals, map[string]bool{"snap-b": true, "snap-c": true, "snap-d": true})
   564  
   565  	c.Assert(snapstate.ProceedWithRefresh(st, "snap-a"), IsNil)
   566  
   567  	held, err = snapstate.HeldSnaps(st)
   568  	c.Assert(err, IsNil)
   569  	c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-d": true})
   570  
   571  	c.Assert(snapstate.ProceedWithRefresh(st, "snap-d"), IsNil)
   572  	held, err = snapstate.HeldSnaps(st)
   573  	c.Assert(err, IsNil)
   574  	c.Check(held, IsNil)
   575  }
   576  
   577  func (s *autorefreshGatingSuite) TestResetGatingForRefreshedHelper(c *C) {
   578  	st := s.state
   579  	st.Lock()
   580  	defer st.Unlock()
   581  
   582  	restore := snapstate.MockTimeNow(func() time.Time {
   583  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   584  		c.Assert(err, IsNil)
   585  		return t
   586  	})
   587  	defer restore()
   588  
   589  	mockInstalledSnap(c, st, snapAyaml, false)
   590  	mockInstalledSnap(c, st, snapByaml, false)
   591  	mockInstalledSnap(c, st, snapCyaml, false)
   592  	mockInstalledSnap(c, st, snapDyaml, false)
   593  
   594  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
   595  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil)
   596  
   597  	c.Assert(snapstate.ResetGatingForRefreshed(st, "snap-b", "snap-c"), IsNil)
   598  	var gating map[string]map[string]*snapstate.HoldState
   599  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   600  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   601  		"snap-d": {
   602  			// holding self set for maxPostponement (95 days - buffer = 90 days)
   603  			"snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-08T10:00:00Z"),
   604  		},
   605  	})
   606  
   607  	held, err := snapstate.HeldSnaps(st)
   608  	c.Assert(err, IsNil)
   609  	c.Check(held, DeepEquals, map[string]bool{"snap-d": true})
   610  }
   611  
   612  const useHook = true
   613  const noHook = false
   614  
   615  func checkGatingTask(c *C, task *state.Task, expected map[string]*snapstate.RefreshCandidate) {
   616  	c.Assert(task.Kind(), Equals, "conditional-auto-refresh")
   617  	var snaps map[string]*snapstate.RefreshCandidate
   618  	c.Assert(task.Get("snaps", &snaps), IsNil)
   619  	c.Check(snaps, DeepEquals, expected)
   620  }
   621  
   622  func (s *autorefreshGatingSuite) TestAffectedByBase(c *C) {
   623  	restore := release.MockOnClassic(true)
   624  	defer restore()
   625  
   626  	st := s.state
   627  
   628  	st.Lock()
   629  	defer st.Unlock()
   630  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
   631  	baseSnapA := mockInstalledSnap(c, s.state, baseSnapAyaml, noHook)
   632  	// unrelated snaps
   633  	snapB := mockInstalledSnap(c, s.state, snapByaml, useHook)
   634  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
   635  
   636  	c.Assert(s.repo.AddSnap(snapB), IsNil)
   637  
   638  	updates := []*snap.Info{baseSnapA}
   639  	affected, err := snapstate.AffectedByRefresh(st, updates)
   640  	c.Assert(err, IsNil)
   641  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   642  		"snap-a": {
   643  			Base: true,
   644  			AffectingSnaps: map[string]bool{
   645  				"base-snap-a": true,
   646  			}}})
   647  }
   648  
   649  func (s *autorefreshGatingSuite) TestAffectedByCore(c *C) {
   650  	restore := release.MockOnClassic(true)
   651  	defer restore()
   652  
   653  	st := s.state
   654  
   655  	st.Lock()
   656  	defer st.Unlock()
   657  	snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook)
   658  	core := mockInstalledSnap(c, s.state, coreYaml, noHook)
   659  	snapB := mockInstalledSnap(c, s.state, snapByaml, useHook)
   660  
   661  	c.Assert(s.repo.AddSnap(core), IsNil)
   662  	c.Assert(s.repo.AddSnap(snapB), IsNil)
   663  	c.Assert(s.repo.AddSnap(snapC), IsNil)
   664  
   665  	updates := []*snap.Info{core}
   666  	affected, err := snapstate.AffectedByRefresh(st, updates)
   667  	c.Assert(err, IsNil)
   668  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   669  		"snap-c": {
   670  			Base: true,
   671  			AffectingSnaps: map[string]bool{
   672  				"core": true,
   673  			}}})
   674  }
   675  
   676  func (s *autorefreshGatingSuite) TestAffectedByKernel(c *C) {
   677  	restore := release.MockOnClassic(true)
   678  	defer restore()
   679  
   680  	st := s.state
   681  
   682  	st.Lock()
   683  	defer st.Unlock()
   684  	kernel := mockInstalledSnap(c, s.state, kernelYaml, noHook)
   685  	mockInstalledSnap(c, s.state, snapCyaml, useHook)
   686  	mockInstalledSnap(c, s.state, snapByaml, noHook)
   687  
   688  	updates := []*snap.Info{kernel}
   689  	affected, err := snapstate.AffectedByRefresh(st, updates)
   690  	c.Assert(err, IsNil)
   691  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   692  		"snap-c": {
   693  			Restart: true,
   694  			AffectingSnaps: map[string]bool{
   695  				"kernel": true,
   696  			}}})
   697  }
   698  
   699  func (s *autorefreshGatingSuite) TestAffectedByGadget(c *C) {
   700  	restore := release.MockOnClassic(true)
   701  	defer restore()
   702  
   703  	st := s.state
   704  
   705  	st.Lock()
   706  	defer st.Unlock()
   707  	kernel := mockInstalledSnap(c, s.state, gadget1Yaml, noHook)
   708  	mockInstalledSnap(c, s.state, snapCyaml, useHook)
   709  	mockInstalledSnap(c, s.state, snapByaml, noHook)
   710  
   711  	updates := []*snap.Info{kernel}
   712  	affected, err := snapstate.AffectedByRefresh(st, updates)
   713  	c.Assert(err, IsNil)
   714  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   715  		"snap-c": {
   716  			Restart: true,
   717  			AffectingSnaps: map[string]bool{
   718  				"gadget": true,
   719  			}}})
   720  }
   721  
   722  func (s *autorefreshGatingSuite) TestAffectedBySlot(c *C) {
   723  	restore := release.MockOnClassic(true)
   724  	defer restore()
   725  
   726  	st := s.state
   727  
   728  	st.Lock()
   729  	defer st.Unlock()
   730  
   731  	snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook)
   732  	snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook)
   733  	// unrelated snap
   734  	snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook)
   735  
   736  	c.Assert(s.repo.AddSnap(snapF), IsNil)
   737  	c.Assert(s.repo.AddSnap(snapD), IsNil)
   738  	c.Assert(s.repo.AddSnap(snapE), IsNil)
   739  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}}
   740  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   741  	c.Assert(err, IsNil)
   742  
   743  	updates := []*snap.Info{snapD}
   744  	affected, err := snapstate.AffectedByRefresh(st, updates)
   745  	c.Assert(err, IsNil)
   746  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   747  		"snap-e": {
   748  			Restart: true,
   749  			AffectingSnaps: map[string]bool{
   750  				"snap-d": true,
   751  			}}})
   752  }
   753  
   754  func (s *autorefreshGatingSuite) TestNotAffectedByCoreOrSnapdSlot(c *C) {
   755  	restore := release.MockOnClassic(true)
   756  	defer restore()
   757  
   758  	st := s.state
   759  
   760  	st.Lock()
   761  	defer st.Unlock()
   762  
   763  	snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook)
   764  	core := mockInstalledSnap(c, s.state, coreYaml, noHook)
   765  	snapB := mockInstalledSnap(c, s.state, snapByaml, useHook)
   766  
   767  	c.Assert(s.repo.AddSnap(snapG), IsNil)
   768  	c.Assert(s.repo.AddSnap(core), IsNil)
   769  	c.Assert(s.repo.AddSnap(snapB), IsNil)
   770  
   771  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "mir"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "mir"}}
   772  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   773  	c.Assert(err, IsNil)
   774  
   775  	updates := []*snap.Info{core}
   776  	affected, err := snapstate.AffectedByRefresh(st, updates)
   777  	c.Assert(err, IsNil)
   778  	c.Check(affected, HasLen, 0)
   779  }
   780  
   781  func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackend(c *C) {
   782  	restore := release.MockOnClassic(true)
   783  	defer restore()
   784  
   785  	st := s.state
   786  
   787  	st.Lock()
   788  	defer st.Unlock()
   789  
   790  	snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook)
   791  	snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook)
   792  	// unrelated snap
   793  	snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook)
   794  
   795  	c.Assert(s.repo.AddSnap(snapF), IsNil)
   796  	c.Assert(s.repo.AddSnap(snapD), IsNil)
   797  	c.Assert(s.repo.AddSnap(snapE), IsNil)
   798  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}}
   799  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   800  	c.Assert(err, IsNil)
   801  
   802  	// snapE has a plug using mount backend and is refreshed, this affects slot of snap-d.
   803  	updates := []*snap.Info{snapE}
   804  	affected, err := snapstate.AffectedByRefresh(st, updates)
   805  	c.Assert(err, IsNil)
   806  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   807  		"snap-d": {
   808  			Restart: true,
   809  			AffectingSnaps: map[string]bool{
   810  				"snap-e": true,
   811  			}}})
   812  }
   813  
   814  func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendSnapdSlot(c *C) {
   815  	restore := release.MockOnClassic(true)
   816  	defer restore()
   817  
   818  	st := s.state
   819  
   820  	st.Lock()
   821  	defer st.Unlock()
   822  
   823  	snapdSnap := mockInstalledSnap(c, s.state, snapdYaml, useHook)
   824  	snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook)
   825  	// unrelated snap
   826  	snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook)
   827  
   828  	c.Assert(s.repo.AddSnap(snapF), IsNil)
   829  	c.Assert(s.repo.AddSnap(snapdSnap), IsNil)
   830  	c.Assert(s.repo.AddSnap(snapG), IsNil)
   831  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "snapd", Name: "desktop"}}
   832  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   833  	c.Assert(err, IsNil)
   834  
   835  	// snapE has a plug using mount backend, refreshing snapd affects snapE.
   836  	updates := []*snap.Info{snapdSnap}
   837  	affected, err := snapstate.AffectedByRefresh(st, updates)
   838  	c.Assert(err, IsNil)
   839  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   840  		"snap-g": {
   841  			Restart: true,
   842  			AffectingSnaps: map[string]bool{
   843  				"snapd": true,
   844  			}}})
   845  }
   846  
   847  func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendCoreSlot(c *C) {
   848  	restore := release.MockOnClassic(true)
   849  	defer restore()
   850  
   851  	st := s.state
   852  
   853  	st.Lock()
   854  	defer st.Unlock()
   855  
   856  	coreSnap := mockInstalledSnap(c, s.state, coreYaml, noHook)
   857  	snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook)
   858  
   859  	c.Assert(s.repo.AddSnap(coreSnap), IsNil)
   860  	c.Assert(s.repo.AddSnap(snapG), IsNil)
   861  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "desktop"}}
   862  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   863  	c.Assert(err, IsNil)
   864  
   865  	// snapG has a plug using mount backend, refreshing core affects snapE.
   866  	updates := []*snap.Info{coreSnap}
   867  	affected, err := snapstate.AffectedByRefresh(st, updates)
   868  	c.Assert(err, IsNil)
   869  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   870  		"snap-g": {
   871  			Restart: true,
   872  			AffectingSnaps: map[string]bool{
   873  				"core": true,
   874  			}}})
   875  }
   876  
   877  func (s *autorefreshGatingSuite) TestAffectedByBootBase(c *C) {
   878  	restore := release.MockOnClassic(false)
   879  	defer restore()
   880  
   881  	st := s.state
   882  
   883  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   884  	defer r()
   885  
   886  	st.Lock()
   887  	defer st.Unlock()
   888  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
   889  	mockInstalledSnap(c, s.state, snapByaml, useHook)
   890  	mockInstalledSnap(c, s.state, snapDyaml, useHook)
   891  	mockInstalledSnap(c, s.state, snapEyaml, useHook)
   892  	core18 := mockInstalledSnap(c, s.state, core18Yaml, noHook)
   893  
   894  	updates := []*snap.Info{core18}
   895  	affected, err := snapstate.AffectedByRefresh(st, updates)
   896  	c.Assert(err, IsNil)
   897  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   898  		"snap-a": {
   899  			Base:    false,
   900  			Restart: true,
   901  			AffectingSnaps: map[string]bool{
   902  				"core18": true,
   903  			},
   904  		},
   905  		"snap-b": {
   906  			Base:    false,
   907  			Restart: true,
   908  			AffectingSnaps: map[string]bool{
   909  				"core18": true,
   910  			},
   911  		},
   912  		"snap-d": {
   913  			Base:    false,
   914  			Restart: true,
   915  			AffectingSnaps: map[string]bool{
   916  				"core18": true,
   917  			},
   918  		},
   919  		"snap-e": {
   920  			Base:    false,
   921  			Restart: true,
   922  			AffectingSnaps: map[string]bool{
   923  				"core18": true,
   924  			}}})
   925  }
   926  
   927  func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) {
   928  	st := s.state
   929  	st.Lock()
   930  	defer st.Unlock()
   931  
   932  	affected := map[string]*snapstate.AffectedSnapInfo{
   933  		"snap-a": {
   934  			Base:    true,
   935  			Restart: true,
   936  		},
   937  		"snap-b": {},
   938  	}
   939  
   940  	seenSnaps := make(map[string]bool)
   941  
   942  	ts := snapstate.CreateGateAutoRefreshHooks(st, affected)
   943  	c.Assert(ts.Tasks(), HasLen, 2)
   944  
   945  	checkHook := func(t *state.Task) {
   946  		c.Assert(t.Kind(), Equals, "run-hook")
   947  		var hs hookstate.HookSetup
   948  		c.Assert(t.Get("hook-setup", &hs), IsNil)
   949  		c.Check(hs.Hook, Equals, "gate-auto-refresh")
   950  		c.Check(hs.Optional, Equals, true)
   951  		seenSnaps[hs.Snap] = true
   952  
   953  		var data interface{}
   954  		c.Assert(t.Get("hook-context", &data), IsNil)
   955  
   956  		// the order of hook tasks is not deterministic
   957  		if hs.Snap == "snap-a" {
   958  			c.Check(data, DeepEquals, map[string]interface{}{"base": true, "restart": true})
   959  		} else {
   960  			c.Assert(hs.Snap, Equals, "snap-b")
   961  			c.Check(data, DeepEquals, map[string]interface{}{"base": false, "restart": false})
   962  		}
   963  	}
   964  
   965  	checkHook(ts.Tasks()[0])
   966  	checkHook(ts.Tasks()[1])
   967  
   968  	c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true})
   969  }
   970  
   971  func (s *autorefreshGatingSuite) TestAutorefreshPhase1FeatureFlag(c *C) {
   972  	st := s.state
   973  	st.Lock()
   974  	defer st.Unlock()
   975  
   976  	st.Set("seeded", true)
   977  
   978  	restore := snapstatetest.MockDeviceModel(DefaultModel())
   979  	defer restore()
   980  
   981  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
   982  		return nil, nil
   983  	}
   984  	defer func() { snapstate.AutoAliases = nil }()
   985  
   986  	s.store.refreshedSnaps = []*snap.Info{{
   987  		Architectures: []string{"all"},
   988  		SnapType:      snap.TypeApp,
   989  		SideInfo: snap.SideInfo{
   990  			RealName: "snap-a",
   991  			Revision: snap.R(8),
   992  		},
   993  	}}
   994  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
   995  
   996  	// gate-auto-refresh-hook feature not enabled, expect old-style refresh.
   997  	_, tss, err := snapstate.AutoRefresh(context.TODO(), st)
   998  	c.Check(err, IsNil)
   999  	c.Assert(tss, HasLen, 2)
  1000  	c.Check(tss[0].Tasks()[0].Kind(), Equals, "prerequisites")
  1001  	c.Check(tss[0].Tasks()[1].Kind(), Equals, "download-snap")
  1002  	c.Check(tss[1].Tasks()[0].Kind(), Equals, "check-rerefresh")
  1003  
  1004  	// enable gate-auto-refresh-hook feature
  1005  	tr := config.NewTransaction(s.state)
  1006  	tr.Set("core", "experimental.gate-auto-refresh-hook", true)
  1007  	tr.Commit()
  1008  
  1009  	_, tss, err = snapstate.AutoRefresh(context.TODO(), st)
  1010  	c.Check(err, IsNil)
  1011  	c.Assert(tss, HasLen, 2)
  1012  	// TODO: verify conditional-auto-refresh task data
  1013  	c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh")
  1014  	c.Check(tss[1].Tasks()[0].Kind(), Equals, "run-hook")
  1015  }
  1016  
  1017  func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) {
  1018  	s.store.refreshedSnaps = []*snap.Info{{
  1019  		Architectures: []string{"all"},
  1020  		SnapType:      snap.TypeApp,
  1021  		SideInfo: snap.SideInfo{
  1022  			RealName: "snap-a",
  1023  			Revision: snap.R(8),
  1024  		},
  1025  	}, {
  1026  		Architectures: []string{"all"},
  1027  		SnapType:      snap.TypeBase,
  1028  		SideInfo: snap.SideInfo{
  1029  			RealName: "base-snap-b",
  1030  			Revision: snap.R(3),
  1031  		},
  1032  	}, {
  1033  		Architectures: []string{"all"},
  1034  		SnapType:      snap.TypeApp,
  1035  		SideInfo: snap.SideInfo{
  1036  			RealName: "snap-c",
  1037  			Revision: snap.R(5),
  1038  		},
  1039  	}}
  1040  
  1041  	st := s.state
  1042  	st.Lock()
  1043  	defer st.Unlock()
  1044  
  1045  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1046  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1047  	mockInstalledSnap(c, s.state, snapCyaml, noHook)
  1048  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1049  
  1050  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1051  	defer restore()
  1052  
  1053  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st)
  1054  	c.Assert(err, IsNil)
  1055  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a", "snap-c"})
  1056  	c.Assert(tss, HasLen, 2)
  1057  
  1058  	c.Assert(tss[0].Tasks(), HasLen, 1)
  1059  	checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{
  1060  		"snap-a": {
  1061  			SnapSetup: snapstate.SnapSetup{
  1062  				Type:      "app",
  1063  				PlugsOnly: true,
  1064  				Flags: snapstate.Flags{
  1065  					IsAutoRefresh: true,
  1066  				},
  1067  				SideInfo: &snap.SideInfo{
  1068  					RealName: "snap-a",
  1069  					Revision: snap.R(8),
  1070  				},
  1071  				DownloadInfo: &snap.DownloadInfo{},
  1072  			},
  1073  		},
  1074  		"base-snap-b": {
  1075  			SnapSetup: snapstate.SnapSetup{
  1076  				Type:      "base",
  1077  				PlugsOnly: true,
  1078  				Flags: snapstate.Flags{
  1079  					IsAutoRefresh: true,
  1080  				},
  1081  				SideInfo: &snap.SideInfo{
  1082  					RealName: "base-snap-b",
  1083  					Revision: snap.R(3),
  1084  				},
  1085  				DownloadInfo: &snap.DownloadInfo{},
  1086  			},
  1087  		},
  1088  		"snap-c": {
  1089  			SnapSetup: snapstate.SnapSetup{
  1090  				Type:      "app",
  1091  				PlugsOnly: true,
  1092  				Flags: snapstate.Flags{
  1093  					IsAutoRefresh: true,
  1094  				},
  1095  				SideInfo: &snap.SideInfo{
  1096  					RealName: "snap-c",
  1097  					Revision: snap.R(5),
  1098  				},
  1099  				DownloadInfo: &snap.DownloadInfo{},
  1100  			},
  1101  		},
  1102  	})
  1103  
  1104  	c.Assert(tss[1].Tasks(), HasLen, 2)
  1105  
  1106  	// check hooks for affected snaps
  1107  	seenSnaps := make(map[string]bool)
  1108  	var hs hookstate.HookSetup
  1109  	c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil)
  1110  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1111  	seenSnaps[hs.Snap] = true
  1112  
  1113  	c.Assert(tss[1].Tasks()[1].Get("hook-setup", &hs), IsNil)
  1114  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1115  	seenSnaps[hs.Snap] = true
  1116  
  1117  	// hook for snap-a because it gets refreshed, for snap-b because its base
  1118  	// gets refreshed. snap-c is refreshed but doesn't have the hook.
  1119  	c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true})
  1120  
  1121  	// check that refresh-candidates in the state were updated
  1122  	var candidates map[string]*snapstate.RefreshCandidate
  1123  	c.Assert(st.Get("refresh-candidates", &candidates), IsNil)
  1124  	c.Assert(candidates, HasLen, 3)
  1125  	c.Check(candidates["snap-a"], NotNil)
  1126  	c.Check(candidates["base-snap-b"], NotNil)
  1127  	c.Check(candidates["snap-c"], NotNil)
  1128  }
  1129  
  1130  func (s *autorefreshGatingSuite) TestAutoRefreshPhase1ConflictsFilteredOut(c *C) {
  1131  	s.store.refreshedSnaps = []*snap.Info{{
  1132  		Architectures: []string{"all"},
  1133  		SnapType:      snap.TypeApp,
  1134  		SideInfo: snap.SideInfo{
  1135  			RealName: "snap-a",
  1136  			Revision: snap.R(8),
  1137  		},
  1138  	}, {
  1139  		Architectures: []string{"all"},
  1140  		SnapType:      snap.TypeBase,
  1141  		SideInfo: snap.SideInfo{
  1142  			RealName: "snap-c",
  1143  			Revision: snap.R(5),
  1144  		},
  1145  	}}
  1146  
  1147  	st := s.state
  1148  	st.Lock()
  1149  	defer st.Unlock()
  1150  
  1151  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1152  	mockInstalledSnap(c, s.state, snapCyaml, noHook)
  1153  
  1154  	conflictChange := st.NewChange("conflicting change", "")
  1155  	conflictTask := st.NewTask("conflicting task", "")
  1156  	si := &snap.SideInfo{
  1157  		RealName: "snap-c",
  1158  		Revision: snap.R(1),
  1159  	}
  1160  	sup := snapstate.SnapSetup{SideInfo: si}
  1161  	conflictTask.Set("snap-setup", sup)
  1162  	conflictChange.AddTask(conflictTask)
  1163  
  1164  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1165  	defer restore()
  1166  
  1167  	logbuf, restoreLogger := logger.MockLogger()
  1168  	defer restoreLogger()
  1169  
  1170  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st)
  1171  	c.Assert(err, IsNil)
  1172  	c.Check(names, DeepEquals, []string{"snap-a"})
  1173  	c.Assert(tss, HasLen, 2)
  1174  
  1175  	c.Assert(tss[0].Tasks(), HasLen, 1)
  1176  	checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{
  1177  		"snap-a": {
  1178  			SnapSetup: snapstate.SnapSetup{
  1179  				Type:      "app",
  1180  				PlugsOnly: true,
  1181  				Flags: snapstate.Flags{
  1182  					IsAutoRefresh: true,
  1183  				},
  1184  				SideInfo: &snap.SideInfo{
  1185  					RealName: "snap-a",
  1186  					Revision: snap.R(8),
  1187  				},
  1188  				DownloadInfo: &snap.DownloadInfo{},
  1189  			}}})
  1190  
  1191  	c.Assert(tss[1].Tasks(), HasLen, 1)
  1192  
  1193  	c.Assert(logbuf.String(), testutil.Contains, `cannot refresh snap "snap-c": snap "snap-c" has "conflicting change" change in progress`)
  1194  
  1195  	seenSnaps := make(map[string]bool)
  1196  	var hs hookstate.HookSetup
  1197  	c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil)
  1198  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1199  	seenSnaps[hs.Snap] = true
  1200  
  1201  	c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true})
  1202  
  1203  	// check that refresh-candidates in the state were updated
  1204  	var candidates map[string]*snapstate.RefreshCandidate
  1205  	c.Assert(st.Get("refresh-candidates", &candidates), IsNil)
  1206  	c.Assert(candidates, HasLen, 2)
  1207  	c.Check(candidates["snap-a"], NotNil)
  1208  	c.Check(candidates["snap-c"], NotNil)
  1209  }
  1210  
  1211  func (s *autorefreshGatingSuite) TestAutoRefreshPhase1NoHooks(c *C) {
  1212  	s.store.refreshedSnaps = []*snap.Info{{
  1213  		Architectures: []string{"all"},
  1214  		SnapType:      snap.TypeBase,
  1215  		SideInfo: snap.SideInfo{
  1216  			RealName: "base-snap-b",
  1217  			Revision: snap.R(3),
  1218  		},
  1219  	}, {
  1220  		Architectures: []string{"all"},
  1221  		SnapType:      snap.TypeBase,
  1222  		SideInfo: snap.SideInfo{
  1223  			RealName: "snap-c",
  1224  			Revision: snap.R(5),
  1225  		},
  1226  	}}
  1227  
  1228  	st := s.state
  1229  	st.Lock()
  1230  	defer st.Unlock()
  1231  
  1232  	mockInstalledSnap(c, s.state, snapByaml, noHook)
  1233  	mockInstalledSnap(c, s.state, snapCyaml, noHook)
  1234  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1235  
  1236  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1237  	defer restore()
  1238  
  1239  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st)
  1240  	c.Assert(err, IsNil)
  1241  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-c"})
  1242  	c.Assert(tss, HasLen, 1)
  1243  
  1244  	c.Assert(tss[0].Tasks(), HasLen, 1)
  1245  	c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh")
  1246  }
  1247  
  1248  func fakeReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) {
  1249  	info := &snap.Info{
  1250  		SuggestedName: name,
  1251  		SideInfo:      *si,
  1252  		Architectures: []string{"all"},
  1253  		SnapType:      snap.TypeApp,
  1254  		Epoch:         snap.Epoch{},
  1255  	}
  1256  	switch name {
  1257  	case "base-snap-b":
  1258  		info.SnapType = snap.TypeBase
  1259  	case "snap-a", "snap-b":
  1260  		info.Hooks = map[string]*snap.HookInfo{
  1261  			"gate-auto-refresh": {
  1262  				Name: "gate-auto-refresh",
  1263  				Snap: info,
  1264  			},
  1265  		}
  1266  		if name == "snap-b" {
  1267  			info.Base = "base-snap-b"
  1268  		}
  1269  	}
  1270  	return info, nil
  1271  }
  1272  
  1273  func (s *snapmgrTestSuite) TestAutoRefreshPhase2(c *C) {
  1274  	st := s.state
  1275  	st.Lock()
  1276  	defer st.Unlock()
  1277  
  1278  	restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) {
  1279  		c.Fatal("unexpected call to installSize")
  1280  		return 0, nil
  1281  	})
  1282  	defer restoreInstallSize()
  1283  
  1284  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  1285  		fakeStore: s.fakeStore,
  1286  		refreshedSnaps: []*snap.Info{{
  1287  			Architectures: []string{"all"},
  1288  			SnapType:      snap.TypeApp,
  1289  			SideInfo: snap.SideInfo{
  1290  				RealName: "snap-a",
  1291  				Revision: snap.R(8),
  1292  			},
  1293  		}, {
  1294  			Architectures: []string{"all"},
  1295  			SnapType:      snap.TypeBase,
  1296  			SideInfo: snap.SideInfo{
  1297  				RealName: "base-snap-b",
  1298  				Revision: snap.R(3),
  1299  			},
  1300  		}}})
  1301  
  1302  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1303  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1304  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1305  
  1306  	snapstate.MockSnapReadInfo(fakeReadInfo)
  1307  
  1308  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1309  	defer restore()
  1310  
  1311  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st)
  1312  	c.Assert(err, IsNil)
  1313  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  1314  
  1315  	chg := s.state.NewChange("refresh", "...")
  1316  	for _, ts := range tss {
  1317  		chg.AddAll(ts)
  1318  	}
  1319  
  1320  	s.state.Unlock()
  1321  	defer s.se.Stop()
  1322  	s.settle(c)
  1323  	s.state.Lock()
  1324  
  1325  	c.Check(chg.Status(), Equals, state.DoneStatus)
  1326  	c.Check(chg.Err(), IsNil)
  1327  
  1328  	expected := []string{
  1329  		"conditional-auto-refresh",
  1330  		"run-hook [snap-a;gate-auto-refresh]",
  1331  		// snap-b hook is triggered because of base-snap-b refresh
  1332  		"run-hook [snap-b;gate-auto-refresh]",
  1333  		"prerequisites",
  1334  		"download-snap",
  1335  		"validate-snap",
  1336  		"mount-snap",
  1337  		"run-hook [base-snap-b;pre-refresh]",
  1338  		"stop-snap-services",
  1339  		"remove-aliases",
  1340  		"unlink-current-snap",
  1341  		"copy-snap-data",
  1342  		"setup-profiles",
  1343  		"link-snap",
  1344  		"auto-connect",
  1345  		"set-auto-aliases",
  1346  		"setup-aliases",
  1347  		"run-hook [base-snap-b;post-refresh]",
  1348  		"start-snap-services",
  1349  		"cleanup",
  1350  		"run-hook [base-snap-b;check-health]",
  1351  		"prerequisites",
  1352  		"download-snap",
  1353  		"validate-snap",
  1354  		"mount-snap",
  1355  		"run-hook [snap-a;pre-refresh]",
  1356  		"stop-snap-services",
  1357  		"remove-aliases",
  1358  		"unlink-current-snap",
  1359  		"copy-snap-data",
  1360  		"setup-profiles",
  1361  		"link-snap",
  1362  		"auto-connect",
  1363  		"set-auto-aliases",
  1364  		"setup-aliases",
  1365  		"run-hook [snap-a;post-refresh]",
  1366  		"start-snap-services",
  1367  		"cleanup",
  1368  		"run-hook [snap-a;configure]",
  1369  		"run-hook [snap-a;check-health]",
  1370  		"check-rerefresh",
  1371  	}
  1372  	verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected)
  1373  }
  1374  
  1375  func (s *snapmgrTestSuite) testAutoRefreshPhase2DiskSpaceCheck(c *C, fail bool) {
  1376  	st := s.state
  1377  	st.Lock()
  1378  	defer st.Unlock()
  1379  
  1380  	restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error {
  1381  		c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123))
  1382  		if fail {
  1383  			return &osutil.NotEnoughDiskSpaceError{}
  1384  		}
  1385  		return nil
  1386  	})
  1387  	defer restore()
  1388  
  1389  	var installSizeCalled bool
  1390  	restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) {
  1391  		installSizeCalled = true
  1392  		seen := map[string]bool{}
  1393  		for _, sn := range snaps {
  1394  			seen[sn.InstanceName()] = true
  1395  		}
  1396  		c.Check(seen, DeepEquals, map[string]bool{
  1397  			"base-snap-b": true,
  1398  			"snap-a":      true,
  1399  		})
  1400  		return 123, nil
  1401  	})
  1402  	defer restoreInstallSize()
  1403  
  1404  	restoreModel := snapstatetest.MockDeviceModel(DefaultModel())
  1405  	defer restoreModel()
  1406  
  1407  	tr := config.NewTransaction(s.state)
  1408  	tr.Set("core", "experimental.check-disk-space-refresh", true)
  1409  	tr.Commit()
  1410  
  1411  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  1412  		fakeStore: s.fakeStore,
  1413  		refreshedSnaps: []*snap.Info{{
  1414  			Architectures: []string{"all"},
  1415  			SnapType:      snap.TypeApp,
  1416  			SideInfo: snap.SideInfo{
  1417  				RealName: "snap-a",
  1418  				Revision: snap.R(8),
  1419  			},
  1420  		}, {
  1421  			Architectures: []string{"all"},
  1422  			SnapType:      snap.TypeBase,
  1423  			SideInfo: snap.SideInfo{
  1424  				RealName: "base-snap-b",
  1425  				Revision: snap.R(3),
  1426  			},
  1427  		}}})
  1428  
  1429  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1430  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1431  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1432  
  1433  	snapstate.MockSnapReadInfo(fakeReadInfo)
  1434  
  1435  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st)
  1436  	c.Assert(err, IsNil)
  1437  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  1438  
  1439  	chg := s.state.NewChange("refresh", "...")
  1440  	for _, ts := range tss {
  1441  		chg.AddAll(ts)
  1442  	}
  1443  
  1444  	s.state.Unlock()
  1445  	defer s.se.Stop()
  1446  	s.settle(c)
  1447  	s.state.Lock()
  1448  
  1449  	c.Check(installSizeCalled, Equals, true)
  1450  	if fail {
  1451  		c.Check(chg.Status(), Equals, state.ErrorStatus)
  1452  		c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- Run auto-refresh for ready snaps \(insufficient space.*`)
  1453  	} else {
  1454  		c.Check(chg.Status(), Equals, state.DoneStatus)
  1455  		c.Check(chg.Err(), IsNil)
  1456  	}
  1457  }
  1458  
  1459  func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceError(c *C) {
  1460  	fail := true
  1461  	s.testAutoRefreshPhase2DiskSpaceCheck(c, fail)
  1462  }
  1463  
  1464  func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceHappy(c *C) {
  1465  	var nofail bool
  1466  	s.testAutoRefreshPhase2DiskSpaceCheck(c, nofail)
  1467  }
  1468  
  1469  // XXX: this case is probably artificial; with proper conflict prevention
  1470  // we shouldn't get conflicts from doInstall in phase2.
  1471  func (s *snapmgrTestSuite) TestAutoRefreshPhase2Conflict(c *C) {
  1472  	st := s.state
  1473  	st.Lock()
  1474  	defer st.Unlock()
  1475  
  1476  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  1477  		fakeStore: s.fakeStore,
  1478  		refreshedSnaps: []*snap.Info{{
  1479  			Architectures: []string{"all"},
  1480  			SnapType:      snap.TypeApp,
  1481  			SideInfo: snap.SideInfo{
  1482  				RealName: "snap-a",
  1483  				Revision: snap.R(8),
  1484  			},
  1485  		}, {
  1486  			Architectures: []string{"all"},
  1487  			SnapType:      snap.TypeBase,
  1488  			SideInfo: snap.SideInfo{
  1489  				RealName: "base-snap-b",
  1490  				Revision: snap.R(3),
  1491  			},
  1492  		}}})
  1493  
  1494  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1495  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1496  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1497  
  1498  	snapstate.MockSnapReadInfo(fakeReadInfo)
  1499  
  1500  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1501  	defer restore()
  1502  
  1503  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st)
  1504  	c.Assert(err, IsNil)
  1505  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  1506  
  1507  	chg := s.state.NewChange("refresh", "...")
  1508  	for _, ts := range tss {
  1509  		chg.AddAll(ts)
  1510  	}
  1511  
  1512  	conflictChange := st.NewChange("conflicting change", "")
  1513  	conflictTask := st.NewTask("conflicting task", "")
  1514  	si := &snap.SideInfo{
  1515  		RealName: "snap-a",
  1516  		Revision: snap.R(1),
  1517  	}
  1518  	sup := snapstate.SnapSetup{SideInfo: si}
  1519  	conflictTask.Set("snap-setup", sup)
  1520  	conflictChange.AddTask(conflictTask)
  1521  	conflictTask.WaitFor(tss[0].Tasks()[0])
  1522  
  1523  	s.state.Unlock()
  1524  	defer s.se.Stop()
  1525  	s.settle(c)
  1526  	s.state.Lock()
  1527  
  1528  	c.Assert(chg.Status(), Equals, state.DoneStatus)
  1529  	c.Check(chg.Err(), IsNil)
  1530  
  1531  	expected := []string{
  1532  		"conditional-auto-refresh",
  1533  		"run-hook [snap-a;gate-auto-refresh]",
  1534  		// snap-b hook is triggered because of base-snap-b refresh
  1535  		"run-hook [snap-b;gate-auto-refresh]",
  1536  		"prerequisites",
  1537  		"download-snap",
  1538  		"validate-snap",
  1539  		"mount-snap",
  1540  		"run-hook [base-snap-b;pre-refresh]",
  1541  		"stop-snap-services",
  1542  		"remove-aliases",
  1543  		"unlink-current-snap",
  1544  		"copy-snap-data",
  1545  		"setup-profiles",
  1546  		"link-snap",
  1547  		"auto-connect",
  1548  		"set-auto-aliases",
  1549  		"setup-aliases",
  1550  		"run-hook [base-snap-b;post-refresh]",
  1551  		"start-snap-services",
  1552  		"cleanup",
  1553  		"run-hook [base-snap-b;check-health]",
  1554  		"check-rerefresh",
  1555  	}
  1556  	verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected)
  1557  }
  1558  
  1559  func (s *snapmgrTestSuite) TestAutoRefreshPhase2GatedSnaps(c *C) {
  1560  	st := s.state
  1561  	st.Lock()
  1562  	defer st.Unlock()
  1563  
  1564  	restore := snapstate.MockSnapsToRefresh(func(gatingTask *state.Task) ([]*snapstate.RefreshCandidate, error) {
  1565  		c.Assert(gatingTask.Kind(), Equals, "conditional-auto-refresh")
  1566  		var candidates map[string]*snapstate.RefreshCandidate
  1567  		c.Assert(gatingTask.Get("snaps", &candidates), IsNil)
  1568  		seenSnaps := make(map[string]bool)
  1569  		var filteredByGatingHooks []*snapstate.RefreshCandidate
  1570  		for _, cand := range candidates {
  1571  			seenSnaps[cand.InstanceName()] = true
  1572  			if cand.InstanceName() == "snap-a" {
  1573  				continue
  1574  			}
  1575  			filteredByGatingHooks = append(filteredByGatingHooks, cand)
  1576  		}
  1577  		c.Check(seenSnaps, DeepEquals, map[string]bool{
  1578  			"snap-a":      true,
  1579  			"base-snap-b": true,
  1580  		})
  1581  		return filteredByGatingHooks, nil
  1582  	})
  1583  	defer restore()
  1584  
  1585  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  1586  		fakeStore: s.fakeStore,
  1587  		refreshedSnaps: []*snap.Info{
  1588  			{
  1589  				Architectures: []string{"all"},
  1590  				SnapType:      snap.TypeApp,
  1591  				SideInfo: snap.SideInfo{
  1592  					RealName: "snap-a",
  1593  					Revision: snap.R(8),
  1594  				},
  1595  			}, {
  1596  				Architectures: []string{"all"},
  1597  				SnapType:      snap.TypeBase,
  1598  				SideInfo: snap.SideInfo{
  1599  					RealName: "base-snap-b",
  1600  					Revision: snap.R(3),
  1601  				},
  1602  			},
  1603  		}})
  1604  
  1605  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1606  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1607  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1608  
  1609  	snapstate.MockSnapReadInfo(fakeReadInfo)
  1610  
  1611  	restoreModel := snapstatetest.MockDeviceModel(DefaultModel())
  1612  	defer restoreModel()
  1613  
  1614  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st)
  1615  	c.Assert(err, IsNil)
  1616  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  1617  
  1618  	chg := s.state.NewChange("refresh", "...")
  1619  	for _, ts := range tss {
  1620  		chg.AddAll(ts)
  1621  	}
  1622  
  1623  	s.state.Unlock()
  1624  	defer s.se.Stop()
  1625  	s.settle(c)
  1626  	s.state.Lock()
  1627  
  1628  	c.Assert(chg.Status(), Equals, state.DoneStatus)
  1629  	c.Check(chg.Err(), IsNil)
  1630  
  1631  	expected := []string{
  1632  		"conditional-auto-refresh",
  1633  		"run-hook [snap-a;gate-auto-refresh]",
  1634  		// snap-b hook is triggered because of base-snap-b refresh
  1635  		"run-hook [snap-b;gate-auto-refresh]",
  1636  		"prerequisites",
  1637  		"download-snap",
  1638  		"validate-snap",
  1639  		"mount-snap",
  1640  		"run-hook [base-snap-b;pre-refresh]",
  1641  		"stop-snap-services",
  1642  		"remove-aliases",
  1643  		"unlink-current-snap",
  1644  		"copy-snap-data",
  1645  		"setup-profiles",
  1646  		"link-snap",
  1647  		"auto-connect",
  1648  		"set-auto-aliases",
  1649  		"setup-aliases",
  1650  		"run-hook [base-snap-b;post-refresh]",
  1651  		"start-snap-services",
  1652  		"cleanup",
  1653  		"run-hook [base-snap-b;check-health]",
  1654  		"check-rerefresh",
  1655  	}
  1656  	verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected)
  1657  }
  1658  
  1659  func verifyPhasedAutorefreshTasks(c *C, tasks []*state.Task, expected []string) {
  1660  	for i, t := range tasks {
  1661  		var got string
  1662  		if t.Kind() == "run-hook" {
  1663  			var hsup hookstate.HookSetup
  1664  			c.Assert(t.Get("hook-setup", &hsup), IsNil)
  1665  			got = fmt.Sprintf("%s [%s;%s]", t.Kind(), hsup.Snap, hsup.Hook)
  1666  		} else {
  1667  			got = t.Kind()
  1668  		}
  1669  		c.Assert(got, Equals, expected[i])
  1670  	}
  1671  }