github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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  	. "gopkg.in/check.v1"
    31  	"gopkg.in/tomb.v2"
    32  
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/interfaces"
    35  	"github.com/snapcore/snapd/interfaces/builtin"
    36  	"github.com/snapcore/snapd/logger"
    37  	"github.com/snapcore/snapd/osutil"
    38  	"github.com/snapcore/snapd/overlord/auth"
    39  	"github.com/snapcore/snapd/overlord/configstate/config"
    40  	"github.com/snapcore/snapd/overlord/hookstate"
    41  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    42  	"github.com/snapcore/snapd/overlord/snapstate"
    43  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    44  	"github.com/snapcore/snapd/overlord/state"
    45  	"github.com/snapcore/snapd/release"
    46  	"github.com/snapcore/snapd/snap"
    47  	"github.com/snapcore/snapd/snap/snaptest"
    48  	"github.com/snapcore/snapd/store"
    49  	"github.com/snapcore/snapd/testutil"
    50  )
    51  
    52  type autoRefreshGatingStore struct {
    53  	*fakeStore
    54  	refreshedSnaps []*snap.Info
    55  }
    56  
    57  type autorefreshGatingSuite struct {
    58  	testutil.BaseTest
    59  	state *state.State
    60  	repo  *interfaces.Repository
    61  	store *autoRefreshGatingStore
    62  }
    63  
    64  var _ = Suite(&autorefreshGatingSuite{})
    65  
    66  func (s *autorefreshGatingSuite) SetUpTest(c *C) {
    67  	s.BaseTest.SetUpTest(c)
    68  	dirs.SetRootDir(c.MkDir())
    69  	s.AddCleanup(func() {
    70  		dirs.SetRootDir("/")
    71  	})
    72  	s.state = state.New(nil)
    73  
    74  	s.repo = interfaces.NewRepository()
    75  	for _, iface := range builtin.Interfaces() {
    76  		c.Assert(s.repo.AddInterface(iface), IsNil)
    77  	}
    78  
    79  	s.state.Lock()
    80  	defer s.state.Unlock()
    81  	ifacerepo.Replace(s.state, s.repo)
    82  
    83  	s.store = &autoRefreshGatingStore{fakeStore: &fakeStore{}}
    84  	snapstate.ReplaceStore(s.state, s.store)
    85  	s.state.Set("refresh-privacy-key", "privacy-key")
    86  }
    87  
    88  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) {
    89  	if assertQuery != nil {
    90  		panic("no assertion query support")
    91  	}
    92  	if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 {
    93  		panic("expected in test one action for each current snaps, and at least one snap")
    94  	}
    95  	for _, a := range actions {
    96  		if a.Action != "refresh" {
    97  			panic("expected refresh actions")
    98  		}
    99  	}
   100  
   101  	res := []store.SnapActionResult{}
   102  	for _, rs := range r.refreshedSnaps {
   103  		res = append(res, store.SnapActionResult{Info: rs})
   104  	}
   105  
   106  	return res, nil, nil
   107  }
   108  
   109  func mockInstalledSnap(c *C, st *state.State, snapYaml string, hasHook bool) *snap.Info {
   110  	snapInfo := snaptest.MockSnap(c, string(snapYaml), &snap.SideInfo{
   111  		Revision: snap.R(1),
   112  	})
   113  
   114  	snapName := snapInfo.SnapName()
   115  	si := &snap.SideInfo{RealName: snapName, SnapID: "id", Revision: snap.R(1)}
   116  	snapstate.Set(st, snapName, &snapstate.SnapState{
   117  		Active:   true,
   118  		Sequence: []*snap.SideInfo{si},
   119  		Current:  si.Revision,
   120  		SnapType: string(snapInfo.Type()),
   121  	})
   122  
   123  	if hasHook {
   124  		c.Assert(os.MkdirAll(snapInfo.HooksDir(), 0775), IsNil)
   125  		err := ioutil.WriteFile(filepath.Join(snapInfo.HooksDir(), "gate-auto-refresh"), nil, 0755)
   126  		c.Assert(err, IsNil)
   127  	}
   128  	return snapInfo
   129  }
   130  
   131  func mockLastRefreshed(c *C, st *state.State, refreshedTime string, snaps ...string) {
   132  	refreshed, err := time.Parse(time.RFC3339, refreshedTime)
   133  	c.Assert(err, IsNil)
   134  	for _, snapName := range snaps {
   135  		var snapst snapstate.SnapState
   136  		c.Assert(snapstate.Get(st, snapName, &snapst), IsNil)
   137  		snapst.LastRefreshTime = &refreshed
   138  		snapstate.Set(st, snapName, &snapst)
   139  	}
   140  }
   141  
   142  const baseSnapAyaml = `name: base-snap-a
   143  type: base
   144  `
   145  
   146  const snapAyaml = `name: snap-a
   147  type: app
   148  base: base-snap-a
   149  `
   150  
   151  const baseSnapByaml = `name: base-snap-b
   152  type: base
   153  `
   154  
   155  const snapByaml = `name: snap-b
   156  type: app
   157  base: base-snap-b
   158  version: 1
   159  `
   160  
   161  const snapBByaml = `name: snap-bb
   162  type: app
   163  base: base-snap-b
   164  version: 1
   165  `
   166  
   167  const kernelYaml = `name: kernel
   168  type: kernel
   169  version: 1
   170  `
   171  
   172  const gadget1Yaml = `name: gadget
   173  type: gadget
   174  version: 1
   175  `
   176  
   177  const snapCyaml = `name: snap-c
   178  type: app
   179  version: 1
   180  `
   181  
   182  const snapDyaml = `name: snap-d
   183  type: app
   184  version: 1
   185  slots:
   186      slot: desktop
   187  `
   188  
   189  const snapEyaml = `name: snap-e
   190  type: app
   191  version: 1
   192  base: other-base
   193  plugs:
   194      plug: desktop
   195  `
   196  
   197  const snapFyaml = `name: snap-f
   198  type: app
   199  version: 1
   200  plugs:
   201      plug: desktop
   202  `
   203  
   204  const snapGyaml = `name: snap-g
   205  type: app
   206  version: 1
   207  base: other-base
   208  plugs:
   209      desktop:
   210      mir:
   211  `
   212  
   213  const coreYaml = `name: core
   214  type: os
   215  version: 1
   216  slots:
   217      desktop:
   218      mir:
   219  `
   220  
   221  const core18Yaml = `name: core18
   222  type: os
   223  version: 1
   224  `
   225  
   226  const snapdYaml = `name: snapd
   227  version: 1
   228  type: snapd
   229  slots:
   230      desktop:
   231  `
   232  
   233  func (s *autorefreshGatingSuite) TestHoldDurationLeft(c *C) {
   234  	now, err := time.Parse(time.RFC3339, "2021-06-03T10:00:00Z")
   235  	c.Assert(err, IsNil)
   236  	maxPostponement := time.Hour * 24 * 90
   237  
   238  	for i, tc := range []struct {
   239  		lastRefresh, firstHeld string
   240  		maxDuration            string
   241  		expected               string
   242  	}{
   243  		{
   244  			"2021-05-03T10:00:00Z", // last refreshed (1 month ago)
   245  			"2021-06-03T10:00:00Z", // first held now
   246  			"48h",                  // max duration
   247  			"48h",                  // expected
   248  		},
   249  		{
   250  			"2021-05-03T10:00:00Z", // last refreshed (1 month ago)
   251  			"2021-06-02T10:00:00Z", // first held (1 day ago)
   252  			"48h",                  // max duration
   253  			"24h",                  // expected
   254  		},
   255  		{
   256  			"2021-05-03T10:00:00Z", // last refreshed (1 month ago)
   257  			"2021-06-01T10:00:00Z", // first held (2 days ago)
   258  			"48h",                  // max duration
   259  			"00h",                  // expected
   260  		},
   261  		{
   262  			"2021-03-08T10:00:00Z", // last refreshed (almost 3 months ago)
   263  			"2021-06-01T10:00:00Z", // first held
   264  			"2160h",                // max duration (90 days)
   265  			"72h",                  // expected
   266  		},
   267  		{
   268  			"2021-03-04T10:00:00Z", // last refreshed
   269  			"2021-06-01T10:00:00Z", // first held (2 days ago)
   270  			"2160h",                // max duration (90 days)
   271  			"-24h",                 // expected (refresh is 1 day overdue)
   272  		},
   273  		{
   274  			"2021-06-01T10:00:00Z", // last refreshed (2 days ago)
   275  			"2021-06-03T10:00:00Z", // first held now
   276  			"2160h",                // max duration (90 days)
   277  			"2112h",                // expected (max minus 2 days)
   278  		},
   279  	} {
   280  		lastRefresh, err := time.Parse(time.RFC3339, tc.lastRefresh)
   281  		c.Assert(err, IsNil)
   282  		firstHeld, err := time.Parse(time.RFC3339, tc.firstHeld)
   283  		c.Assert(err, IsNil)
   284  		maxDuration, err := time.ParseDuration(tc.maxDuration)
   285  		c.Assert(err, IsNil)
   286  		expected, err := time.ParseDuration(tc.expected)
   287  		c.Assert(err, IsNil)
   288  
   289  		left := snapstate.HoldDurationLeft(now, lastRefresh, firstHeld, maxDuration, maxPostponement)
   290  		c.Check(left, Equals, expected, Commentf("case #%d", i))
   291  	}
   292  }
   293  
   294  func (s *autorefreshGatingSuite) TestLastRefreshedHelper(c *C) {
   295  	st := s.state
   296  	st.Lock()
   297  	defer st.Unlock()
   298  
   299  	inf := mockInstalledSnap(c, st, snapAyaml, false)
   300  	stat, err := os.Stat(inf.MountFile())
   301  	c.Assert(err, IsNil)
   302  
   303  	refreshed, err := snapstate.LastRefreshed(st, "snap-a")
   304  	c.Assert(err, IsNil)
   305  	c.Check(refreshed, DeepEquals, stat.ModTime())
   306  
   307  	t, err := time.Parse(time.RFC3339, "2021-01-01T10:00:00Z")
   308  	c.Assert(err, IsNil)
   309  
   310  	var snapst snapstate.SnapState
   311  	c.Assert(snapstate.Get(st, "snap-a", &snapst), IsNil)
   312  	snapst.LastRefreshTime = &t
   313  	snapstate.Set(st, "snap-a", &snapst)
   314  
   315  	refreshed, err = snapstate.LastRefreshed(st, "snap-a")
   316  	c.Assert(err, IsNil)
   317  	c.Check(refreshed, DeepEquals, t)
   318  }
   319  
   320  func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) {
   321  	st := s.state
   322  	st.Lock()
   323  	defer st.Unlock()
   324  
   325  	restore := snapstate.MockTimeNow(func() time.Time {
   326  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   327  		c.Assert(err, IsNil)
   328  		return t
   329  	})
   330  	defer restore()
   331  
   332  	mockInstalledSnap(c, st, snapAyaml, false)
   333  	mockInstalledSnap(c, st, snapByaml, false)
   334  	mockInstalledSnap(c, st, snapCyaml, false)
   335  	mockInstalledSnap(c, st, snapDyaml, false)
   336  	mockInstalledSnap(c, st, snapEyaml, false)
   337  	mockInstalledSnap(c, st, snapFyaml, false)
   338  
   339  	mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f")
   340  
   341  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
   342  	// this could be merged with the above HoldRefresh call, but it's fine if
   343  	// done separately too.
   344  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-e"), IsNil)
   345  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-e"), IsNil)
   346  	c.Assert(snapstate.HoldRefresh(st, "snap-f", 0, "snap-f"), IsNil)
   347  
   348  	var gating map[string]map[string]*snapstate.HoldState
   349  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   350  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   351  		"snap-b": {
   352  			// holding of other snaps for maxOtherHoldDuration (48h)
   353  			"snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   354  		},
   355  		"snap-c": {
   356  			"snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   357  		},
   358  		"snap-e": {
   359  			"snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   360  			"snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   361  		},
   362  		"snap-f": {
   363  			// holding self set for maxPostponement minus 1 day due to last refresh.
   364  			"snap-f": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-07T10:00:00Z"),
   365  		},
   366  	})
   367  }
   368  
   369  func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) {
   370  	st := s.state
   371  	st.Lock()
   372  	defer st.Unlock()
   373  
   374  	lastRefreshed := "2021-05-09T10:00:00Z"
   375  	now := "2021-05-10T10:00:00Z"
   376  	restore := snapstate.MockTimeNow(func() time.Time {
   377  		t, err := time.Parse(time.RFC3339, now)
   378  		c.Assert(err, IsNil)
   379  		return t
   380  	})
   381  	defer restore()
   382  
   383  	mockInstalledSnap(c, st, snapAyaml, false)
   384  	mockInstalledSnap(c, st, snapByaml, false)
   385  	// snap-a was last refreshed yesterday
   386  	mockLastRefreshed(c, st, lastRefreshed, "snap-a")
   387  
   388  	// hold it for just a bit (10h) initially
   389  	hold := time.Hour * 10
   390  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   391  	var gating map[string]map[string]*snapstate.HoldState
   392  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   393  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   394  		"snap-a": {
   395  			"snap-b": snapstate.MockHoldState(now, "2021-05-10T20:00:00Z"),
   396  		},
   397  	})
   398  
   399  	// holding for a shorter time is fine too
   400  	hold = time.Hour * 5
   401  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   402  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   403  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   404  		"snap-a": {
   405  			"snap-b": snapstate.MockHoldState(now, "2021-05-10T15:00:00Z"),
   406  		},
   407  	})
   408  
   409  	oldNow := now
   410  
   411  	// a refresh on next day
   412  	now = "2021-05-11T08:00:00Z"
   413  
   414  	// default hold time requested
   415  	hold = 0
   416  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   417  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   418  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   419  		"snap-a": {
   420  			// maximum for holding other snaps, but taking into consideration
   421  			// firstHeld time = "2021-05-10T10:00:00".
   422  			"snap-b": snapstate.MockHoldState(oldNow, "2021-05-12T10:00:00Z"),
   423  		},
   424  	})
   425  }
   426  
   427  func (s *autorefreshGatingSuite) TestHoldRefreshHelperCloseToMaxPostponement(c *C) {
   428  	st := s.state
   429  	st.Lock()
   430  	defer st.Unlock()
   431  
   432  	lastRefreshedStr := "2021-01-01T10:00:00Z"
   433  	lastRefreshed, err := time.Parse(time.RFC3339, lastRefreshedStr)
   434  	c.Assert(err, IsNil)
   435  	// we are 1 day before maxPostponent
   436  	now := lastRefreshed.Add(89 * time.Hour * 24)
   437  
   438  	restore := snapstate.MockTimeNow(func() time.Time { return now })
   439  	defer restore()
   440  
   441  	mockInstalledSnap(c, st, snapAyaml, false)
   442  	mockInstalledSnap(c, st, snapByaml, false)
   443  	mockLastRefreshed(c, st, lastRefreshedStr, "snap-a")
   444  
   445  	// request default hold time
   446  	var hold time.Duration
   447  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   448  
   449  	var gating map[string]map[string]*snapstate.HoldState
   450  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   451  	c.Assert(gating, HasLen, 1)
   452  	c.Check(gating["snap-a"]["snap-b"].HoldUntil.String(), DeepEquals, lastRefreshed.Add(90*time.Hour*24).String())
   453  }
   454  
   455  func (s *autorefreshGatingSuite) TestHoldRefreshExplicitHoldTime(c *C) {
   456  	st := s.state
   457  	st.Lock()
   458  	defer st.Unlock()
   459  
   460  	now := "2021-05-10T10:00:00Z"
   461  	restore := snapstate.MockTimeNow(func() time.Time {
   462  		t, err := time.Parse(time.RFC3339, now)
   463  		c.Assert(err, IsNil)
   464  		return t
   465  	})
   466  	defer restore()
   467  
   468  	mockInstalledSnap(c, st, snapAyaml, false)
   469  	mockInstalledSnap(c, st, snapByaml, false)
   470  
   471  	hold := time.Hour * 24 * 3
   472  	// holding self for 3 days
   473  	c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), IsNil)
   474  
   475  	// snap-b holds snap-a for 1 day
   476  	hold = time.Hour * 24
   477  	c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
   478  
   479  	var gating map[string]map[string]*snapstate.HoldState
   480  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   481  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   482  		"snap-a": {
   483  			"snap-a": snapstate.MockHoldState(now, "2021-05-13T10:00:00Z"),
   484  			"snap-b": snapstate.MockHoldState(now, "2021-05-11T10:00:00Z"),
   485  		},
   486  	})
   487  }
   488  
   489  func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) {
   490  	st := s.state
   491  	st.Lock()
   492  	defer st.Unlock()
   493  
   494  	now := "2021-05-10T10:00:00Z"
   495  	restore := snapstate.MockTimeNow(func() time.Time {
   496  		t, err := time.Parse(time.RFC3339, now)
   497  		c.Assert(err, IsNil)
   498  		return t
   499  	})
   500  	defer restore()
   501  
   502  	mockInstalledSnap(c, st, snapAyaml, false)
   503  	mockInstalledSnap(c, st, snapByaml, false)
   504  	// snap-b was refreshed a few days ago
   505  	mockLastRefreshed(c, st, "2021-05-01T10:00:00Z", "snap-b")
   506  
   507  	// holding itself
   508  	hold := time.Hour * 24 * 96
   509  	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`)
   510  
   511  	// holding other snap
   512  	hold = time.Hour * 49
   513  	err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-b")
   514  	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`)
   515  	herr, ok := err.(*snapstate.HoldError)
   516  	c.Assert(ok, Equals, true)
   517  	c.Check(herr.SnapsInError, DeepEquals, map[string]snapstate.HoldDurationError{
   518  		"snap-b": {
   519  			Err:          fmt.Errorf(`requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`),
   520  			DurationLeft: 48 * time.Hour,
   521  		},
   522  	})
   523  
   524  	// hold for maximum allowed for other snaps
   525  	hold = time.Hour * 48
   526  	c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), IsNil)
   527  	// 2 days passed since it was first held
   528  	now = "2021-05-12T10:00:00Z"
   529  	hold = time.Minute * 2
   530  	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`)
   531  
   532  	// refreshed long time ago (> maxPostponement)
   533  	mockLastRefreshed(c, st, "2021-01-01T10:00:00Z", "snap-b")
   534  	hold = time.Hour * 2
   535  	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`)
   536  	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`)
   537  }
   538  
   539  func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) {
   540  	st := s.state
   541  	st.Lock()
   542  	defer st.Unlock()
   543  
   544  	mockInstalledSnap(c, st, snapAyaml, false)
   545  	mockInstalledSnap(c, st, snapByaml, false)
   546  	mockInstalledSnap(c, st, snapCyaml, false)
   547  	mockInstalledSnap(c, st, snapDyaml, false)
   548  
   549  	mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-b", "snap-c", "snap-d")
   550  
   551  	restore := snapstate.MockTimeNow(func() time.Time {
   552  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   553  		c.Assert(err, IsNil)
   554  		return t
   555  	})
   556  	defer restore()
   557  
   558  	// nothing is held initially
   559  	held, err := snapstate.HeldSnaps(st)
   560  	c.Assert(err, IsNil)
   561  	c.Check(held, IsNil)
   562  
   563  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
   564  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil)
   565  	// holding self
   566  	c.Assert(snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d"), IsNil)
   567  
   568  	held, err = snapstate.HeldSnaps(st)
   569  	c.Assert(err, IsNil)
   570  	c.Check(held, DeepEquals, map[string]bool{"snap-b": true, "snap-c": true, "snap-d": true})
   571  
   572  	c.Assert(snapstate.ProceedWithRefresh(st, "snap-a"), IsNil)
   573  
   574  	held, err = snapstate.HeldSnaps(st)
   575  	c.Assert(err, IsNil)
   576  	c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-d": true})
   577  
   578  	c.Assert(snapstate.ProceedWithRefresh(st, "snap-d"), IsNil)
   579  	held, err = snapstate.HeldSnaps(st)
   580  	c.Assert(err, IsNil)
   581  	c.Check(held, IsNil)
   582  }
   583  
   584  // Test that if all snaps cannot be held anymore, we don't hold only some of them
   585  // e.g. is a snap and its base snap have updates and the snap wants to hold (itself
   586  // and the base) but the base cannot be held, it doesn't make sense to refresh the
   587  // base but hold the affected snap.
   588  func (s *autorefreshGatingSuite) TestDontHoldSomeSnapsIfSomeFail(c *C) {
   589  	st := s.state
   590  	st.Lock()
   591  	defer st.Unlock()
   592  
   593  	// snap-b and snap-bb have base-snap-b base
   594  	mockInstalledSnap(c, st, snapByaml, useHook)
   595  	mockInstalledSnap(c, st, snapBByaml, useHook)
   596  	mockInstalledSnap(c, st, baseSnapByaml, noHook)
   597  
   598  	mockInstalledSnap(c, st, snapCyaml, useHook)
   599  	mockInstalledSnap(c, st, snapDyaml, useHook)
   600  
   601  	now := "2021-05-01T10:00:00Z"
   602  	restore := snapstate.MockTimeNow(func() time.Time {
   603  		t, err := time.Parse(time.RFC3339, now)
   604  		c.Assert(err, IsNil)
   605  		return t
   606  	})
   607  	defer restore()
   608  
   609  	// snap-b, base-snap-b get refreshed and affect snap-b (gating snap)
   610  	c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b"), IsNil)
   611  	// unrealted snap-d gets refreshed and holds itself
   612  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d"), IsNil)
   613  
   614  	// advance time by 49h
   615  	now = "2021-05-03T11:00:00Z"
   616  	// snap-b, base-snap-b and snap-c get refreshed and snap-a (gating snap) wants to hold them
   617  	c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b", "snap-c"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "base-snap-b" anymore, maximum refresh postponement exceeded`)
   618  	// snap-bb (gating snap) wants to hold base-snap-b as well and succeeds since it didn't exceed its holding time yet
   619  	c.Assert(snapstate.HoldRefresh(st, "snap-bb", 0, "base-snap-b"), IsNil)
   620  
   621  	held, err := snapstate.HeldSnaps(st)
   622  	c.Assert(err, IsNil)
   623  	// note, snap-b couldn't hold base-snap-b anymore so we didn't hold snap-b
   624  	// and snap-c. base-snap-b was held by snap-bb.
   625  	c.Check(held, DeepEquals, map[string]bool{
   626  		"snap-d":      true,
   627  		"base-snap-b": true,
   628  	})
   629  }
   630  
   631  func (s *autorefreshGatingSuite) TestPruneGatingHelper(c *C) {
   632  	st := s.state
   633  	st.Lock()
   634  	defer st.Unlock()
   635  
   636  	restore := snapstate.MockTimeNow(func() time.Time {
   637  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   638  		c.Assert(err, IsNil)
   639  		return t
   640  	})
   641  	defer restore()
   642  
   643  	mockInstalledSnap(c, st, snapAyaml, false)
   644  	mockInstalledSnap(c, st, snapByaml, false)
   645  	mockInstalledSnap(c, st, snapCyaml, false)
   646  	mockInstalledSnap(c, st, snapDyaml, false)
   647  
   648  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
   649  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil)
   650  	// sanity
   651  	held, err := snapstate.HeldSnaps(st)
   652  	c.Assert(err, IsNil)
   653  	c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-b": true, "snap-d": true})
   654  
   655  	candidates := map[string]*snapstate.RefreshCandidate{"snap-c": {}}
   656  
   657  	// only snap-c has a refresh candidate, snap-b and snap-d should be forgotten.
   658  	c.Assert(snapstate.PruneGating(st, candidates), IsNil)
   659  	var gating map[string]map[string]*snapstate.HoldState
   660  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   661  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   662  		"snap-c": {
   663  			"snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   664  			"snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
   665  		},
   666  	})
   667  	held, err = snapstate.HeldSnaps(st)
   668  	c.Assert(err, IsNil)
   669  	c.Check(held, DeepEquals, map[string]bool{"snap-c": true})
   670  }
   671  
   672  func (s *autorefreshGatingSuite) TestPruneGatingHelperNoGating(c *C) {
   673  	st := s.state
   674  	st.Lock()
   675  	defer st.Unlock()
   676  
   677  	restore := snapstate.MockTimeNow(func() time.Time {
   678  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   679  		c.Assert(err, IsNil)
   680  		return t
   681  	})
   682  	defer restore()
   683  
   684  	mockInstalledSnap(c, st, snapAyaml, false)
   685  
   686  	held, err := snapstate.HeldSnaps(st)
   687  	c.Assert(err, IsNil)
   688  	c.Check(held, HasLen, 0)
   689  
   690  	snapstate.MockTimeNow(func() time.Time {
   691  		c.Fatalf("not expected")
   692  		return time.Time{}
   693  	})
   694  
   695  	candidates := map[string]*snapstate.RefreshCandidate{"snap-a": {}}
   696  	c.Assert(snapstate.PruneGating(st, candidates), IsNil)
   697  	held, err = snapstate.HeldSnaps(st)
   698  	c.Assert(err, IsNil)
   699  	c.Check(held, HasLen, 0)
   700  }
   701  
   702  func (s *autorefreshGatingSuite) TestResetGatingForRefreshedHelper(c *C) {
   703  	st := s.state
   704  	st.Lock()
   705  	defer st.Unlock()
   706  
   707  	restore := snapstate.MockTimeNow(func() time.Time {
   708  		t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z")
   709  		c.Assert(err, IsNil)
   710  		return t
   711  	})
   712  	defer restore()
   713  
   714  	mockInstalledSnap(c, st, snapAyaml, false)
   715  	mockInstalledSnap(c, st, snapByaml, false)
   716  	mockInstalledSnap(c, st, snapCyaml, false)
   717  	mockInstalledSnap(c, st, snapDyaml, false)
   718  
   719  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
   720  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil)
   721  
   722  	c.Assert(snapstate.ResetGatingForRefreshed(st, "snap-b", "snap-c"), IsNil)
   723  	var gating map[string]map[string]*snapstate.HoldState
   724  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   725  	c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
   726  		"snap-d": {
   727  			// holding self set for maxPostponement (95 days - buffer = 90 days)
   728  			"snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-08T10:00:00Z"),
   729  		},
   730  	})
   731  
   732  	held, err := snapstate.HeldSnaps(st)
   733  	c.Assert(err, IsNil)
   734  	c.Check(held, DeepEquals, map[string]bool{"snap-d": true})
   735  }
   736  
   737  func (s *autorefreshGatingSuite) TestPruneSnapsHold(c *C) {
   738  	st := s.state
   739  	st.Lock()
   740  	defer st.Unlock()
   741  
   742  	mockInstalledSnap(c, st, snapAyaml, false)
   743  	mockInstalledSnap(c, st, snapByaml, false)
   744  	mockInstalledSnap(c, st, snapCyaml, false)
   745  	mockInstalledSnap(c, st, snapDyaml, false)
   746  
   747  	// snap-a is holding itself and 3 other snaps
   748  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-d"), IsNil)
   749  	// in addition, snap-c is held by snap-d.
   750  	c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil)
   751  
   752  	// sanity check
   753  	held, err := snapstate.HeldSnaps(st)
   754  	c.Assert(err, IsNil)
   755  	c.Check(held, DeepEquals, map[string]bool{
   756  		"snap-a": true,
   757  		"snap-b": true,
   758  		"snap-c": true,
   759  		"snap-d": true,
   760  	})
   761  
   762  	c.Check(snapstate.PruneSnapsHold(st, "snap-a"), IsNil)
   763  
   764  	// after pruning snap-a, snap-c is still held.
   765  	held, err = snapstate.HeldSnaps(st)
   766  	c.Assert(err, IsNil)
   767  	c.Check(held, DeepEquals, map[string]bool{
   768  		"snap-c": true,
   769  	})
   770  	var gating map[string]map[string]*snapstate.HoldState
   771  	c.Assert(st.Get("snaps-hold", &gating), IsNil)
   772  	c.Assert(gating, HasLen, 1)
   773  	c.Check(gating["snap-c"], HasLen, 1)
   774  	c.Check(gating["snap-c"]["snap-d"], NotNil)
   775  }
   776  
   777  const useHook = true
   778  const noHook = false
   779  
   780  func checkGatingTask(c *C, task *state.Task, expected map[string]*snapstate.RefreshCandidate) {
   781  	c.Assert(task.Kind(), Equals, "conditional-auto-refresh")
   782  	var snaps map[string]*snapstate.RefreshCandidate
   783  	c.Assert(task.Get("snaps", &snaps), IsNil)
   784  	c.Check(snaps, DeepEquals, expected)
   785  }
   786  
   787  func (s *autorefreshGatingSuite) TestAffectedByBase(c *C) {
   788  	restore := release.MockOnClassic(true)
   789  	defer restore()
   790  
   791  	st := s.state
   792  
   793  	st.Lock()
   794  	defer st.Unlock()
   795  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
   796  	baseSnapA := mockInstalledSnap(c, s.state, baseSnapAyaml, noHook)
   797  	// unrelated snaps
   798  	snapB := mockInstalledSnap(c, s.state, snapByaml, useHook)
   799  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
   800  
   801  	c.Assert(s.repo.AddSnap(snapB), IsNil)
   802  
   803  	updates := []string{baseSnapA.InstanceName()}
   804  	affected, err := snapstate.AffectedByRefresh(st, updates)
   805  	c.Assert(err, IsNil)
   806  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   807  		"snap-a": {
   808  			Base: true,
   809  			AffectingSnaps: map[string]bool{
   810  				"base-snap-a": true,
   811  			}}})
   812  }
   813  
   814  func (s *autorefreshGatingSuite) TestAffectedByCore(c *C) {
   815  	restore := release.MockOnClassic(true)
   816  	defer restore()
   817  
   818  	st := s.state
   819  
   820  	st.Lock()
   821  	defer st.Unlock()
   822  	snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook)
   823  	core := mockInstalledSnap(c, s.state, coreYaml, noHook)
   824  	snapB := mockInstalledSnap(c, s.state, snapByaml, useHook)
   825  
   826  	c.Assert(s.repo.AddSnap(core), IsNil)
   827  	c.Assert(s.repo.AddSnap(snapB), IsNil)
   828  	c.Assert(s.repo.AddSnap(snapC), IsNil)
   829  
   830  	updates := []string{core.InstanceName()}
   831  	affected, err := snapstate.AffectedByRefresh(st, updates)
   832  	c.Assert(err, IsNil)
   833  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   834  		"snap-c": {
   835  			Base: true,
   836  			AffectingSnaps: map[string]bool{
   837  				"core": true,
   838  			}}})
   839  }
   840  
   841  func (s *autorefreshGatingSuite) TestAffectedByKernel(c *C) {
   842  	restore := release.MockOnClassic(true)
   843  	defer restore()
   844  
   845  	st := s.state
   846  
   847  	st.Lock()
   848  	defer st.Unlock()
   849  	kernel := mockInstalledSnap(c, s.state, kernelYaml, noHook)
   850  	mockInstalledSnap(c, s.state, snapCyaml, useHook)
   851  	mockInstalledSnap(c, s.state, snapByaml, noHook)
   852  
   853  	updates := []string{kernel.InstanceName()}
   854  	affected, err := snapstate.AffectedByRefresh(st, updates)
   855  	c.Assert(err, IsNil)
   856  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   857  		"snap-c": {
   858  			Restart: true,
   859  			AffectingSnaps: map[string]bool{
   860  				"kernel": true,
   861  			}}})
   862  }
   863  
   864  func (s *autorefreshGatingSuite) TestAffectedBySelf(c *C) {
   865  	restore := release.MockOnClassic(true)
   866  	defer restore()
   867  
   868  	st := s.state
   869  
   870  	st.Lock()
   871  	defer st.Unlock()
   872  
   873  	snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook)
   874  	updates := []string{snapC.InstanceName()}
   875  	affected, err := snapstate.AffectedByRefresh(st, updates)
   876  	c.Assert(err, IsNil)
   877  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   878  		"snap-c": {
   879  			AffectingSnaps: map[string]bool{
   880  				"snap-c": true,
   881  			}}})
   882  }
   883  
   884  func (s *autorefreshGatingSuite) TestAffectedByGadget(c *C) {
   885  	restore := release.MockOnClassic(true)
   886  	defer restore()
   887  
   888  	st := s.state
   889  
   890  	st.Lock()
   891  	defer st.Unlock()
   892  	kernel := mockInstalledSnap(c, s.state, gadget1Yaml, noHook)
   893  	mockInstalledSnap(c, s.state, snapCyaml, useHook)
   894  	mockInstalledSnap(c, s.state, snapByaml, noHook)
   895  
   896  	updates := []string{kernel.InstanceName()}
   897  	affected, err := snapstate.AffectedByRefresh(st, updates)
   898  	c.Assert(err, IsNil)
   899  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   900  		"snap-c": {
   901  			Restart: true,
   902  			AffectingSnaps: map[string]bool{
   903  				"gadget": true,
   904  			}}})
   905  }
   906  
   907  func (s *autorefreshGatingSuite) TestAffectedBySlot(c *C) {
   908  	restore := release.MockOnClassic(true)
   909  	defer restore()
   910  
   911  	st := s.state
   912  
   913  	st.Lock()
   914  	defer st.Unlock()
   915  
   916  	snapD := mockInstalledSnap(c, s.state, snapDyaml, noHook)
   917  	snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook)
   918  	// unrelated snap
   919  	snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook)
   920  
   921  	c.Assert(s.repo.AddSnap(snapF), IsNil)
   922  	c.Assert(s.repo.AddSnap(snapD), IsNil)
   923  	c.Assert(s.repo.AddSnap(snapE), IsNil)
   924  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}}
   925  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   926  	c.Assert(err, IsNil)
   927  
   928  	updates := []string{snapD.InstanceName()}
   929  	affected, err := snapstate.AffectedByRefresh(st, updates)
   930  	c.Assert(err, IsNil)
   931  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   932  		"snap-e": {
   933  			Restart: true,
   934  			AffectingSnaps: map[string]bool{
   935  				"snap-d": true,
   936  			}}})
   937  }
   938  
   939  func (s *autorefreshGatingSuite) TestNotAffectedByCoreOrSnapdSlot(c *C) {
   940  	restore := release.MockOnClassic(true)
   941  	defer restore()
   942  
   943  	st := s.state
   944  
   945  	st.Lock()
   946  	defer st.Unlock()
   947  
   948  	snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook)
   949  	core := mockInstalledSnap(c, s.state, coreYaml, noHook)
   950  	snapB := mockInstalledSnap(c, s.state, snapByaml, useHook)
   951  
   952  	c.Assert(s.repo.AddSnap(snapG), IsNil)
   953  	c.Assert(s.repo.AddSnap(core), IsNil)
   954  	c.Assert(s.repo.AddSnap(snapB), IsNil)
   955  
   956  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "mir"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "mir"}}
   957  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   958  	c.Assert(err, IsNil)
   959  
   960  	updates := []string{core.InstanceName()}
   961  	affected, err := snapstate.AffectedByRefresh(st, updates)
   962  	c.Assert(err, IsNil)
   963  	c.Check(affected, HasLen, 0)
   964  }
   965  
   966  func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackend(c *C) {
   967  	restore := release.MockOnClassic(true)
   968  	defer restore()
   969  
   970  	st := s.state
   971  
   972  	st.Lock()
   973  	defer st.Unlock()
   974  
   975  	snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook)
   976  	snapE := mockInstalledSnap(c, s.state, snapEyaml, noHook)
   977  	// unrelated snap
   978  	snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook)
   979  
   980  	c.Assert(s.repo.AddSnap(snapF), IsNil)
   981  	c.Assert(s.repo.AddSnap(snapD), IsNil)
   982  	c.Assert(s.repo.AddSnap(snapE), IsNil)
   983  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}}
   984  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
   985  	c.Assert(err, IsNil)
   986  
   987  	// snapE has a plug using mount backend and is refreshed, this affects slot of snap-d.
   988  	updates := []string{snapE.InstanceName()}
   989  	affected, err := snapstate.AffectedByRefresh(st, updates)
   990  	c.Assert(err, IsNil)
   991  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
   992  		"snap-d": {
   993  			Restart: true,
   994  			AffectingSnaps: map[string]bool{
   995  				"snap-e": true,
   996  			}}})
   997  }
   998  
   999  func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendSnapdSlot(c *C) {
  1000  	restore := release.MockOnClassic(true)
  1001  	defer restore()
  1002  
  1003  	st := s.state
  1004  
  1005  	st.Lock()
  1006  	defer st.Unlock()
  1007  
  1008  	snapdSnap := mockInstalledSnap(c, s.state, snapdYaml, noHook)
  1009  	snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook)
  1010  	// unrelated snap
  1011  	snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook)
  1012  
  1013  	c.Assert(s.repo.AddSnap(snapF), IsNil)
  1014  	c.Assert(s.repo.AddSnap(snapdSnap), IsNil)
  1015  	c.Assert(s.repo.AddSnap(snapG), IsNil)
  1016  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "snapd", Name: "desktop"}}
  1017  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
  1018  	c.Assert(err, IsNil)
  1019  
  1020  	// snapE has a plug using mount backend, refreshing snapd affects snapE.
  1021  	updates := []string{snapdSnap.InstanceName()}
  1022  	affected, err := snapstate.AffectedByRefresh(st, updates)
  1023  	c.Assert(err, IsNil)
  1024  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
  1025  		"snap-g": {
  1026  			Restart: true,
  1027  			AffectingSnaps: map[string]bool{
  1028  				"snapd": true,
  1029  			}}})
  1030  }
  1031  
  1032  func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendCoreSlot(c *C) {
  1033  	restore := release.MockOnClassic(true)
  1034  	defer restore()
  1035  
  1036  	st := s.state
  1037  
  1038  	st.Lock()
  1039  	defer st.Unlock()
  1040  
  1041  	coreSnap := mockInstalledSnap(c, s.state, coreYaml, noHook)
  1042  	snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook)
  1043  
  1044  	c.Assert(s.repo.AddSnap(coreSnap), IsNil)
  1045  	c.Assert(s.repo.AddSnap(snapG), IsNil)
  1046  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "desktop"}}
  1047  	_, err := s.repo.Connect(cref, nil, nil, nil, nil, nil)
  1048  	c.Assert(err, IsNil)
  1049  
  1050  	// snapG has a plug using mount backend, refreshing core affects snapE.
  1051  	updates := []string{coreSnap.InstanceName()}
  1052  	affected, err := snapstate.AffectedByRefresh(st, updates)
  1053  	c.Assert(err, IsNil)
  1054  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
  1055  		"snap-g": {
  1056  			Restart: true,
  1057  			AffectingSnaps: map[string]bool{
  1058  				"core": true,
  1059  			}}})
  1060  }
  1061  
  1062  func (s *autorefreshGatingSuite) TestAffectedByBootBase(c *C) {
  1063  	restore := release.MockOnClassic(false)
  1064  	defer restore()
  1065  
  1066  	st := s.state
  1067  
  1068  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
  1069  	defer r()
  1070  
  1071  	st.Lock()
  1072  	defer st.Unlock()
  1073  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1074  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1075  	mockInstalledSnap(c, s.state, snapDyaml, useHook)
  1076  	mockInstalledSnap(c, s.state, snapEyaml, useHook)
  1077  	core18 := mockInstalledSnap(c, s.state, core18Yaml, noHook)
  1078  
  1079  	updates := []string{core18.InstanceName()}
  1080  	affected, err := snapstate.AffectedByRefresh(st, updates)
  1081  	c.Assert(err, IsNil)
  1082  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
  1083  		"snap-a": {
  1084  			Base:    false,
  1085  			Restart: true,
  1086  			AffectingSnaps: map[string]bool{
  1087  				"core18": true,
  1088  			},
  1089  		},
  1090  		"snap-b": {
  1091  			Base:    false,
  1092  			Restart: true,
  1093  			AffectingSnaps: map[string]bool{
  1094  				"core18": true,
  1095  			},
  1096  		},
  1097  		"snap-d": {
  1098  			Base:    false,
  1099  			Restart: true,
  1100  			AffectingSnaps: map[string]bool{
  1101  				"core18": true,
  1102  			},
  1103  		},
  1104  		"snap-e": {
  1105  			Base:    false,
  1106  			Restart: true,
  1107  			AffectingSnaps: map[string]bool{
  1108  				"core18": true,
  1109  			}}})
  1110  }
  1111  
  1112  func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) {
  1113  	st := s.state
  1114  	st.Lock()
  1115  	defer st.Unlock()
  1116  
  1117  	affected := map[string]*snapstate.AffectedSnapInfo{
  1118  		"snap-a": {
  1119  			Base:    true,
  1120  			Restart: true,
  1121  			AffectingSnaps: map[string]bool{
  1122  				"snap-c": true,
  1123  				"snap-d": true,
  1124  			},
  1125  		},
  1126  		"snap-b": {
  1127  			AffectingSnaps: map[string]bool{
  1128  				"snap-e": true,
  1129  				"snap-f": true,
  1130  			},
  1131  		},
  1132  	}
  1133  
  1134  	seenSnaps := make(map[string]bool)
  1135  
  1136  	ts := snapstate.CreateGateAutoRefreshHooks(st, affected)
  1137  	c.Assert(ts.Tasks(), HasLen, 2)
  1138  
  1139  	checkHook := func(t *state.Task) {
  1140  		c.Assert(t.Kind(), Equals, "run-hook")
  1141  		var hs hookstate.HookSetup
  1142  		c.Assert(t.Get("hook-setup", &hs), IsNil)
  1143  		c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1144  		c.Check(hs.Optional, Equals, true)
  1145  		seenSnaps[hs.Snap] = true
  1146  
  1147  		var data interface{}
  1148  		c.Assert(t.Get("hook-context", &data), IsNil)
  1149  
  1150  		// the order of hook tasks is not deterministic
  1151  		if hs.Snap == "snap-a" {
  1152  			c.Check(data, DeepEquals, map[string]interface{}{
  1153  				"base":            true,
  1154  				"restart":         true,
  1155  				"affecting-snaps": []interface{}{"snap-c", "snap-d"}})
  1156  		} else {
  1157  			c.Assert(hs.Snap, Equals, "snap-b")
  1158  			c.Check(data, DeepEquals, map[string]interface{}{
  1159  				"base":            false,
  1160  				"restart":         false,
  1161  				"affecting-snaps": []interface{}{"snap-e", "snap-f"}})
  1162  		}
  1163  	}
  1164  
  1165  	checkHook(ts.Tasks()[0])
  1166  	checkHook(ts.Tasks()[1])
  1167  
  1168  	c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true})
  1169  }
  1170  
  1171  func (s *autorefreshGatingSuite) TestAffectedByRefreshCandidates(c *C) {
  1172  	st := s.state
  1173  	st.Lock()
  1174  	defer st.Unlock()
  1175  
  1176  	mockInstalledSnap(c, st, snapAyaml, useHook)
  1177  	// unrelated snap
  1178  	mockInstalledSnap(c, st, snapByaml, useHook)
  1179  
  1180  	// no refresh-candidates in state
  1181  	affected, err := snapstate.AffectedByRefreshCandidates(st)
  1182  	c.Assert(err, IsNil)
  1183  	c.Check(affected, HasLen, 0)
  1184  
  1185  	candidates := map[string]*snapstate.RefreshCandidate{"snap-a": {}}
  1186  	st.Set("refresh-candidates", &candidates)
  1187  
  1188  	affected, err = snapstate.AffectedByRefreshCandidates(st)
  1189  	c.Assert(err, IsNil)
  1190  	c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{
  1191  		"snap-a": {
  1192  			AffectingSnaps: map[string]bool{
  1193  				"snap-a": true,
  1194  			}}})
  1195  }
  1196  
  1197  func (s *autorefreshGatingSuite) TestAutorefreshPhase1FeatureFlag(c *C) {
  1198  	st := s.state
  1199  	st.Lock()
  1200  	defer st.Unlock()
  1201  
  1202  	st.Set("seeded", true)
  1203  
  1204  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1205  	defer restore()
  1206  
  1207  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  1208  		return nil, nil
  1209  	}
  1210  	defer func() { snapstate.AutoAliases = nil }()
  1211  
  1212  	s.store.refreshedSnaps = []*snap.Info{{
  1213  		Architectures: []string{"all"},
  1214  		SnapType:      snap.TypeApp,
  1215  		SideInfo: snap.SideInfo{
  1216  			RealName: "snap-a",
  1217  			Revision: snap.R(8),
  1218  		},
  1219  	}}
  1220  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1221  
  1222  	// gate-auto-refresh-hook feature not enabled, expect old-style refresh.
  1223  	_, tss, err := snapstate.AutoRefresh(context.TODO(), st)
  1224  	c.Check(err, IsNil)
  1225  	c.Assert(tss, HasLen, 2)
  1226  	c.Check(tss[0].Tasks()[0].Kind(), Equals, "prerequisites")
  1227  	c.Check(tss[0].Tasks()[1].Kind(), Equals, "download-snap")
  1228  	c.Check(tss[1].Tasks()[0].Kind(), Equals, "check-rerefresh")
  1229  
  1230  	// enable gate-auto-refresh-hook feature
  1231  	tr := config.NewTransaction(s.state)
  1232  	tr.Set("core", "experimental.gate-auto-refresh-hook", true)
  1233  	tr.Commit()
  1234  
  1235  	_, tss, err = snapstate.AutoRefresh(context.TODO(), st)
  1236  	c.Check(err, IsNil)
  1237  	c.Assert(tss, HasLen, 2)
  1238  	task := tss[0].Tasks()[0]
  1239  	c.Check(task.Kind(), Equals, "conditional-auto-refresh")
  1240  	var toUpdate map[string]*snapstate.RefreshCandidate
  1241  	c.Assert(task.Get("snaps", &toUpdate), IsNil)
  1242  	seenSnaps := make(map[string]bool)
  1243  	for up := range toUpdate {
  1244  		seenSnaps[up] = true
  1245  	}
  1246  	c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true})
  1247  	c.Check(tss[1].Tasks()[0].Kind(), Equals, "run-hook")
  1248  }
  1249  
  1250  func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) {
  1251  	s.store.refreshedSnaps = []*snap.Info{{
  1252  		Architectures: []string{"all"},
  1253  		SnapType:      snap.TypeApp,
  1254  		SideInfo: snap.SideInfo{
  1255  			RealName: "snap-a",
  1256  			Revision: snap.R(8),
  1257  		},
  1258  	}, {
  1259  		Architectures: []string{"all"},
  1260  		SnapType:      snap.TypeBase,
  1261  		SideInfo: snap.SideInfo{
  1262  			RealName: "base-snap-b",
  1263  			Revision: snap.R(3),
  1264  		},
  1265  	}, {
  1266  		Architectures: []string{"all"},
  1267  		SnapType:      snap.TypeApp,
  1268  		SideInfo: snap.SideInfo{
  1269  			RealName: "snap-c",
  1270  			Revision: snap.R(5),
  1271  		},
  1272  	}}
  1273  
  1274  	st := s.state
  1275  	st.Lock()
  1276  	defer st.Unlock()
  1277  
  1278  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1279  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1280  	mockInstalledSnap(c, s.state, snapCyaml, noHook)
  1281  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1282  	mockInstalledSnap(c, s.state, snapDyaml, noHook)
  1283  
  1284  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1285  	defer restore()
  1286  
  1287  	// pretend some snaps are held
  1288  	c.Assert(snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d"), IsNil)
  1289  	// sanity check
  1290  	heldSnaps, err := snapstate.HeldSnaps(st)
  1291  	c.Assert(err, IsNil)
  1292  	c.Check(heldSnaps, DeepEquals, map[string]bool{
  1293  		"snap-a": true,
  1294  		"snap-d": true,
  1295  	})
  1296  
  1297  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  1298  	c.Assert(err, IsNil)
  1299  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a", "snap-c"})
  1300  	c.Assert(tss, HasLen, 2)
  1301  
  1302  	c.Assert(tss[0].Tasks(), HasLen, 1)
  1303  	checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{
  1304  		"snap-a": {
  1305  			SnapSetup: snapstate.SnapSetup{
  1306  				Type:      "app",
  1307  				PlugsOnly: true,
  1308  				Flags: snapstate.Flags{
  1309  					IsAutoRefresh: true,
  1310  				},
  1311  				SideInfo: &snap.SideInfo{
  1312  					RealName: "snap-a",
  1313  					Revision: snap.R(8),
  1314  				},
  1315  				DownloadInfo: &snap.DownloadInfo{},
  1316  			},
  1317  		},
  1318  		"base-snap-b": {
  1319  			SnapSetup: snapstate.SnapSetup{
  1320  				Type:      "base",
  1321  				PlugsOnly: true,
  1322  				Flags: snapstate.Flags{
  1323  					IsAutoRefresh: true,
  1324  				},
  1325  				SideInfo: &snap.SideInfo{
  1326  					RealName: "base-snap-b",
  1327  					Revision: snap.R(3),
  1328  				},
  1329  				DownloadInfo: &snap.DownloadInfo{},
  1330  			},
  1331  		},
  1332  		"snap-c": {
  1333  			SnapSetup: snapstate.SnapSetup{
  1334  				Type:      "app",
  1335  				PlugsOnly: true,
  1336  				Flags: snapstate.Flags{
  1337  					IsAutoRefresh: true,
  1338  				},
  1339  				SideInfo: &snap.SideInfo{
  1340  					RealName: "snap-c",
  1341  					Revision: snap.R(5),
  1342  				},
  1343  				DownloadInfo: &snap.DownloadInfo{},
  1344  			},
  1345  		},
  1346  	})
  1347  
  1348  	c.Assert(tss[1].Tasks(), HasLen, 2)
  1349  
  1350  	var snapAhookData, snapBhookData map[string]interface{}
  1351  
  1352  	// check hooks for affected snaps
  1353  	seenSnaps := make(map[string]bool)
  1354  	var hs hookstate.HookSetup
  1355  	task := tss[1].Tasks()[0]
  1356  	c.Assert(task.Get("hook-setup", &hs), IsNil)
  1357  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1358  	seenSnaps[hs.Snap] = true
  1359  	switch hs.Snap {
  1360  	case "snap-a":
  1361  		task.Get("hook-context", &snapAhookData)
  1362  	case "snap-b":
  1363  		task.Get("hook-context", &snapBhookData)
  1364  	default:
  1365  		c.Fatalf("unexpected snap %q", hs.Snap)
  1366  	}
  1367  
  1368  	task = tss[1].Tasks()[1]
  1369  	c.Assert(task.Get("hook-setup", &hs), IsNil)
  1370  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1371  	seenSnaps[hs.Snap] = true
  1372  	switch hs.Snap {
  1373  	case "snap-a":
  1374  		task.Get("hook-context", &snapAhookData)
  1375  	case "snap-b":
  1376  		task.Get("hook-context", &snapBhookData)
  1377  	default:
  1378  		c.Fatalf("unexpected snap %q", hs.Snap)
  1379  	}
  1380  
  1381  	c.Check(snapAhookData["affecting-snaps"], DeepEquals, []interface{}{"snap-a"})
  1382  	c.Check(snapBhookData["affecting-snaps"], DeepEquals, []interface{}{"base-snap-b"})
  1383  
  1384  	// hook for snap-a because it gets refreshed, for snap-b because its base
  1385  	// gets refreshed. snap-c is refreshed but doesn't have the hook.
  1386  	c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true})
  1387  
  1388  	// check that refresh-candidates in the state were updated
  1389  	var candidates map[string]*snapstate.RefreshCandidate
  1390  	c.Assert(st.Get("refresh-candidates", &candidates), IsNil)
  1391  	c.Assert(candidates, HasLen, 3)
  1392  	c.Check(candidates["snap-a"], NotNil)
  1393  	c.Check(candidates["base-snap-b"], NotNil)
  1394  	c.Check(candidates["snap-c"], NotNil)
  1395  
  1396  	// check that after autoRefreshPhase1 any held snaps that are not in refresh
  1397  	// candidates got removed.
  1398  	heldSnaps, err = snapstate.HeldSnaps(st)
  1399  	c.Assert(err, IsNil)
  1400  	// snap-d got removed from held snaps.
  1401  	c.Check(heldSnaps, DeepEquals, map[string]bool{
  1402  		"snap-a": true,
  1403  	})
  1404  }
  1405  
  1406  // this test demonstrates that affectedByRefresh uses current snap info (not
  1407  // snap infos of store updates) by simulating a different base for the updated
  1408  // snap from the store.
  1409  func (s *autorefreshGatingSuite) TestAffectedByRefreshUsesCurrentSnapInfo(c *C) {
  1410  	s.store.refreshedSnaps = []*snap.Info{{
  1411  		Architectures: []string{"all"},
  1412  		SnapType:      snap.TypeBase,
  1413  		SideInfo: snap.SideInfo{
  1414  			RealName: "base-snap-b",
  1415  			Revision: snap.R(3),
  1416  		},
  1417  	}, {
  1418  		Architectures: []string{"all"},
  1419  		Base:          "new-base",
  1420  		SnapType:      snap.TypeApp,
  1421  		SideInfo: snap.SideInfo{
  1422  			RealName: "snap-b",
  1423  			Revision: snap.R(5),
  1424  		},
  1425  	}}
  1426  
  1427  	st := s.state
  1428  	st.Lock()
  1429  	defer st.Unlock()
  1430  
  1431  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1432  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1433  
  1434  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1435  	defer restore()
  1436  
  1437  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  1438  	c.Assert(err, IsNil)
  1439  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-b"})
  1440  	c.Assert(tss, HasLen, 2)
  1441  
  1442  	c.Assert(tss[0].Tasks(), HasLen, 1)
  1443  	checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{
  1444  		"snap-b": {
  1445  			SnapSetup: snapstate.SnapSetup{
  1446  				Type:      "app",
  1447  				Base:      "new-base",
  1448  				PlugsOnly: true,
  1449  				Flags: snapstate.Flags{
  1450  					IsAutoRefresh: true,
  1451  				},
  1452  				SideInfo: &snap.SideInfo{
  1453  					RealName: "snap-b",
  1454  					Revision: snap.R(5),
  1455  				},
  1456  				DownloadInfo: &snap.DownloadInfo{},
  1457  			},
  1458  		},
  1459  		"base-snap-b": {
  1460  			SnapSetup: snapstate.SnapSetup{
  1461  				Type:      "base",
  1462  				PlugsOnly: true,
  1463  				Flags: snapstate.Flags{
  1464  					IsAutoRefresh: true,
  1465  				},
  1466  				SideInfo: &snap.SideInfo{
  1467  					RealName: "base-snap-b",
  1468  					Revision: snap.R(3),
  1469  				},
  1470  				DownloadInfo: &snap.DownloadInfo{},
  1471  			},
  1472  		},
  1473  	})
  1474  
  1475  	c.Assert(tss[1].Tasks(), HasLen, 1)
  1476  	var hs hookstate.HookSetup
  1477  	task := tss[1].Tasks()[0]
  1478  	c.Assert(task.Get("hook-setup", &hs), IsNil)
  1479  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1480  	c.Check(hs.Snap, Equals, "snap-b")
  1481  	var data interface{}
  1482  	c.Assert(task.Get("hook-context", &data), IsNil)
  1483  	c.Check(data, DeepEquals, map[string]interface{}{
  1484  		"base":            true,
  1485  		"restart":         false,
  1486  		"affecting-snaps": []interface{}{"base-snap-b", "snap-b"}})
  1487  
  1488  	// check that refresh-candidates in the state were updated
  1489  	var candidates map[string]*snapstate.RefreshCandidate
  1490  	c.Assert(st.Get("refresh-candidates", &candidates), IsNil)
  1491  	c.Assert(candidates, HasLen, 2)
  1492  	c.Check(candidates["snap-b"], NotNil)
  1493  	c.Check(candidates["base-snap-b"], NotNil)
  1494  }
  1495  
  1496  func (s *autorefreshGatingSuite) TestAutoRefreshPhase1ConflictsFilteredOut(c *C) {
  1497  	s.store.refreshedSnaps = []*snap.Info{{
  1498  		Architectures: []string{"all"},
  1499  		SnapType:      snap.TypeApp,
  1500  		SideInfo: snap.SideInfo{
  1501  			RealName: "snap-a",
  1502  			Revision: snap.R(8),
  1503  		},
  1504  	}, {
  1505  		Architectures: []string{"all"},
  1506  		SnapType:      snap.TypeBase,
  1507  		SideInfo: snap.SideInfo{
  1508  			RealName: "snap-c",
  1509  			Revision: snap.R(5),
  1510  		},
  1511  	}}
  1512  
  1513  	st := s.state
  1514  	st.Lock()
  1515  	defer st.Unlock()
  1516  
  1517  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1518  	mockInstalledSnap(c, s.state, snapCyaml, noHook)
  1519  
  1520  	conflictChange := st.NewChange("conflicting change", "")
  1521  	conflictTask := st.NewTask("conflicting task", "")
  1522  	si := &snap.SideInfo{
  1523  		RealName: "snap-c",
  1524  		Revision: snap.R(1),
  1525  	}
  1526  	sup := snapstate.SnapSetup{SideInfo: si}
  1527  	conflictTask.Set("snap-setup", sup)
  1528  	conflictChange.AddTask(conflictTask)
  1529  
  1530  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1531  	defer restore()
  1532  
  1533  	logbuf, restoreLogger := logger.MockLogger()
  1534  	defer restoreLogger()
  1535  
  1536  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  1537  	c.Assert(err, IsNil)
  1538  	c.Check(names, DeepEquals, []string{"snap-a"})
  1539  	c.Assert(tss, HasLen, 2)
  1540  
  1541  	c.Assert(tss[0].Tasks(), HasLen, 1)
  1542  	checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{
  1543  		"snap-a": {
  1544  			SnapSetup: snapstate.SnapSetup{
  1545  				Type:      "app",
  1546  				PlugsOnly: true,
  1547  				Flags: snapstate.Flags{
  1548  					IsAutoRefresh: true,
  1549  				},
  1550  				SideInfo: &snap.SideInfo{
  1551  					RealName: "snap-a",
  1552  					Revision: snap.R(8),
  1553  				},
  1554  				DownloadInfo: &snap.DownloadInfo{},
  1555  			}}})
  1556  
  1557  	c.Assert(tss[1].Tasks(), HasLen, 1)
  1558  
  1559  	c.Assert(logbuf.String(), testutil.Contains, `cannot refresh snap "snap-c": snap "snap-c" has "conflicting change" change in progress`)
  1560  
  1561  	seenSnaps := make(map[string]bool)
  1562  	var hs hookstate.HookSetup
  1563  	c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil)
  1564  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  1565  	seenSnaps[hs.Snap] = true
  1566  
  1567  	c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true})
  1568  
  1569  	// check that refresh-candidates in the state were updated
  1570  	var candidates map[string]*snapstate.RefreshCandidate
  1571  	c.Assert(st.Get("refresh-candidates", &candidates), IsNil)
  1572  	c.Assert(candidates, HasLen, 2)
  1573  	c.Check(candidates["snap-a"], NotNil)
  1574  	c.Check(candidates["snap-c"], NotNil)
  1575  }
  1576  
  1577  func (s *autorefreshGatingSuite) TestAutoRefreshPhase1NoHooks(c *C) {
  1578  	s.store.refreshedSnaps = []*snap.Info{{
  1579  		Architectures: []string{"all"},
  1580  		SnapType:      snap.TypeBase,
  1581  		SideInfo: snap.SideInfo{
  1582  			RealName: "base-snap-b",
  1583  			Revision: snap.R(3),
  1584  		},
  1585  	}, {
  1586  		Architectures: []string{"all"},
  1587  		SnapType:      snap.TypeBase,
  1588  		SideInfo: snap.SideInfo{
  1589  			RealName: "snap-c",
  1590  			Revision: snap.R(5),
  1591  		},
  1592  	}}
  1593  
  1594  	st := s.state
  1595  	st.Lock()
  1596  	defer st.Unlock()
  1597  
  1598  	mockInstalledSnap(c, s.state, snapByaml, noHook)
  1599  	mockInstalledSnap(c, s.state, snapCyaml, noHook)
  1600  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1601  
  1602  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1603  	defer restore()
  1604  
  1605  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  1606  	c.Assert(err, IsNil)
  1607  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-c"})
  1608  	c.Assert(tss, HasLen, 1)
  1609  
  1610  	c.Assert(tss[0].Tasks(), HasLen, 1)
  1611  	c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh")
  1612  }
  1613  
  1614  func fakeReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) {
  1615  	info := &snap.Info{
  1616  		SuggestedName: name,
  1617  		SideInfo:      *si,
  1618  		Architectures: []string{"all"},
  1619  		SnapType:      snap.TypeApp,
  1620  		Epoch:         snap.Epoch{},
  1621  	}
  1622  	switch name {
  1623  	case "base-snap-b":
  1624  		info.SnapType = snap.TypeBase
  1625  	case "snap-a", "snap-b":
  1626  		info.Hooks = map[string]*snap.HookInfo{
  1627  			"gate-auto-refresh": {
  1628  				Name: "gate-auto-refresh",
  1629  				Snap: info,
  1630  			},
  1631  		}
  1632  		if name == "snap-b" {
  1633  			info.Base = "base-snap-b"
  1634  		}
  1635  	}
  1636  	return info, nil
  1637  }
  1638  
  1639  func (s *snapmgrTestSuite) testAutoRefreshPhase2(c *C, beforePhase1 func(), gateAutoRefreshHook func(snapName string), expected []string) *state.Change {
  1640  	st := s.state
  1641  	st.Lock()
  1642  	defer st.Unlock()
  1643  
  1644  	s.o.TaskRunner().AddHandler("run-hook", func(t *state.Task, tomb *tomb.Tomb) error {
  1645  		var hsup hookstate.HookSetup
  1646  		t.State().Lock()
  1647  		defer t.State().Unlock()
  1648  		c.Assert(t.Get("hook-setup", &hsup), IsNil)
  1649  		if hsup.Hook == "gate-auto-refresh" && gateAutoRefreshHook != nil {
  1650  			gateAutoRefreshHook(hsup.Snap)
  1651  		}
  1652  		return nil
  1653  	}, nil)
  1654  
  1655  	restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) {
  1656  		c.Fatal("unexpected call to installSize")
  1657  		return 0, nil
  1658  	})
  1659  	defer restoreInstallSize()
  1660  
  1661  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  1662  		fakeStore: s.fakeStore,
  1663  		refreshedSnaps: []*snap.Info{{
  1664  			Architectures: []string{"all"},
  1665  			SnapType:      snap.TypeApp,
  1666  			SideInfo: snap.SideInfo{
  1667  				RealName: "snap-a",
  1668  				Revision: snap.R(8),
  1669  			},
  1670  		}, {
  1671  			Architectures: []string{"all"},
  1672  			SnapType:      snap.TypeBase,
  1673  			SideInfo: snap.SideInfo{
  1674  				RealName: "base-snap-b",
  1675  				Revision: snap.R(3),
  1676  			},
  1677  		}}})
  1678  
  1679  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1680  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1681  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1682  
  1683  	snapstate.MockSnapReadInfo(fakeReadInfo)
  1684  
  1685  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  1686  	defer restore()
  1687  
  1688  	if beforePhase1 != nil {
  1689  		beforePhase1()
  1690  	}
  1691  
  1692  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  1693  	c.Assert(err, IsNil)
  1694  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  1695  
  1696  	chg := s.state.NewChange("refresh", "...")
  1697  	for _, ts := range tss {
  1698  		chg.AddAll(ts)
  1699  	}
  1700  
  1701  	s.state.Unlock()
  1702  	defer s.se.Stop()
  1703  	s.settle(c)
  1704  	s.state.Lock()
  1705  
  1706  	c.Check(chg.Status(), Equals, state.DoneStatus)
  1707  	c.Check(chg.Err(), IsNil)
  1708  
  1709  	verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected)
  1710  
  1711  	return chg
  1712  }
  1713  
  1714  func (s *snapmgrTestSuite) TestAutoRefreshPhase2(c *C) {
  1715  	expected := []string{
  1716  		"conditional-auto-refresh",
  1717  		"run-hook [snap-a;gate-auto-refresh]",
  1718  		// snap-b hook is triggered because of base-snap-b refresh
  1719  		"run-hook [snap-b;gate-auto-refresh]",
  1720  		"prerequisites",
  1721  		"download-snap",
  1722  		"validate-snap",
  1723  		"mount-snap",
  1724  		"run-hook [base-snap-b;pre-refresh]",
  1725  		"stop-snap-services",
  1726  		"remove-aliases",
  1727  		"unlink-current-snap",
  1728  		"copy-snap-data",
  1729  		"setup-profiles",
  1730  		"link-snap",
  1731  		"auto-connect",
  1732  		"set-auto-aliases",
  1733  		"setup-aliases",
  1734  		"run-hook [base-snap-b;post-refresh]",
  1735  		"start-snap-services",
  1736  		"cleanup",
  1737  		"run-hook [base-snap-b;check-health]",
  1738  		"prerequisites",
  1739  		"download-snap",
  1740  		"validate-snap",
  1741  		"mount-snap",
  1742  		"run-hook [snap-a;pre-refresh]",
  1743  		"stop-snap-services",
  1744  		"remove-aliases",
  1745  		"unlink-current-snap",
  1746  		"copy-snap-data",
  1747  		"setup-profiles",
  1748  		"link-snap",
  1749  		"auto-connect",
  1750  		"set-auto-aliases",
  1751  		"setup-aliases",
  1752  		"run-hook [snap-a;post-refresh]",
  1753  		"start-snap-services",
  1754  		"cleanup",
  1755  		"run-hook [snap-a;configure]",
  1756  		"run-hook [snap-a;check-health]",
  1757  		"check-rerefresh",
  1758  	}
  1759  
  1760  	seenSnapsWithGateAutoRefreshHook := make(map[string]bool)
  1761  
  1762  	chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) {
  1763  		seenSnapsWithGateAutoRefreshHook[snapName] = true
  1764  	}, expected)
  1765  
  1766  	c.Check(seenSnapsWithGateAutoRefreshHook, DeepEquals, map[string]bool{
  1767  		"snap-a": true,
  1768  		"snap-b": true,
  1769  	})
  1770  
  1771  	s.state.Lock()
  1772  	defer s.state.Unlock()
  1773  
  1774  	tasks := chg.Tasks()
  1775  	c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "base-snap-b", "snap-a" as needed`)
  1776  
  1777  	// all snaps refreshed, all removed from refresh-candidates.
  1778  	var candidates map[string]*snapstate.RefreshCandidate
  1779  	c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil)
  1780  	c.Assert(candidates, HasLen, 0)
  1781  }
  1782  
  1783  func (s *snapmgrTestSuite) TestAutoRefreshPhase2Held(c *C) {
  1784  	logbuf, restoreLogger := logger.MockLogger()
  1785  	defer restoreLogger()
  1786  
  1787  	expected := []string{
  1788  		"conditional-auto-refresh",
  1789  		"run-hook [snap-a;gate-auto-refresh]",
  1790  		// snap-b hook is triggered because of base-snap-b refresh
  1791  		"run-hook [snap-b;gate-auto-refresh]",
  1792  		"prerequisites",
  1793  		"download-snap",
  1794  		"validate-snap",
  1795  		"mount-snap",
  1796  		"run-hook [snap-a;pre-refresh]",
  1797  		"stop-snap-services",
  1798  		"remove-aliases",
  1799  		"unlink-current-snap",
  1800  		"copy-snap-data",
  1801  		"setup-profiles",
  1802  		"link-snap",
  1803  		"auto-connect",
  1804  		"set-auto-aliases",
  1805  		"setup-aliases",
  1806  		"run-hook [snap-a;post-refresh]",
  1807  		"start-snap-services",
  1808  		"cleanup",
  1809  		"run-hook [snap-a;configure]",
  1810  		"run-hook [snap-a;check-health]",
  1811  		"check-rerefresh",
  1812  	}
  1813  
  1814  	chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) {
  1815  		if snapName == "snap-b" {
  1816  			// pretend than snap-b calls snapctl --hold to hold refresh of base-snap-b
  1817  			c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil)
  1818  		}
  1819  	}, expected)
  1820  
  1821  	s.state.Lock()
  1822  	defer s.state.Unlock()
  1823  
  1824  	c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`)
  1825  	tasks := chg.Tasks()
  1826  	// no re-refresh for base-snap-b because it was held.
  1827  	c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "snap-a" as needed`)
  1828  }
  1829  
  1830  func (s *snapmgrTestSuite) TestAutoRefreshPhase2Proceed(c *C) {
  1831  	logbuf, restoreLogger := logger.MockLogger()
  1832  	defer restoreLogger()
  1833  
  1834  	expected := []string{
  1835  		"conditional-auto-refresh",
  1836  		"run-hook [snap-a;gate-auto-refresh]",
  1837  		// snap-b hook is triggered because of base-snap-b refresh
  1838  		"run-hook [snap-b;gate-auto-refresh]",
  1839  		"prerequisites",
  1840  		"download-snap",
  1841  		"validate-snap",
  1842  		"mount-snap",
  1843  		"run-hook [snap-a;pre-refresh]",
  1844  		"stop-snap-services",
  1845  		"remove-aliases",
  1846  		"unlink-current-snap",
  1847  		"copy-snap-data",
  1848  		"setup-profiles",
  1849  		"link-snap",
  1850  		"auto-connect",
  1851  		"set-auto-aliases",
  1852  		"setup-aliases",
  1853  		"run-hook [snap-a;post-refresh]",
  1854  		"start-snap-services",
  1855  		"cleanup",
  1856  		"run-hook [snap-a;configure]",
  1857  		"run-hook [snap-a;check-health]",
  1858  		"check-rerefresh",
  1859  	}
  1860  
  1861  	s.testAutoRefreshPhase2(c, func() {
  1862  		// pretend that snap-a and base-snap-b are initially held
  1863  		c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil)
  1864  		c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil)
  1865  	}, func(snapName string) {
  1866  		if snapName == "snap-a" {
  1867  			// pretend than snap-a calls snapctl --proceed
  1868  			c.Assert(snapstate.ProceedWithRefresh(s.state, "snap-a"), IsNil)
  1869  		}
  1870  		// note, do nothing about snap-b which just keeps its hold state in
  1871  		// the test, but if we were using real gate-auto-refresh hook
  1872  		// handler, the default behavior for snap-b if it doesn't call --hold
  1873  		// would be to proceed (hook handler would take care of that).
  1874  	}, expected)
  1875  
  1876  	c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`)
  1877  }
  1878  
  1879  func (s *snapmgrTestSuite) TestAutoRefreshPhase2AllHeld(c *C) {
  1880  	logbuf, restoreLogger := logger.MockLogger()
  1881  	defer restoreLogger()
  1882  
  1883  	expected := []string{
  1884  		"conditional-auto-refresh",
  1885  		"run-hook [snap-a;gate-auto-refresh]",
  1886  		// snap-b hook is triggered because of base-snap-b refresh
  1887  		"run-hook [snap-b;gate-auto-refresh]",
  1888  	}
  1889  
  1890  	s.testAutoRefreshPhase2(c, nil, func(snapName string) {
  1891  		switch snapName {
  1892  		case "snap-b":
  1893  			// pretend that snap-b calls snapctl --hold to hold refresh of base-snap-b
  1894  			c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil)
  1895  		case "snap-a":
  1896  			// pretend that snap-a calls snapctl --hold to hold itself
  1897  			c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil)
  1898  		default:
  1899  			c.Fatalf("unexpected snap %q", snapName)
  1900  		}
  1901  	}, expected)
  1902  
  1903  	c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b,snap-a`)
  1904  }
  1905  
  1906  func (s *snapmgrTestSuite) testAutoRefreshPhase2DiskSpaceCheck(c *C, fail bool) {
  1907  	st := s.state
  1908  	st.Lock()
  1909  	defer st.Unlock()
  1910  
  1911  	restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error {
  1912  		c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123))
  1913  		if fail {
  1914  			return &osutil.NotEnoughDiskSpaceError{}
  1915  		}
  1916  		return nil
  1917  	})
  1918  	defer restore()
  1919  
  1920  	var installSizeCalled bool
  1921  	restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) {
  1922  		installSizeCalled = true
  1923  		seen := map[string]bool{}
  1924  		for _, sn := range snaps {
  1925  			seen[sn.InstanceName()] = true
  1926  		}
  1927  		c.Check(seen, DeepEquals, map[string]bool{
  1928  			"base-snap-b": true,
  1929  			"snap-a":      true,
  1930  		})
  1931  		return 123, nil
  1932  	})
  1933  	defer restoreInstallSize()
  1934  
  1935  	restoreModel := snapstatetest.MockDeviceModel(DefaultModel())
  1936  	defer restoreModel()
  1937  
  1938  	tr := config.NewTransaction(s.state)
  1939  	tr.Set("core", "experimental.check-disk-space-refresh", true)
  1940  	tr.Commit()
  1941  
  1942  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  1943  		fakeStore: s.fakeStore,
  1944  		refreshedSnaps: []*snap.Info{{
  1945  			Architectures: []string{"all"},
  1946  			SnapType:      snap.TypeApp,
  1947  			SideInfo: snap.SideInfo{
  1948  				RealName: "snap-a",
  1949  				Revision: snap.R(8),
  1950  			},
  1951  		}, {
  1952  			Architectures: []string{"all"},
  1953  			SnapType:      snap.TypeBase,
  1954  			SideInfo: snap.SideInfo{
  1955  				RealName: "base-snap-b",
  1956  				Revision: snap.R(3),
  1957  			},
  1958  		}}})
  1959  
  1960  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  1961  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  1962  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  1963  
  1964  	snapstate.MockSnapReadInfo(fakeReadInfo)
  1965  
  1966  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  1967  	c.Assert(err, IsNil)
  1968  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  1969  
  1970  	chg := s.state.NewChange("refresh", "...")
  1971  	for _, ts := range tss {
  1972  		chg.AddAll(ts)
  1973  	}
  1974  
  1975  	s.state.Unlock()
  1976  	defer s.se.Stop()
  1977  	s.settle(c)
  1978  	s.state.Lock()
  1979  
  1980  	c.Check(installSizeCalled, Equals, true)
  1981  	if fail {
  1982  		c.Check(chg.Status(), Equals, state.ErrorStatus)
  1983  		c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- Run auto-refresh for ready snaps \(insufficient space.*`)
  1984  	} else {
  1985  		c.Check(chg.Status(), Equals, state.DoneStatus)
  1986  		c.Check(chg.Err(), IsNil)
  1987  	}
  1988  }
  1989  
  1990  func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceError(c *C) {
  1991  	fail := true
  1992  	s.testAutoRefreshPhase2DiskSpaceCheck(c, fail)
  1993  }
  1994  
  1995  func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceHappy(c *C) {
  1996  	var nofail bool
  1997  	s.testAutoRefreshPhase2DiskSpaceCheck(c, nofail)
  1998  }
  1999  
  2000  // XXX: this case is probably artificial; with proper conflict prevention
  2001  // we shouldn't get conflicts from doInstall in phase2.
  2002  func (s *snapmgrTestSuite) TestAutoRefreshPhase2Conflict(c *C) {
  2003  	st := s.state
  2004  	st.Lock()
  2005  	defer st.Unlock()
  2006  
  2007  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  2008  		fakeStore: s.fakeStore,
  2009  		refreshedSnaps: []*snap.Info{{
  2010  			Architectures: []string{"all"},
  2011  			SnapType:      snap.TypeApp,
  2012  			SideInfo: snap.SideInfo{
  2013  				RealName: "snap-a",
  2014  				Revision: snap.R(8),
  2015  			},
  2016  		}, {
  2017  			Architectures: []string{"all"},
  2018  			SnapType:      snap.TypeBase,
  2019  			SideInfo: snap.SideInfo{
  2020  				RealName: "base-snap-b",
  2021  				Revision: snap.R(3),
  2022  			},
  2023  		}}})
  2024  
  2025  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  2026  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  2027  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  2028  
  2029  	snapstate.MockSnapReadInfo(fakeReadInfo)
  2030  
  2031  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  2032  	defer restore()
  2033  
  2034  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  2035  	c.Assert(err, IsNil)
  2036  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  2037  
  2038  	chg := s.state.NewChange("refresh", "...")
  2039  	for _, ts := range tss {
  2040  		chg.AddAll(ts)
  2041  	}
  2042  
  2043  	conflictChange := st.NewChange("conflicting change", "")
  2044  	conflictTask := st.NewTask("conflicting task", "")
  2045  	si := &snap.SideInfo{
  2046  		RealName: "snap-a",
  2047  		Revision: snap.R(1),
  2048  	}
  2049  	sup := snapstate.SnapSetup{SideInfo: si}
  2050  	conflictTask.Set("snap-setup", sup)
  2051  	conflictChange.AddTask(conflictTask)
  2052  	conflictTask.WaitFor(tss[0].Tasks()[0])
  2053  
  2054  	s.state.Unlock()
  2055  	defer s.se.Stop()
  2056  	s.settle(c)
  2057  	s.state.Lock()
  2058  
  2059  	c.Assert(chg.Status(), Equals, state.DoneStatus)
  2060  	c.Check(chg.Err(), IsNil)
  2061  
  2062  	// no refresh of snap-a because of the conflict.
  2063  	expected := []string{
  2064  		"conditional-auto-refresh",
  2065  		"run-hook [snap-a;gate-auto-refresh]",
  2066  		// snap-b hook is triggered because of base-snap-b refresh
  2067  		"run-hook [snap-b;gate-auto-refresh]",
  2068  		"prerequisites",
  2069  		"download-snap",
  2070  		"validate-snap",
  2071  		"mount-snap",
  2072  		"run-hook [base-snap-b;pre-refresh]",
  2073  		"stop-snap-services",
  2074  		"remove-aliases",
  2075  		"unlink-current-snap",
  2076  		"copy-snap-data",
  2077  		"setup-profiles",
  2078  		"link-snap",
  2079  		"auto-connect",
  2080  		"set-auto-aliases",
  2081  		"setup-aliases",
  2082  		"run-hook [base-snap-b;post-refresh]",
  2083  		"start-snap-services",
  2084  		"cleanup",
  2085  		"run-hook [base-snap-b;check-health]",
  2086  		"check-rerefresh",
  2087  	}
  2088  	verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected)
  2089  }
  2090  
  2091  func (s *snapmgrTestSuite) TestAutoRefreshPhase2ConflictOtherSnapOp(c *C) {
  2092  	st := s.state
  2093  	st.Lock()
  2094  	defer st.Unlock()
  2095  
  2096  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  2097  		fakeStore: s.fakeStore,
  2098  		refreshedSnaps: []*snap.Info{{
  2099  			Architectures: []string{"all"},
  2100  			SnapType:      snap.TypeApp,
  2101  			SideInfo: snap.SideInfo{
  2102  				RealName: "snap-a",
  2103  				Revision: snap.R(8),
  2104  			},
  2105  		}}})
  2106  
  2107  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  2108  
  2109  	snapstate.MockSnapReadInfo(fakeReadInfo)
  2110  
  2111  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  2112  	defer restore()
  2113  
  2114  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  2115  	c.Assert(err, IsNil)
  2116  	c.Check(names, DeepEquals, []string{"snap-a"})
  2117  
  2118  	chg := s.state.NewChange("fake-auto-refresh", "...")
  2119  	for _, ts := range tss {
  2120  		chg.AddAll(ts)
  2121  	}
  2122  
  2123  	s.state.Unlock()
  2124  	// run first task
  2125  	s.se.Ensure()
  2126  	s.se.Wait()
  2127  
  2128  	s.state.Lock()
  2129  
  2130  	_, err = snapstate.Remove(s.state, "snap-a", snap.R(8), nil)
  2131  	c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{
  2132  		ChangeKind: "fake-auto-refresh",
  2133  		Snap:       "snap-a",
  2134  	})
  2135  
  2136  	_, err = snapstate.Update(s.state, "snap-a", nil, 0, snapstate.Flags{})
  2137  	c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{
  2138  		ChangeKind: "fake-auto-refresh",
  2139  		Snap:       "snap-a",
  2140  	})
  2141  
  2142  	// only 2 tasks because we don't run settle() so conditional-auto-refresh
  2143  	// doesn't run and no new tasks get created.
  2144  	expected := []string{
  2145  		"conditional-auto-refresh",
  2146  		"run-hook [snap-a;gate-auto-refresh]",
  2147  	}
  2148  	verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected)
  2149  }
  2150  
  2151  func (s *snapmgrTestSuite) TestAutoRefreshPhase2GatedSnaps(c *C) {
  2152  	st := s.state
  2153  	st.Lock()
  2154  	defer st.Unlock()
  2155  
  2156  	restore := snapstate.MockSnapsToRefresh(func(gatingTask *state.Task) ([]*snapstate.RefreshCandidate, error) {
  2157  		c.Assert(gatingTask.Kind(), Equals, "conditional-auto-refresh")
  2158  		var candidates map[string]*snapstate.RefreshCandidate
  2159  		c.Assert(gatingTask.Get("snaps", &candidates), IsNil)
  2160  		seenSnaps := make(map[string]bool)
  2161  		var filteredByGatingHooks []*snapstate.RefreshCandidate
  2162  		for _, cand := range candidates {
  2163  			seenSnaps[cand.InstanceName()] = true
  2164  			if cand.InstanceName() == "snap-a" {
  2165  				continue
  2166  			}
  2167  			filteredByGatingHooks = append(filteredByGatingHooks, cand)
  2168  		}
  2169  		c.Check(seenSnaps, DeepEquals, map[string]bool{
  2170  			"snap-a":      true,
  2171  			"base-snap-b": true,
  2172  		})
  2173  		return filteredByGatingHooks, nil
  2174  	})
  2175  	defer restore()
  2176  
  2177  	snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{
  2178  		fakeStore: s.fakeStore,
  2179  		refreshedSnaps: []*snap.Info{
  2180  			{
  2181  				Architectures: []string{"all"},
  2182  				SnapType:      snap.TypeApp,
  2183  				SideInfo: snap.SideInfo{
  2184  					RealName: "snap-a",
  2185  					Revision: snap.R(8),
  2186  				},
  2187  			}, {
  2188  				Architectures: []string{"all"},
  2189  				SnapType:      snap.TypeBase,
  2190  				SideInfo: snap.SideInfo{
  2191  					RealName: "base-snap-b",
  2192  					Revision: snap.R(3),
  2193  				},
  2194  			},
  2195  		}})
  2196  
  2197  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  2198  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  2199  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  2200  
  2201  	snapstate.MockSnapReadInfo(fakeReadInfo)
  2202  
  2203  	restoreModel := snapstatetest.MockDeviceModel(DefaultModel())
  2204  	defer restoreModel()
  2205  
  2206  	names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "")
  2207  	c.Assert(err, IsNil)
  2208  	c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"})
  2209  
  2210  	chg := s.state.NewChange("refresh", "...")
  2211  	for _, ts := range tss {
  2212  		chg.AddAll(ts)
  2213  	}
  2214  
  2215  	s.state.Unlock()
  2216  	defer s.se.Stop()
  2217  	s.settle(c)
  2218  	s.state.Lock()
  2219  
  2220  	c.Assert(chg.Status(), Equals, state.DoneStatus)
  2221  	c.Check(chg.Err(), IsNil)
  2222  
  2223  	expected := []string{
  2224  		"conditional-auto-refresh",
  2225  		"run-hook [snap-a;gate-auto-refresh]",
  2226  		// snap-b hook is triggered because of base-snap-b refresh
  2227  		"run-hook [snap-b;gate-auto-refresh]",
  2228  		"prerequisites",
  2229  		"download-snap",
  2230  		"validate-snap",
  2231  		"mount-snap",
  2232  		"run-hook [base-snap-b;pre-refresh]",
  2233  		"stop-snap-services",
  2234  		"remove-aliases",
  2235  		"unlink-current-snap",
  2236  		"copy-snap-data",
  2237  		"setup-profiles",
  2238  		"link-snap",
  2239  		"auto-connect",
  2240  		"set-auto-aliases",
  2241  		"setup-aliases",
  2242  		"run-hook [base-snap-b;post-refresh]",
  2243  		"start-snap-services",
  2244  		"cleanup",
  2245  		"run-hook [base-snap-b;check-health]",
  2246  		"check-rerefresh",
  2247  	}
  2248  	tasks := chg.Tasks()
  2249  	verifyPhasedAutorefreshTasks(c, tasks, expected)
  2250  	// no re-refresh for snap-a because it was held.
  2251  	c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "base-snap-b" as needed`)
  2252  
  2253  	// only snap-a remains in refresh-candidates because it was held;
  2254  	// base-snap-b got pruned (was refreshed).
  2255  	var candidates map[string]*snapstate.RefreshCandidate
  2256  	c.Assert(st.Get("refresh-candidates", &candidates), IsNil)
  2257  	c.Assert(candidates, HasLen, 1)
  2258  	c.Check(candidates["snap-a"], NotNil)
  2259  }
  2260  
  2261  func (s *snapmgrTestSuite) TestAutoRefreshForGatingSnapErrorAutoRefreshInProgress(c *C) {
  2262  	st := s.state
  2263  	st.Lock()
  2264  	defer st.Unlock()
  2265  
  2266  	chg := st.NewChange("auto-refresh", "...")
  2267  	task := st.NewTask("foo", "...")
  2268  	chg.AddTask(task)
  2269  
  2270  	c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-a"), ErrorMatches, `there is an auto-refresh in progress`)
  2271  }
  2272  
  2273  func (s *snapmgrTestSuite) TestAutoRefreshForGatingSnapErrorNothingHeld(c *C) {
  2274  	st := s.state
  2275  	st.Lock()
  2276  	defer st.Unlock()
  2277  
  2278  	c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-a"), ErrorMatches, `no snaps are held by snap "snap-a"`)
  2279  }
  2280  
  2281  func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnap(c *C) {
  2282  	s.store.refreshedSnaps = []*snap.Info{{
  2283  		Architectures: []string{"all"},
  2284  		SnapType:      snap.TypeApp,
  2285  		SideInfo: snap.SideInfo{
  2286  			RealName: "snap-a",
  2287  			Revision: snap.R(8),
  2288  		},
  2289  	}, {
  2290  		Architectures: []string{"all"},
  2291  		SnapType:      snap.TypeApp,
  2292  		Base:          "base-snap-b",
  2293  		SideInfo: snap.SideInfo{
  2294  			RealName: "snap-b",
  2295  			Revision: snap.R(2),
  2296  		},
  2297  	}, {
  2298  		Architectures: []string{"all"},
  2299  		SnapType:      snap.TypeBase,
  2300  		SideInfo: snap.SideInfo{
  2301  			RealName: "base-snap-b",
  2302  			Revision: snap.R(3),
  2303  		},
  2304  	}}
  2305  
  2306  	st := s.state
  2307  	st.Lock()
  2308  	defer st.Unlock()
  2309  
  2310  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  2311  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  2312  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  2313  
  2314  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  2315  	defer restore()
  2316  
  2317  	// pretend some snaps are held
  2318  	c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil)
  2319  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil)
  2320  
  2321  	lastRefreshTime := time.Now().Add(-99 * time.Hour)
  2322  	st.Set("last-refresh", lastRefreshTime)
  2323  
  2324  	// pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed)
  2325  	c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil)
  2326  
  2327  	changes := st.Changes()
  2328  	c.Assert(changes, HasLen, 1)
  2329  	chg := changes[0]
  2330  	c.Assert(chg.Kind(), Equals, "auto-refresh")
  2331  	c.Check(chg.Summary(), Equals, `Auto-refresh snaps "base-snap-b", "snap-b"`)
  2332  	var snapNames []string
  2333  	var apiData map[string]interface{}
  2334  	c.Assert(chg.Get("snap-names", &snapNames), IsNil)
  2335  	c.Check(snapNames, DeepEquals, []string{"base-snap-b", "snap-b"})
  2336  	c.Assert(chg.Get("api-data", &apiData), IsNil)
  2337  	c.Check(apiData, DeepEquals, map[string]interface{}{
  2338  		"snap-names": []interface{}{"base-snap-b", "snap-b"},
  2339  	})
  2340  
  2341  	tasks := chg.Tasks()
  2342  	c.Assert(tasks, HasLen, 2)
  2343  	conditionalRefreshTask := tasks[0]
  2344  	checkGatingTask(c, conditionalRefreshTask, map[string]*snapstate.RefreshCandidate{
  2345  		"base-snap-b": {
  2346  			SnapSetup: snapstate.SnapSetup{
  2347  				Type:      "base",
  2348  				PlugsOnly: true,
  2349  				Flags: snapstate.Flags{
  2350  					IsAutoRefresh: true,
  2351  				},
  2352  				SideInfo: &snap.SideInfo{
  2353  					RealName: "base-snap-b",
  2354  					Revision: snap.R(3),
  2355  				},
  2356  				DownloadInfo: &snap.DownloadInfo{},
  2357  			},
  2358  		},
  2359  		"snap-b": {
  2360  			SnapSetup: snapstate.SnapSetup{
  2361  				Type:      "app",
  2362  				Base:      "base-snap-b",
  2363  				PlugsOnly: true,
  2364  				Flags: snapstate.Flags{
  2365  					IsAutoRefresh: true,
  2366  				},
  2367  				SideInfo: &snap.SideInfo{
  2368  					RealName: "snap-b",
  2369  					Revision: snap.R(2),
  2370  				},
  2371  				DownloadInfo: &snap.DownloadInfo{},
  2372  			},
  2373  		},
  2374  	})
  2375  
  2376  	// the gate-auto-refresh hook task for snap-b is present
  2377  	c.Check(tasks[1].Kind(), Equals, "run-hook")
  2378  	var hs hookstate.HookSetup
  2379  	c.Assert(tasks[1].Get("hook-setup", &hs), IsNil)
  2380  	c.Check(hs.Hook, Equals, "gate-auto-refresh")
  2381  	c.Check(hs.Snap, Equals, "snap-b")
  2382  	c.Check(hs.Optional, Equals, true)
  2383  
  2384  	var data interface{}
  2385  	c.Assert(tasks[1].Get("hook-context", &data), IsNil)
  2386  	c.Check(data, DeepEquals, map[string]interface{}{
  2387  		"base":            true,
  2388  		"restart":         false,
  2389  		"affecting-snaps": []interface{}{"base-snap-b", "snap-b"},
  2390  	})
  2391  
  2392  	// last-refresh wasn't modified
  2393  	var lr time.Time
  2394  	st.Get("last-refresh", &lr)
  2395  	c.Check(lr.Equal(lastRefreshTime), Equals, true)
  2396  }
  2397  
  2398  func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapMoreAffectedSnaps(c *C) {
  2399  	s.store.refreshedSnaps = []*snap.Info{{
  2400  		Architectures: []string{"all"},
  2401  		SnapType:      snap.TypeApp,
  2402  		SideInfo: snap.SideInfo{
  2403  			RealName: "snap-a",
  2404  			Revision: snap.R(8),
  2405  		},
  2406  	}, {
  2407  		Architectures: []string{"all"},
  2408  		SnapType:      snap.TypeBase,
  2409  		SideInfo: snap.SideInfo{
  2410  			RealName: "base-snap-b",
  2411  			Revision: snap.R(3),
  2412  		},
  2413  	}, {
  2414  		Architectures: []string{"all"},
  2415  		SnapType:      snap.TypeApp,
  2416  		Base:          "base-snap-b",
  2417  		SideInfo: snap.SideInfo{
  2418  			RealName: "snap-b",
  2419  			Revision: snap.R(2),
  2420  		},
  2421  	}}
  2422  
  2423  	st := s.state
  2424  	st.Lock()
  2425  	defer st.Unlock()
  2426  
  2427  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  2428  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  2429  	mockInstalledSnap(c, s.state, snapBByaml, useHook)
  2430  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  2431  
  2432  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  2433  	defer restore()
  2434  
  2435  	// pretend snap-b holds base-snap-b.
  2436  	c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil)
  2437  
  2438  	// pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed)
  2439  	c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil)
  2440  
  2441  	changes := st.Changes()
  2442  	c.Assert(changes, HasLen, 1)
  2443  	chg := changes[0]
  2444  	c.Assert(chg.Kind(), Equals, "auto-refresh")
  2445  	c.Check(chg.Summary(), Equals, `Auto-refresh snaps "base-snap-b", "snap-b"`)
  2446  	var snapNames []string
  2447  	var apiData map[string]interface{}
  2448  	c.Assert(chg.Get("snap-names", &snapNames), IsNil)
  2449  	c.Check(snapNames, DeepEquals, []string{"base-snap-b", "snap-b"})
  2450  	c.Assert(chg.Get("api-data", &apiData), IsNil)
  2451  	c.Check(apiData, DeepEquals, map[string]interface{}{
  2452  		"snap-names": []interface{}{"base-snap-b", "snap-b"},
  2453  	})
  2454  
  2455  	tasks := chg.Tasks()
  2456  	c.Assert(tasks, HasLen, 3)
  2457  	conditionalRefreshTask := tasks[0]
  2458  	checkGatingTask(c, conditionalRefreshTask, map[string]*snapstate.RefreshCandidate{
  2459  		"base-snap-b": {
  2460  			SnapSetup: snapstate.SnapSetup{
  2461  				Type:      "base",
  2462  				PlugsOnly: true,
  2463  				Flags: snapstate.Flags{
  2464  					IsAutoRefresh: true,
  2465  				},
  2466  				SideInfo: &snap.SideInfo{
  2467  					RealName: "base-snap-b",
  2468  					Revision: snap.R(3),
  2469  				},
  2470  				DownloadInfo: &snap.DownloadInfo{},
  2471  			},
  2472  		},
  2473  		"snap-b": {
  2474  			SnapSetup: snapstate.SnapSetup{
  2475  				Type:      "app",
  2476  				Base:      "base-snap-b",
  2477  				PlugsOnly: true,
  2478  				Flags: snapstate.Flags{
  2479  					IsAutoRefresh: true,
  2480  				},
  2481  				SideInfo: &snap.SideInfo{
  2482  					RealName: "snap-b",
  2483  					Revision: snap.R(2),
  2484  				},
  2485  				DownloadInfo: &snap.DownloadInfo{},
  2486  			},
  2487  		},
  2488  	})
  2489  
  2490  	seenSnaps := make(map[string]bool)
  2491  
  2492  	// check that the gate-auto-refresh hooks are run.
  2493  	// snap-bb's hook is triggered because it is affected by base-snap-b refresh
  2494  	// (and intersects with affecting snap of snap-b). Note, snap-a is not here
  2495  	// because it is not affected by snaps affecting snap-b.
  2496  	for i := 1; i <= 2; i++ {
  2497  		c.Assert(tasks[i].Kind(), Equals, "run-hook")
  2498  		var hs hookstate.HookSetup
  2499  		c.Assert(tasks[i].Get("hook-setup", &hs), IsNil)
  2500  		c.Check(hs.Hook, Equals, "gate-auto-refresh")
  2501  		c.Check(hs.Optional, Equals, true)
  2502  		seenSnaps[hs.Snap] = true
  2503  		var data interface{}
  2504  		c.Assert(tasks[i].Get("hook-context", &data), IsNil)
  2505  		switch hs.Snap {
  2506  		case "snap-b":
  2507  			c.Check(data, DeepEquals, map[string]interface{}{
  2508  				"base":            true,
  2509  				"restart":         false,
  2510  				"affecting-snaps": []interface{}{"base-snap-b", "snap-b"},
  2511  			})
  2512  		case "snap-bb":
  2513  			c.Check(data, DeepEquals, map[string]interface{}{
  2514  				"base":            true,
  2515  				"restart":         false,
  2516  				"affecting-snaps": []interface{}{"base-snap-b"},
  2517  			})
  2518  		default:
  2519  			c.Fatalf("unexpected snap %q", hs.Snap)
  2520  		}
  2521  	}
  2522  	c.Check(seenSnaps, DeepEquals, map[string]bool{
  2523  		"snap-b":  true,
  2524  		"snap-bb": true,
  2525  	})
  2526  }
  2527  
  2528  func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapNoCandidatesAnymore(c *C) {
  2529  	// only snap-a will have a refresh available
  2530  	s.store.refreshedSnaps = []*snap.Info{{
  2531  		Architectures: []string{"all"},
  2532  		SnapType:      snap.TypeApp,
  2533  		SideInfo: snap.SideInfo{
  2534  			RealName: "snap-a",
  2535  			Revision: snap.R(8),
  2536  		},
  2537  	}}
  2538  
  2539  	logbuf, restoreLogger := logger.MockLogger()
  2540  	defer restoreLogger()
  2541  
  2542  	st := s.state
  2543  	st.Lock()
  2544  	defer st.Unlock()
  2545  
  2546  	mockInstalledSnap(c, s.state, snapAyaml, useHook)
  2547  	mockInstalledSnap(c, s.state, snapByaml, useHook)
  2548  	mockInstalledSnap(c, s.state, baseSnapByaml, noHook)
  2549  
  2550  	restore := snapstatetest.MockDeviceModel(DefaultModel())
  2551  	defer restore()
  2552  
  2553  	// pretend some snaps are held
  2554  	c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil)
  2555  	c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil)
  2556  
  2557  	// pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed)
  2558  	c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil)
  2559  	c.Assert(st.Changes(), HasLen, 0)
  2560  
  2561  	// but base-snap-b has no update anymore.
  2562  	c.Check(logbuf.String(), testutil.Contains, `auto-refresh: all snaps previously held by "snap-b" are up-to-date`)
  2563  }
  2564  
  2565  func verifyPhasedAutorefreshTasks(c *C, tasks []*state.Task, expected []string) {
  2566  	c.Assert(len(tasks), Equals, len(expected))
  2567  	for i, t := range tasks {
  2568  		var got string
  2569  		if t.Kind() == "run-hook" {
  2570  			var hsup hookstate.HookSetup
  2571  			c.Assert(t.Get("hook-setup", &hsup), IsNil)
  2572  			got = fmt.Sprintf("%s [%s;%s]", t.Kind(), hsup.Snap, hsup.Hook)
  2573  		} else {
  2574  			got = t.Kind()
  2575  		}
  2576  		c.Assert(got, Equals, expected[i], Commentf("#%d", i))
  2577  	}
  2578  }