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