github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/overlord/snapstate/refreshhints_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017-2018 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package snapstate_test
    21  
    22  import (
    23  	"context"
    24  	"time"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/dirs"
    29  	"github.com/snapcore/snapd/interfaces"
    30  	"github.com/snapcore/snapd/interfaces/builtin"
    31  	"github.com/snapcore/snapd/overlord/auth"
    32  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    33  	"github.com/snapcore/snapd/overlord/snapstate"
    34  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    35  	"github.com/snapcore/snapd/overlord/state"
    36  	"github.com/snapcore/snapd/release"
    37  	"github.com/snapcore/snapd/snap"
    38  	"github.com/snapcore/snapd/snap/snaptest"
    39  	"github.com/snapcore/snapd/store"
    40  	"github.com/snapcore/snapd/store/storetest"
    41  )
    42  
    43  type recordingStore struct {
    44  	storetest.Store
    45  
    46  	ops            []string
    47  	refreshedSnaps []*snap.Info
    48  }
    49  
    50  func (r *recordingStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) {
    51  	if assertQuery != nil {
    52  		panic("no assertion query support")
    53  	}
    54  	if ctx == nil || !auth.IsEnsureContext(ctx) {
    55  		panic("Ensure marked context required")
    56  	}
    57  	if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 {
    58  		panic("expected in test one action for each current snaps, and at least one snap")
    59  	}
    60  	for _, a := range actions {
    61  		if a.Action != "refresh" {
    62  			panic("expected refresh actions")
    63  		}
    64  	}
    65  	r.ops = append(r.ops, "list-refresh")
    66  
    67  	res := []store.SnapActionResult{}
    68  	for _, rs := range r.refreshedSnaps {
    69  		res = append(res, store.SnapActionResult{Info: rs})
    70  	}
    71  	return res, nil, nil
    72  }
    73  
    74  type refreshHintsTestSuite struct {
    75  	state *state.State
    76  
    77  	store        *recordingStore
    78  	restoreModel func()
    79  }
    80  
    81  var _ = Suite(&refreshHintsTestSuite{})
    82  
    83  func (s *refreshHintsTestSuite) SetUpTest(c *C) {
    84  	dirs.SetRootDir(c.MkDir())
    85  
    86  	s.state = state.New(nil)
    87  
    88  	s.store = &recordingStore{}
    89  	s.state.Lock()
    90  	defer s.state.Unlock()
    91  	snapstate.ReplaceStore(s.state, s.store)
    92  
    93  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
    94  		Active: true,
    95  		Sequence: []*snap.SideInfo{
    96  			{RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"},
    97  		},
    98  		Current:         snap.R(5),
    99  		SnapType:        "app",
   100  		UserID:          1,
   101  		CohortKey:       "cohort",
   102  		TrackingChannel: "stable",
   103  	})
   104  
   105  	snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil }
   106  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
   107  		return nil, nil
   108  	}
   109  
   110  	s.state.Set("refresh-privacy-key", "privacy-key")
   111  
   112  	s.restoreModel = snapstatetest.MockDeviceModel(DefaultModel())
   113  }
   114  
   115  func (s *refreshHintsTestSuite) TearDownTest(c *C) {
   116  	dirs.SetRootDir("/")
   117  	snapstate.CanAutoRefresh = nil
   118  	snapstate.AutoAliases = nil
   119  	s.restoreModel()
   120  }
   121  
   122  func (s *refreshHintsTestSuite) TestLastRefresh(c *C) {
   123  	rh := snapstate.NewRefreshHints(s.state)
   124  	err := rh.Ensure()
   125  	c.Check(err, IsNil)
   126  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   127  }
   128  
   129  func (s *refreshHintsTestSuite) TestLastRefreshNoRefreshNeeded(c *C) {
   130  	s.state.Lock()
   131  	s.state.Set("last-refresh-hints", time.Now().Add(-23*time.Hour))
   132  	s.state.Unlock()
   133  
   134  	rh := snapstate.NewRefreshHints(s.state)
   135  	err := rh.Ensure()
   136  	c.Check(err, IsNil)
   137  	c.Check(s.store.ops, HasLen, 0)
   138  }
   139  
   140  func (s *refreshHintsTestSuite) TestLastRefreshNoRefreshNeededBecauseOfFullAutoRefresh(c *C) {
   141  	s.state.Lock()
   142  	s.state.Set("last-refresh-hints", time.Now().Add(-48*time.Hour))
   143  	s.state.Unlock()
   144  
   145  	s.state.Lock()
   146  	s.state.Set("last-refresh", time.Now().Add(-23*time.Hour))
   147  	s.state.Unlock()
   148  
   149  	rh := snapstate.NewRefreshHints(s.state)
   150  	err := rh.Ensure()
   151  	c.Check(err, IsNil)
   152  	c.Check(s.store.ops, HasLen, 0)
   153  }
   154  
   155  func (s *refreshHintsTestSuite) TestAtSeedPolicy(c *C) {
   156  	r := release.MockOnClassic(false)
   157  	defer r()
   158  
   159  	s.state.Lock()
   160  	defer s.state.Unlock()
   161  
   162  	rh := snapstate.NewRefreshHints(s.state)
   163  
   164  	// on core, does nothing
   165  	err := rh.AtSeed()
   166  	c.Assert(err, IsNil)
   167  	var t1 time.Time
   168  	err = s.state.Get("last-refresh-hints", &t1)
   169  	c.Check(err, Equals, state.ErrNoState)
   170  
   171  	release.MockOnClassic(true)
   172  	// on classic it sets last-refresh-hints to now,
   173  	// postponing it of 24h
   174  	err = rh.AtSeed()
   175  	c.Assert(err, IsNil)
   176  	err = s.state.Get("last-refresh-hints", &t1)
   177  	c.Check(err, IsNil)
   178  
   179  	// nop if tried again
   180  	err = rh.AtSeed()
   181  	c.Assert(err, IsNil)
   182  	var t2 time.Time
   183  	err = s.state.Get("last-refresh-hints", &t2)
   184  	c.Check(err, IsNil)
   185  	c.Check(t1.Equal(t2), Equals, true)
   186  }
   187  
   188  func (s *refreshHintsTestSuite) TestRefreshHintsStoresRefreshCandidates(c *C) {
   189  	s.state.Lock()
   190  	repo := interfaces.NewRepository()
   191  	for _, iface := range builtin.Interfaces() {
   192  		err := repo.AddInterface(iface)
   193  		c.Assert(err, IsNil)
   194  	}
   195  	ifacerepo.Replace(s.state, repo)
   196  
   197  	snapstate.Set(s.state, "other-snap", &snapstate.SnapState{
   198  		Active: true,
   199  		Sequence: []*snap.SideInfo{
   200  			{RealName: "other-snap", Revision: snap.R(1), SnapID: "other-snap-id"},
   201  		},
   202  		Current:         snap.R(1),
   203  		SnapType:        "app",
   204  		TrackingChannel: "devel",
   205  		UserID:          0,
   206  	})
   207  	s.state.Unlock()
   208  
   209  	info2 := &snap.Info{
   210  		Version:       "v1",
   211  		Architectures: []string{"all"},
   212  		SnapType:      snap.TypeApp,
   213  		SideInfo: snap.SideInfo{
   214  			RealName: "other-snap",
   215  			Revision: snap.R(2),
   216  		},
   217  		DownloadInfo: snap.DownloadInfo{
   218  			Size: int64(88),
   219  		},
   220  	}
   221  	plugs := map[string]*snap.PlugInfo{
   222  		"plug": {
   223  			Snap:      info2,
   224  			Name:      "plug",
   225  			Interface: "content",
   226  			Attrs: map[string]interface{}{
   227  				"default-provider": "foo-snap:",
   228  				"content":          "some-content",
   229  			},
   230  			Apps:  map[string]*snap.AppInfo{},
   231  			Hooks: map[string]*snap.HookInfo{},
   232  		}}
   233  	info2.Plugs = plugs
   234  
   235  	s.store.refreshedSnaps = []*snap.Info{{
   236  		Version:       "2",
   237  		Architectures: []string{"all"},
   238  		Base:          "some-base",
   239  		SnapType:      snap.TypeApp,
   240  		SideInfo: snap.SideInfo{
   241  			RealName: "some-snap",
   242  			Revision: snap.R(1),
   243  		},
   244  		DownloadInfo: snap.DownloadInfo{
   245  			Size: int64(99),
   246  		},
   247  	}, info2}
   248  
   249  	rh := snapstate.NewRefreshHints(s.state)
   250  	err := rh.Ensure()
   251  	c.Check(err, IsNil)
   252  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   253  
   254  	s.state.Lock()
   255  	defer s.state.Unlock()
   256  
   257  	var candidates map[string]*snapstate.RefreshCandidate
   258  	c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil)
   259  	c.Assert(candidates, HasLen, 2)
   260  	cand1 := candidates["some-snap"]
   261  	c.Assert(cand1, NotNil)
   262  	c.Check(cand1.InstanceName(), Equals, "some-snap")
   263  	c.Check(cand1.SnapBase(), Equals, "some-base")
   264  	c.Check(cand1.Type(), Equals, snap.TypeApp)
   265  	c.Check(cand1.DownloadSize(), Equals, int64(99))
   266  	c.Check(cand1.Version, Equals, "2")
   267  
   268  	cand2 := candidates["other-snap"]
   269  	c.Assert(cand2, NotNil)
   270  	c.Check(cand2.InstanceName(), Equals, "other-snap")
   271  	c.Check(cand2.SnapBase(), Equals, "")
   272  	c.Check(cand2.Type(), Equals, snap.TypeApp)
   273  	c.Check(cand2.DownloadSize(), Equals, int64(88))
   274  	c.Check(cand2.Version, Equals, "v1")
   275  
   276  	var snapst1 snapstate.SnapState
   277  	err = snapstate.Get(s.state, "some-snap", &snapst1)
   278  	c.Assert(err, IsNil)
   279  
   280  	sup, snapst, err := cand1.SnapSetupForUpdate(s.state, nil, 0, nil)
   281  	c.Assert(err, IsNil)
   282  	c.Check(sup, DeepEquals, &snapstate.SnapSetup{
   283  		Base: "some-base",
   284  		Type: "app",
   285  		SideInfo: &snap.SideInfo{
   286  			RealName: "some-snap",
   287  			Revision: snap.R(1),
   288  		},
   289  		PlugsOnly: true,
   290  		CohortKey: "cohort",
   291  		Channel:   "stable",
   292  		Flags: snapstate.Flags{
   293  			IsAutoRefresh: true,
   294  		},
   295  		DownloadInfo: &snap.DownloadInfo{
   296  			Size: int64(99),
   297  		},
   298  	})
   299  	c.Check(snapst, DeepEquals, &snapst1)
   300  
   301  	var snapst2 snapstate.SnapState
   302  	err = snapstate.Get(s.state, "other-snap", &snapst2)
   303  	c.Assert(err, IsNil)
   304  
   305  	sup, snapst, err = cand2.SnapSetupForUpdate(s.state, nil, 0, nil)
   306  	c.Assert(err, IsNil)
   307  	c.Check(sup, DeepEquals, &snapstate.SnapSetup{
   308  		Type: "app",
   309  		SideInfo: &snap.SideInfo{
   310  			RealName: "other-snap",
   311  			Revision: snap.R(2),
   312  		},
   313  		Prereq:    []string{"foo-snap"},
   314  		PlugsOnly: true,
   315  		Channel:   "devel",
   316  		Flags: snapstate.Flags{
   317  			IsAutoRefresh: true,
   318  		},
   319  		DownloadInfo: &snap.DownloadInfo{
   320  			Size: int64(88),
   321  		},
   322  	})
   323  	c.Check(snapst, DeepEquals, &snapst2)
   324  }
   325  
   326  func (s *refreshHintsTestSuite) TestRefreshHintsNotApplicableWrongArch(c *C) {
   327  	s.state.Lock()
   328  	snapstate.Set(s.state, "other-snap", &snapstate.SnapState{
   329  		Active: true,
   330  		Sequence: []*snap.SideInfo{
   331  			{RealName: "other-snap", Revision: snap.R(1), SnapID: "other-snap-id"},
   332  		},
   333  		Current:  snap.R(1),
   334  		SnapType: "app",
   335  	})
   336  	s.state.Unlock()
   337  
   338  	s.store.refreshedSnaps = []*snap.Info{{
   339  		Architectures: []string{"all"},
   340  		SnapType:      snap.TypeApp,
   341  		SideInfo: snap.SideInfo{
   342  			RealName: "some-snap",
   343  			Revision: snap.R(1),
   344  		},
   345  	}, {
   346  		Architectures: []string{"somearch"},
   347  		SnapType:      snap.TypeApp,
   348  		SideInfo: snap.SideInfo{
   349  			RealName: "other-snap",
   350  			Revision: snap.R(2),
   351  		},
   352  	}}
   353  
   354  	rh := snapstate.NewRefreshHints(s.state)
   355  	c.Assert(rh.Ensure(), IsNil)
   356  
   357  	s.state.Lock()
   358  	defer s.state.Unlock()
   359  
   360  	var candidates map[string]*snapstate.RefreshCandidate
   361  	c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil)
   362  	c.Assert(candidates, HasLen, 1)
   363  	c.Check(candidates["some-snap"], NotNil)
   364  }
   365  
   366  const otherSnapYaml = `name: other-snap
   367  version: 1.0
   368  epoch: 1
   369  type: app
   370  `
   371  
   372  func (s *refreshHintsTestSuite) TestRefreshHintsNotApplicableWrongEpoch(c *C) {
   373  	s.state.Lock()
   374  
   375  	si := &snap.SideInfo{RealName: "other-snap", Revision: snap.R(1), SnapID: "other-snap-id"}
   376  	snaptest.MockSnap(c, otherSnapYaml, si)
   377  	snapstate.Set(s.state, "other-snap", &snapstate.SnapState{
   378  		Active:   true,
   379  		Sequence: []*snap.SideInfo{si},
   380  		Current:  snap.R(1),
   381  		SnapType: "app",
   382  	})
   383  	s.state.Unlock()
   384  
   385  	s.store.refreshedSnaps = []*snap.Info{{
   386  		Architectures: []string{"all"},
   387  		SnapType:      snap.TypeApp,
   388  		SideInfo: snap.SideInfo{
   389  			RealName: "some-snap",
   390  			Revision: snap.R(1),
   391  		},
   392  	}, {
   393  		Architectures: []string{"all"},
   394  		SnapType:      snap.TypeApp,
   395  		SideInfo: snap.SideInfo{
   396  			RealName: "other-snap",
   397  			Revision: snap.R(2),
   398  		},
   399  		Epoch: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}},
   400  	}}
   401  
   402  	rh := snapstate.NewRefreshHints(s.state)
   403  	c.Assert(rh.Ensure(), IsNil)
   404  
   405  	s.state.Lock()
   406  	defer s.state.Unlock()
   407  
   408  	var candidates map[string]*snapstate.RefreshCandidate
   409  	c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil)
   410  	c.Assert(candidates, HasLen, 1)
   411  	// other-snap ignored due to epoch
   412  	c.Check(candidates["some-snap"], NotNil)
   413  }