gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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) TestPruneRefreshCandidates(c *C) { 327 st := s.state 328 st.Lock() 329 defer st.Unlock() 330 331 // check that calling PruneRefreshCandidates when there is nothing to do is fine. 332 c.Assert(snapstate.PruneRefreshCandidates(st, "some-snap"), IsNil) 333 334 candidates := map[string]*snapstate.RefreshCandidate{ 335 "snap-a": { 336 SnapSetup: snapstate.SnapSetup{ 337 Type: "app", 338 SideInfo: &snap.SideInfo{ 339 RealName: "snap-a", 340 Revision: snap.R(1), 341 }, 342 }, 343 }, 344 "snap-b": { 345 SnapSetup: snapstate.SnapSetup{ 346 Type: "app", 347 SideInfo: &snap.SideInfo{ 348 RealName: "snap-b", 349 Revision: snap.R(1), 350 }, 351 }, 352 }, 353 "snap-c": { 354 SnapSetup: snapstate.SnapSetup{ 355 Type: "app", 356 SideInfo: &snap.SideInfo{ 357 RealName: "snap-c", 358 Revision: snap.R(1), 359 }, 360 }, 361 }, 362 } 363 st.Set("refresh-candidates", candidates) 364 365 c.Assert(snapstate.PruneRefreshCandidates(st, "snap-a"), IsNil) 366 367 var candidates2 map[string]*snapstate.RefreshCandidate 368 c.Assert(st.Get("refresh-candidates", &candidates2), IsNil) 369 _, ok := candidates2["snap-a"] 370 c.Check(ok, Equals, false) 371 _, ok = candidates2["snap-b"] 372 c.Check(ok, Equals, true) 373 _, ok = candidates2["snap-c"] 374 c.Check(ok, Equals, true) 375 376 var candidates3 map[string]*snapstate.RefreshCandidate 377 c.Assert(snapstate.PruneRefreshCandidates(st, "snap-b"), IsNil) 378 c.Assert(st.Get("refresh-candidates", &candidates3), IsNil) 379 _, ok = candidates3["snap-a"] 380 c.Check(ok, Equals, false) 381 _, ok = candidates3["snap-b"] 382 c.Check(ok, Equals, false) 383 _, ok = candidates3["snap-c"] 384 c.Check(ok, Equals, true) 385 } 386 387 func (s *refreshHintsTestSuite) TestRefreshHintsNotApplicableWrongArch(c *C) { 388 s.state.Lock() 389 snapstate.Set(s.state, "other-snap", &snapstate.SnapState{ 390 Active: true, 391 Sequence: []*snap.SideInfo{ 392 {RealName: "other-snap", Revision: snap.R(1), SnapID: "other-snap-id"}, 393 }, 394 Current: snap.R(1), 395 SnapType: "app", 396 }) 397 s.state.Unlock() 398 399 s.store.refreshedSnaps = []*snap.Info{{ 400 Architectures: []string{"all"}, 401 SnapType: snap.TypeApp, 402 SideInfo: snap.SideInfo{ 403 RealName: "some-snap", 404 Revision: snap.R(1), 405 }, 406 }, { 407 Architectures: []string{"somearch"}, 408 SnapType: snap.TypeApp, 409 SideInfo: snap.SideInfo{ 410 RealName: "other-snap", 411 Revision: snap.R(2), 412 }, 413 }} 414 415 rh := snapstate.NewRefreshHints(s.state) 416 c.Assert(rh.Ensure(), IsNil) 417 418 s.state.Lock() 419 defer s.state.Unlock() 420 421 var candidates map[string]*snapstate.RefreshCandidate 422 c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil) 423 c.Assert(candidates, HasLen, 1) 424 c.Check(candidates["some-snap"], NotNil) 425 } 426 427 const otherSnapYaml = `name: other-snap 428 version: 1.0 429 epoch: 1 430 type: app 431 ` 432 433 func (s *refreshHintsTestSuite) TestRefreshHintsNotApplicableWrongEpoch(c *C) { 434 s.state.Lock() 435 436 si := &snap.SideInfo{RealName: "other-snap", Revision: snap.R(1), SnapID: "other-snap-id"} 437 snaptest.MockSnap(c, otherSnapYaml, si) 438 snapstate.Set(s.state, "other-snap", &snapstate.SnapState{ 439 Active: true, 440 Sequence: []*snap.SideInfo{si}, 441 Current: snap.R(1), 442 SnapType: "app", 443 }) 444 s.state.Unlock() 445 446 s.store.refreshedSnaps = []*snap.Info{{ 447 Architectures: []string{"all"}, 448 SnapType: snap.TypeApp, 449 SideInfo: snap.SideInfo{ 450 RealName: "some-snap", 451 Revision: snap.R(1), 452 }, 453 }, { 454 Architectures: []string{"all"}, 455 SnapType: snap.TypeApp, 456 SideInfo: snap.SideInfo{ 457 RealName: "other-snap", 458 Revision: snap.R(2), 459 }, 460 Epoch: snap.Epoch{Read: []uint32{2}, Write: []uint32{2}}, 461 }} 462 463 rh := snapstate.NewRefreshHints(s.state) 464 c.Assert(rh.Ensure(), IsNil) 465 466 s.state.Lock() 467 defer s.state.Unlock() 468 469 var candidates map[string]*snapstate.RefreshCandidate 470 c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil) 471 c.Assert(candidates, HasLen, 1) 472 // other-snap ignored due to epoch 473 c.Check(candidates["some-snap"], NotNil) 474 }