github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/overlord/snapstate/autorefresh_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 "fmt" 25 "os" 26 "path/filepath" 27 "strings" 28 "time" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/httputil" 34 "github.com/snapcore/snapd/logger" 35 "github.com/snapcore/snapd/overlord/auth" 36 "github.com/snapcore/snapd/overlord/configstate/config" 37 "github.com/snapcore/snapd/overlord/snapstate" 38 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 39 "github.com/snapcore/snapd/overlord/state" 40 "github.com/snapcore/snapd/release" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/store" 43 "github.com/snapcore/snapd/store/storetest" 44 "github.com/snapcore/snapd/testutil" 45 "github.com/snapcore/snapd/timeutil" 46 ) 47 48 type autoRefreshStore struct { 49 storetest.Store 50 51 ops []string 52 53 err error 54 55 snapActionOpsFunc func() 56 } 57 58 func (r *autoRefreshStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) { 59 // this is a bit of a hack to simulate race conditions where while the store 60 // has unlocked the global state lock something else could come in and 61 // change the auto-refresh hold 62 if r.snapActionOpsFunc != nil { 63 r.snapActionOpsFunc() 64 return nil, nil, r.err 65 } 66 67 if assertQuery != nil { 68 panic("no assertion query support") 69 } 70 if !opts.IsAutoRefresh { 71 panic("AutoRefresh snap action did not set IsAutoRefresh flag") 72 } 73 74 if ctx == nil || !auth.IsEnsureContext(ctx) { 75 panic("Ensure marked context required") 76 } 77 if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 { 78 panic("expected in test one action for each current snaps, and at least one snap") 79 } 80 for _, a := range actions { 81 if a.Action != "refresh" { 82 panic("expected refresh actions") 83 } 84 } 85 86 r.ops = append(r.ops, "list-refresh") 87 88 return nil, nil, r.err 89 } 90 91 type autoRefreshTestSuite struct { 92 testutil.BaseTest 93 state *state.State 94 95 store *autoRefreshStore 96 97 restore func() 98 } 99 100 var _ = Suite(&autoRefreshTestSuite{}) 101 102 func (s *autoRefreshTestSuite) SetUpTest(c *C) { 103 dirs.SetRootDir(c.MkDir()) 104 s.AddCleanup(func() { dirs.SetRootDir("") }) 105 106 s.state = state.New(nil) 107 108 s.store = &autoRefreshStore{} 109 110 s.AddCleanup(func() { s.store.snapActionOpsFunc = nil }) 111 112 s.state.Lock() 113 defer s.state.Unlock() 114 snapstate.ReplaceStore(s.state, s.store) 115 116 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 117 Active: true, 118 Sequence: []*snap.SideInfo{ 119 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 120 }, 121 Current: snap.R(5), 122 SnapType: "app", 123 UserID: 1, 124 }) 125 126 snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil } 127 s.AddCleanup(func() { snapstate.CanAutoRefresh = nil }) 128 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 129 return nil, nil 130 } 131 s.AddCleanup(func() { snapstate.AutoAliases = nil }) 132 snapstate.IsOnMeteredConnection = func() (bool, error) { return false, nil } 133 134 s.state.Set("seeded", true) 135 s.state.Set("seed-time", time.Now()) 136 s.state.Set("refresh-privacy-key", "privacy-key") 137 s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel())) 138 } 139 140 func (s *autoRefreshTestSuite) TestLastRefresh(c *C) { 141 // this does an immediate refresh 142 143 af := snapstate.NewAutoRefresh(s.state) 144 err := af.Ensure() 145 c.Check(err, IsNil) 146 c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) 147 148 var lastRefresh time.Time 149 s.state.Lock() 150 s.state.Get("last-refresh", &lastRefresh) 151 s.state.Unlock() 152 c.Check(lastRefresh.Year(), Equals, time.Now().Year()) 153 } 154 155 func (s *autoRefreshTestSuite) TestLastRefreshRefreshManaged(c *C) { 156 snapstate.CanManageRefreshes = func(st *state.State) bool { 157 return true 158 } 159 defer func() { snapstate.CanManageRefreshes = nil }() 160 161 logbuf, restore := logger.MockLogger() 162 defer restore() 163 164 s.state.Lock() 165 defer s.state.Unlock() 166 167 for _, t := range []struct { 168 conf string 169 legacy bool 170 }{ 171 {"refresh.timer", false}, 172 {"refresh.schedule", true}, 173 } { 174 tr := config.NewTransaction(s.state) 175 tr.Set("core", t.conf, "managed") 176 tr.Commit() 177 178 af := snapstate.NewAutoRefresh(s.state) 179 s.state.Unlock() 180 err := af.Ensure() 181 s.state.Lock() 182 c.Check(err, IsNil) 183 c.Check(s.store.ops, HasLen, 0) 184 185 refreshScheduleStr, legacy, err := af.RefreshSchedule() 186 c.Check(refreshScheduleStr, Equals, "managed") 187 c.Check(legacy, Equals, t.legacy) 188 c.Check(err, IsNil) 189 190 c.Check(af.NextRefresh(), DeepEquals, time.Time{}) 191 192 count := strings.Count(logbuf.String(), 193 ": refresh is managed via the snapd-control interface\n") 194 c.Check(count, Equals, 1, Commentf("too many occurrences:\n%s", logbuf.String())) 195 196 // ensure clean config for the next run 197 s.state.Set("config", nil) 198 logbuf.Reset() 199 } 200 } 201 202 func (s *autoRefreshTestSuite) TestRefreshManagedTimerWins(c *C) { 203 snapstate.CanManageRefreshes = func(st *state.State) bool { 204 return true 205 } 206 defer func() { snapstate.CanManageRefreshes = nil }() 207 208 s.state.Lock() 209 defer s.state.Unlock() 210 211 tr := config.NewTransaction(s.state) 212 // the "refresh.timer" setting always takes precedence over 213 // refresh.schedule 214 tr.Set("core", "refresh.timer", "00:00-12:00") 215 tr.Set("core", "refresh.schedule", "managed") 216 tr.Commit() 217 218 af := snapstate.NewAutoRefresh(s.state) 219 s.state.Unlock() 220 err := af.Ensure() 221 s.state.Lock() 222 c.Check(err, IsNil) 223 c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) 224 225 refreshScheduleStr, legacy, err := af.RefreshSchedule() 226 c.Check(refreshScheduleStr, Equals, "00:00-12:00") 227 c.Check(legacy, Equals, false) 228 c.Check(err, IsNil) 229 } 230 231 func (s *autoRefreshTestSuite) TestRefreshManagedDenied(c *C) { 232 canManageCalled := false 233 snapstate.CanManageRefreshes = func(st *state.State) bool { 234 canManageCalled = true 235 // always deny 236 return false 237 } 238 defer func() { snapstate.CanManageRefreshes = nil }() 239 240 logbuf, restore := logger.MockLogger() 241 defer restore() 242 243 s.state.Lock() 244 defer s.state.Unlock() 245 246 for _, conf := range []string{"refresh.timer", "refresh.schedule"} { 247 tr := config.NewTransaction(s.state) 248 tr.Set("core", conf, "managed") 249 tr.Commit() 250 251 af := snapstate.NewAutoRefresh(s.state) 252 for i := 0; i < 2; i++ { 253 c.Logf("ensure iteration: %v", i) 254 s.state.Unlock() 255 err := af.Ensure() 256 s.state.Lock() 257 c.Check(err, IsNil) 258 c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) 259 260 refreshScheduleStr, _, err := af.RefreshSchedule() 261 c.Check(refreshScheduleStr, Equals, snapstate.DefaultRefreshSchedule) 262 c.Check(err, IsNil) 263 c.Check(canManageCalled, Equals, true) 264 count := strings.Count(logbuf.String(), 265 ": managed refresh schedule denied, no properly configured snapd-control\n") 266 c.Check(count, Equals, 1, Commentf("too many occurrences:\n%s", logbuf.String())) 267 268 canManageCalled = false 269 } 270 271 // ensure clean config for the next run 272 s.state.Set("config", nil) 273 logbuf.Reset() 274 canManageCalled = false 275 } 276 } 277 278 func (s *autoRefreshTestSuite) TestLastRefreshNoRefreshNeeded(c *C) { 279 s.state.Lock() 280 s.state.Set("last-refresh", time.Now()) 281 s.state.Unlock() 282 283 af := snapstate.NewAutoRefresh(s.state) 284 err := af.Ensure() 285 c.Check(err, IsNil) 286 c.Check(s.store.ops, HasLen, 0) 287 } 288 289 func (s *autoRefreshTestSuite) TestRefreshBackoff(c *C) { 290 s.store.err = fmt.Errorf("random store error") 291 af := snapstate.NewAutoRefresh(s.state) 292 err := af.Ensure() 293 c.Check(err, ErrorMatches, "random store error") 294 c.Check(s.store.ops, HasLen, 1) 295 296 // override next refresh to be here already 297 now := time.Now() 298 snapstate.MockNextRefresh(af, now) 299 300 // call ensure again, our back-off will prevent the store from 301 // being hit again 302 err = af.Ensure() 303 c.Check(err, IsNil) 304 c.Check(s.store.ops, HasLen, 1) 305 306 // nextRefresh unchanged 307 c.Check(af.NextRefresh().Equal(now), Equals, true) 308 309 // fake that the retryRefreshDelay is over 310 restore := snapstate.MockRefreshRetryDelay(1 * time.Millisecond) 311 defer restore() 312 time.Sleep(10 * time.Millisecond) 313 314 // ensure hits the store again 315 err = af.Ensure() 316 c.Check(err, ErrorMatches, "random store error") 317 c.Check(s.store.ops, HasLen, 2) 318 319 // nextRefresh now zero 320 c.Check(af.NextRefresh().IsZero(), Equals, true) 321 // set it to something in the future 322 snapstate.MockNextRefresh(af, time.Now().Add(time.Minute)) 323 324 // nothing really happens yet: the previous autorefresh failed 325 // but it still counts as having tried to autorefresh 326 err = af.Ensure() 327 c.Check(err, IsNil) 328 c.Check(s.store.ops, HasLen, 2) 329 330 // pretend the time for next refresh is here 331 snapstate.MockNextRefresh(af, time.Now()) 332 // including the wait for the retryRefreshDelay backoff 333 time.Sleep(10 * time.Millisecond) 334 335 // now yes it happens again 336 err = af.Ensure() 337 c.Check(err, ErrorMatches, "random store error") 338 c.Check(s.store.ops, HasLen, 3) 339 // and not *again* again 340 err = af.Ensure() 341 c.Check(err, IsNil) 342 c.Check(s.store.ops, HasLen, 3) 343 344 c.Check(s.store.ops, DeepEquals, []string{"list-refresh", "list-refresh", "list-refresh"}) 345 } 346 347 func (s *autoRefreshTestSuite) TestRefreshPersistentError(c *C) { 348 // fake that the retryRefreshDelay is over 349 restore := snapstate.MockRefreshRetryDelay(1 * time.Millisecond) 350 defer restore() 351 352 initialLastRefresh := time.Now().Add(-12 * time.Hour) 353 s.state.Lock() 354 s.state.Set("last-refresh", initialLastRefresh) 355 s.state.Unlock() 356 357 s.store.err = &httputil.PersistentNetworkError{Err: fmt.Errorf("error")} 358 af := snapstate.NewAutoRefresh(s.state) 359 err := af.Ensure() 360 c.Check(err, ErrorMatches, "persistent network error: error") 361 c.Check(s.store.ops, HasLen, 1) 362 363 // last-refresh time remains untouched 364 var lastRefresh time.Time 365 s.state.Lock() 366 s.state.Get("last-refresh", &lastRefresh) 367 s.state.Unlock() 368 c.Check(lastRefresh.Format(time.RFC3339), Equals, initialLastRefresh.Format(time.RFC3339)) 369 370 s.store.err = nil 371 time.Sleep(10 * time.Millisecond) 372 373 // call ensure again, refresh should be attempted again 374 err = af.Ensure() 375 c.Check(err, IsNil) 376 c.Check(s.store.ops, HasLen, 2) 377 } 378 379 func (s *autoRefreshTestSuite) TestDefaultScheduleIsRandomized(c *C) { 380 schedule, err := timeutil.ParseSchedule(snapstate.DefaultRefreshSchedule) 381 c.Assert(err, IsNil) 382 383 for _, sched := range schedule { 384 for _, span := range sched.ClockSpans { 385 c.Check(span.Start == span.End, Equals, false, 386 Commentf("clock span %v is a single time, expected an actual span", span)) 387 c.Check(span.Spread, Equals, true, 388 Commentf("clock span %v is not randomized", span)) 389 } 390 } 391 } 392 393 func (s *autoRefreshTestSuite) TestLastRefreshRefreshHold(c *C) { 394 s.state.Lock() 395 defer s.state.Unlock() 396 397 t0 := time.Now() 398 s.state.Set("last-refresh", t0.Add(-12*time.Hour)) 399 400 holdTime := t0.Add(5 * time.Minute) 401 tr := config.NewTransaction(s.state) 402 tr.Set("core", "refresh.hold", holdTime) 403 tr.Commit() 404 405 af := snapstate.NewAutoRefresh(s.state) 406 s.state.Unlock() 407 err := af.Ensure() 408 s.state.Lock() 409 c.Check(err, IsNil) 410 411 // no refresh 412 c.Check(s.store.ops, HasLen, 0) 413 414 // hold still kept 415 tr = config.NewTransaction(s.state) 416 var t1 time.Time 417 err = tr.Get("core", "refresh.hold", &t1) 418 c.Assert(err, IsNil) 419 c.Check(t1.Equal(holdTime), Equals, true) 420 } 421 422 func (s *autoRefreshTestSuite) TestLastRefreshRefreshHoldExpired(c *C) { 423 s.state.Lock() 424 defer s.state.Unlock() 425 426 t0 := time.Now() 427 s.state.Set("last-refresh", t0.Add(-12*time.Hour)) 428 429 holdTime := t0.Add(-5 * time.Minute) 430 tr := config.NewTransaction(s.state) 431 tr.Set("core", "refresh.hold", holdTime) 432 tr.Commit() 433 434 af := snapstate.NewAutoRefresh(s.state) 435 s.state.Unlock() 436 err := af.Ensure() 437 s.state.Lock() 438 c.Check(err, IsNil) 439 440 // refresh happened 441 c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) 442 443 var lastRefresh time.Time 444 s.state.Get("last-refresh", &lastRefresh) 445 c.Check(lastRefresh.Year(), Equals, time.Now().Year()) 446 447 // hold was reset 448 tr = config.NewTransaction(s.state) 449 var t1 time.Time 450 err = tr.Get("core", "refresh.hold", &t1) 451 c.Assert(config.IsNoOption(err), Equals, true) 452 } 453 454 func (s *autoRefreshTestSuite) TestLastRefreshRefreshHoldExpiredButResetWhileLockUnlocked(c *C) { 455 s.state.Lock() 456 defer s.state.Unlock() 457 458 t0 := time.Now() 459 twelveHoursAgo := t0.Add(-12 * time.Hour) 460 fiveMinutesAgo := t0.Add(-5 * time.Minute) 461 oneHourInFuture := t0.Add(time.Hour) 462 s.state.Set("last-refresh", twelveHoursAgo) 463 464 holdTime := fiveMinutesAgo 465 tr := config.NewTransaction(s.state) 466 tr.Set("core", "refresh.hold", holdTime) 467 tr.Commit() 468 469 logbuf, restore := logger.MockLogger() 470 defer restore() 471 472 sent := false 473 ch := make(chan struct{}) 474 // make the store snap action function trigger a background go routine to 475 // change the held-time underneath the auto-refresh 476 go func() { 477 // wait to be triggered by the snap action ops func 478 <-ch 479 s.state.Lock() 480 defer s.state.Unlock() 481 482 // now change the refresh.hold time to be an hour in the future 483 tr := config.NewTransaction(s.state) 484 tr.Set("core", "refresh.hold", oneHourInFuture) 485 tr.Commit() 486 487 // trigger the snap action ops func to proceed 488 ch <- struct{}{} 489 }() 490 491 s.store.snapActionOpsFunc = func() { 492 // only need to send once, this will be invoked multiple times for 493 // multiple snaps 494 if !sent { 495 ch <- struct{}{} 496 sent = true 497 // wait for a response to ensure that we block waiting for the new 498 // refresh time to be committed in time for us to read it after 499 // returning in this go routine 500 <-ch 501 } 502 } 503 504 af := snapstate.NewAutoRefresh(s.state) 505 s.state.Unlock() 506 err := af.Ensure() 507 s.state.Lock() 508 c.Check(err, IsNil) 509 510 var lastRefresh time.Time 511 s.state.Get("last-refresh", &lastRefresh) 512 c.Check(lastRefresh.Year(), Equals, time.Now().Year()) 513 514 // hold was reset mid-way to a new value one hour into the future 515 tr = config.NewTransaction(s.state) 516 var t1 time.Time 517 err = tr.Get("core", "refresh.hold", &t1) 518 c.Assert(err, IsNil) 519 520 // when traversing json through the core config transaction, there will be 521 // different wall/monotonic clock times, we remove this ambiguity by 522 // formatting as rfc3339 which will strip this negligible difference in time 523 c.Assert(t1.Format(time.RFC3339), Equals, oneHourInFuture.Format(time.RFC3339)) 524 525 // we shouldn't have had a message about "all snaps are up to date", we 526 // should have a message about being aborted mid way 527 528 c.Assert(logbuf.String(), testutil.Contains, "Auto-refresh was delayed mid-way through launching, aborting to try again later") 529 c.Assert(logbuf.String(), Not(testutil.Contains), "auto-refresh: all snaps are up-to-date") 530 } 531 532 func (s *autoRefreshTestSuite) TestLastRefreshRefreshHoldExpiredReschedule(c *C) { 533 s.state.Lock() 534 defer s.state.Unlock() 535 536 t0 := time.Now() 537 s.state.Set("last-refresh", t0.Add(-12*time.Hour)) 538 539 holdTime := t0.Add(-1 * time.Minute) 540 tr := config.NewTransaction(s.state) 541 tr.Set("core", "refresh.hold", holdTime) 542 543 nextRefresh := t0.Add(5 * time.Minute).Truncate(time.Minute) 544 schedule := fmt.Sprintf("%02d:%02d-%02d:59", nextRefresh.Hour(), nextRefresh.Minute(), nextRefresh.Hour()) 545 tr.Set("core", "refresh.timer", schedule) 546 tr.Commit() 547 548 af := snapstate.NewAutoRefresh(s.state) 549 snapstate.MockLastRefreshSchedule(af, schedule) 550 snapstate.MockNextRefresh(af, holdTime.Add(-2*time.Minute)) 551 552 s.state.Unlock() 553 err := af.Ensure() 554 s.state.Lock() 555 c.Check(err, IsNil) 556 557 // refresh did not happen yet 558 c.Check(s.store.ops, HasLen, 0) 559 560 // hold was reset 561 tr = config.NewTransaction(s.state) 562 var t1 time.Time 563 err = tr.Get("core", "refresh.hold", &t1) 564 c.Assert(config.IsNoOption(err), Equals, true) 565 566 // check next refresh 567 nextRefresh1 := af.NextRefresh() 568 c.Check(nextRefresh1.Before(nextRefresh), Equals, false) 569 } 570 571 func (s *autoRefreshTestSuite) TestEffectiveRefreshHold(c *C) { 572 s.state.Lock() 573 defer s.state.Unlock() 574 575 // assume no seed-time 576 s.state.Set("seed-time", nil) 577 578 af := snapstate.NewAutoRefresh(s.state) 579 580 t0, err := af.EffectiveRefreshHold() 581 c.Assert(err, IsNil) 582 c.Check(t0.IsZero(), Equals, true) 583 584 holdTime := time.Now() 585 tr := config.NewTransaction(s.state) 586 tr.Set("core", "refresh.hold", holdTime) 587 tr.Commit() 588 589 seedTime := holdTime.Add(-70 * 24 * time.Hour) 590 s.state.Set("seed-time", seedTime) 591 592 t1, err := af.EffectiveRefreshHold() 593 c.Assert(err, IsNil) 594 c.Check(t1.Equal(seedTime.Add(60*24*time.Hour)), Equals, true) 595 596 lastRefresh := holdTime.Add(-65 * 24 * time.Hour) 597 s.state.Set("last-refresh", lastRefresh) 598 599 t1, err = af.EffectiveRefreshHold() 600 c.Assert(err, IsNil) 601 c.Check(t1.Equal(lastRefresh.Add(60*24*time.Hour)), Equals, true) 602 603 s.state.Set("last-refresh", holdTime.Add(-6*time.Hour)) 604 t1, err = af.EffectiveRefreshHold() 605 c.Assert(err, IsNil) 606 c.Check(t1.Equal(holdTime), Equals, true) 607 } 608 609 func (s *autoRefreshTestSuite) TestEnsureLastRefreshAnchor(c *C) { 610 s.state.Lock() 611 defer s.state.Unlock() 612 // set hold => no refreshes 613 t0 := time.Now() 614 holdTime := t0.Add(1 * time.Hour) 615 tr := config.NewTransaction(s.state) 616 tr.Set("core", "refresh.hold", holdTime) 617 tr.Commit() 618 619 // with seed-time 620 s.state.Set("seed-time", t0.Add(-1*time.Hour)) 621 622 af := snapstate.NewAutoRefresh(s.state) 623 s.state.Unlock() 624 err := af.Ensure() 625 s.state.Lock() 626 c.Check(err, IsNil) 627 // no refresh 628 c.Check(s.store.ops, HasLen, 0) 629 lastRefresh, err := af.LastRefresh() 630 c.Assert(err, IsNil) 631 c.Check(lastRefresh.IsZero(), Equals, true) 632 633 // no seed-time 634 s.state.Set("seed-time", nil) 635 636 // fallback to time of executable 637 st, err := os.Stat("/proc/self/exe") 638 c.Assert(err, IsNil) 639 exeTime := st.ModTime() 640 641 af = snapstate.NewAutoRefresh(s.state) 642 s.state.Unlock() 643 err = af.Ensure() 644 s.state.Lock() 645 c.Check(err, IsNil) 646 // no refresh 647 c.Check(s.store.ops, HasLen, 0) 648 lastRefresh, err = af.LastRefresh() 649 c.Assert(err, IsNil) 650 c.Check(lastRefresh.Equal(exeTime), Equals, true) 651 652 // clear 653 s.state.Set("last-refresh", nil) 654 // use core last refresh time 655 coreCurrent := filepath.Join(dirs.SnapMountDir, "core", "current") 656 err = os.MkdirAll(coreCurrent, 0755) 657 c.Assert(err, IsNil) 658 st, err = os.Stat(coreCurrent) 659 c.Assert(err, IsNil) 660 coreRefreshed := st.ModTime() 661 662 af = snapstate.NewAutoRefresh(s.state) 663 s.state.Unlock() 664 err = af.Ensure() 665 s.state.Lock() 666 c.Check(err, IsNil) 667 // no refresh 668 c.Check(s.store.ops, HasLen, 0) 669 lastRefresh, err = af.LastRefresh() 670 c.Assert(err, IsNil) 671 c.Check(lastRefresh.Equal(coreRefreshed), Equals, true) 672 } 673 674 func (s *autoRefreshTestSuite) TestAtSeedPolicy(c *C) { 675 r := release.MockOnClassic(false) 676 defer r() 677 678 s.state.Lock() 679 defer s.state.Unlock() 680 681 af := snapstate.NewAutoRefresh(s.state) 682 683 // on core, does nothing 684 err := af.AtSeed() 685 c.Assert(err, IsNil) 686 c.Check(af.NextRefresh().IsZero(), Equals, true) 687 tr := config.NewTransaction(s.state) 688 var t1 time.Time 689 err = tr.Get("core", "refresh.hold", &t1) 690 c.Check(config.IsNoOption(err), Equals, true) 691 692 release.MockOnClassic(true) 693 now := time.Now() 694 // on classic it sets a refresh hold of 2h 695 err = af.AtSeed() 696 c.Assert(err, IsNil) 697 c.Check(af.NextRefresh().IsZero(), Equals, false) 698 tr = config.NewTransaction(s.state) 699 err = tr.Get("core", "refresh.hold", &t1) 700 c.Check(err, IsNil) 701 c.Check(t1.Before(now.Add(2*time.Hour)), Equals, false) 702 c.Check(t1.After(now.Add(2*time.Hour+5*time.Minute)), Equals, false) 703 704 // nop 705 err = af.AtSeed() 706 c.Assert(err, IsNil) 707 var t2 time.Time 708 tr = config.NewTransaction(s.state) 709 err = tr.Get("core", "refresh.hold", &t2) 710 c.Check(err, IsNil) 711 c.Check(t1.Equal(t2), Equals, true) 712 } 713 714 func (s *autoRefreshTestSuite) TestCanRefreshOnMetered(c *C) { 715 s.state.Lock() 716 defer s.state.Unlock() 717 718 can, err := snapstate.CanRefreshOnMeteredConnection(s.state) 719 c.Assert(can, Equals, true) 720 c.Assert(err, Equals, nil) 721 722 // enable holding refreshes when on metered connection 723 tr := config.NewTransaction(s.state) 724 err = tr.Set("core", "refresh.metered", "hold") 725 c.Assert(err, IsNil) 726 tr.Commit() 727 728 can, err = snapstate.CanRefreshOnMeteredConnection(s.state) 729 c.Assert(can, Equals, false) 730 c.Assert(err, Equals, nil) 731 732 // explicitly disable holding refreshes when on metered connection 733 tr = config.NewTransaction(s.state) 734 err = tr.Set("core", "refresh.metered", "") 735 c.Assert(err, IsNil) 736 tr.Commit() 737 738 can, err = snapstate.CanRefreshOnMeteredConnection(s.state) 739 c.Assert(can, Equals, true) 740 c.Assert(err, Equals, nil) 741 } 742 743 func (s *autoRefreshTestSuite) TestRefreshOnMeteredConnIsMetered(c *C) { 744 // pretend we're on metered connection 745 revert := snapstate.MockIsOnMeteredConnection(func() (bool, error) { 746 return true, nil 747 }) 748 defer revert() 749 750 s.state.Lock() 751 defer s.state.Unlock() 752 753 tr := config.NewTransaction(s.state) 754 tr.Set("core", "refresh.metered", "hold") 755 tr.Commit() 756 757 af := snapstate.NewAutoRefresh(s.state) 758 759 s.state.Set("last-refresh", time.Now().Add(-5*24*time.Hour)) 760 s.state.Unlock() 761 err := af.Ensure() 762 s.state.Lock() 763 c.Check(err, IsNil) 764 // no refresh 765 c.Check(s.store.ops, HasLen, 0) 766 767 c.Check(af.NextRefresh(), DeepEquals, time.Time{}) 768 769 // last refresh over 60 days ago, new one is launched regardless of 770 // connection being metered 771 s.state.Set("last-refresh", time.Now().Add(-61*24*time.Hour)) 772 s.state.Unlock() 773 err = af.Ensure() 774 s.state.Lock() 775 c.Check(err, IsNil) 776 c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) 777 } 778 779 func (s *autoRefreshTestSuite) TestRefreshOnMeteredConnNotMetered(c *C) { 780 // pretend we're on non-metered connection 781 revert := snapstate.MockIsOnMeteredConnection(func() (bool, error) { 782 return false, nil 783 }) 784 defer revert() 785 786 s.state.Lock() 787 defer s.state.Unlock() 788 789 tr := config.NewTransaction(s.state) 790 tr.Set("core", "refresh.metered", "hold") 791 tr.Commit() 792 793 af := snapstate.NewAutoRefresh(s.state) 794 795 s.state.Set("last-refresh", time.Now().Add(-5*24*time.Hour)) 796 s.state.Unlock() 797 err := af.Ensure() 798 s.state.Lock() 799 c.Check(err, IsNil) 800 c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) 801 } 802 803 func (s *autoRefreshTestSuite) TestInhibitRefreshWithinInhibitWindow(c *C) { 804 s.state.Lock() 805 defer s.state.Unlock() 806 807 si := &snap.SideInfo{RealName: "pkg", Revision: snap.R(1)} 808 info := &snap.Info{SideInfo: *si} 809 snapst := &snapstate.SnapState{ 810 Sequence: []*snap.SideInfo{si}, 811 Current: si.Revision, 812 } 813 err := snapstate.InhibitRefresh(s.state, snapst, info, func(si *snap.Info) error { 814 return &snapstate.BusySnapError{SnapName: "pkg"} 815 }) 816 c.Assert(err, ErrorMatches, `snap "pkg" has running apps or hooks`) 817 818 pending, _ := s.state.PendingWarnings() 819 c.Assert(pending, HasLen, 1) 820 c.Check(pending[0].String(), Equals, `snap "pkg" is currently in use. Its refresh will be postponed for up to 14 days to wait for the snap to no longer be in use.`) 821 } 822 823 func (s *autoRefreshTestSuite) TestInhibitRefreshWarnsAndRefreshesWhenOverdue(c *C) { 824 s.state.Lock() 825 defer s.state.Unlock() 826 827 instant := time.Now() 828 pastInstant := instant.Add(-snapstate.MaxInhibition * 2) 829 830 si := &snap.SideInfo{RealName: "pkg", Revision: snap.R(1)} 831 info := &snap.Info{SideInfo: *si} 832 snapst := &snapstate.SnapState{ 833 Sequence: []*snap.SideInfo{si}, 834 Current: si.Revision, 835 RefreshInhibitedTime: &pastInstant, 836 } 837 err := snapstate.InhibitRefresh(s.state, snapst, info, func(si *snap.Info) error { 838 return &snapstate.BusySnapError{SnapName: "pkg"} 839 }) 840 c.Assert(err, IsNil) 841 842 pending, _ := s.state.PendingWarnings() 843 c.Assert(pending, HasLen, 1) 844 c.Check(pending[0].String(), Equals, `snap "pkg" has been running for the maximum allowable 14 days since its refresh was postponed. It will now be refreshed.`) 845 }