github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 kernelYaml = `name: kernel 162 type: kernel 163 version: 1 164 ` 165 166 const gadget1Yaml = `name: gadget 167 type: gadget 168 version: 1 169 ` 170 171 const snapCyaml = `name: snap-c 172 type: app 173 version: 1 174 ` 175 176 const snapDyaml = `name: snap-d 177 type: app 178 version: 1 179 slots: 180 slot: desktop 181 ` 182 183 const snapEyaml = `name: snap-e 184 type: app 185 version: 1 186 base: other-base 187 plugs: 188 plug: desktop 189 ` 190 191 const snapFyaml = `name: snap-f 192 type: app 193 version: 1 194 plugs: 195 plug: desktop 196 ` 197 198 const snapGyaml = `name: snap-g 199 type: app 200 version: 1 201 base: other-base 202 plugs: 203 desktop: 204 mir: 205 ` 206 207 const coreYaml = `name: core 208 type: os 209 version: 1 210 slots: 211 desktop: 212 mir: 213 ` 214 215 const core18Yaml = `name: core18 216 type: os 217 version: 1 218 ` 219 220 const snapdYaml = `name: snapd 221 version: 1 222 type: snapd 223 slots: 224 desktop: 225 ` 226 227 func (s *autorefreshGatingSuite) TestHoldDurationLeft(c *C) { 228 now, err := time.Parse(time.RFC3339, "2021-06-03T10:00:00Z") 229 c.Assert(err, IsNil) 230 maxPostponement := time.Hour * 24 * 90 231 232 for i, tc := range []struct { 233 lastRefresh, firstHeld string 234 maxDuration string 235 expected string 236 }{ 237 { 238 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 239 "2021-06-03T10:00:00Z", // first held now 240 "48h", // max duration 241 "48h", // expected 242 }, 243 { 244 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 245 "2021-06-02T10:00:00Z", // first held (1 day ago) 246 "48h", // max duration 247 "24h", // expected 248 }, 249 { 250 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 251 "2021-06-01T10:00:00Z", // first held (2 days ago) 252 "48h", // max duration 253 "00h", // expected 254 }, 255 { 256 "2021-03-08T10:00:00Z", // last refreshed (almost 3 months ago) 257 "2021-06-01T10:00:00Z", // first held 258 "2160h", // max duration (90 days) 259 "72h", // expected 260 }, 261 { 262 "2021-03-04T10:00:00Z", // last refreshed 263 "2021-06-01T10:00:00Z", // first held (2 days ago) 264 "2160h", // max duration (90 days) 265 "-24h", // expected (refresh is 1 day overdue) 266 }, 267 { 268 "2021-06-01T10:00:00Z", // last refreshed (2 days ago) 269 "2021-06-03T10:00:00Z", // first held now 270 "2160h", // max duration (90 days) 271 "2112h", // expected (max minus 2 days) 272 }, 273 } { 274 lastRefresh, err := time.Parse(time.RFC3339, tc.lastRefresh) 275 c.Assert(err, IsNil) 276 firstHeld, err := time.Parse(time.RFC3339, tc.firstHeld) 277 c.Assert(err, IsNil) 278 maxDuration, err := time.ParseDuration(tc.maxDuration) 279 c.Assert(err, IsNil) 280 expected, err := time.ParseDuration(tc.expected) 281 c.Assert(err, IsNil) 282 283 left := snapstate.HoldDurationLeft(now, lastRefresh, firstHeld, maxDuration, maxPostponement) 284 c.Check(left, Equals, expected, Commentf("case #%d", i)) 285 } 286 } 287 288 func (s *autorefreshGatingSuite) TestLastRefreshedHelper(c *C) { 289 st := s.state 290 st.Lock() 291 defer st.Unlock() 292 293 inf := mockInstalledSnap(c, st, snapAyaml, false) 294 stat, err := os.Stat(inf.MountFile()) 295 c.Assert(err, IsNil) 296 297 refreshed, err := snapstate.LastRefreshed(st, "snap-a") 298 c.Assert(err, IsNil) 299 c.Check(refreshed, DeepEquals, stat.ModTime()) 300 301 t, err := time.Parse(time.RFC3339, "2021-01-01T10:00:00Z") 302 c.Assert(err, IsNil) 303 304 var snapst snapstate.SnapState 305 c.Assert(snapstate.Get(st, "snap-a", &snapst), IsNil) 306 snapst.LastRefreshTime = &t 307 snapstate.Set(st, "snap-a", &snapst) 308 309 refreshed, err = snapstate.LastRefreshed(st, "snap-a") 310 c.Assert(err, IsNil) 311 c.Check(refreshed, DeepEquals, t) 312 } 313 314 func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) { 315 st := s.state 316 st.Lock() 317 defer st.Unlock() 318 319 restore := snapstate.MockTimeNow(func() time.Time { 320 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 321 c.Assert(err, IsNil) 322 return t 323 }) 324 defer restore() 325 326 mockInstalledSnap(c, st, snapAyaml, false) 327 mockInstalledSnap(c, st, snapByaml, false) 328 mockInstalledSnap(c, st, snapCyaml, false) 329 mockInstalledSnap(c, st, snapDyaml, false) 330 mockInstalledSnap(c, st, snapEyaml, false) 331 mockInstalledSnap(c, st, snapFyaml, false) 332 333 mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f") 334 335 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 336 // this could be merged with the above HoldRefresh call, but it's fine if 337 // done separately too. 338 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-e"), IsNil) 339 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-e"), IsNil) 340 c.Assert(snapstate.HoldRefresh(st, "snap-f", 0, "snap-f"), IsNil) 341 342 var gating map[string]map[string]*snapstate.HoldState 343 c.Assert(st.Get("snaps-hold", &gating), IsNil) 344 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 345 "snap-b": { 346 // holding of other snaps for maxOtherHoldDuration (48h) 347 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 348 }, 349 "snap-c": { 350 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 351 }, 352 "snap-e": { 353 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 354 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 355 }, 356 "snap-f": { 357 // holding self set for maxPostponement minus 1 day due to last refresh. 358 "snap-f": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-07T10:00:00Z"), 359 }, 360 }) 361 } 362 363 func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) { 364 st := s.state 365 st.Lock() 366 defer st.Unlock() 367 368 lastRefreshed := "2021-05-09T10:00:00Z" 369 now := "2021-05-10T10:00:00Z" 370 restore := snapstate.MockTimeNow(func() time.Time { 371 t, err := time.Parse(time.RFC3339, now) 372 c.Assert(err, IsNil) 373 return t 374 }) 375 defer restore() 376 377 mockInstalledSnap(c, st, snapAyaml, false) 378 mockInstalledSnap(c, st, snapByaml, false) 379 // snap-a was last refreshed yesterday 380 mockLastRefreshed(c, st, lastRefreshed, "snap-a") 381 382 // hold it for just a bit (10h) initially 383 hold := time.Hour * 10 384 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 385 var gating map[string]map[string]*snapstate.HoldState 386 c.Assert(st.Get("snaps-hold", &gating), IsNil) 387 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 388 "snap-a": { 389 "snap-b": snapstate.MockHoldState(now, "2021-05-10T20:00:00Z"), 390 }, 391 }) 392 393 // holding for a shorter time is fine too 394 hold = time.Hour * 5 395 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 396 c.Assert(st.Get("snaps-hold", &gating), IsNil) 397 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 398 "snap-a": { 399 "snap-b": snapstate.MockHoldState(now, "2021-05-10T15:00:00Z"), 400 }, 401 }) 402 403 oldNow := now 404 405 // a refresh on next day 406 now = "2021-05-11T08:00:00Z" 407 408 // default hold time requested 409 hold = 0 410 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 411 c.Assert(st.Get("snaps-hold", &gating), IsNil) 412 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 413 "snap-a": { 414 // maximum for holding other snaps, but taking into consideration 415 // firstHeld time = "2021-05-10T10:00:00". 416 "snap-b": snapstate.MockHoldState(oldNow, "2021-05-12T10:00:00Z"), 417 }, 418 }) 419 } 420 421 func (s *autorefreshGatingSuite) TestHoldRefreshHelperCloseToMaxPostponement(c *C) { 422 st := s.state 423 st.Lock() 424 defer st.Unlock() 425 426 lastRefreshedStr := "2021-01-01T10:00:00Z" 427 lastRefreshed, err := time.Parse(time.RFC3339, lastRefreshedStr) 428 c.Assert(err, IsNil) 429 // we are 1 day before maxPostponent 430 now := lastRefreshed.Add(89 * time.Hour * 24) 431 432 restore := snapstate.MockTimeNow(func() time.Time { return now }) 433 defer restore() 434 435 mockInstalledSnap(c, st, snapAyaml, false) 436 mockInstalledSnap(c, st, snapByaml, false) 437 mockLastRefreshed(c, st, lastRefreshedStr, "snap-a") 438 439 // request default hold time 440 var hold time.Duration 441 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 442 443 var gating map[string]map[string]*snapstate.HoldState 444 c.Assert(st.Get("snaps-hold", &gating), IsNil) 445 c.Assert(gating, HasLen, 1) 446 c.Check(gating["snap-a"]["snap-b"].HoldUntil.String(), DeepEquals, lastRefreshed.Add(90*time.Hour*24).String()) 447 } 448 449 func (s *autorefreshGatingSuite) TestHoldRefreshExplicitHoldTime(c *C) { 450 st := s.state 451 st.Lock() 452 defer st.Unlock() 453 454 now := "2021-05-10T10:00:00Z" 455 restore := snapstate.MockTimeNow(func() time.Time { 456 t, err := time.Parse(time.RFC3339, now) 457 c.Assert(err, IsNil) 458 return t 459 }) 460 defer restore() 461 462 mockInstalledSnap(c, st, snapAyaml, false) 463 mockInstalledSnap(c, st, snapByaml, false) 464 465 hold := time.Hour * 24 * 3 466 // holding self for 3 days 467 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), IsNil) 468 469 // snap-b holds snap-a for 1 day 470 hold = time.Hour * 24 471 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 472 473 var gating map[string]map[string]*snapstate.HoldState 474 c.Assert(st.Get("snaps-hold", &gating), IsNil) 475 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 476 "snap-a": { 477 "snap-a": snapstate.MockHoldState(now, "2021-05-13T10:00:00Z"), 478 "snap-b": snapstate.MockHoldState(now, "2021-05-11T10:00:00Z"), 479 }, 480 }) 481 } 482 483 func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) { 484 st := s.state 485 st.Lock() 486 defer st.Unlock() 487 488 now := "2021-05-10T10:00:00Z" 489 restore := snapstate.MockTimeNow(func() time.Time { 490 t, err := time.Parse(time.RFC3339, now) 491 c.Assert(err, IsNil) 492 return t 493 }) 494 defer restore() 495 496 mockInstalledSnap(c, st, snapAyaml, false) 497 mockInstalledSnap(c, st, snapByaml, false) 498 // snap-b was refreshed a few days ago 499 mockLastRefreshed(c, st, "2021-05-01T10:00:00Z", "snap-b") 500 501 // holding itself 502 hold := time.Hour * 24 * 96 503 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`) 504 505 // holding other snap 506 hold = time.Hour * 49 507 err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-b") 508 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`) 509 herr, ok := err.(*snapstate.HoldError) 510 c.Assert(ok, Equals, true) 511 c.Check(herr.SnapsInError, DeepEquals, map[string]snapstate.HoldDurationError{ 512 "snap-b": { 513 Err: fmt.Errorf(`requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`), 514 DurationLeft: 48 * time.Hour, 515 }, 516 }) 517 518 // hold for maximum allowed for other snaps 519 hold = time.Hour * 48 520 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), IsNil) 521 // 2 days passed since it was first held 522 now = "2021-05-12T10:00:00Z" 523 hold = time.Minute * 2 524 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`) 525 526 // refreshed long time ago (> maxPostponement) 527 mockLastRefreshed(c, st, "2021-01-01T10:00:00Z", "snap-b") 528 hold = time.Hour * 2 529 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`) 530 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`) 531 } 532 533 func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) { 534 st := s.state 535 st.Lock() 536 defer st.Unlock() 537 538 mockInstalledSnap(c, st, snapAyaml, false) 539 mockInstalledSnap(c, st, snapByaml, false) 540 mockInstalledSnap(c, st, snapCyaml, false) 541 mockInstalledSnap(c, st, snapDyaml, false) 542 543 mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-b", "snap-c", "snap-d") 544 545 restore := snapstate.MockTimeNow(func() time.Time { 546 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 547 c.Assert(err, IsNil) 548 return t 549 }) 550 defer restore() 551 552 // nothing is held initially 553 held, err := snapstate.HeldSnaps(st) 554 c.Assert(err, IsNil) 555 c.Check(held, IsNil) 556 557 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 558 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil) 559 // holding self 560 c.Assert(snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d"), IsNil) 561 562 held, err = snapstate.HeldSnaps(st) 563 c.Assert(err, IsNil) 564 c.Check(held, DeepEquals, map[string]bool{"snap-b": true, "snap-c": true, "snap-d": true}) 565 566 c.Assert(snapstate.ProceedWithRefresh(st, "snap-a"), IsNil) 567 568 held, err = snapstate.HeldSnaps(st) 569 c.Assert(err, IsNil) 570 c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-d": true}) 571 572 c.Assert(snapstate.ProceedWithRefresh(st, "snap-d"), IsNil) 573 held, err = snapstate.HeldSnaps(st) 574 c.Assert(err, IsNil) 575 c.Check(held, IsNil) 576 } 577 578 func (s *autorefreshGatingSuite) TestPruneGatingHelper(c *C) { 579 st := s.state 580 st.Lock() 581 defer st.Unlock() 582 583 restore := snapstate.MockTimeNow(func() time.Time { 584 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 585 c.Assert(err, IsNil) 586 return t 587 }) 588 defer restore() 589 590 mockInstalledSnap(c, st, snapAyaml, false) 591 mockInstalledSnap(c, st, snapByaml, false) 592 mockInstalledSnap(c, st, snapCyaml, false) 593 mockInstalledSnap(c, st, snapDyaml, false) 594 595 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 596 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil) 597 // sanity 598 held, err := snapstate.HeldSnaps(st) 599 c.Assert(err, IsNil) 600 c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-b": true, "snap-d": true}) 601 602 candidates := map[string]*snapstate.RefreshCandidate{"snap-c": {}} 603 604 // only snap-c has a refresh candidate, snap-b and snap-d should be forgotten. 605 c.Assert(snapstate.PruneGating(st, candidates), IsNil) 606 var gating map[string]map[string]*snapstate.HoldState 607 c.Assert(st.Get("snaps-hold", &gating), IsNil) 608 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 609 "snap-c": { 610 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 611 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 612 }, 613 }) 614 held, err = snapstate.HeldSnaps(st) 615 c.Assert(err, IsNil) 616 c.Check(held, DeepEquals, map[string]bool{"snap-c": true}) 617 } 618 619 func (s *autorefreshGatingSuite) TestPruneGatingHelperNoGating(c *C) { 620 st := s.state 621 st.Lock() 622 defer st.Unlock() 623 624 restore := snapstate.MockTimeNow(func() time.Time { 625 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 626 c.Assert(err, IsNil) 627 return t 628 }) 629 defer restore() 630 631 mockInstalledSnap(c, st, snapAyaml, false) 632 633 held, err := snapstate.HeldSnaps(st) 634 c.Assert(err, IsNil) 635 c.Check(held, HasLen, 0) 636 637 snapstate.MockTimeNow(func() time.Time { 638 c.Fatalf("not expected") 639 return time.Time{} 640 }) 641 642 candidates := map[string]*snapstate.RefreshCandidate{"snap-a": {}} 643 c.Assert(snapstate.PruneGating(st, candidates), IsNil) 644 held, err = snapstate.HeldSnaps(st) 645 c.Assert(err, IsNil) 646 c.Check(held, HasLen, 0) 647 } 648 649 func (s *autorefreshGatingSuite) TestResetGatingForRefreshedHelper(c *C) { 650 st := s.state 651 st.Lock() 652 defer st.Unlock() 653 654 restore := snapstate.MockTimeNow(func() time.Time { 655 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 656 c.Assert(err, IsNil) 657 return t 658 }) 659 defer restore() 660 661 mockInstalledSnap(c, st, snapAyaml, false) 662 mockInstalledSnap(c, st, snapByaml, false) 663 mockInstalledSnap(c, st, snapCyaml, false) 664 mockInstalledSnap(c, st, snapDyaml, false) 665 666 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 667 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil) 668 669 c.Assert(snapstate.ResetGatingForRefreshed(st, "snap-b", "snap-c"), IsNil) 670 var gating map[string]map[string]*snapstate.HoldState 671 c.Assert(st.Get("snaps-hold", &gating), IsNil) 672 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 673 "snap-d": { 674 // holding self set for maxPostponement (95 days - buffer = 90 days) 675 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-08T10:00:00Z"), 676 }, 677 }) 678 679 held, err := snapstate.HeldSnaps(st) 680 c.Assert(err, IsNil) 681 c.Check(held, DeepEquals, map[string]bool{"snap-d": true}) 682 } 683 684 func (s *autorefreshGatingSuite) TestPruneSnapsHold(c *C) { 685 st := s.state 686 st.Lock() 687 defer st.Unlock() 688 689 mockInstalledSnap(c, st, snapAyaml, false) 690 mockInstalledSnap(c, st, snapByaml, false) 691 mockInstalledSnap(c, st, snapCyaml, false) 692 mockInstalledSnap(c, st, snapDyaml, false) 693 694 // snap-a is holding itself and 3 other snaps 695 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-d"), IsNil) 696 // in addition, snap-c is held by snap-d. 697 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil) 698 699 // sanity check 700 held, err := snapstate.HeldSnaps(st) 701 c.Assert(err, IsNil) 702 c.Check(held, DeepEquals, map[string]bool{ 703 "snap-a": true, 704 "snap-b": true, 705 "snap-c": true, 706 "snap-d": true, 707 }) 708 709 c.Check(snapstate.PruneSnapsHold(st, "snap-a"), IsNil) 710 711 // after pruning snap-a, snap-c is still held. 712 held, err = snapstate.HeldSnaps(st) 713 c.Assert(err, IsNil) 714 c.Check(held, DeepEquals, map[string]bool{ 715 "snap-c": true, 716 }) 717 var gating map[string]map[string]*snapstate.HoldState 718 c.Assert(st.Get("snaps-hold", &gating), IsNil) 719 c.Assert(gating, HasLen, 1) 720 c.Check(gating["snap-c"], HasLen, 1) 721 c.Check(gating["snap-c"]["snap-d"], NotNil) 722 } 723 724 const useHook = true 725 const noHook = false 726 727 func checkGatingTask(c *C, task *state.Task, expected map[string]*snapstate.RefreshCandidate) { 728 c.Assert(task.Kind(), Equals, "conditional-auto-refresh") 729 var snaps map[string]*snapstate.RefreshCandidate 730 c.Assert(task.Get("snaps", &snaps), IsNil) 731 c.Check(snaps, DeepEquals, expected) 732 } 733 734 func (s *autorefreshGatingSuite) TestAffectedByBase(c *C) { 735 restore := release.MockOnClassic(true) 736 defer restore() 737 738 st := s.state 739 740 st.Lock() 741 defer st.Unlock() 742 mockInstalledSnap(c, s.state, snapAyaml, useHook) 743 baseSnapA := mockInstalledSnap(c, s.state, baseSnapAyaml, noHook) 744 // unrelated snaps 745 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 746 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 747 748 c.Assert(s.repo.AddSnap(snapB), IsNil) 749 750 updates := []*snap.Info{baseSnapA} 751 affected, err := snapstate.AffectedByRefresh(st, updates) 752 c.Assert(err, IsNil) 753 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 754 "snap-a": { 755 Base: true, 756 AffectingSnaps: map[string]bool{ 757 "base-snap-a": true, 758 }}}) 759 } 760 761 func (s *autorefreshGatingSuite) TestAffectedByCore(c *C) { 762 restore := release.MockOnClassic(true) 763 defer restore() 764 765 st := s.state 766 767 st.Lock() 768 defer st.Unlock() 769 snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook) 770 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 771 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 772 773 c.Assert(s.repo.AddSnap(core), IsNil) 774 c.Assert(s.repo.AddSnap(snapB), IsNil) 775 c.Assert(s.repo.AddSnap(snapC), IsNil) 776 777 updates := []*snap.Info{core} 778 affected, err := snapstate.AffectedByRefresh(st, updates) 779 c.Assert(err, IsNil) 780 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 781 "snap-c": { 782 Base: true, 783 AffectingSnaps: map[string]bool{ 784 "core": true, 785 }}}) 786 } 787 788 func (s *autorefreshGatingSuite) TestAffectedByKernel(c *C) { 789 restore := release.MockOnClassic(true) 790 defer restore() 791 792 st := s.state 793 794 st.Lock() 795 defer st.Unlock() 796 kernel := mockInstalledSnap(c, s.state, kernelYaml, noHook) 797 mockInstalledSnap(c, s.state, snapCyaml, useHook) 798 mockInstalledSnap(c, s.state, snapByaml, noHook) 799 800 updates := []*snap.Info{kernel} 801 affected, err := snapstate.AffectedByRefresh(st, updates) 802 c.Assert(err, IsNil) 803 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 804 "snap-c": { 805 Restart: true, 806 AffectingSnaps: map[string]bool{ 807 "kernel": true, 808 }}}) 809 } 810 811 func (s *autorefreshGatingSuite) TestAffectedByGadget(c *C) { 812 restore := release.MockOnClassic(true) 813 defer restore() 814 815 st := s.state 816 817 st.Lock() 818 defer st.Unlock() 819 kernel := mockInstalledSnap(c, s.state, gadget1Yaml, noHook) 820 mockInstalledSnap(c, s.state, snapCyaml, useHook) 821 mockInstalledSnap(c, s.state, snapByaml, noHook) 822 823 updates := []*snap.Info{kernel} 824 affected, err := snapstate.AffectedByRefresh(st, updates) 825 c.Assert(err, IsNil) 826 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 827 "snap-c": { 828 Restart: true, 829 AffectingSnaps: map[string]bool{ 830 "gadget": true, 831 }}}) 832 } 833 834 func (s *autorefreshGatingSuite) TestAffectedBySlot(c *C) { 835 restore := release.MockOnClassic(true) 836 defer restore() 837 838 st := s.state 839 840 st.Lock() 841 defer st.Unlock() 842 843 snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook) 844 snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook) 845 // unrelated snap 846 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 847 848 c.Assert(s.repo.AddSnap(snapF), IsNil) 849 c.Assert(s.repo.AddSnap(snapD), IsNil) 850 c.Assert(s.repo.AddSnap(snapE), IsNil) 851 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 852 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 853 c.Assert(err, IsNil) 854 855 updates := []*snap.Info{snapD} 856 affected, err := snapstate.AffectedByRefresh(st, updates) 857 c.Assert(err, IsNil) 858 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 859 "snap-e": { 860 Restart: true, 861 AffectingSnaps: map[string]bool{ 862 "snap-d": true, 863 }}}) 864 } 865 866 func (s *autorefreshGatingSuite) TestNotAffectedByCoreOrSnapdSlot(c *C) { 867 restore := release.MockOnClassic(true) 868 defer restore() 869 870 st := s.state 871 872 st.Lock() 873 defer st.Unlock() 874 875 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 876 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 877 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 878 879 c.Assert(s.repo.AddSnap(snapG), IsNil) 880 c.Assert(s.repo.AddSnap(core), IsNil) 881 c.Assert(s.repo.AddSnap(snapB), IsNil) 882 883 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "mir"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "mir"}} 884 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 885 c.Assert(err, IsNil) 886 887 updates := []*snap.Info{core} 888 affected, err := snapstate.AffectedByRefresh(st, updates) 889 c.Assert(err, IsNil) 890 c.Check(affected, HasLen, 0) 891 } 892 893 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackend(c *C) { 894 restore := release.MockOnClassic(true) 895 defer restore() 896 897 st := s.state 898 899 st.Lock() 900 defer st.Unlock() 901 902 snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook) 903 snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook) 904 // unrelated snap 905 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 906 907 c.Assert(s.repo.AddSnap(snapF), IsNil) 908 c.Assert(s.repo.AddSnap(snapD), IsNil) 909 c.Assert(s.repo.AddSnap(snapE), IsNil) 910 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 911 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 912 c.Assert(err, IsNil) 913 914 // snapE has a plug using mount backend and is refreshed, this affects slot of snap-d. 915 updates := []*snap.Info{snapE} 916 affected, err := snapstate.AffectedByRefresh(st, updates) 917 c.Assert(err, IsNil) 918 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 919 "snap-d": { 920 Restart: true, 921 AffectingSnaps: map[string]bool{ 922 "snap-e": true, 923 }}}) 924 } 925 926 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendSnapdSlot(c *C) { 927 restore := release.MockOnClassic(true) 928 defer restore() 929 930 st := s.state 931 932 st.Lock() 933 defer st.Unlock() 934 935 snapdSnap := mockInstalledSnap(c, s.state, snapdYaml, useHook) 936 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 937 // unrelated snap 938 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 939 940 c.Assert(s.repo.AddSnap(snapF), IsNil) 941 c.Assert(s.repo.AddSnap(snapdSnap), IsNil) 942 c.Assert(s.repo.AddSnap(snapG), IsNil) 943 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "snapd", Name: "desktop"}} 944 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 945 c.Assert(err, IsNil) 946 947 // snapE has a plug using mount backend, refreshing snapd affects snapE. 948 updates := []*snap.Info{snapdSnap} 949 affected, err := snapstate.AffectedByRefresh(st, updates) 950 c.Assert(err, IsNil) 951 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 952 "snap-g": { 953 Restart: true, 954 AffectingSnaps: map[string]bool{ 955 "snapd": true, 956 }}}) 957 } 958 959 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendCoreSlot(c *C) { 960 restore := release.MockOnClassic(true) 961 defer restore() 962 963 st := s.state 964 965 st.Lock() 966 defer st.Unlock() 967 968 coreSnap := mockInstalledSnap(c, s.state, coreYaml, noHook) 969 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 970 971 c.Assert(s.repo.AddSnap(coreSnap), IsNil) 972 c.Assert(s.repo.AddSnap(snapG), IsNil) 973 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "desktop"}} 974 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 975 c.Assert(err, IsNil) 976 977 // snapG has a plug using mount backend, refreshing core affects snapE. 978 updates := []*snap.Info{coreSnap} 979 affected, err := snapstate.AffectedByRefresh(st, updates) 980 c.Assert(err, IsNil) 981 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 982 "snap-g": { 983 Restart: true, 984 AffectingSnaps: map[string]bool{ 985 "core": true, 986 }}}) 987 } 988 989 func (s *autorefreshGatingSuite) TestAffectedByBootBase(c *C) { 990 restore := release.MockOnClassic(false) 991 defer restore() 992 993 st := s.state 994 995 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 996 defer r() 997 998 st.Lock() 999 defer st.Unlock() 1000 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1001 mockInstalledSnap(c, s.state, snapByaml, useHook) 1002 mockInstalledSnap(c, s.state, snapDyaml, useHook) 1003 mockInstalledSnap(c, s.state, snapEyaml, useHook) 1004 core18 := mockInstalledSnap(c, s.state, core18Yaml, noHook) 1005 1006 updates := []*snap.Info{core18} 1007 affected, err := snapstate.AffectedByRefresh(st, updates) 1008 c.Assert(err, IsNil) 1009 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 1010 "snap-a": { 1011 Base: false, 1012 Restart: true, 1013 AffectingSnaps: map[string]bool{ 1014 "core18": true, 1015 }, 1016 }, 1017 "snap-b": { 1018 Base: false, 1019 Restart: true, 1020 AffectingSnaps: map[string]bool{ 1021 "core18": true, 1022 }, 1023 }, 1024 "snap-d": { 1025 Base: false, 1026 Restart: true, 1027 AffectingSnaps: map[string]bool{ 1028 "core18": true, 1029 }, 1030 }, 1031 "snap-e": { 1032 Base: false, 1033 Restart: true, 1034 AffectingSnaps: map[string]bool{ 1035 "core18": true, 1036 }}}) 1037 } 1038 1039 func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) { 1040 st := s.state 1041 st.Lock() 1042 defer st.Unlock() 1043 1044 affected := map[string]*snapstate.AffectedSnapInfo{ 1045 "snap-a": { 1046 Base: true, 1047 Restart: true, 1048 AffectingSnaps: map[string]bool{ 1049 "snap-c": true, 1050 "snap-d": true, 1051 }, 1052 }, 1053 "snap-b": { 1054 AffectingSnaps: map[string]bool{ 1055 "snap-e": true, 1056 "snap-f": true, 1057 }, 1058 }, 1059 } 1060 1061 seenSnaps := make(map[string]bool) 1062 1063 ts := snapstate.CreateGateAutoRefreshHooks(st, affected) 1064 c.Assert(ts.Tasks(), HasLen, 2) 1065 1066 checkHook := func(t *state.Task) { 1067 c.Assert(t.Kind(), Equals, "run-hook") 1068 var hs hookstate.HookSetup 1069 c.Assert(t.Get("hook-setup", &hs), IsNil) 1070 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1071 c.Check(hs.Optional, Equals, true) 1072 seenSnaps[hs.Snap] = true 1073 1074 var data interface{} 1075 c.Assert(t.Get("hook-context", &data), IsNil) 1076 1077 // the order of hook tasks is not deterministic 1078 if hs.Snap == "snap-a" { 1079 c.Check(data, DeepEquals, map[string]interface{}{ 1080 "base": true, 1081 "restart": true, 1082 "affecting-snaps": []interface{}{"snap-c", "snap-d"}}) 1083 } else { 1084 c.Assert(hs.Snap, Equals, "snap-b") 1085 c.Check(data, DeepEquals, map[string]interface{}{ 1086 "base": false, 1087 "restart": false, 1088 "affecting-snaps": []interface{}{"snap-e", "snap-f"}}) 1089 } 1090 } 1091 1092 checkHook(ts.Tasks()[0]) 1093 checkHook(ts.Tasks()[1]) 1094 1095 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 1096 } 1097 1098 func (s *autorefreshGatingSuite) TestAutorefreshPhase1FeatureFlag(c *C) { 1099 st := s.state 1100 st.Lock() 1101 defer st.Unlock() 1102 1103 st.Set("seeded", true) 1104 1105 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1106 defer restore() 1107 1108 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 1109 return nil, nil 1110 } 1111 defer func() { snapstate.AutoAliases = nil }() 1112 1113 s.store.refreshedSnaps = []*snap.Info{{ 1114 Architectures: []string{"all"}, 1115 SnapType: snap.TypeApp, 1116 SideInfo: snap.SideInfo{ 1117 RealName: "snap-a", 1118 Revision: snap.R(8), 1119 }, 1120 }} 1121 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1122 1123 // gate-auto-refresh-hook feature not enabled, expect old-style refresh. 1124 _, tss, err := snapstate.AutoRefresh(context.TODO(), st) 1125 c.Check(err, IsNil) 1126 c.Assert(tss, HasLen, 2) 1127 c.Check(tss[0].Tasks()[0].Kind(), Equals, "prerequisites") 1128 c.Check(tss[0].Tasks()[1].Kind(), Equals, "download-snap") 1129 c.Check(tss[1].Tasks()[0].Kind(), Equals, "check-rerefresh") 1130 1131 // enable gate-auto-refresh-hook feature 1132 tr := config.NewTransaction(s.state) 1133 tr.Set("core", "experimental.gate-auto-refresh-hook", true) 1134 tr.Commit() 1135 1136 _, tss, err = snapstate.AutoRefresh(context.TODO(), st) 1137 c.Check(err, IsNil) 1138 c.Assert(tss, HasLen, 2) 1139 task := tss[0].Tasks()[0] 1140 c.Check(task.Kind(), Equals, "conditional-auto-refresh") 1141 var toUpdate map[string]*snapstate.RefreshCandidate 1142 c.Assert(task.Get("snaps", &toUpdate), IsNil) 1143 seenSnaps := make(map[string]bool) 1144 for up := range toUpdate { 1145 seenSnaps[up] = true 1146 } 1147 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true}) 1148 c.Check(tss[1].Tasks()[0].Kind(), Equals, "run-hook") 1149 } 1150 1151 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) { 1152 s.store.refreshedSnaps = []*snap.Info{{ 1153 Architectures: []string{"all"}, 1154 SnapType: snap.TypeApp, 1155 SideInfo: snap.SideInfo{ 1156 RealName: "snap-a", 1157 Revision: snap.R(8), 1158 }, 1159 }, { 1160 Architectures: []string{"all"}, 1161 SnapType: snap.TypeBase, 1162 SideInfo: snap.SideInfo{ 1163 RealName: "base-snap-b", 1164 Revision: snap.R(3), 1165 }, 1166 }, { 1167 Architectures: []string{"all"}, 1168 SnapType: snap.TypeApp, 1169 SideInfo: snap.SideInfo{ 1170 RealName: "snap-c", 1171 Revision: snap.R(5), 1172 }, 1173 }} 1174 1175 st := s.state 1176 st.Lock() 1177 defer st.Unlock() 1178 1179 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1180 mockInstalledSnap(c, s.state, snapByaml, useHook) 1181 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1182 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1183 mockInstalledSnap(c, s.state, snapDyaml, noHook) 1184 1185 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1186 defer restore() 1187 1188 // pretend some snaps are held 1189 c.Assert(snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d"), IsNil) 1190 // sanity check 1191 heldSnaps, err := snapstate.HeldSnaps(st) 1192 c.Assert(err, IsNil) 1193 c.Check(heldSnaps, DeepEquals, map[string]bool{ 1194 "snap-a": true, 1195 "snap-d": true, 1196 }) 1197 1198 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1199 c.Assert(err, IsNil) 1200 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a", "snap-c"}) 1201 c.Assert(tss, HasLen, 2) 1202 1203 c.Assert(tss[0].Tasks(), HasLen, 1) 1204 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1205 "snap-a": { 1206 SnapSetup: snapstate.SnapSetup{ 1207 Type: "app", 1208 PlugsOnly: true, 1209 Flags: snapstate.Flags{ 1210 IsAutoRefresh: true, 1211 }, 1212 SideInfo: &snap.SideInfo{ 1213 RealName: "snap-a", 1214 Revision: snap.R(8), 1215 }, 1216 DownloadInfo: &snap.DownloadInfo{}, 1217 }, 1218 }, 1219 "base-snap-b": { 1220 SnapSetup: snapstate.SnapSetup{ 1221 Type: "base", 1222 PlugsOnly: true, 1223 Flags: snapstate.Flags{ 1224 IsAutoRefresh: true, 1225 }, 1226 SideInfo: &snap.SideInfo{ 1227 RealName: "base-snap-b", 1228 Revision: snap.R(3), 1229 }, 1230 DownloadInfo: &snap.DownloadInfo{}, 1231 }, 1232 }, 1233 "snap-c": { 1234 SnapSetup: snapstate.SnapSetup{ 1235 Type: "app", 1236 PlugsOnly: true, 1237 Flags: snapstate.Flags{ 1238 IsAutoRefresh: true, 1239 }, 1240 SideInfo: &snap.SideInfo{ 1241 RealName: "snap-c", 1242 Revision: snap.R(5), 1243 }, 1244 DownloadInfo: &snap.DownloadInfo{}, 1245 }, 1246 }, 1247 }) 1248 1249 c.Assert(tss[1].Tasks(), HasLen, 2) 1250 1251 var snapAhookData, snapBhookData map[string]interface{} 1252 1253 // check hooks for affected snaps 1254 seenSnaps := make(map[string]bool) 1255 var hs hookstate.HookSetup 1256 task := tss[1].Tasks()[0] 1257 c.Assert(task.Get("hook-setup", &hs), IsNil) 1258 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1259 seenSnaps[hs.Snap] = true 1260 switch hs.Snap { 1261 case "snap-a": 1262 task.Get("hook-context", &snapAhookData) 1263 case "snap-b": 1264 task.Get("hook-context", &snapBhookData) 1265 default: 1266 c.Fatalf("unexpected snap %q", hs.Snap) 1267 } 1268 1269 task = tss[1].Tasks()[1] 1270 c.Assert(task.Get("hook-setup", &hs), IsNil) 1271 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1272 seenSnaps[hs.Snap] = true 1273 switch hs.Snap { 1274 case "snap-a": 1275 task.Get("hook-context", &snapAhookData) 1276 case "snap-b": 1277 task.Get("hook-context", &snapBhookData) 1278 default: 1279 c.Fatalf("unexpected snap %q", hs.Snap) 1280 } 1281 1282 c.Check(snapAhookData["affecting-snaps"], DeepEquals, []interface{}{"snap-a"}) 1283 c.Check(snapBhookData["affecting-snaps"], DeepEquals, []interface{}{"base-snap-b"}) 1284 1285 // hook for snap-a because it gets refreshed, for snap-b because its base 1286 // gets refreshed. snap-c is refreshed but doesn't have the hook. 1287 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 1288 1289 // check that refresh-candidates in the state were updated 1290 var candidates map[string]*snapstate.RefreshCandidate 1291 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1292 c.Assert(candidates, HasLen, 3) 1293 c.Check(candidates["snap-a"], NotNil) 1294 c.Check(candidates["base-snap-b"], NotNil) 1295 c.Check(candidates["snap-c"], NotNil) 1296 1297 // check that after autoRefreshPhase1 any held snaps that are not in refresh 1298 // candidates got removed. 1299 heldSnaps, err = snapstate.HeldSnaps(st) 1300 c.Assert(err, IsNil) 1301 // snap-d got removed from held snaps. 1302 c.Check(heldSnaps, DeepEquals, map[string]bool{ 1303 "snap-a": true, 1304 }) 1305 } 1306 1307 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1ConflictsFilteredOut(c *C) { 1308 s.store.refreshedSnaps = []*snap.Info{{ 1309 Architectures: []string{"all"}, 1310 SnapType: snap.TypeApp, 1311 SideInfo: snap.SideInfo{ 1312 RealName: "snap-a", 1313 Revision: snap.R(8), 1314 }, 1315 }, { 1316 Architectures: []string{"all"}, 1317 SnapType: snap.TypeBase, 1318 SideInfo: snap.SideInfo{ 1319 RealName: "snap-c", 1320 Revision: snap.R(5), 1321 }, 1322 }} 1323 1324 st := s.state 1325 st.Lock() 1326 defer st.Unlock() 1327 1328 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1329 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1330 1331 conflictChange := st.NewChange("conflicting change", "") 1332 conflictTask := st.NewTask("conflicting task", "") 1333 si := &snap.SideInfo{ 1334 RealName: "snap-c", 1335 Revision: snap.R(1), 1336 } 1337 sup := snapstate.SnapSetup{SideInfo: si} 1338 conflictTask.Set("snap-setup", sup) 1339 conflictChange.AddTask(conflictTask) 1340 1341 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1342 defer restore() 1343 1344 logbuf, restoreLogger := logger.MockLogger() 1345 defer restoreLogger() 1346 1347 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1348 c.Assert(err, IsNil) 1349 c.Check(names, DeepEquals, []string{"snap-a"}) 1350 c.Assert(tss, HasLen, 2) 1351 1352 c.Assert(tss[0].Tasks(), HasLen, 1) 1353 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1354 "snap-a": { 1355 SnapSetup: snapstate.SnapSetup{ 1356 Type: "app", 1357 PlugsOnly: true, 1358 Flags: snapstate.Flags{ 1359 IsAutoRefresh: true, 1360 }, 1361 SideInfo: &snap.SideInfo{ 1362 RealName: "snap-a", 1363 Revision: snap.R(8), 1364 }, 1365 DownloadInfo: &snap.DownloadInfo{}, 1366 }}}) 1367 1368 c.Assert(tss[1].Tasks(), HasLen, 1) 1369 1370 c.Assert(logbuf.String(), testutil.Contains, `cannot refresh snap "snap-c": snap "snap-c" has "conflicting change" change in progress`) 1371 1372 seenSnaps := make(map[string]bool) 1373 var hs hookstate.HookSetup 1374 c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil) 1375 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1376 seenSnaps[hs.Snap] = true 1377 1378 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true}) 1379 1380 // check that refresh-candidates in the state were updated 1381 var candidates map[string]*snapstate.RefreshCandidate 1382 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1383 c.Assert(candidates, HasLen, 2) 1384 c.Check(candidates["snap-a"], NotNil) 1385 c.Check(candidates["snap-c"], NotNil) 1386 } 1387 1388 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1NoHooks(c *C) { 1389 s.store.refreshedSnaps = []*snap.Info{{ 1390 Architectures: []string{"all"}, 1391 SnapType: snap.TypeBase, 1392 SideInfo: snap.SideInfo{ 1393 RealName: "base-snap-b", 1394 Revision: snap.R(3), 1395 }, 1396 }, { 1397 Architectures: []string{"all"}, 1398 SnapType: snap.TypeBase, 1399 SideInfo: snap.SideInfo{ 1400 RealName: "snap-c", 1401 Revision: snap.R(5), 1402 }, 1403 }} 1404 1405 st := s.state 1406 st.Lock() 1407 defer st.Unlock() 1408 1409 mockInstalledSnap(c, s.state, snapByaml, noHook) 1410 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1411 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1412 1413 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1414 defer restore() 1415 1416 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1417 c.Assert(err, IsNil) 1418 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-c"}) 1419 c.Assert(tss, HasLen, 1) 1420 1421 c.Assert(tss[0].Tasks(), HasLen, 1) 1422 c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh") 1423 } 1424 1425 func fakeReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) { 1426 info := &snap.Info{ 1427 SuggestedName: name, 1428 SideInfo: *si, 1429 Architectures: []string{"all"}, 1430 SnapType: snap.TypeApp, 1431 Epoch: snap.Epoch{}, 1432 } 1433 switch name { 1434 case "base-snap-b": 1435 info.SnapType = snap.TypeBase 1436 case "snap-a", "snap-b": 1437 info.Hooks = map[string]*snap.HookInfo{ 1438 "gate-auto-refresh": { 1439 Name: "gate-auto-refresh", 1440 Snap: info, 1441 }, 1442 } 1443 if name == "snap-b" { 1444 info.Base = "base-snap-b" 1445 } 1446 } 1447 return info, nil 1448 } 1449 1450 func (s *snapmgrTestSuite) testAutoRefreshPhase2(c *C, beforePhase1 func(), gateAutoRefreshHook func(snapName string), expected []string) *state.Change { 1451 st := s.state 1452 st.Lock() 1453 defer st.Unlock() 1454 1455 s.o.TaskRunner().AddHandler("run-hook", func(t *state.Task, tomb *tomb.Tomb) error { 1456 var hsup hookstate.HookSetup 1457 t.State().Lock() 1458 defer t.State().Unlock() 1459 c.Assert(t.Get("hook-setup", &hsup), IsNil) 1460 if hsup.Hook == "gate-auto-refresh" && gateAutoRefreshHook != nil { 1461 gateAutoRefreshHook(hsup.Snap) 1462 } 1463 return nil 1464 }, nil) 1465 1466 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1467 c.Fatal("unexpected call to installSize") 1468 return 0, nil 1469 }) 1470 defer restoreInstallSize() 1471 1472 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1473 fakeStore: s.fakeStore, 1474 refreshedSnaps: []*snap.Info{{ 1475 Architectures: []string{"all"}, 1476 SnapType: snap.TypeApp, 1477 SideInfo: snap.SideInfo{ 1478 RealName: "snap-a", 1479 Revision: snap.R(8), 1480 }, 1481 }, { 1482 Architectures: []string{"all"}, 1483 SnapType: snap.TypeBase, 1484 SideInfo: snap.SideInfo{ 1485 RealName: "base-snap-b", 1486 Revision: snap.R(3), 1487 }, 1488 }}}) 1489 1490 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1491 mockInstalledSnap(c, s.state, snapByaml, useHook) 1492 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1493 1494 snapstate.MockSnapReadInfo(fakeReadInfo) 1495 1496 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1497 defer restore() 1498 1499 if beforePhase1 != nil { 1500 beforePhase1() 1501 } 1502 1503 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1504 c.Assert(err, IsNil) 1505 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1506 1507 chg := s.state.NewChange("refresh", "...") 1508 for _, ts := range tss { 1509 chg.AddAll(ts) 1510 } 1511 1512 s.state.Unlock() 1513 defer s.se.Stop() 1514 s.settle(c) 1515 s.state.Lock() 1516 1517 c.Check(chg.Status(), Equals, state.DoneStatus) 1518 c.Check(chg.Err(), IsNil) 1519 1520 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1521 1522 return chg 1523 } 1524 1525 func (s *snapmgrTestSuite) TestAutoRefreshPhase2(c *C) { 1526 expected := []string{ 1527 "conditional-auto-refresh", 1528 "run-hook [snap-a;gate-auto-refresh]", 1529 // snap-b hook is triggered because of base-snap-b refresh 1530 "run-hook [snap-b;gate-auto-refresh]", 1531 "prerequisites", 1532 "download-snap", 1533 "validate-snap", 1534 "mount-snap", 1535 "run-hook [base-snap-b;pre-refresh]", 1536 "stop-snap-services", 1537 "remove-aliases", 1538 "unlink-current-snap", 1539 "copy-snap-data", 1540 "setup-profiles", 1541 "link-snap", 1542 "auto-connect", 1543 "set-auto-aliases", 1544 "setup-aliases", 1545 "run-hook [base-snap-b;post-refresh]", 1546 "start-snap-services", 1547 "cleanup", 1548 "run-hook [base-snap-b;check-health]", 1549 "prerequisites", 1550 "download-snap", 1551 "validate-snap", 1552 "mount-snap", 1553 "run-hook [snap-a;pre-refresh]", 1554 "stop-snap-services", 1555 "remove-aliases", 1556 "unlink-current-snap", 1557 "copy-snap-data", 1558 "setup-profiles", 1559 "link-snap", 1560 "auto-connect", 1561 "set-auto-aliases", 1562 "setup-aliases", 1563 "run-hook [snap-a;post-refresh]", 1564 "start-snap-services", 1565 "cleanup", 1566 "run-hook [snap-a;configure]", 1567 "run-hook [snap-a;check-health]", 1568 "check-rerefresh", 1569 } 1570 1571 seenSnapsWithGateAutoRefreshHook := make(map[string]bool) 1572 1573 chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1574 seenSnapsWithGateAutoRefreshHook[snapName] = true 1575 }, expected) 1576 1577 c.Check(seenSnapsWithGateAutoRefreshHook, DeepEquals, map[string]bool{ 1578 "snap-a": true, 1579 "snap-b": true, 1580 }) 1581 1582 s.state.Lock() 1583 defer s.state.Unlock() 1584 1585 tasks := chg.Tasks() 1586 c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "base-snap-b", "snap-a" as needed`) 1587 1588 // all snaps refreshed, all removed from refresh-candidates. 1589 var candidates map[string]*snapstate.RefreshCandidate 1590 c.Assert(s.state.Get("refresh-candidates", &candidates), IsNil) 1591 c.Assert(candidates, HasLen, 0) 1592 } 1593 1594 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Held(c *C) { 1595 logbuf, restoreLogger := logger.MockLogger() 1596 defer restoreLogger() 1597 1598 expected := []string{ 1599 "conditional-auto-refresh", 1600 "run-hook [snap-a;gate-auto-refresh]", 1601 // snap-b hook is triggered because of base-snap-b refresh 1602 "run-hook [snap-b;gate-auto-refresh]", 1603 "prerequisites", 1604 "download-snap", 1605 "validate-snap", 1606 "mount-snap", 1607 "run-hook [snap-a;pre-refresh]", 1608 "stop-snap-services", 1609 "remove-aliases", 1610 "unlink-current-snap", 1611 "copy-snap-data", 1612 "setup-profiles", 1613 "link-snap", 1614 "auto-connect", 1615 "set-auto-aliases", 1616 "setup-aliases", 1617 "run-hook [snap-a;post-refresh]", 1618 "start-snap-services", 1619 "cleanup", 1620 "run-hook [snap-a;configure]", 1621 "run-hook [snap-a;check-health]", 1622 "check-rerefresh", 1623 } 1624 1625 chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1626 if snapName == "snap-b" { 1627 // pretend than snap-b calls snapctl --hold to hold refresh of base-snap-b 1628 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1629 } 1630 }, expected) 1631 1632 s.state.Lock() 1633 defer s.state.Unlock() 1634 1635 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`) 1636 tasks := chg.Tasks() 1637 // no re-refresh for base-snap-b because it was held. 1638 c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "snap-a" as needed`) 1639 } 1640 1641 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Proceed(c *C) { 1642 logbuf, restoreLogger := logger.MockLogger() 1643 defer restoreLogger() 1644 1645 expected := []string{ 1646 "conditional-auto-refresh", 1647 "run-hook [snap-a;gate-auto-refresh]", 1648 // snap-b hook is triggered because of base-snap-b refresh 1649 "run-hook [snap-b;gate-auto-refresh]", 1650 "prerequisites", 1651 "download-snap", 1652 "validate-snap", 1653 "mount-snap", 1654 "run-hook [snap-a;pre-refresh]", 1655 "stop-snap-services", 1656 "remove-aliases", 1657 "unlink-current-snap", 1658 "copy-snap-data", 1659 "setup-profiles", 1660 "link-snap", 1661 "auto-connect", 1662 "set-auto-aliases", 1663 "setup-aliases", 1664 "run-hook [snap-a;post-refresh]", 1665 "start-snap-services", 1666 "cleanup", 1667 "run-hook [snap-a;configure]", 1668 "run-hook [snap-a;check-health]", 1669 "check-rerefresh", 1670 } 1671 1672 s.testAutoRefreshPhase2(c, func() { 1673 // pretend that snap-a and base-snap-b are initially held 1674 c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) 1675 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1676 }, func(snapName string) { 1677 if snapName == "snap-a" { 1678 // pretend than snap-a calls snapctl --proceed 1679 c.Assert(snapstate.ProceedWithRefresh(s.state, "snap-a"), IsNil) 1680 } 1681 // note, do nothing about snap-b which just keeps its hold state in 1682 // the test, but if we were using real gate-auto-refresh hook 1683 // handler, the default behavior for snap-b if it doesn't call --hold 1684 // would be to proceed (hook handler would take care of that). 1685 }, expected) 1686 1687 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`) 1688 } 1689 1690 func (s *snapmgrTestSuite) TestAutoRefreshPhase2AllHeld(c *C) { 1691 logbuf, restoreLogger := logger.MockLogger() 1692 defer restoreLogger() 1693 1694 expected := []string{ 1695 "conditional-auto-refresh", 1696 "run-hook [snap-a;gate-auto-refresh]", 1697 // snap-b hook is triggered because of base-snap-b refresh 1698 "run-hook [snap-b;gate-auto-refresh]", 1699 } 1700 1701 s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1702 switch snapName { 1703 case "snap-b": 1704 // pretend that snap-b calls snapctl --hold to hold refresh of base-snap-b 1705 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1706 case "snap-a": 1707 // pretend that snap-a calls snapctl --hold to hold itself 1708 c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) 1709 default: 1710 c.Fatalf("unexpected snap %q", snapName) 1711 } 1712 }, expected) 1713 1714 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b,snap-a`) 1715 } 1716 1717 func (s *snapmgrTestSuite) testAutoRefreshPhase2DiskSpaceCheck(c *C, fail bool) { 1718 st := s.state 1719 st.Lock() 1720 defer st.Unlock() 1721 1722 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 1723 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 1724 if fail { 1725 return &osutil.NotEnoughDiskSpaceError{} 1726 } 1727 return nil 1728 }) 1729 defer restore() 1730 1731 var installSizeCalled bool 1732 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1733 installSizeCalled = true 1734 seen := map[string]bool{} 1735 for _, sn := range snaps { 1736 seen[sn.InstanceName()] = true 1737 } 1738 c.Check(seen, DeepEquals, map[string]bool{ 1739 "base-snap-b": true, 1740 "snap-a": true, 1741 }) 1742 return 123, nil 1743 }) 1744 defer restoreInstallSize() 1745 1746 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 1747 defer restoreModel() 1748 1749 tr := config.NewTransaction(s.state) 1750 tr.Set("core", "experimental.check-disk-space-refresh", true) 1751 tr.Commit() 1752 1753 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1754 fakeStore: s.fakeStore, 1755 refreshedSnaps: []*snap.Info{{ 1756 Architectures: []string{"all"}, 1757 SnapType: snap.TypeApp, 1758 SideInfo: snap.SideInfo{ 1759 RealName: "snap-a", 1760 Revision: snap.R(8), 1761 }, 1762 }, { 1763 Architectures: []string{"all"}, 1764 SnapType: snap.TypeBase, 1765 SideInfo: snap.SideInfo{ 1766 RealName: "base-snap-b", 1767 Revision: snap.R(3), 1768 }, 1769 }}}) 1770 1771 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1772 mockInstalledSnap(c, s.state, snapByaml, useHook) 1773 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1774 1775 snapstate.MockSnapReadInfo(fakeReadInfo) 1776 1777 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1778 c.Assert(err, IsNil) 1779 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1780 1781 chg := s.state.NewChange("refresh", "...") 1782 for _, ts := range tss { 1783 chg.AddAll(ts) 1784 } 1785 1786 s.state.Unlock() 1787 defer s.se.Stop() 1788 s.settle(c) 1789 s.state.Lock() 1790 1791 c.Check(installSizeCalled, Equals, true) 1792 if fail { 1793 c.Check(chg.Status(), Equals, state.ErrorStatus) 1794 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- Run auto-refresh for ready snaps \(insufficient space.*`) 1795 } else { 1796 c.Check(chg.Status(), Equals, state.DoneStatus) 1797 c.Check(chg.Err(), IsNil) 1798 } 1799 } 1800 1801 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceError(c *C) { 1802 fail := true 1803 s.testAutoRefreshPhase2DiskSpaceCheck(c, fail) 1804 } 1805 1806 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceHappy(c *C) { 1807 var nofail bool 1808 s.testAutoRefreshPhase2DiskSpaceCheck(c, nofail) 1809 } 1810 1811 // XXX: this case is probably artificial; with proper conflict prevention 1812 // we shouldn't get conflicts from doInstall in phase2. 1813 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Conflict(c *C) { 1814 st := s.state 1815 st.Lock() 1816 defer st.Unlock() 1817 1818 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1819 fakeStore: s.fakeStore, 1820 refreshedSnaps: []*snap.Info{{ 1821 Architectures: []string{"all"}, 1822 SnapType: snap.TypeApp, 1823 SideInfo: snap.SideInfo{ 1824 RealName: "snap-a", 1825 Revision: snap.R(8), 1826 }, 1827 }, { 1828 Architectures: []string{"all"}, 1829 SnapType: snap.TypeBase, 1830 SideInfo: snap.SideInfo{ 1831 RealName: "base-snap-b", 1832 Revision: snap.R(3), 1833 }, 1834 }}}) 1835 1836 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1837 mockInstalledSnap(c, s.state, snapByaml, useHook) 1838 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1839 1840 snapstate.MockSnapReadInfo(fakeReadInfo) 1841 1842 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1843 defer restore() 1844 1845 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1846 c.Assert(err, IsNil) 1847 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1848 1849 chg := s.state.NewChange("refresh", "...") 1850 for _, ts := range tss { 1851 chg.AddAll(ts) 1852 } 1853 1854 conflictChange := st.NewChange("conflicting change", "") 1855 conflictTask := st.NewTask("conflicting task", "") 1856 si := &snap.SideInfo{ 1857 RealName: "snap-a", 1858 Revision: snap.R(1), 1859 } 1860 sup := snapstate.SnapSetup{SideInfo: si} 1861 conflictTask.Set("snap-setup", sup) 1862 conflictChange.AddTask(conflictTask) 1863 conflictTask.WaitFor(tss[0].Tasks()[0]) 1864 1865 s.state.Unlock() 1866 defer s.se.Stop() 1867 s.settle(c) 1868 s.state.Lock() 1869 1870 c.Assert(chg.Status(), Equals, state.DoneStatus) 1871 c.Check(chg.Err(), IsNil) 1872 1873 // no refresh of snap-a because of the conflict. 1874 expected := []string{ 1875 "conditional-auto-refresh", 1876 "run-hook [snap-a;gate-auto-refresh]", 1877 // snap-b hook is triggered because of base-snap-b refresh 1878 "run-hook [snap-b;gate-auto-refresh]", 1879 "prerequisites", 1880 "download-snap", 1881 "validate-snap", 1882 "mount-snap", 1883 "run-hook [base-snap-b;pre-refresh]", 1884 "stop-snap-services", 1885 "remove-aliases", 1886 "unlink-current-snap", 1887 "copy-snap-data", 1888 "setup-profiles", 1889 "link-snap", 1890 "auto-connect", 1891 "set-auto-aliases", 1892 "setup-aliases", 1893 "run-hook [base-snap-b;post-refresh]", 1894 "start-snap-services", 1895 "cleanup", 1896 "run-hook [base-snap-b;check-health]", 1897 "check-rerefresh", 1898 } 1899 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1900 } 1901 1902 func (s *snapmgrTestSuite) TestAutoRefreshPhase2ConflictOtherSnapOp(c *C) { 1903 st := s.state 1904 st.Lock() 1905 defer st.Unlock() 1906 1907 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1908 fakeStore: s.fakeStore, 1909 refreshedSnaps: []*snap.Info{{ 1910 Architectures: []string{"all"}, 1911 SnapType: snap.TypeApp, 1912 SideInfo: snap.SideInfo{ 1913 RealName: "snap-a", 1914 Revision: snap.R(8), 1915 }, 1916 }}}) 1917 1918 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1919 1920 snapstate.MockSnapReadInfo(fakeReadInfo) 1921 1922 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1923 defer restore() 1924 1925 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1926 c.Assert(err, IsNil) 1927 c.Check(names, DeepEquals, []string{"snap-a"}) 1928 1929 chg := s.state.NewChange("fake-auto-refresh", "...") 1930 for _, ts := range tss { 1931 chg.AddAll(ts) 1932 } 1933 1934 s.state.Unlock() 1935 // run first task 1936 s.se.Ensure() 1937 s.se.Wait() 1938 1939 s.state.Lock() 1940 1941 _, err = snapstate.Remove(s.state, "snap-a", snap.R(8), nil) 1942 c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{ 1943 ChangeKind: "fake-auto-refresh", 1944 Snap: "snap-a", 1945 }) 1946 1947 _, err = snapstate.Update(s.state, "snap-a", nil, 0, snapstate.Flags{}) 1948 c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{ 1949 ChangeKind: "fake-auto-refresh", 1950 Snap: "snap-a", 1951 }) 1952 1953 // only 2 tasks because we don't run settle() so conditional-auto-refresh 1954 // doesn't run and no new tasks get created. 1955 expected := []string{ 1956 "conditional-auto-refresh", 1957 "run-hook [snap-a;gate-auto-refresh]", 1958 } 1959 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1960 } 1961 1962 func (s *snapmgrTestSuite) TestAutoRefreshPhase2GatedSnaps(c *C) { 1963 st := s.state 1964 st.Lock() 1965 defer st.Unlock() 1966 1967 restore := snapstate.MockSnapsToRefresh(func(gatingTask *state.Task) ([]*snapstate.RefreshCandidate, error) { 1968 c.Assert(gatingTask.Kind(), Equals, "conditional-auto-refresh") 1969 var candidates map[string]*snapstate.RefreshCandidate 1970 c.Assert(gatingTask.Get("snaps", &candidates), IsNil) 1971 seenSnaps := make(map[string]bool) 1972 var filteredByGatingHooks []*snapstate.RefreshCandidate 1973 for _, cand := range candidates { 1974 seenSnaps[cand.InstanceName()] = true 1975 if cand.InstanceName() == "snap-a" { 1976 continue 1977 } 1978 filteredByGatingHooks = append(filteredByGatingHooks, cand) 1979 } 1980 c.Check(seenSnaps, DeepEquals, map[string]bool{ 1981 "snap-a": true, 1982 "base-snap-b": true, 1983 }) 1984 return filteredByGatingHooks, nil 1985 }) 1986 defer restore() 1987 1988 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1989 fakeStore: s.fakeStore, 1990 refreshedSnaps: []*snap.Info{ 1991 { 1992 Architectures: []string{"all"}, 1993 SnapType: snap.TypeApp, 1994 SideInfo: snap.SideInfo{ 1995 RealName: "snap-a", 1996 Revision: snap.R(8), 1997 }, 1998 }, { 1999 Architectures: []string{"all"}, 2000 SnapType: snap.TypeBase, 2001 SideInfo: snap.SideInfo{ 2002 RealName: "base-snap-b", 2003 Revision: snap.R(3), 2004 }, 2005 }, 2006 }}) 2007 2008 mockInstalledSnap(c, s.state, snapAyaml, useHook) 2009 mockInstalledSnap(c, s.state, snapByaml, useHook) 2010 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 2011 2012 snapstate.MockSnapReadInfo(fakeReadInfo) 2013 2014 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 2015 defer restoreModel() 2016 2017 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 2018 c.Assert(err, IsNil) 2019 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 2020 2021 chg := s.state.NewChange("refresh", "...") 2022 for _, ts := range tss { 2023 chg.AddAll(ts) 2024 } 2025 2026 s.state.Unlock() 2027 defer s.se.Stop() 2028 s.settle(c) 2029 s.state.Lock() 2030 2031 c.Assert(chg.Status(), Equals, state.DoneStatus) 2032 c.Check(chg.Err(), IsNil) 2033 2034 expected := []string{ 2035 "conditional-auto-refresh", 2036 "run-hook [snap-a;gate-auto-refresh]", 2037 // snap-b hook is triggered because of base-snap-b refresh 2038 "run-hook [snap-b;gate-auto-refresh]", 2039 "prerequisites", 2040 "download-snap", 2041 "validate-snap", 2042 "mount-snap", 2043 "run-hook [base-snap-b;pre-refresh]", 2044 "stop-snap-services", 2045 "remove-aliases", 2046 "unlink-current-snap", 2047 "copy-snap-data", 2048 "setup-profiles", 2049 "link-snap", 2050 "auto-connect", 2051 "set-auto-aliases", 2052 "setup-aliases", 2053 "run-hook [base-snap-b;post-refresh]", 2054 "start-snap-services", 2055 "cleanup", 2056 "run-hook [base-snap-b;check-health]", 2057 "check-rerefresh", 2058 } 2059 tasks := chg.Tasks() 2060 verifyPhasedAutorefreshTasks(c, tasks, expected) 2061 // no re-refresh for snap-a because it was held. 2062 c.Check(tasks[len(tasks)-1].Summary(), Equals, `Handling re-refresh of "base-snap-b" as needed`) 2063 2064 // only snap-a remains in refresh-candidates because it was held; 2065 // base-snap-b got pruned (was refreshed). 2066 var candidates map[string]*snapstate.RefreshCandidate 2067 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 2068 c.Assert(candidates, HasLen, 1) 2069 c.Check(candidates["snap-a"], NotNil) 2070 } 2071 2072 func verifyPhasedAutorefreshTasks(c *C, tasks []*state.Task, expected []string) { 2073 c.Assert(len(tasks), Equals, len(expected)) 2074 for i, t := range tasks { 2075 var got string 2076 if t.Kind() == "run-hook" { 2077 var hsup hookstate.HookSetup 2078 c.Assert(t.Get("hook-setup", &hsup), IsNil) 2079 got = fmt.Sprintf("%s [%s;%s]", t.Kind(), hsup.Snap, hsup.Hook) 2080 } else { 2081 got = t.Kind() 2082 } 2083 c.Assert(got, Equals, expected[i], Commentf("#%d", i)) 2084 } 2085 }