github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/snapstate/catalogrefresh_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  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/advisor"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/overlord/auth"
    36  	"github.com/snapcore/snapd/overlord/snapstate"
    37  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  	"github.com/snapcore/snapd/snapdenv"
    40  	"github.com/snapcore/snapd/store"
    41  	"github.com/snapcore/snapd/store/storetest"
    42  	"github.com/snapcore/snapd/testutil"
    43  )
    44  
    45  type catalogStore struct {
    46  	storetest.Store
    47  
    48  	ops     []string
    49  	tooMany bool
    50  }
    51  
    52  func (r *catalogStore) WriteCatalogs(ctx context.Context, w io.Writer, a store.SnapAdder) error {
    53  	if ctx == nil || !auth.IsEnsureContext(ctx) {
    54  		panic("Ensure marked context required")
    55  	}
    56  	r.ops = append(r.ops, "write-catalog")
    57  	if r.tooMany {
    58  		return store.ErrTooManyRequests
    59  	}
    60  	w.Write([]byte("pkg1\npkg2"))
    61  	a.AddSnap("foo", "1.0", "foo summary", []string{"foo", "meh"})
    62  	a.AddSnap("bar", "2.0", "bar summray", []string{"bar", "meh"})
    63  	return nil
    64  }
    65  
    66  func (r *catalogStore) Sections(ctx context.Context, _ *auth.UserState) ([]string, error) {
    67  	if ctx == nil || !auth.IsEnsureContext(ctx) {
    68  		panic("Ensure marked context required")
    69  	}
    70  	r.ops = append(r.ops, "sections")
    71  	if r.tooMany {
    72  		return nil, store.ErrTooManyRequests
    73  	}
    74  	return []string{"section1", "section2"}, nil
    75  }
    76  
    77  type catalogRefreshTestSuite struct {
    78  	state *state.State
    79  
    80  	store  *catalogStore
    81  	tmpdir string
    82  
    83  	testutil.BaseTest
    84  }
    85  
    86  var _ = Suite(&catalogRefreshTestSuite{})
    87  
    88  func (s *catalogRefreshTestSuite) SetUpTest(c *C) {
    89  	s.tmpdir = c.MkDir()
    90  	dirs.SetRootDir(s.tmpdir)
    91  	s.state = state.New(nil)
    92  	s.store = &catalogStore{}
    93  	s.state.Lock()
    94  	defer s.state.Unlock()
    95  	snapstate.ReplaceStore(s.state, s.store)
    96  	// mark system as seeded
    97  	s.state.Set("seeded", true)
    98  
    99  	// setup a simple deviceCtx since we check that for install mode
   100  	s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel()))
   101  
   102  	snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil }
   103  	s.AddCleanup(func() { snapstate.CanAutoRefresh = nil })
   104  }
   105  
   106  func (s *catalogRefreshTestSuite) TestCatalogRefresh(c *C) {
   107  	// start with no catalog
   108  	c.Check(dirs.SnapSectionsFile, testutil.FileAbsent)
   109  	c.Check(dirs.SnapNamesFile, testutil.FileAbsent)
   110  	c.Check(dirs.SnapCommandsDB, testutil.FileAbsent)
   111  
   112  	cr7 := snapstate.NewCatalogRefresh(s.state)
   113  	// next is initially zero
   114  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   115  	t0 := time.Now()
   116  
   117  	err := cr7.Ensure()
   118  	c.Check(err, IsNil)
   119  
   120  	// next now has a delta (next refresh is not before t0 + delta)
   121  	c.Check(snapstate.NextCatalogRefresh(cr7).Before(t0.Add(snapstate.CatalogRefreshDelayWithDelta)), Equals, false)
   122  
   123  	c.Check(s.store.ops, DeepEquals, []string{"sections", "write-catalog"})
   124  
   125  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, true)
   126  	c.Check(dirs.SnapSectionsFile, testutil.FileEquals, "section1\nsection2")
   127  
   128  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, true)
   129  	c.Check(dirs.SnapNamesFile, testutil.FileEquals, "pkg1\npkg2")
   130  
   131  	c.Check(osutil.FileExists(dirs.SnapCommandsDB), Equals, true)
   132  	dump, err := advisor.DumpCommands()
   133  	c.Assert(err, IsNil)
   134  	c.Check(dump, DeepEquals, map[string]string{
   135  		"foo": `[{"snap":"foo","version":"1.0"}]`,
   136  		"bar": `[{"snap":"bar","version":"2.0"}]`,
   137  		"meh": `[{"snap":"foo","version":"1.0"},{"snap":"bar","version":"2.0"}]`,
   138  	})
   139  }
   140  
   141  func (s *catalogRefreshTestSuite) TestCatalogRefreshTooMany(c *C) {
   142  	s.store.tooMany = true
   143  
   144  	cr7 := snapstate.NewCatalogRefresh(s.state)
   145  	// next is initially zero
   146  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   147  	t0 := time.Now()
   148  
   149  	err := cr7.Ensure()
   150  	c.Check(err, IsNil) // !!
   151  
   152  	// next now has a delta (next refresh is not before t0 + delta)
   153  	c.Check(snapstate.NextCatalogRefresh(cr7).Before(t0.Add(snapstate.CatalogRefreshDelayWithDelta)), Equals, false)
   154  
   155  	// it tried one endpoint and bailed at the first 429
   156  	c.Check(s.store.ops, HasLen, 1)
   157  
   158  	// nothing got created
   159  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, false)
   160  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, false)
   161  	c.Check(osutil.FileExists(dirs.SnapCommandsDB), Equals, false)
   162  }
   163  
   164  func (s *catalogRefreshTestSuite) TestCatalogRefreshNotNeeded(c *C) {
   165  	cr7 := snapstate.NewCatalogRefresh(s.state)
   166  	snapstate.MockCatalogRefreshNextRefresh(cr7, time.Now().Add(1*time.Hour))
   167  	err := cr7.Ensure()
   168  	c.Check(err, IsNil)
   169  	c.Check(s.store.ops, HasLen, 0)
   170  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, false)
   171  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, false)
   172  }
   173  
   174  func (s *catalogRefreshTestSuite) TestCatalogRefreshNewEnough(c *C) {
   175  	// write a fake sections file just to have it
   176  	c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapNamesFile), 0755), IsNil)
   177  	c.Assert(ioutil.WriteFile(dirs.SnapNamesFile, nil, 0644), IsNil)
   178  	// set the timestamp to something known
   179  	t0 := time.Now().Truncate(time.Hour)
   180  	c.Assert(os.Chtimes(dirs.SnapNamesFile, t0, t0), IsNil)
   181  
   182  	cr7 := snapstate.NewCatalogRefresh(s.state)
   183  	// next is initially zero
   184  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   185  	err := cr7.Ensure()
   186  	c.Assert(err, IsNil)
   187  	c.Check(s.store.ops, HasLen, 0)
   188  	next := snapstate.NextCatalogRefresh(cr7)
   189  	// next is no longer zero,
   190  	c.Check(next.IsZero(), Equals, false)
   191  	// but has a delta WRT the timestamp
   192  	c.Check(next.Equal(t0.Add(snapstate.CatalogRefreshDelayWithDelta)), Equals, true)
   193  }
   194  
   195  func (s *catalogRefreshTestSuite) TestCatalogRefreshTooNew(c *C) {
   196  	// write a fake sections file just to have it
   197  	c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapNamesFile), 0755), IsNil)
   198  	c.Assert(ioutil.WriteFile(dirs.SnapNamesFile, nil, 0644), IsNil)
   199  	// but set the timestamp in the future
   200  	t := time.Now().Add(time.Hour)
   201  	c.Assert(os.Chtimes(dirs.SnapNamesFile, t, t), IsNil)
   202  
   203  	cr7 := snapstate.NewCatalogRefresh(s.state)
   204  	err := cr7.Ensure()
   205  	c.Check(err, IsNil)
   206  	c.Check(s.store.ops, DeepEquals, []string{"sections", "write-catalog"})
   207  }
   208  
   209  func (s *catalogRefreshTestSuite) TestCatalogRefreshUnSeeded(c *C) {
   210  	// mark system as unseeded (first boot)
   211  	s.state.Lock()
   212  	s.state.Set("seeded", nil)
   213  	s.state.Unlock()
   214  
   215  	cr7 := snapstate.NewCatalogRefresh(s.state)
   216  	// next is initially zero
   217  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   218  
   219  	err := cr7.Ensure()
   220  	c.Assert(err, IsNil)
   221  
   222  	// next should be still zero as we skipped refresh on unseeded system
   223  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   224  	// nothing got created
   225  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, false)
   226  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, false)
   227  	c.Check(osutil.FileExists(dirs.SnapCommandsDB), Equals, false)
   228  }
   229  
   230  func (s *catalogRefreshTestSuite) TestCatalogRefreshUC20InstallMode(c *C) {
   231  	// mark system as being in install mode
   232  	trivialInstallDevice := &snapstatetest.TrivialDeviceContext{
   233  		DeviceModel: DefaultModel(),
   234  		SysMode:     "install",
   235  	}
   236  
   237  	r := snapstatetest.MockDeviceContext(trivialInstallDevice)
   238  	defer r()
   239  
   240  	cr7 := snapstate.NewCatalogRefresh(s.state)
   241  	// next is initially zero
   242  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   243  
   244  	err := cr7.Ensure()
   245  	c.Assert(err, IsNil)
   246  
   247  	// next should be still zero as we skipped refresh on unseeded system
   248  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   249  	// nothing got created
   250  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, false)
   251  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, false)
   252  	c.Check(osutil.FileExists(dirs.SnapCommandsDB), Equals, false)
   253  }
   254  
   255  func (s *catalogRefreshTestSuite) TestCatalogRefreshSkipWhenTesting(c *C) {
   256  	restore := snapdenv.MockTesting(true)
   257  	defer restore()
   258  	// catalog refresh disabled
   259  	os.Setenv("SNAPD_CATALOG_REFRESH", "0")
   260  	defer os.Unsetenv("SNAPD_CATALOG_REFRESH")
   261  
   262  	// start with no catalog
   263  	c.Check(dirs.SnapSectionsFile, testutil.FileAbsent)
   264  	c.Check(dirs.SnapNamesFile, testutil.FileAbsent)
   265  	c.Check(dirs.SnapCommandsDB, testutil.FileAbsent)
   266  
   267  	cr7 := snapstate.NewCatalogRefresh(s.state)
   268  	// next is initially zero
   269  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   270  
   271  	err := cr7.Ensure()
   272  	c.Check(err, IsNil)
   273  
   274  	c.Check(s.store.ops, HasLen, 0)
   275  
   276  	c.Check(dirs.SnapSectionsFile, testutil.FileAbsent)
   277  	c.Check(dirs.SnapNamesFile, testutil.FileAbsent)
   278  	c.Check(dirs.SnapCommandsDB, testutil.FileAbsent)
   279  
   280  	// allow the refresh now
   281  	os.Setenv("SNAPD_CATALOG_REFRESH", "1")
   282  
   283  	// and reset the next refresh time
   284  	snapstate.MockCatalogRefreshNextRefresh(cr7, time.Time{})
   285  	// sanity
   286  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   287  
   288  	err = cr7.Ensure()
   289  	c.Check(err, IsNil)
   290  
   291  	// refresh happened
   292  	c.Check(s.store.ops, DeepEquals, []string{"sections", "write-catalog"})
   293  
   294  	c.Check(dirs.SnapSectionsFile, testutil.FilePresent)
   295  	c.Check(dirs.SnapNamesFile, testutil.FilePresent)
   296  	c.Check(dirs.SnapCommandsDB, testutil.FilePresent)
   297  }