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