github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/overlord/snapstate/autorefresh_gating_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "io/ioutil" 26 "os" 27 "path/filepath" 28 "time" 29 30 . "gopkg.in/check.v1" 31 "gopkg.in/tomb.v2" 32 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/interfaces" 35 "github.com/snapcore/snapd/interfaces/builtin" 36 "github.com/snapcore/snapd/logger" 37 "github.com/snapcore/snapd/osutil" 38 "github.com/snapcore/snapd/overlord/auth" 39 "github.com/snapcore/snapd/overlord/configstate/config" 40 "github.com/snapcore/snapd/overlord/hookstate" 41 "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 44 "github.com/snapcore/snapd/overlord/state" 45 "github.com/snapcore/snapd/release" 46 "github.com/snapcore/snapd/snap" 47 "github.com/snapcore/snapd/snap/snaptest" 48 "github.com/snapcore/snapd/store" 49 "github.com/snapcore/snapd/testutil" 50 ) 51 52 type autoRefreshGatingStore struct { 53 *fakeStore 54 refreshedSnaps []*snap.Info 55 } 56 57 type autorefreshGatingSuite struct { 58 testutil.BaseTest 59 state *state.State 60 repo *interfaces.Repository 61 store *autoRefreshGatingStore 62 } 63 64 var _ = Suite(&autorefreshGatingSuite{}) 65 66 func (s *autorefreshGatingSuite) SetUpTest(c *C) { 67 s.BaseTest.SetUpTest(c) 68 dirs.SetRootDir(c.MkDir()) 69 s.AddCleanup(func() { 70 dirs.SetRootDir("/") 71 }) 72 s.state = state.New(nil) 73 74 s.repo = interfaces.NewRepository() 75 for _, iface := range builtin.Interfaces() { 76 c.Assert(s.repo.AddInterface(iface), IsNil) 77 } 78 79 s.state.Lock() 80 defer s.state.Unlock() 81 ifacerepo.Replace(s.state, s.repo) 82 83 s.store = &autoRefreshGatingStore{fakeStore: &fakeStore{}} 84 snapstate.ReplaceStore(s.state, s.store) 85 s.state.Set("refresh-privacy-key", "privacy-key") 86 } 87 88 func (r *autoRefreshGatingStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) { 89 if assertQuery != nil { 90 panic("no assertion query support") 91 } 92 if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 { 93 panic("expected in test one action for each current snaps, and at least one snap") 94 } 95 for _, a := range actions { 96 if a.Action != "refresh" { 97 panic("expected refresh actions") 98 } 99 } 100 101 res := []store.SnapActionResult{} 102 for _, rs := range r.refreshedSnaps { 103 res = append(res, store.SnapActionResult{Info: rs}) 104 } 105 106 return res, nil, nil 107 } 108 109 func mockInstalledSnap(c *C, st *state.State, snapYaml string, hasHook bool) *snap.Info { 110 snapInfo := snaptest.MockSnap(c, string(snapYaml), &snap.SideInfo{ 111 Revision: snap.R(1), 112 }) 113 114 snapName := snapInfo.SnapName() 115 si := &snap.SideInfo{RealName: snapName, SnapID: "id", Revision: snap.R(1)} 116 snapstate.Set(st, snapName, &snapstate.SnapState{ 117 Active: true, 118 Sequence: []*snap.SideInfo{si}, 119 Current: si.Revision, 120 SnapType: string(snapInfo.Type()), 121 }) 122 123 if hasHook { 124 c.Assert(os.MkdirAll(snapInfo.HooksDir(), 0775), IsNil) 125 err := ioutil.WriteFile(filepath.Join(snapInfo.HooksDir(), "gate-auto-refresh"), nil, 0755) 126 c.Assert(err, IsNil) 127 } 128 return snapInfo 129 } 130 131 func mockLastRefreshed(c *C, st *state.State, refreshedTime string, snaps ...string) { 132 refreshed, err := time.Parse(time.RFC3339, refreshedTime) 133 c.Assert(err, IsNil) 134 for _, snapName := range snaps { 135 var snapst snapstate.SnapState 136 c.Assert(snapstate.Get(st, snapName, &snapst), IsNil) 137 snapst.LastRefreshTime = &refreshed 138 snapstate.Set(st, snapName, &snapst) 139 } 140 } 141 142 const baseSnapAyaml = `name: base-snap-a 143 type: base 144 ` 145 146 const snapAyaml = `name: snap-a 147 type: app 148 base: base-snap-a 149 ` 150 151 const baseSnapByaml = `name: base-snap-b 152 type: base 153 ` 154 155 const snapByaml = `name: snap-b 156 type: app 157 base: base-snap-b 158 version: 1 159 ` 160 161 const snapBByaml = `name: snap-bb 162 type: app 163 base: base-snap-b 164 version: 1 165 ` 166 167 const kernelYaml = `name: kernel 168 type: kernel 169 version: 1 170 ` 171 172 const gadget1Yaml = `name: gadget 173 type: gadget 174 version: 1 175 ` 176 177 const snapCyaml = `name: snap-c 178 type: app 179 version: 1 180 ` 181 182 const snapDyaml = `name: snap-d 183 type: app 184 version: 1 185 slots: 186 slot: desktop 187 ` 188 189 const snapEyaml = `name: snap-e 190 type: app 191 version: 1 192 base: other-base 193 plugs: 194 plug: desktop 195 ` 196 197 const snapFyaml = `name: snap-f 198 type: app 199 version: 1 200 plugs: 201 plug: desktop 202 ` 203 204 const snapGyaml = `name: snap-g 205 type: app 206 version: 1 207 base: other-base 208 plugs: 209 desktop: 210 mir: 211 ` 212 213 const coreYaml = `name: core 214 type: os 215 version: 1 216 slots: 217 desktop: 218 mir: 219 ` 220 221 const core18Yaml = `name: core18 222 type: os 223 version: 1 224 ` 225 226 const snapdYaml = `name: snapd 227 version: 1 228 type: snapd 229 slots: 230 desktop: 231 ` 232 233 func (s *autorefreshGatingSuite) TestHoldDurationLeft(c *C) { 234 now, err := time.Parse(time.RFC3339, "2021-06-03T10:00:00Z") 235 c.Assert(err, IsNil) 236 maxPostponement := time.Hour * 24 * 90 237 238 for i, tc := range []struct { 239 lastRefresh, firstHeld string 240 maxDuration string 241 expected string 242 }{ 243 { 244 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 245 "2021-06-03T10:00:00Z", // first held now 246 "48h", // max duration 247 "48h", // expected 248 }, 249 { 250 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 251 "2021-06-02T10:00:00Z", // first held (1 day ago) 252 "48h", // max duration 253 "24h", // expected 254 }, 255 { 256 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 257 "2021-06-01T10:00:00Z", // first held (2 days ago) 258 "48h", // max duration 259 "00h", // expected 260 }, 261 { 262 "2021-03-08T10:00:00Z", // last refreshed (almost 3 months ago) 263 "2021-06-01T10:00:00Z", // first held 264 "2160h", // max duration (90 days) 265 "72h", // expected 266 }, 267 { 268 "2021-03-04T10:00:00Z", // last refreshed 269 "2021-06-01T10:00:00Z", // first held (2 days ago) 270 "2160h", // max duration (90 days) 271 "-24h", // expected (refresh is 1 day overdue) 272 }, 273 { 274 "2021-06-01T10:00:00Z", // last refreshed (2 days ago) 275 "2021-06-03T10:00:00Z", // first held now 276 "2160h", // max duration (90 days) 277 "2112h", // expected (max minus 2 days) 278 }, 279 } { 280 lastRefresh, err := time.Parse(time.RFC3339, tc.lastRefresh) 281 c.Assert(err, IsNil) 282 firstHeld, err := time.Parse(time.RFC3339, tc.firstHeld) 283 c.Assert(err, IsNil) 284 maxDuration, err := time.ParseDuration(tc.maxDuration) 285 c.Assert(err, IsNil) 286 expected, err := time.ParseDuration(tc.expected) 287 c.Assert(err, IsNil) 288 289 left := snapstate.HoldDurationLeft(now, lastRefresh, firstHeld, maxDuration, maxPostponement) 290 c.Check(left, Equals, expected, Commentf("case #%d", i)) 291 } 292 } 293 294 func (s *autorefreshGatingSuite) TestLastRefreshedHelper(c *C) { 295 st := s.state 296 st.Lock() 297 defer st.Unlock() 298 299 inf := mockInstalledSnap(c, st, snapAyaml, false) 300 stat, err := os.Stat(inf.MountFile()) 301 c.Assert(err, IsNil) 302 303 refreshed, err := snapstate.LastRefreshed(st, "snap-a") 304 c.Assert(err, IsNil) 305 c.Check(refreshed, DeepEquals, stat.ModTime()) 306 307 t, err := time.Parse(time.RFC3339, "2021-01-01T10:00:00Z") 308 c.Assert(err, IsNil) 309 310 var snapst snapstate.SnapState 311 c.Assert(snapstate.Get(st, "snap-a", &snapst), IsNil) 312 snapst.LastRefreshTime = &t 313 snapstate.Set(st, "snap-a", &snapst) 314 315 refreshed, err = snapstate.LastRefreshed(st, "snap-a") 316 c.Assert(err, IsNil) 317 c.Check(refreshed, DeepEquals, t) 318 } 319 320 func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) { 321 st := s.state 322 st.Lock() 323 defer st.Unlock() 324 325 restore := snapstate.MockTimeNow(func() time.Time { 326 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 327 c.Assert(err, IsNil) 328 return t 329 }) 330 defer restore() 331 332 mockInstalledSnap(c, st, snapAyaml, false) 333 mockInstalledSnap(c, st, snapByaml, false) 334 mockInstalledSnap(c, st, snapCyaml, false) 335 mockInstalledSnap(c, st, snapDyaml, false) 336 mockInstalledSnap(c, st, snapEyaml, false) 337 mockInstalledSnap(c, st, snapFyaml, false) 338 339 mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f") 340 341 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 342 // this could be merged with the above HoldRefresh call, but it's fine if 343 // done separately too. 344 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-e"), IsNil) 345 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-e"), IsNil) 346 c.Assert(snapstate.HoldRefresh(st, "snap-f", 0, "snap-f"), IsNil) 347 348 var gating map[string]map[string]*snapstate.HoldState 349 c.Assert(st.Get("snaps-hold", &gating), IsNil) 350 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 351 "snap-b": { 352 // holding of other snaps for maxOtherHoldDuration (48h) 353 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 354 }, 355 "snap-c": { 356 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 357 }, 358 "snap-e": { 359 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 360 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 361 }, 362 "snap-f": { 363 // holding self set for maxPostponement minus 1 day due to last refresh. 364 "snap-f": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-07T10:00:00Z"), 365 }, 366 }) 367 } 368 369 func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) { 370 st := s.state 371 st.Lock() 372 defer st.Unlock() 373 374 lastRefreshed := "2021-05-09T10:00:00Z" 375 now := "2021-05-10T10:00:00Z" 376 restore := snapstate.MockTimeNow(func() time.Time { 377 t, err := time.Parse(time.RFC3339, now) 378 c.Assert(err, IsNil) 379 return t 380 }) 381 defer restore() 382 383 mockInstalledSnap(c, st, snapAyaml, false) 384 mockInstalledSnap(c, st, snapByaml, false) 385 // snap-a was last refreshed yesterday 386 mockLastRefreshed(c, st, lastRefreshed, "snap-a") 387 388 // hold it for just a bit (10h) initially 389 hold := time.Hour * 10 390 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 391 var gating map[string]map[string]*snapstate.HoldState 392 c.Assert(st.Get("snaps-hold", &gating), IsNil) 393 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 394 "snap-a": { 395 "snap-b": snapstate.MockHoldState(now, "2021-05-10T20:00:00Z"), 396 }, 397 }) 398 399 // holding for a shorter time is fine too 400 hold = time.Hour * 5 401 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 402 c.Assert(st.Get("snaps-hold", &gating), IsNil) 403 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 404 "snap-a": { 405 "snap-b": snapstate.MockHoldState(now, "2021-05-10T15:00:00Z"), 406 }, 407 }) 408 409 oldNow := now 410 411 // a refresh on next day 412 now = "2021-05-11T08:00:00Z" 413 414 // default hold time requested 415 hold = 0 416 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 417 c.Assert(st.Get("snaps-hold", &gating), IsNil) 418 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 419 "snap-a": { 420 // maximum for holding other snaps, but taking into consideration 421 // firstHeld time = "2021-05-10T10:00:00". 422 "snap-b": snapstate.MockHoldState(oldNow, "2021-05-12T10:00:00Z"), 423 }, 424 }) 425 } 426 427 func (s *autorefreshGatingSuite) TestHoldRefreshHelperCloseToMaxPostponement(c *C) { 428 st := s.state 429 st.Lock() 430 defer st.Unlock() 431 432 lastRefreshedStr := "2021-01-01T10:00:00Z" 433 lastRefreshed, err := time.Parse(time.RFC3339, lastRefreshedStr) 434 c.Assert(err, IsNil) 435 // we are 1 day before maxPostponent 436 now := lastRefreshed.Add(89 * time.Hour * 24) 437 438 restore := snapstate.MockTimeNow(func() time.Time { return now }) 439 defer restore() 440 441 mockInstalledSnap(c, st, snapAyaml, false) 442 mockInstalledSnap(c, st, snapByaml, false) 443 mockLastRefreshed(c, st, lastRefreshedStr, "snap-a") 444 445 // request default hold time 446 var hold time.Duration 447 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 448 449 var gating map[string]map[string]*snapstate.HoldState 450 c.Assert(st.Get("snaps-hold", &gating), IsNil) 451 c.Assert(gating, HasLen, 1) 452 c.Check(gating["snap-a"]["snap-b"].HoldUntil.String(), DeepEquals, lastRefreshed.Add(90*time.Hour*24).String()) 453 } 454 455 func (s *autorefreshGatingSuite) TestHoldRefreshExplicitHoldTime(c *C) { 456 st := s.state 457 st.Lock() 458 defer st.Unlock() 459 460 now := "2021-05-10T10:00:00Z" 461 restore := snapstate.MockTimeNow(func() time.Time { 462 t, err := time.Parse(time.RFC3339, now) 463 c.Assert(err, IsNil) 464 return t 465 }) 466 defer restore() 467 468 mockInstalledSnap(c, st, snapAyaml, false) 469 mockInstalledSnap(c, st, snapByaml, false) 470 471 hold := time.Hour * 24 * 3 472 // holding self for 3 days 473 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), IsNil) 474 475 // snap-b holds snap-a for 1 day 476 hold = time.Hour * 24 477 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 478 479 var gating map[string]map[string]*snapstate.HoldState 480 c.Assert(st.Get("snaps-hold", &gating), IsNil) 481 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 482 "snap-a": { 483 "snap-a": snapstate.MockHoldState(now, "2021-05-13T10:00:00Z"), 484 "snap-b": snapstate.MockHoldState(now, "2021-05-11T10:00:00Z"), 485 }, 486 }) 487 } 488 489 func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) { 490 st := s.state 491 st.Lock() 492 defer st.Unlock() 493 494 now := "2021-05-10T10:00:00Z" 495 restore := snapstate.MockTimeNow(func() time.Time { 496 t, err := time.Parse(time.RFC3339, now) 497 c.Assert(err, IsNil) 498 return t 499 }) 500 defer restore() 501 502 mockInstalledSnap(c, st, snapAyaml, false) 503 mockInstalledSnap(c, st, snapByaml, false) 504 // snap-b was refreshed a few days ago 505 mockLastRefreshed(c, st, "2021-05-01T10:00:00Z", "snap-b") 506 507 // holding itself 508 hold := time.Hour * 24 * 96 509 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-a" of 2304h0m0s by snap "snap-a" exceeds maximum holding time`) 510 511 // holding other snap 512 hold = time.Hour * 49 513 err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-b") 514 c.Check(err, ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`) 515 herr, ok := err.(*snapstate.HoldError) 516 c.Assert(ok, Equals, true) 517 c.Check(herr.SnapsInError, DeepEquals, map[string]snapstate.HoldDurationError{ 518 "snap-b": { 519 Err: fmt.Errorf(`requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`), 520 DurationLeft: 48 * time.Hour, 521 }, 522 }) 523 524 // hold for maximum allowed for other snaps 525 hold = time.Hour * 48 526 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), IsNil) 527 // 2 days passed since it was first held 528 now = "2021-05-12T10:00:00Z" 529 hold = time.Minute * 2 530 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-a" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) 531 532 // refreshed long time ago (> maxPostponement) 533 mockLastRefreshed(c, st, "2021-01-01T10:00:00Z", "snap-b") 534 hold = time.Hour * 2 535 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) 536 c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) 537 } 538 539 func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) { 540 st := s.state 541 st.Lock() 542 defer st.Unlock() 543 544 mockInstalledSnap(c, st, snapAyaml, false) 545 mockInstalledSnap(c, st, snapByaml, false) 546 mockInstalledSnap(c, st, snapCyaml, false) 547 mockInstalledSnap(c, st, snapDyaml, false) 548 549 mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-b", "snap-c", "snap-d") 550 551 restore := snapstate.MockTimeNow(func() time.Time { 552 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 553 c.Assert(err, IsNil) 554 return t 555 }) 556 defer restore() 557 558 // nothing is held initially 559 held, err := snapstate.HeldSnaps(st) 560 c.Assert(err, IsNil) 561 c.Check(held, IsNil) 562 563 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 564 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil) 565 // holding self 566 c.Assert(snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d"), IsNil) 567 568 held, err = snapstate.HeldSnaps(st) 569 c.Assert(err, IsNil) 570 c.Check(held, DeepEquals, map[string]bool{"snap-b": true, "snap-c": true, "snap-d": true}) 571 572 c.Assert(snapstate.ProceedWithRefresh(st, "snap-a"), IsNil) 573 574 held, err = snapstate.HeldSnaps(st) 575 c.Assert(err, IsNil) 576 c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-d": true}) 577 578 c.Assert(snapstate.ProceedWithRefresh(st, "snap-d"), IsNil) 579 held, err = snapstate.HeldSnaps(st) 580 c.Assert(err, IsNil) 581 c.Check(held, IsNil) 582 } 583 584 // Test that if all snaps cannot be held anymore, we don't hold only some of them 585 // e.g. is a snap and its base snap have updates and the snap wants to hold (itself 586 // and the base) but the base cannot be held, it doesn't make sense to refresh the 587 // base but hold the affected snap. 588 func (s *autorefreshGatingSuite) TestDontHoldSomeSnapsIfSomeFail(c *C) { 589 st := s.state 590 st.Lock() 591 defer st.Unlock() 592 593 // snap-b and snap-bb have base-snap-b base 594 mockInstalledSnap(c, st, snapByaml, useHook) 595 mockInstalledSnap(c, st, snapBByaml, useHook) 596 mockInstalledSnap(c, st, baseSnapByaml, noHook) 597 598 mockInstalledSnap(c, st, snapCyaml, useHook) 599 mockInstalledSnap(c, st, snapDyaml, useHook) 600 601 now := "2021-05-01T10:00:00Z" 602 restore := snapstate.MockTimeNow(func() time.Time { 603 t, err := time.Parse(time.RFC3339, now) 604 c.Assert(err, IsNil) 605 return t 606 }) 607 defer restore() 608 609 // snap-b, base-snap-b get refreshed and affect snap-b (gating snap) 610 c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b"), IsNil) 611 // unrealted snap-d gets refreshed and holds itself 612 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d"), IsNil) 613 614 // advance time by 49h 615 now = "2021-05-03T11:00:00Z" 616 // snap-b, base-snap-b and snap-c get refreshed and snap-a (gating snap) wants to hold them 617 c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b", "snap-c"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "base-snap-b" anymore, maximum refresh postponement exceeded`) 618 // snap-bb (gating snap) wants to hold base-snap-b as well and succeeds since it didn't exceed its holding time yet 619 c.Assert(snapstate.HoldRefresh(st, "snap-bb", 0, "base-snap-b"), IsNil) 620 621 held, err := snapstate.HeldSnaps(st) 622 c.Assert(err, IsNil) 623 // note, snap-b couldn't hold base-snap-b anymore so we didn't hold snap-b 624 // and snap-c. base-snap-b was held by snap-bb. 625 c.Check(held, DeepEquals, map[string]bool{ 626 "snap-d": true, 627 "base-snap-b": true, 628 }) 629 } 630 631 func (s *autorefreshGatingSuite) TestPruneGatingHelper(c *C) { 632 st := s.state 633 st.Lock() 634 defer st.Unlock() 635 636 restore := snapstate.MockTimeNow(func() time.Time { 637 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 638 c.Assert(err, IsNil) 639 return t 640 }) 641 defer restore() 642 643 mockInstalledSnap(c, st, snapAyaml, false) 644 mockInstalledSnap(c, st, snapByaml, false) 645 mockInstalledSnap(c, st, snapCyaml, false) 646 mockInstalledSnap(c, st, snapDyaml, false) 647 648 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 649 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil) 650 // sanity 651 held, err := snapstate.HeldSnaps(st) 652 c.Assert(err, IsNil) 653 c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-b": true, "snap-d": true}) 654 655 candidates := map[string]*snapstate.RefreshCandidate{"snap-c": {}} 656 657 // only snap-c has a refresh candidate, snap-b and snap-d should be forgotten. 658 c.Assert(snapstate.PruneGating(st, candidates), IsNil) 659 var gating map[string]map[string]*snapstate.HoldState 660 c.Assert(st.Get("snaps-hold", &gating), IsNil) 661 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 662 "snap-c": { 663 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 664 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 665 }, 666 }) 667 held, err = snapstate.HeldSnaps(st) 668 c.Assert(err, IsNil) 669 c.Check(held, DeepEquals, map[string]bool{"snap-c": true}) 670 } 671 672 func (s *autorefreshGatingSuite) TestPruneGatingHelperNoGating(c *C) { 673 st := s.state 674 st.Lock() 675 defer st.Unlock() 676 677 restore := snapstate.MockTimeNow(func() time.Time { 678 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 679 c.Assert(err, IsNil) 680 return t 681 }) 682 defer restore() 683 684 mockInstalledSnap(c, st, snapAyaml, false) 685 686 held, err := snapstate.HeldSnaps(st) 687 c.Assert(err, IsNil) 688 c.Check(held, HasLen, 0) 689 690 snapstate.MockTimeNow(func() time.Time { 691 c.Fatalf("not expected") 692 return time.Time{} 693 }) 694 695 candidates := map[string]*snapstate.RefreshCandidate{"snap-a": {}} 696 c.Assert(snapstate.PruneGating(st, candidates), IsNil) 697 held, err = snapstate.HeldSnaps(st) 698 c.Assert(err, IsNil) 699 c.Check(held, HasLen, 0) 700 } 701 702 func (s *autorefreshGatingSuite) TestResetGatingForRefreshedHelper(c *C) { 703 st := s.state 704 st.Lock() 705 defer st.Unlock() 706 707 restore := snapstate.MockTimeNow(func() time.Time { 708 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 709 c.Assert(err, IsNil) 710 return t 711 }) 712 defer restore() 713 714 mockInstalledSnap(c, st, snapAyaml, false) 715 mockInstalledSnap(c, st, snapByaml, false) 716 mockInstalledSnap(c, st, snapCyaml, false) 717 mockInstalledSnap(c, st, snapDyaml, false) 718 719 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 720 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil) 721 722 c.Assert(snapstate.ResetGatingForRefreshed(st, "snap-b", "snap-c"), IsNil) 723 var gating map[string]map[string]*snapstate.HoldState 724 c.Assert(st.Get("snaps-hold", &gating), IsNil) 725 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 726 "snap-d": { 727 // holding self set for maxPostponement (95 days - buffer = 90 days) 728 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-08T10:00:00Z"), 729 }, 730 }) 731 732 held, err := snapstate.HeldSnaps(st) 733 c.Assert(err, IsNil) 734 c.Check(held, DeepEquals, map[string]bool{"snap-d": true}) 735 } 736 737 func (s *autorefreshGatingSuite) TestPruneSnapsHold(c *C) { 738 st := s.state 739 st.Lock() 740 defer st.Unlock() 741 742 mockInstalledSnap(c, st, snapAyaml, false) 743 mockInstalledSnap(c, st, snapByaml, false) 744 mockInstalledSnap(c, st, snapCyaml, false) 745 mockInstalledSnap(c, st, snapDyaml, false) 746 747 // snap-a is holding itself and 3 other snaps 748 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-d"), IsNil) 749 // in addition, snap-c is held by snap-d. 750 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil) 751 752 // sanity check 753 held, err := snapstate.HeldSnaps(st) 754 c.Assert(err, IsNil) 755 c.Check(held, DeepEquals, map[string]bool{ 756 "snap-a": true, 757 "snap-b": true, 758 "snap-c": true, 759 "snap-d": true, 760 }) 761 762 c.Check(snapstate.PruneSnapsHold(st, "snap-a"), IsNil) 763 764 // after pruning snap-a, snap-c is still held. 765 held, err = snapstate.HeldSnaps(st) 766 c.Assert(err, IsNil) 767 c.Check(held, DeepEquals, map[string]bool{ 768 "snap-c": true, 769 }) 770 var gating map[string]map[string]*snapstate.HoldState 771 c.Assert(st.Get("snaps-hold", &gating), IsNil) 772 c.Assert(gating, HasLen, 1) 773 c.Check(gating["snap-c"], HasLen, 1) 774 c.Check(gating["snap-c"]["snap-d"], NotNil) 775 } 776 777 const useHook = true 778 const noHook = false 779 780 func checkGatingTask(c *C, task *state.Task, expected map[string]*snapstate.RefreshCandidate) { 781 c.Assert(task.Kind(), Equals, "conditional-auto-refresh") 782 var snaps map[string]*snapstate.RefreshCandidate 783 c.Assert(task.Get("snaps", &snaps), IsNil) 784 c.Check(snaps, DeepEquals, expected) 785 } 786 787 func (s *autorefreshGatingSuite) TestAffectedByBase(c *C) { 788 restore := release.MockOnClassic(true) 789 defer restore() 790 791 st := s.state 792 793 st.Lock() 794 defer st.Unlock() 795 mockInstalledSnap(c, s.state, snapAyaml, useHook) 796 baseSnapA := mockInstalledSnap(c, s.state, baseSnapAyaml, noHook) 797 // unrelated snaps 798 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 799 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 800 801 c.Assert(s.repo.AddSnap(snapB), IsNil) 802 803 updates := []string{baseSnapA.InstanceName()} 804 affected, err := snapstate.AffectedByRefresh(st, updates) 805 c.Assert(err, IsNil) 806 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 807 "snap-a": { 808 Base: true, 809 AffectingSnaps: map[string]bool{ 810 "base-snap-a": true, 811 }}}) 812 } 813 814 func (s *autorefreshGatingSuite) TestAffectedByCore(c *C) { 815 restore := release.MockOnClassic(true) 816 defer restore() 817 818 st := s.state 819 820 st.Lock() 821 defer st.Unlock() 822 snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook) 823 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 824 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 825 826 c.Assert(s.repo.AddSnap(core), IsNil) 827 c.Assert(s.repo.AddSnap(snapB), IsNil) 828 c.Assert(s.repo.AddSnap(snapC), IsNil) 829 830 updates := []string{core.InstanceName()} 831 affected, err := snapstate.AffectedByRefresh(st, updates) 832 c.Assert(err, IsNil) 833 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 834 "snap-c": { 835 Base: true, 836 AffectingSnaps: map[string]bool{ 837 "core": true, 838 }}}) 839 } 840 841 func (s *autorefreshGatingSuite) TestAffectedByKernel(c *C) { 842 restore := release.MockOnClassic(true) 843 defer restore() 844 845 st := s.state 846 847 st.Lock() 848 defer st.Unlock() 849 kernel := mockInstalledSnap(c, s.state, kernelYaml, noHook) 850 mockInstalledSnap(c, s.state, snapCyaml, useHook) 851 mockInstalledSnap(c, s.state, snapByaml, noHook) 852 853 updates := []string{kernel.InstanceName()} 854 affected, err := snapstate.AffectedByRefresh(st, updates) 855 c.Assert(err, IsNil) 856 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 857 "snap-c": { 858 Restart: true, 859 AffectingSnaps: map[string]bool{ 860 "kernel": true, 861 }}}) 862 } 863 864 func (s *autorefreshGatingSuite) TestAffectedBySelf(c *C) { 865 restore := release.MockOnClassic(true) 866 defer restore() 867 868 st := s.state 869 870 st.Lock() 871 defer st.Unlock() 872 873 snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook) 874 updates := []string{snapC.InstanceName()} 875 affected, err := snapstate.AffectedByRefresh(st, updates) 876 c.Assert(err, IsNil) 877 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 878 "snap-c": { 879 AffectingSnaps: map[string]bool{ 880 "snap-c": true, 881 }}}) 882 } 883 884 func (s *autorefreshGatingSuite) TestAffectedByGadget(c *C) { 885 restore := release.MockOnClassic(true) 886 defer restore() 887 888 st := s.state 889 890 st.Lock() 891 defer st.Unlock() 892 kernel := mockInstalledSnap(c, s.state, gadget1Yaml, noHook) 893 mockInstalledSnap(c, s.state, snapCyaml, useHook) 894 mockInstalledSnap(c, s.state, snapByaml, noHook) 895 896 updates := []string{kernel.InstanceName()} 897 affected, err := snapstate.AffectedByRefresh(st, updates) 898 c.Assert(err, IsNil) 899 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 900 "snap-c": { 901 Restart: true, 902 AffectingSnaps: map[string]bool{ 903 "gadget": true, 904 }}}) 905 } 906 907 func (s *autorefreshGatingSuite) TestAffectedBySlot(c *C) { 908 restore := release.MockOnClassic(true) 909 defer restore() 910 911 st := s.state 912 913 st.Lock() 914 defer st.Unlock() 915 916 snapD := mockInstalledSnap(c, s.state, snapDyaml, noHook) 917 snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook) 918 // unrelated snap 919 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 920 921 c.Assert(s.repo.AddSnap(snapF), IsNil) 922 c.Assert(s.repo.AddSnap(snapD), IsNil) 923 c.Assert(s.repo.AddSnap(snapE), IsNil) 924 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 925 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 926 c.Assert(err, IsNil) 927 928 updates := []string{snapD.InstanceName()} 929 affected, err := snapstate.AffectedByRefresh(st, updates) 930 c.Assert(err, IsNil) 931 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 932 "snap-e": { 933 Restart: true, 934 AffectingSnaps: map[string]bool{ 935 "snap-d": true, 936 }}}) 937 } 938 939 func (s *autorefreshGatingSuite) TestNotAffectedByCoreOrSnapdSlot(c *C) { 940 restore := release.MockOnClassic(true) 941 defer restore() 942 943 st := s.state 944 945 st.Lock() 946 defer st.Unlock() 947 948 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 949 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 950 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 951 952 c.Assert(s.repo.AddSnap(snapG), IsNil) 953 c.Assert(s.repo.AddSnap(core), IsNil) 954 c.Assert(s.repo.AddSnap(snapB), IsNil) 955 956 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "mir"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "mir"}} 957 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 958 c.Assert(err, IsNil) 959 960 updates := []string{core.InstanceName()} 961 affected, err := snapstate.AffectedByRefresh(st, updates) 962 c.Assert(err, IsNil) 963 c.Check(affected, HasLen, 0) 964 } 965 966 func (s *autorefreshGatingSuite) TestNotAffectedByPlugWithMountBackend(c *C) { 967 restore := release.MockOnClassic(true) 968 defer restore() 969 970 st := s.state 971 972 st.Lock() 973 defer st.Unlock() 974 975 snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook) 976 snapE := mockInstalledSnap(c, s.state, snapEyaml, noHook) 977 // unrelated snap 978 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 979 980 c.Assert(s.repo.AddSnap(snapF), IsNil) 981 c.Assert(s.repo.AddSnap(snapD), IsNil) 982 c.Assert(s.repo.AddSnap(snapE), IsNil) 983 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 984 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 985 c.Assert(err, IsNil) 986 987 // snapE has a plug using mount backend and is refreshed, this doesn't affect slot of snap-d. 988 updates := []string{snapE.InstanceName()} 989 affected, err := snapstate.AffectedByRefresh(st, updates) 990 c.Assert(err, IsNil) 991 c.Check(affected, HasLen, 0) 992 } 993 994 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendSnapdSlot(c *C) { 995 restore := release.MockOnClassic(true) 996 defer restore() 997 998 st := s.state 999 1000 st.Lock() 1001 defer st.Unlock() 1002 1003 snapdSnap := mockInstalledSnap(c, s.state, snapdYaml, noHook) 1004 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 1005 // unrelated snap 1006 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 1007 1008 c.Assert(s.repo.AddSnap(snapF), IsNil) 1009 c.Assert(s.repo.AddSnap(snapdSnap), IsNil) 1010 c.Assert(s.repo.AddSnap(snapG), IsNil) 1011 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "snapd", Name: "desktop"}} 1012 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 1013 c.Assert(err, IsNil) 1014 1015 // snapE has a plug using mount backend, refreshing snapd affects snapE. 1016 updates := []string{snapdSnap.InstanceName()} 1017 affected, err := snapstate.AffectedByRefresh(st, updates) 1018 c.Assert(err, IsNil) 1019 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 1020 "snap-g": { 1021 Restart: true, 1022 AffectingSnaps: map[string]bool{ 1023 "snapd": true, 1024 }}}) 1025 } 1026 1027 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendCoreSlot(c *C) { 1028 restore := release.MockOnClassic(true) 1029 defer restore() 1030 1031 st := s.state 1032 1033 st.Lock() 1034 defer st.Unlock() 1035 1036 coreSnap := mockInstalledSnap(c, s.state, coreYaml, noHook) 1037 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 1038 1039 c.Assert(s.repo.AddSnap(coreSnap), IsNil) 1040 c.Assert(s.repo.AddSnap(snapG), IsNil) 1041 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "desktop"}} 1042 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 1043 c.Assert(err, IsNil) 1044 1045 // snapG has a plug using mount backend, refreshing core affects snapE. 1046 updates := []string{coreSnap.InstanceName()} 1047 affected, err := snapstate.AffectedByRefresh(st, updates) 1048 c.Assert(err, IsNil) 1049 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 1050 "snap-g": { 1051 Restart: true, 1052 AffectingSnaps: map[string]bool{ 1053 "core": true, 1054 }}}) 1055 } 1056 1057 func (s *autorefreshGatingSuite) TestAffectedByBootBase(c *C) { 1058 restore := release.MockOnClassic(false) 1059 defer restore() 1060 1061 st := s.state 1062 1063 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 1064 defer r() 1065 1066 st.Lock() 1067 defer st.Unlock() 1068 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1069 mockInstalledSnap(c, s.state, snapByaml, useHook) 1070 mockInstalledSnap(c, s.state, snapDyaml, useHook) 1071 mockInstalledSnap(c, s.state, snapEyaml, useHook) 1072 core18 := mockInstalledSnap(c, s.state, core18Yaml, noHook) 1073 1074 updates := []string{core18.InstanceName()} 1075 affected, err := snapstate.AffectedByRefresh(st, updates) 1076 c.Assert(err, IsNil) 1077 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 1078 "snap-a": { 1079 Base: false, 1080 Restart: true, 1081 AffectingSnaps: map[string]bool{ 1082 "core18": true, 1083 }, 1084 }, 1085 "snap-b": { 1086 Base: false, 1087 Restart: true, 1088 AffectingSnaps: map[string]bool{ 1089 "core18": true, 1090 }, 1091 }, 1092 "snap-d": { 1093 Base: false, 1094 Restart: true, 1095 AffectingSnaps: map[string]bool{ 1096 "core18": true, 1097 }, 1098 }, 1099 "snap-e": { 1100 Base: false, 1101 Restart: true, 1102 AffectingSnaps: map[string]bool{ 1103 "core18": true, 1104 }}}) 1105 } 1106 1107 func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) { 1108 st := s.state 1109 st.Lock() 1110 defer st.Unlock() 1111 1112 affected := []string{"snap-a", "snap-b"} 1113 seenSnaps := make(map[string]bool) 1114 1115 ts := snapstate.CreateGateAutoRefreshHooks(st, affected) 1116 c.Assert(ts.Tasks(), HasLen, 2) 1117 1118 checkHook := func(t *state.Task) { 1119 c.Assert(t.Kind(), Equals, "run-hook") 1120 var hs hookstate.HookSetup 1121 c.Assert(t.Get("hook-setup", &hs), IsNil) 1122 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1123 c.Check(hs.Optional, Equals, true) 1124 seenSnaps[hs.Snap] = true 1125 } 1126 1127 checkHook(ts.Tasks()[0]) 1128 checkHook(ts.Tasks()[1]) 1129 1130 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 1131 } 1132 1133 func (s *autorefreshGatingSuite) TestAffectedByRefreshCandidates(c *C) { 1134 st := s.state 1135 st.Lock() 1136 defer st.Unlock() 1137 1138 mockInstalledSnap(c, st, snapAyaml, useHook) 1139 // unrelated snap 1140 mockInstalledSnap(c, st, snapByaml, useHook) 1141 1142 // no refresh-candidates in state 1143 affected, err := snapstate.AffectedByRefreshCandidates(st) 1144 c.Assert(err, IsNil) 1145 c.Check(affected, HasLen, 0) 1146 1147 candidates := map[string]*snapstate.RefreshCandidate{"snap-a": {}} 1148 st.Set("refresh-candidates", &candidates) 1149 1150 affected, err = snapstate.AffectedByRefreshCandidates(st) 1151 c.Assert(err, IsNil) 1152 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 1153 "snap-a": { 1154 AffectingSnaps: map[string]bool{ 1155 "snap-a": true, 1156 }}}) 1157 } 1158 1159 func (s *autorefreshGatingSuite) TestAffectingSnapsForAffectedByRefreshCandidates(c *C) { 1160 st := s.state 1161 st.Lock() 1162 defer st.Unlock() 1163 1164 mockInstalledSnap(c, st, snapAyaml, useHook) 1165 mockInstalledSnap(c, st, snapByaml, useHook) 1166 mockInstalledSnap(c, st, baseSnapByaml, useHook) 1167 1168 candidates := map[string]*snapstate.RefreshCandidate{ 1169 "snap-a": {}, 1170 "snap-b": {}, 1171 "base-snap-b": {}, 1172 } 1173 st.Set("refresh-candidates", &candidates) 1174 1175 affecting, err := snapstate.AffectingSnapsForAffectedByRefreshCandidates(st, "snap-b") 1176 c.Assert(err, IsNil) 1177 c.Check(affecting, DeepEquals, []string{"base-snap-b", "snap-b"}) 1178 } 1179 1180 func (s *autorefreshGatingSuite) TestAutorefreshPhase1FeatureFlag(c *C) { 1181 st := s.state 1182 st.Lock() 1183 defer st.Unlock() 1184 1185 st.Set("seeded", true) 1186 1187 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1188 defer restore() 1189 1190 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 1191 return nil, nil 1192 } 1193 defer func() { snapstate.AutoAliases = nil }() 1194 1195 s.store.refreshedSnaps = []*snap.Info{{ 1196 Architectures: []string{"all"}, 1197 SnapType: snap.TypeApp, 1198 SideInfo: snap.SideInfo{ 1199 RealName: "snap-a", 1200 Revision: snap.R(8), 1201 }, 1202 }} 1203 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1204 1205 // gate-auto-refresh-hook feature not enabled, expect old-style refresh. 1206 _, tss, err := snapstate.AutoRefresh(context.TODO(), st) 1207 c.Check(err, IsNil) 1208 c.Assert(tss, HasLen, 2) 1209 c.Check(tss[0].Tasks()[0].Kind(), Equals, "prerequisites") 1210 c.Check(tss[0].Tasks()[1].Kind(), Equals, "download-snap") 1211 c.Check(tss[1].Tasks()[0].Kind(), Equals, "check-rerefresh") 1212 1213 // enable gate-auto-refresh-hook feature 1214 tr := config.NewTransaction(s.state) 1215 tr.Set("core", "experimental.gate-auto-refresh-hook", true) 1216 tr.Commit() 1217 1218 _, tss, err = snapstate.AutoRefresh(context.TODO(), st) 1219 c.Check(err, IsNil) 1220 c.Assert(tss, HasLen, 2) 1221 task := tss[0].Tasks()[0] 1222 c.Check(task.Kind(), Equals, "conditional-auto-refresh") 1223 var toUpdate map[string]*snapstate.RefreshCandidate 1224 c.Assert(task.Get("snaps", &toUpdate), IsNil) 1225 seenSnaps := make(map[string]bool) 1226 for up := range toUpdate { 1227 seenSnaps[up] = true 1228 } 1229 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true}) 1230 c.Check(tss[1].Tasks()[0].Kind(), Equals, "run-hook") 1231 } 1232 1233 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) { 1234 s.store.refreshedSnaps = []*snap.Info{{ 1235 Architectures: []string{"all"}, 1236 SnapType: snap.TypeApp, 1237 SideInfo: snap.SideInfo{ 1238 RealName: "snap-a", 1239 Revision: snap.R(8), 1240 }, 1241 }, { 1242 Architectures: []string{"all"}, 1243 SnapType: snap.TypeBase, 1244 SideInfo: snap.SideInfo{ 1245 RealName: "base-snap-b", 1246 Revision: snap.R(3), 1247 }, 1248 }, { 1249 Architectures: []string{"all"}, 1250 SnapType: snap.TypeApp, 1251 SideInfo: snap.SideInfo{ 1252 RealName: "snap-c", 1253 Revision: snap.R(5), 1254 }, 1255 }} 1256 1257 st := s.state 1258 st.Lock() 1259 defer st.Unlock() 1260 1261 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1262 mockInstalledSnap(c, s.state, snapByaml, useHook) 1263 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1264 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1265 mockInstalledSnap(c, s.state, snapDyaml, noHook) 1266 1267 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1268 defer restore() 1269 1270 // pretend some snaps are held 1271 c.Assert(snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d"), IsNil) 1272 // sanity check 1273 heldSnaps, err := snapstate.HeldSnaps(st) 1274 c.Assert(err, IsNil) 1275 c.Check(heldSnaps, DeepEquals, map[string]bool{ 1276 "snap-a": true, 1277 "snap-d": true, 1278 }) 1279 1280 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 1281 c.Assert(err, IsNil) 1282 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a", "snap-c"}) 1283 c.Assert(tss, HasLen, 2) 1284 1285 c.Assert(tss[0].Tasks(), HasLen, 1) 1286 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1287 "snap-a": { 1288 SnapSetup: snapstate.SnapSetup{ 1289 Type: "app", 1290 PlugsOnly: true, 1291 Flags: snapstate.Flags{ 1292 IsAutoRefresh: true, 1293 }, 1294 SideInfo: &snap.SideInfo{ 1295 RealName: "snap-a", 1296 Revision: snap.R(8), 1297 }, 1298 DownloadInfo: &snap.DownloadInfo{}, 1299 }, 1300 }, 1301 "base-snap-b": { 1302 SnapSetup: snapstate.SnapSetup{ 1303 Type: "base", 1304 PlugsOnly: true, 1305 Flags: snapstate.Flags{ 1306 IsAutoRefresh: true, 1307 }, 1308 SideInfo: &snap.SideInfo{ 1309 RealName: "base-snap-b", 1310 Revision: snap.R(3), 1311 }, 1312 DownloadInfo: &snap.DownloadInfo{}, 1313 }, 1314 }, 1315 "snap-c": { 1316 SnapSetup: snapstate.SnapSetup{ 1317 Type: "app", 1318 PlugsOnly: true, 1319 Flags: snapstate.Flags{ 1320 IsAutoRefresh: true, 1321 }, 1322 SideInfo: &snap.SideInfo{ 1323 RealName: "snap-c", 1324 Revision: snap.R(5), 1325 }, 1326 DownloadInfo: &snap.DownloadInfo{}, 1327 }, 1328 }, 1329 }) 1330 1331 c.Assert(tss[1].Tasks(), HasLen, 2) 1332 1333 // check hooks for affected snaps 1334 seenSnaps := make(map[string]bool) 1335 var hs hookstate.HookSetup 1336 task := tss[1].Tasks()[0] 1337 c.Assert(task.Get("hook-setup", &hs), IsNil) 1338 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1339 seenSnaps[hs.Snap] = true 1340 1341 task = tss[1].Tasks()[1] 1342 c.Assert(task.Get("hook-setup", &hs), IsNil) 1343 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1344 seenSnaps[hs.Snap] = true 1345 1346 // hook for snap-a because it gets refreshed, for snap-b because its base 1347 // gets refreshed. snap-c is refreshed but doesn't have the hook. 1348 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 1349 1350 // check that refresh-candidates in the state were updated 1351 var candidates map[string]*snapstate.RefreshCandidate 1352 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1353 c.Assert(candidates, HasLen, 3) 1354 c.Check(candidates["snap-a"], NotNil) 1355 c.Check(candidates["base-snap-b"], NotNil) 1356 c.Check(candidates["snap-c"], NotNil) 1357 1358 // check that after autoRefreshPhase1 any held snaps that are not in refresh 1359 // candidates got removed. 1360 heldSnaps, err = snapstate.HeldSnaps(st) 1361 c.Assert(err, IsNil) 1362 // snap-d got removed from held snaps. 1363 c.Check(heldSnaps, DeepEquals, map[string]bool{ 1364 "snap-a": true, 1365 }) 1366 } 1367 1368 // this test demonstrates that affectedByRefresh uses current snap info (not 1369 // snap infos of store updates) by simulating a different base for the updated 1370 // snap from the store. 1371 func (s *autorefreshGatingSuite) TestAffectedByRefreshUsesCurrentSnapInfo(c *C) { 1372 s.store.refreshedSnaps = []*snap.Info{{ 1373 Architectures: []string{"all"}, 1374 SnapType: snap.TypeBase, 1375 SideInfo: snap.SideInfo{ 1376 RealName: "base-snap-b", 1377 Revision: snap.R(3), 1378 }, 1379 }, { 1380 Architectures: []string{"all"}, 1381 Base: "new-base", 1382 SnapType: snap.TypeApp, 1383 SideInfo: snap.SideInfo{ 1384 RealName: "snap-b", 1385 Revision: snap.R(5), 1386 }, 1387 }} 1388 1389 st := s.state 1390 st.Lock() 1391 defer st.Unlock() 1392 1393 mockInstalledSnap(c, s.state, snapByaml, useHook) 1394 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1395 1396 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1397 defer restore() 1398 1399 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 1400 c.Assert(err, IsNil) 1401 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-b"}) 1402 c.Assert(tss, HasLen, 2) 1403 1404 c.Assert(tss[0].Tasks(), HasLen, 1) 1405 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1406 "snap-b": { 1407 SnapSetup: snapstate.SnapSetup{ 1408 Type: "app", 1409 Base: "new-base", 1410 PlugsOnly: true, 1411 Flags: snapstate.Flags{ 1412 IsAutoRefresh: true, 1413 }, 1414 SideInfo: &snap.SideInfo{ 1415 RealName: "snap-b", 1416 Revision: snap.R(5), 1417 }, 1418 DownloadInfo: &snap.DownloadInfo{}, 1419 }, 1420 }, 1421 "base-snap-b": { 1422 SnapSetup: snapstate.SnapSetup{ 1423 Type: "base", 1424 PlugsOnly: true, 1425 Flags: snapstate.Flags{ 1426 IsAutoRefresh: true, 1427 }, 1428 SideInfo: &snap.SideInfo{ 1429 RealName: "base-snap-b", 1430 Revision: snap.R(3), 1431 }, 1432 DownloadInfo: &snap.DownloadInfo{}, 1433 }, 1434 }, 1435 }) 1436 1437 c.Assert(tss[1].Tasks(), HasLen, 1) 1438 var hs hookstate.HookSetup 1439 task := tss[1].Tasks()[0] 1440 c.Assert(task.Get("hook-setup", &hs), IsNil) 1441 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1442 c.Check(hs.Snap, Equals, "snap-b") 1443 1444 // check that refresh-candidates in the state were updated 1445 var candidates map[string]*snapstate.RefreshCandidate 1446 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1447 c.Assert(candidates, HasLen, 2) 1448 c.Check(candidates["snap-b"], NotNil) 1449 c.Check(candidates["base-snap-b"], NotNil) 1450 } 1451 1452 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1ConflictsFilteredOut(c *C) { 1453 s.store.refreshedSnaps = []*snap.Info{{ 1454 Architectures: []string{"all"}, 1455 SnapType: snap.TypeApp, 1456 SideInfo: snap.SideInfo{ 1457 RealName: "snap-a", 1458 Revision: snap.R(8), 1459 }, 1460 }, { 1461 Architectures: []string{"all"}, 1462 SnapType: snap.TypeBase, 1463 SideInfo: snap.SideInfo{ 1464 RealName: "snap-c", 1465 Revision: snap.R(5), 1466 }, 1467 }} 1468 1469 st := s.state 1470 st.Lock() 1471 defer st.Unlock() 1472 1473 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1474 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1475 1476 conflictChange := st.NewChange("conflicting change", "") 1477 conflictTask := st.NewTask("conflicting task", "") 1478 si := &snap.SideInfo{ 1479 RealName: "snap-c", 1480 Revision: snap.R(1), 1481 } 1482 sup := snapstate.SnapSetup{SideInfo: si} 1483 conflictTask.Set("snap-setup", sup) 1484 conflictChange.AddTask(conflictTask) 1485 1486 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1487 defer restore() 1488 1489 logbuf, restoreLogger := logger.MockLogger() 1490 defer restoreLogger() 1491 1492 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 1493 c.Assert(err, IsNil) 1494 c.Check(names, DeepEquals, []string{"snap-a"}) 1495 c.Assert(tss, HasLen, 2) 1496 1497 c.Assert(tss[0].Tasks(), HasLen, 1) 1498 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1499 "snap-a": { 1500 SnapSetup: snapstate.SnapSetup{ 1501 Type: "app", 1502 PlugsOnly: true, 1503 Flags: snapstate.Flags{ 1504 IsAutoRefresh: true, 1505 }, 1506 SideInfo: &snap.SideInfo{ 1507 RealName: "snap-a", 1508 Revision: snap.R(8), 1509 }, 1510 DownloadInfo: &snap.DownloadInfo{}, 1511 }}}) 1512 1513 c.Assert(tss[1].Tasks(), HasLen, 1) 1514 1515 c.Assert(logbuf.String(), testutil.Contains, `cannot refresh snap "snap-c": snap "snap-c" has "conflicting change" change in progress`) 1516 1517 seenSnaps := make(map[string]bool) 1518 var hs hookstate.HookSetup 1519 c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil) 1520 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1521 seenSnaps[hs.Snap] = true 1522 1523 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true}) 1524 1525 // check that refresh-candidates in the state were updated 1526 var candidates map[string]*snapstate.RefreshCandidate 1527 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1528 c.Assert(candidates, HasLen, 2) 1529 c.Check(candidates["snap-a"], NotNil) 1530 c.Check(candidates["snap-c"], NotNil) 1531 } 1532 1533 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1NoHooks(c *C) { 1534 s.store.refreshedSnaps = []*snap.Info{{ 1535 Architectures: []string{"all"}, 1536 SnapType: snap.TypeBase, 1537 SideInfo: snap.SideInfo{ 1538 RealName: "base-snap-b", 1539 Revision: snap.R(3), 1540 }, 1541 }, { 1542 Architectures: []string{"all"}, 1543 SnapType: snap.TypeBase, 1544 SideInfo: snap.SideInfo{ 1545 RealName: "snap-c", 1546 Revision: snap.R(5), 1547 }, 1548 }} 1549 1550 st := s.state 1551 st.Lock() 1552 defer st.Unlock() 1553 1554 mockInstalledSnap(c, s.state, snapByaml, noHook) 1555 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1556 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1557 1558 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1559 defer restore() 1560 1561 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 1562 c.Assert(err, IsNil) 1563 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-c"}) 1564 c.Assert(tss, HasLen, 1) 1565 1566 c.Assert(tss[0].Tasks(), HasLen, 1) 1567 c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh") 1568 } 1569 1570 func fakeReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) { 1571 info := &snap.Info{ 1572 SuggestedName: name, 1573 SideInfo: *si, 1574 Architectures: []string{"all"}, 1575 SnapType: snap.TypeApp, 1576 Epoch: snap.Epoch{}, 1577 } 1578 switch name { 1579 case "base-snap-b": 1580 info.SnapType = snap.TypeBase 1581 case "snap-a", "snap-b": 1582 info.Hooks = map[string]*snap.HookInfo{ 1583 "gate-auto-refresh": { 1584 Name: "gate-auto-refresh", 1585 Snap: info, 1586 }, 1587 } 1588 if name == "snap-b" { 1589 info.Base = "base-snap-b" 1590 } 1591 } 1592 return info, nil 1593 } 1594 1595 func (s *snapmgrTestSuite) testAutoRefreshPhase2(c *C, beforePhase1 func(), gateAutoRefreshHook func(snapName string), expected []string) *state.Change { 1596 st := s.state 1597 st.Lock() 1598 defer st.Unlock() 1599 1600 s.o.TaskRunner().AddHandler("run-hook", func(t *state.Task, tomb *tomb.Tomb) error { 1601 var hsup hookstate.HookSetup 1602 t.State().Lock() 1603 defer t.State().Unlock() 1604 c.Assert(t.Get("hook-setup", &hsup), IsNil) 1605 if hsup.Hook == "gate-auto-refresh" && gateAutoRefreshHook != nil { 1606 gateAutoRefreshHook(hsup.Snap) 1607 } 1608 return nil 1609 }, nil) 1610 1611 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1612 c.Fatal("unexpected call to installSize") 1613 return 0, nil 1614 }) 1615 defer restoreInstallSize() 1616 1617 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1618 fakeStore: s.fakeStore, 1619 refreshedSnaps: []*snap.Info{{ 1620 Architectures: []string{"all"}, 1621 SnapType: snap.TypeApp, 1622 SideInfo: snap.SideInfo{ 1623 RealName: "snap-a", 1624 Revision: snap.R(8), 1625 }, 1626 }, { 1627 Architectures: []string{"all"}, 1628 SnapType: snap.TypeBase, 1629 SideInfo: snap.SideInfo{ 1630 RealName: "base-snap-b", 1631 Revision: snap.R(3), 1632 }, 1633 }}}) 1634 1635 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1636 mockInstalledSnap(c, s.state, snapByaml, useHook) 1637 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1638 1639 snapstate.MockSnapReadInfo(fakeReadInfo) 1640 1641 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1642 defer restore() 1643 1644 if beforePhase1 != nil { 1645 beforePhase1() 1646 } 1647 1648 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 1649 c.Assert(err, IsNil) 1650 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1651 1652 chg := s.state.NewChange("refresh", "...") 1653 for _, ts := range tss { 1654 chg.AddAll(ts) 1655 } 1656 1657 s.state.Unlock() 1658 defer s.se.Stop() 1659 s.settle(c) 1660 s.state.Lock() 1661 1662 c.Check(chg.Status(), Equals, state.DoneStatus) 1663 c.Check(chg.Err(), IsNil) 1664 1665 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1666 1667 return chg 1668 } 1669 1670 func (s *snapmgrTestSuite) TestAutoRefreshPhase2(c *C) { 1671 expected := []string{ 1672 "conditional-auto-refresh", 1673 "run-hook [snap-a;gate-auto-refresh]", 1674 // snap-b hook is triggered because of base-snap-b refresh 1675 "run-hook [snap-b;gate-auto-refresh]", 1676 "prerequisites", 1677 "download-snap", 1678 "validate-snap", 1679 "mount-snap", 1680 "run-hook [base-snap-b;pre-refresh]", 1681 "stop-snap-services", 1682 "remove-aliases", 1683 "unlink-current-snap", 1684 "copy-snap-data", 1685 "setup-profiles", 1686 "link-snap", 1687 "auto-connect", 1688 "set-auto-aliases", 1689 "setup-aliases", 1690 "run-hook [base-snap-b;post-refresh]", 1691 "start-snap-services", 1692 "cleanup", 1693 "run-hook [base-snap-b;check-health]", 1694 "prerequisites", 1695 "download-snap", 1696 "validate-snap", 1697 "mount-snap", 1698 "run-hook [snap-a;pre-refresh]", 1699 "stop-snap-services", 1700 "remove-aliases", 1701 "unlink-current-snap", 1702 "copy-snap-data", 1703 "setup-profiles", 1704 "link-snap", 1705 "auto-connect", 1706 "set-auto-aliases", 1707 "setup-aliases", 1708 "run-hook [snap-a;post-refresh]", 1709 "start-snap-services", 1710 "cleanup", 1711 "run-hook [snap-a;configure]", 1712 "run-hook [snap-a;check-health]", 1713 "check-rerefresh", 1714 } 1715 1716 seenSnapsWithGateAutoRefreshHook := make(map[string]bool) 1717 1718 chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1719 seenSnapsWithGateAutoRefreshHook[snapName] = true 1720 }, expected) 1721 1722 c.Check(seenSnapsWithGateAutoRefreshHook, DeepEquals, map[string]bool{ 1723 "snap-a": true, 1724 "snap-b": true, 1725 }) 1726 1727 s.state.Lock() 1728 defer s.state.Unlock() 1729 1730 tasks := chg.Tasks() 1731 c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "base-snap-b", "snap-a" as needed`) 1732 1733 // all snaps refreshed, all removed from refresh-candidates. 1734 var candidates map[string]*snapstate.RefreshCandidate 1735 c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil) 1736 c.Assert(candidates, HasLen, 0) 1737 } 1738 1739 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Held(c *C) { 1740 logbuf, restoreLogger := logger.MockLogger() 1741 defer restoreLogger() 1742 1743 expected := []string{ 1744 "conditional-auto-refresh", 1745 "run-hook [snap-a;gate-auto-refresh]", 1746 // snap-b hook is triggered because of base-snap-b refresh 1747 "run-hook [snap-b;gate-auto-refresh]", 1748 "prerequisites", 1749 "download-snap", 1750 "validate-snap", 1751 "mount-snap", 1752 "run-hook [snap-a;pre-refresh]", 1753 "stop-snap-services", 1754 "remove-aliases", 1755 "unlink-current-snap", 1756 "copy-snap-data", 1757 "setup-profiles", 1758 "link-snap", 1759 "auto-connect", 1760 "set-auto-aliases", 1761 "setup-aliases", 1762 "run-hook [snap-a;post-refresh]", 1763 "start-snap-services", 1764 "cleanup", 1765 "run-hook [snap-a;configure]", 1766 "run-hook [snap-a;check-health]", 1767 "check-rerefresh", 1768 } 1769 1770 chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1771 if snapName == "snap-b" { 1772 // pretend than snap-b calls snapctl --hold to hold refresh of base-snap-b 1773 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1774 } 1775 }, expected) 1776 1777 s.state.Lock() 1778 defer s.state.Unlock() 1779 1780 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`) 1781 tasks := chg.Tasks() 1782 // no re-refresh for base-snap-b because it was held. 1783 c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "snap-a" as needed`) 1784 } 1785 1786 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Proceed(c *C) { 1787 logbuf, restoreLogger := logger.MockLogger() 1788 defer restoreLogger() 1789 1790 expected := []string{ 1791 "conditional-auto-refresh", 1792 "run-hook [snap-a;gate-auto-refresh]", 1793 // snap-b hook is triggered because of base-snap-b refresh 1794 "run-hook [snap-b;gate-auto-refresh]", 1795 "prerequisites", 1796 "download-snap", 1797 "validate-snap", 1798 "mount-snap", 1799 "run-hook [snap-a;pre-refresh]", 1800 "stop-snap-services", 1801 "remove-aliases", 1802 "unlink-current-snap", 1803 "copy-snap-data", 1804 "setup-profiles", 1805 "link-snap", 1806 "auto-connect", 1807 "set-auto-aliases", 1808 "setup-aliases", 1809 "run-hook [snap-a;post-refresh]", 1810 "start-snap-services", 1811 "cleanup", 1812 "run-hook [snap-a;configure]", 1813 "run-hook [snap-a;check-health]", 1814 "check-rerefresh", 1815 } 1816 1817 s.testAutoRefreshPhase2(c, func() { 1818 // pretend that snap-a and base-snap-b are initially held 1819 c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) 1820 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1821 }, func(snapName string) { 1822 if snapName == "snap-a" { 1823 // pretend than snap-a calls snapctl --proceed 1824 c.Assert(snapstate.ProceedWithRefresh(s.state, "snap-a"), IsNil) 1825 } 1826 // note, do nothing about snap-b which just keeps its hold state in 1827 // the test, but if we were using real gate-auto-refresh hook 1828 // handler, the default behavior for snap-b if it doesn't call --hold 1829 // would be to proceed (hook handler would take care of that). 1830 }, expected) 1831 1832 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`) 1833 } 1834 1835 func (s *snapmgrTestSuite) TestAutoRefreshPhase2AllHeld(c *C) { 1836 logbuf, restoreLogger := logger.MockLogger() 1837 defer restoreLogger() 1838 1839 expected := []string{ 1840 "conditional-auto-refresh", 1841 "run-hook [snap-a;gate-auto-refresh]", 1842 // snap-b hook is triggered because of base-snap-b refresh 1843 "run-hook [snap-b;gate-auto-refresh]", 1844 } 1845 1846 s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1847 switch snapName { 1848 case "snap-b": 1849 // pretend that snap-b calls snapctl --hold to hold refresh of base-snap-b 1850 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1851 case "snap-a": 1852 // pretend that snap-a calls snapctl --hold to hold itself 1853 c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) 1854 default: 1855 c.Fatalf("unexpected snap %q", snapName) 1856 } 1857 }, expected) 1858 1859 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b,snap-a`) 1860 } 1861 1862 func (s *snapmgrTestSuite) testAutoRefreshPhase2DiskSpaceCheck(c *C, fail bool) { 1863 st := s.state 1864 st.Lock() 1865 defer st.Unlock() 1866 1867 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 1868 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 1869 if fail { 1870 return &osutil.NotEnoughDiskSpaceError{} 1871 } 1872 return nil 1873 }) 1874 defer restore() 1875 1876 var installSizeCalled bool 1877 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1878 installSizeCalled = true 1879 seen := map[string]bool{} 1880 for _, sn := range snaps { 1881 seen[sn.InstanceName()] = true 1882 } 1883 c.Check(seen, DeepEquals, map[string]bool{ 1884 "base-snap-b": true, 1885 "snap-a": true, 1886 }) 1887 return 123, nil 1888 }) 1889 defer restoreInstallSize() 1890 1891 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 1892 defer restoreModel() 1893 1894 tr := config.NewTransaction(s.state) 1895 tr.Set("core", "experimental.check-disk-space-refresh", true) 1896 tr.Commit() 1897 1898 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1899 fakeStore: s.fakeStore, 1900 refreshedSnaps: []*snap.Info{{ 1901 Architectures: []string{"all"}, 1902 SnapType: snap.TypeApp, 1903 SideInfo: snap.SideInfo{ 1904 RealName: "snap-a", 1905 Revision: snap.R(8), 1906 }, 1907 }, { 1908 Architectures: []string{"all"}, 1909 SnapType: snap.TypeBase, 1910 SideInfo: snap.SideInfo{ 1911 RealName: "base-snap-b", 1912 Revision: snap.R(3), 1913 }, 1914 }}}) 1915 1916 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1917 mockInstalledSnap(c, s.state, snapByaml, useHook) 1918 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1919 1920 snapstate.MockSnapReadInfo(fakeReadInfo) 1921 1922 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 1923 c.Assert(err, IsNil) 1924 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1925 1926 chg := s.state.NewChange("refresh", "...") 1927 for _, ts := range tss { 1928 chg.AddAll(ts) 1929 } 1930 1931 s.state.Unlock() 1932 defer s.se.Stop() 1933 s.settle(c) 1934 s.state.Lock() 1935 1936 c.Check(installSizeCalled, Equals, true) 1937 if fail { 1938 c.Check(chg.Status(), Equals, state.ErrorStatus) 1939 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- Run auto-refresh for ready snaps \(insufficient space.*`) 1940 } else { 1941 c.Check(chg.Status(), Equals, state.DoneStatus) 1942 c.Check(chg.Err(), IsNil) 1943 } 1944 } 1945 1946 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceError(c *C) { 1947 fail := true 1948 s.testAutoRefreshPhase2DiskSpaceCheck(c, fail) 1949 } 1950 1951 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceHappy(c *C) { 1952 var nofail bool 1953 s.testAutoRefreshPhase2DiskSpaceCheck(c, nofail) 1954 } 1955 1956 // XXX: this case is probably artificial; with proper conflict prevention 1957 // we shouldn't get conflicts from doInstall in phase2. 1958 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Conflict(c *C) { 1959 st := s.state 1960 st.Lock() 1961 defer st.Unlock() 1962 1963 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1964 fakeStore: s.fakeStore, 1965 refreshedSnaps: []*snap.Info{{ 1966 Architectures: []string{"all"}, 1967 SnapType: snap.TypeApp, 1968 SideInfo: snap.SideInfo{ 1969 RealName: "snap-a", 1970 Revision: snap.R(8), 1971 }, 1972 }, { 1973 Architectures: []string{"all"}, 1974 SnapType: snap.TypeBase, 1975 SideInfo: snap.SideInfo{ 1976 RealName: "base-snap-b", 1977 Revision: snap.R(3), 1978 }, 1979 }}}) 1980 1981 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1982 mockInstalledSnap(c, s.state, snapByaml, useHook) 1983 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1984 1985 snapstate.MockSnapReadInfo(fakeReadInfo) 1986 1987 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1988 defer restore() 1989 1990 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 1991 c.Assert(err, IsNil) 1992 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1993 1994 chg := s.state.NewChange("refresh", "...") 1995 for _, ts := range tss { 1996 chg.AddAll(ts) 1997 } 1998 1999 conflictChange := st.NewChange("conflicting change", "") 2000 conflictTask := st.NewTask("conflicting task", "") 2001 si := &snap.SideInfo{ 2002 RealName: "snap-a", 2003 Revision: snap.R(1), 2004 } 2005 sup := snapstate.SnapSetup{SideInfo: si} 2006 conflictTask.Set("snap-setup", sup) 2007 conflictChange.AddTask(conflictTask) 2008 conflictTask.WaitFor(tss[0].Tasks()[0]) 2009 2010 s.state.Unlock() 2011 defer s.se.Stop() 2012 s.settle(c) 2013 s.state.Lock() 2014 2015 c.Assert(chg.Status(), Equals, state.DoneStatus) 2016 c.Check(chg.Err(), IsNil) 2017 2018 // no refresh of snap-a because of the conflict. 2019 expected := []string{ 2020 "conditional-auto-refresh", 2021 "run-hook [snap-a;gate-auto-refresh]", 2022 // snap-b hook is triggered because of base-snap-b refresh 2023 "run-hook [snap-b;gate-auto-refresh]", 2024 "prerequisites", 2025 "download-snap", 2026 "validate-snap", 2027 "mount-snap", 2028 "run-hook [base-snap-b;pre-refresh]", 2029 "stop-snap-services", 2030 "remove-aliases", 2031 "unlink-current-snap", 2032 "copy-snap-data", 2033 "setup-profiles", 2034 "link-snap", 2035 "auto-connect", 2036 "set-auto-aliases", 2037 "setup-aliases", 2038 "run-hook [base-snap-b;post-refresh]", 2039 "start-snap-services", 2040 "cleanup", 2041 "run-hook [base-snap-b;check-health]", 2042 "check-rerefresh", 2043 } 2044 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 2045 } 2046 2047 func (s *snapmgrTestSuite) TestAutoRefreshPhase2ConflictOtherSnapOp(c *C) { 2048 st := s.state 2049 st.Lock() 2050 defer st.Unlock() 2051 2052 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 2053 fakeStore: s.fakeStore, 2054 refreshedSnaps: []*snap.Info{{ 2055 Architectures: []string{"all"}, 2056 SnapType: snap.TypeApp, 2057 SideInfo: snap.SideInfo{ 2058 RealName: "snap-a", 2059 Revision: snap.R(8), 2060 }, 2061 }}}) 2062 2063 mockInstalledSnap(c, s.state, snapAyaml, useHook) 2064 2065 snapstate.MockSnapReadInfo(fakeReadInfo) 2066 2067 restore := snapstatetest.MockDeviceModel(DefaultModel()) 2068 defer restore() 2069 2070 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 2071 c.Assert(err, IsNil) 2072 c.Check(names, DeepEquals, []string{"snap-a"}) 2073 2074 chg := s.state.NewChange("fake-auto-refresh", "...") 2075 for _, ts := range tss { 2076 chg.AddAll(ts) 2077 } 2078 2079 s.state.Unlock() 2080 // run first task 2081 s.se.Ensure() 2082 s.se.Wait() 2083 2084 s.state.Lock() 2085 2086 _, err = snapstate.Remove(s.state, "snap-a", snap.R(8), nil) 2087 c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{ 2088 ChangeKind: "fake-auto-refresh", 2089 Snap: "snap-a", 2090 }) 2091 2092 _, err = snapstate.Update(s.state, "snap-a", nil, 0, snapstate.Flags{}) 2093 c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{ 2094 ChangeKind: "fake-auto-refresh", 2095 Snap: "snap-a", 2096 }) 2097 2098 // only 2 tasks because we don't run settle() so conditional-auto-refresh 2099 // doesn't run and no new tasks get created. 2100 expected := []string{ 2101 "conditional-auto-refresh", 2102 "run-hook [snap-a;gate-auto-refresh]", 2103 } 2104 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 2105 } 2106 2107 func (s *snapmgrTestSuite) TestAutoRefreshPhase2GatedSnaps(c *C) { 2108 st := s.state 2109 st.Lock() 2110 defer st.Unlock() 2111 2112 restore := snapstate.MockSnapsToRefresh(func(gatingTask *state.Task) ([]*snapstate.RefreshCandidate, error) { 2113 c.Assert(gatingTask.Kind(), Equals, "conditional-auto-refresh") 2114 var candidates map[string]*snapstate.RefreshCandidate 2115 c.Assert(gatingTask.Get("snaps", &candidates), IsNil) 2116 seenSnaps := make(map[string]bool) 2117 var filteredByGatingHooks []*snapstate.RefreshCandidate 2118 for _, cand := range candidates { 2119 seenSnaps[cand.InstanceName()] = true 2120 if cand.InstanceName() == "snap-a" { 2121 continue 2122 } 2123 filteredByGatingHooks = append(filteredByGatingHooks, cand) 2124 } 2125 c.Check(seenSnaps, DeepEquals, map[string]bool{ 2126 "snap-a": true, 2127 "base-snap-b": true, 2128 }) 2129 return filteredByGatingHooks, nil 2130 }) 2131 defer restore() 2132 2133 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 2134 fakeStore: s.fakeStore, 2135 refreshedSnaps: []*snap.Info{ 2136 { 2137 Architectures: []string{"all"}, 2138 SnapType: snap.TypeApp, 2139 SideInfo: snap.SideInfo{ 2140 RealName: "snap-a", 2141 Revision: snap.R(8), 2142 }, 2143 }, { 2144 Architectures: []string{"all"}, 2145 SnapType: snap.TypeBase, 2146 SideInfo: snap.SideInfo{ 2147 RealName: "base-snap-b", 2148 Revision: snap.R(3), 2149 }, 2150 }, 2151 }}) 2152 2153 mockInstalledSnap(c, s.state, snapAyaml, useHook) 2154 mockInstalledSnap(c, s.state, snapByaml, useHook) 2155 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 2156 2157 snapstate.MockSnapReadInfo(fakeReadInfo) 2158 2159 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 2160 defer restoreModel() 2161 2162 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st, "") 2163 c.Assert(err, IsNil) 2164 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 2165 2166 chg := s.state.NewChange("refresh", "...") 2167 for _, ts := range tss { 2168 chg.AddAll(ts) 2169 } 2170 2171 s.state.Unlock() 2172 defer s.se.Stop() 2173 s.settle(c) 2174 s.state.Lock() 2175 2176 c.Assert(chg.Status(), Equals, state.DoneStatus) 2177 c.Check(chg.Err(), IsNil) 2178 2179 expected := []string{ 2180 "conditional-auto-refresh", 2181 "run-hook [snap-a;gate-auto-refresh]", 2182 // snap-b hook is triggered because of base-snap-b refresh 2183 "run-hook [snap-b;gate-auto-refresh]", 2184 "prerequisites", 2185 "download-snap", 2186 "validate-snap", 2187 "mount-snap", 2188 "run-hook [base-snap-b;pre-refresh]", 2189 "stop-snap-services", 2190 "remove-aliases", 2191 "unlink-current-snap", 2192 "copy-snap-data", 2193 "setup-profiles", 2194 "link-snap", 2195 "auto-connect", 2196 "set-auto-aliases", 2197 "setup-aliases", 2198 "run-hook [base-snap-b;post-refresh]", 2199 "start-snap-services", 2200 "cleanup", 2201 "run-hook [base-snap-b;check-health]", 2202 "check-rerefresh", 2203 } 2204 tasks := chg.Tasks() 2205 verifyPhasedAutorefreshTasks(c, tasks, expected) 2206 // no re-refresh for snap-a because it was held. 2207 c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "base-snap-b" as needed`) 2208 2209 // only snap-a remains in refresh-candidates because it was held; 2210 // base-snap-b got pruned (was refreshed). 2211 var candidates map[string]*snapstate.RefreshCandidate 2212 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 2213 c.Assert(candidates, HasLen, 1) 2214 c.Check(candidates["snap-a"], NotNil) 2215 } 2216 2217 func (s *snapmgrTestSuite) TestAutoRefreshForGatingSnapErrorAutoRefreshInProgress(c *C) { 2218 st := s.state 2219 st.Lock() 2220 defer st.Unlock() 2221 2222 chg := st.NewChange("auto-refresh", "...") 2223 task := st.NewTask("foo", "...") 2224 chg.AddTask(task) 2225 2226 c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-a"), ErrorMatches, `there is an auto-refresh in progress`) 2227 } 2228 2229 func (s *snapmgrTestSuite) TestAutoRefreshForGatingSnapErrorNothingHeld(c *C) { 2230 st := s.state 2231 st.Lock() 2232 defer st.Unlock() 2233 2234 c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-a"), ErrorMatches, `no snaps are held by snap "snap-a"`) 2235 } 2236 2237 func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnap(c *C) { 2238 s.store.refreshedSnaps = []*snap.Info{{ 2239 Architectures: []string{"all"}, 2240 SnapType: snap.TypeApp, 2241 SideInfo: snap.SideInfo{ 2242 RealName: "snap-a", 2243 Revision: snap.R(8), 2244 }, 2245 }, { 2246 Architectures: []string{"all"}, 2247 SnapType: snap.TypeApp, 2248 Base: "base-snap-b", 2249 SideInfo: snap.SideInfo{ 2250 RealName: "snap-b", 2251 Revision: snap.R(2), 2252 }, 2253 }, { 2254 Architectures: []string{"all"}, 2255 SnapType: snap.TypeBase, 2256 SideInfo: snap.SideInfo{ 2257 RealName: "base-snap-b", 2258 Revision: snap.R(3), 2259 }, 2260 }} 2261 2262 st := s.state 2263 st.Lock() 2264 defer st.Unlock() 2265 2266 mockInstalledSnap(c, s.state, snapAyaml, useHook) 2267 mockInstalledSnap(c, s.state, snapByaml, useHook) 2268 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 2269 2270 restore := snapstatetest.MockDeviceModel(DefaultModel()) 2271 defer restore() 2272 2273 // pretend some snaps are held 2274 c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil) 2275 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil) 2276 2277 lastRefreshTime := time.Now().Add(-99 * time.Hour) 2278 st.Set("last-refresh", lastRefreshTime) 2279 2280 // pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed) 2281 c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil) 2282 2283 changes := st.Changes() 2284 c.Assert(changes, HasLen, 1) 2285 chg := changes[0] 2286 c.Assert(chg.Kind(), Equals, "auto-refresh") 2287 c.Check(chg.Summary(), Equals, `Auto-refresh snaps "base-snap-b", "snap-b"`) 2288 var snapNames []string 2289 var apiData map[string]interface{} 2290 c.Assert(chg.Get("snap-names", &snapNames), IsNil) 2291 c.Check(snapNames, DeepEquals, []string{"base-snap-b", "snap-b"}) 2292 c.Assert(chg.Get("api-data", &apiData), IsNil) 2293 c.Check(apiData, DeepEquals, map[string]interface{}{ 2294 "snap-names": []interface{}{"base-snap-b", "snap-b"}, 2295 }) 2296 2297 tasks := chg.Tasks() 2298 c.Assert(tasks, HasLen, 2) 2299 conditionalRefreshTask := tasks[0] 2300 checkGatingTask(c, conditionalRefreshTask, map[string]*snapstate.RefreshCandidate{ 2301 "base-snap-b": { 2302 SnapSetup: snapstate.SnapSetup{ 2303 Type: "base", 2304 PlugsOnly: true, 2305 Flags: snapstate.Flags{ 2306 IsAutoRefresh: true, 2307 }, 2308 SideInfo: &snap.SideInfo{ 2309 RealName: "base-snap-b", 2310 Revision: snap.R(3), 2311 }, 2312 DownloadInfo: &snap.DownloadInfo{}, 2313 }, 2314 }, 2315 "snap-b": { 2316 SnapSetup: snapstate.SnapSetup{ 2317 Type: "app", 2318 Base: "base-snap-b", 2319 PlugsOnly: true, 2320 Flags: snapstate.Flags{ 2321 IsAutoRefresh: true, 2322 }, 2323 SideInfo: &snap.SideInfo{ 2324 RealName: "snap-b", 2325 Revision: snap.R(2), 2326 }, 2327 DownloadInfo: &snap.DownloadInfo{}, 2328 }, 2329 }, 2330 }) 2331 2332 // the gate-auto-refresh hook task for snap-b is present 2333 c.Check(tasks[1].Kind(), Equals, "run-hook") 2334 var hs hookstate.HookSetup 2335 c.Assert(tasks[1].Get("hook-setup", &hs), IsNil) 2336 c.Check(hs.Hook, Equals, "gate-auto-refresh") 2337 c.Check(hs.Snap, Equals, "snap-b") 2338 c.Check(hs.Optional, Equals, true) 2339 2340 // last-refresh wasn't modified 2341 var lr time.Time 2342 st.Get("last-refresh", &lr) 2343 c.Check(lr.Equal(lastRefreshTime), Equals, true) 2344 } 2345 2346 func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapMoreAffectedSnaps(c *C) { 2347 s.store.refreshedSnaps = []*snap.Info{{ 2348 Architectures: []string{"all"}, 2349 SnapType: snap.TypeApp, 2350 SideInfo: snap.SideInfo{ 2351 RealName: "snap-a", 2352 Revision: snap.R(8), 2353 }, 2354 }, { 2355 Architectures: []string{"all"}, 2356 SnapType: snap.TypeBase, 2357 SideInfo: snap.SideInfo{ 2358 RealName: "base-snap-b", 2359 Revision: snap.R(3), 2360 }, 2361 }, { 2362 Architectures: []string{"all"}, 2363 SnapType: snap.TypeApp, 2364 Base: "base-snap-b", 2365 SideInfo: snap.SideInfo{ 2366 RealName: "snap-b", 2367 Revision: snap.R(2), 2368 }, 2369 }} 2370 2371 st := s.state 2372 st.Lock() 2373 defer st.Unlock() 2374 2375 mockInstalledSnap(c, s.state, snapAyaml, useHook) 2376 mockInstalledSnap(c, s.state, snapByaml, useHook) 2377 mockInstalledSnap(c, s.state, snapBByaml, useHook) 2378 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 2379 2380 restore := snapstatetest.MockDeviceModel(DefaultModel()) 2381 defer restore() 2382 2383 // pretend snap-b holds base-snap-b. 2384 c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil) 2385 2386 // pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed) 2387 c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil) 2388 2389 changes := st.Changes() 2390 c.Assert(changes, HasLen, 1) 2391 chg := changes[0] 2392 c.Assert(chg.Kind(), Equals, "auto-refresh") 2393 c.Check(chg.Summary(), Equals, `Auto-refresh snaps "base-snap-b", "snap-b"`) 2394 var snapNames []string 2395 var apiData map[string]interface{} 2396 c.Assert(chg.Get("snap-names", &snapNames), IsNil) 2397 c.Check(snapNames, DeepEquals, []string{"base-snap-b", "snap-b"}) 2398 c.Assert(chg.Get("api-data", &apiData), IsNil) 2399 c.Check(apiData, DeepEquals, map[string]interface{}{ 2400 "snap-names": []interface{}{"base-snap-b", "snap-b"}, 2401 }) 2402 2403 tasks := chg.Tasks() 2404 c.Assert(tasks, HasLen, 3) 2405 conditionalRefreshTask := tasks[0] 2406 checkGatingTask(c, conditionalRefreshTask, map[string]*snapstate.RefreshCandidate{ 2407 "base-snap-b": { 2408 SnapSetup: snapstate.SnapSetup{ 2409 Type: "base", 2410 PlugsOnly: true, 2411 Flags: snapstate.Flags{ 2412 IsAutoRefresh: true, 2413 }, 2414 SideInfo: &snap.SideInfo{ 2415 RealName: "base-snap-b", 2416 Revision: snap.R(3), 2417 }, 2418 DownloadInfo: &snap.DownloadInfo{}, 2419 }, 2420 }, 2421 "snap-b": { 2422 SnapSetup: snapstate.SnapSetup{ 2423 Type: "app", 2424 Base: "base-snap-b", 2425 PlugsOnly: true, 2426 Flags: snapstate.Flags{ 2427 IsAutoRefresh: true, 2428 }, 2429 SideInfo: &snap.SideInfo{ 2430 RealName: "snap-b", 2431 Revision: snap.R(2), 2432 }, 2433 DownloadInfo: &snap.DownloadInfo{}, 2434 }, 2435 }, 2436 }) 2437 2438 seenSnaps := make(map[string]bool) 2439 2440 // check that the gate-auto-refresh hooks are run. 2441 // snap-bb's hook is triggered because it is affected by base-snap-b refresh 2442 // (and intersects with affecting snap of snap-b). Note, snap-a is not here 2443 // because it is not affected by snaps affecting snap-b. 2444 for i := 1; i <= 2; i++ { 2445 c.Assert(tasks[i].Kind(), Equals, "run-hook") 2446 var hs hookstate.HookSetup 2447 c.Assert(tasks[i].Get("hook-setup", &hs), IsNil) 2448 c.Check(hs.Hook, Equals, "gate-auto-refresh") 2449 c.Check(hs.Optional, Equals, true) 2450 seenSnaps[hs.Snap] = true 2451 } 2452 c.Check(seenSnaps, DeepEquals, map[string]bool{ 2453 "snap-b": true, 2454 "snap-bb": true, 2455 }) 2456 } 2457 2458 func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapNoCandidatesAnymore(c *C) { 2459 // only snap-a will have a refresh available 2460 s.store.refreshedSnaps = []*snap.Info{{ 2461 Architectures: []string{"all"}, 2462 SnapType: snap.TypeApp, 2463 SideInfo: snap.SideInfo{ 2464 RealName: "snap-a", 2465 Revision: snap.R(8), 2466 }, 2467 }} 2468 2469 logbuf, restoreLogger := logger.MockLogger() 2470 defer restoreLogger() 2471 2472 st := s.state 2473 st.Lock() 2474 defer st.Unlock() 2475 2476 mockInstalledSnap(c, s.state, snapAyaml, useHook) 2477 mockInstalledSnap(c, s.state, snapByaml, useHook) 2478 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 2479 2480 restore := snapstatetest.MockDeviceModel(DefaultModel()) 2481 defer restore() 2482 2483 // pretend some snaps are held 2484 c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil) 2485 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil) 2486 2487 // pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed) 2488 c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil) 2489 c.Assert(st.Changes(), HasLen, 0) 2490 2491 // but base-snap-b has no update anymore. 2492 c.Check(logbuf.String(), testutil.Contains, `auto-refresh: all snaps previously held by "snap-b" are up-to-date`) 2493 } 2494 2495 func verifyPhasedAutorefreshTasks(c *C, tasks []*state.Task, expected []string) { 2496 c.Assert(len(tasks), Equals, len(expected)) 2497 for i, t := range tasks { 2498 var got string 2499 if t.Kind() == "run-hook" { 2500 var hsup hookstate.HookSetup 2501 c.Assert(t.Get("hook-setup", &hsup), IsNil) 2502 got = fmt.Sprintf("%s [%s;%s]", t.Kind(), hsup.Snap, hsup.Hook) 2503 } else { 2504 got = t.Kind() 2505 } 2506 c.Assert(got, Equals, expected[i], Commentf("#%d", i)) 2507 } 2508 }