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