github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 }