github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/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 const useHook = true 685 const noHook = false 686 687 func checkGatingTask(c *C, task *state.Task, expected map[string]*snapstate.RefreshCandidate) { 688 c.Assert(task.Kind(), Equals, "conditional-auto-refresh") 689 var snaps map[string]*snapstate.RefreshCandidate 690 c.Assert(task.Get("snaps", &snaps), IsNil) 691 c.Check(snaps, DeepEquals, expected) 692 } 693 694 func (s *autorefreshGatingSuite) TestAffectedByBase(c *C) { 695 restore := release.MockOnClassic(true) 696 defer restore() 697 698 st := s.state 699 700 st.Lock() 701 defer st.Unlock() 702 mockInstalledSnap(c, s.state, snapAyaml, useHook) 703 baseSnapA := mockInstalledSnap(c, s.state, baseSnapAyaml, noHook) 704 // unrelated snaps 705 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 706 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 707 708 c.Assert(s.repo.AddSnap(snapB), IsNil) 709 710 updates := []*snap.Info{baseSnapA} 711 affected, err := snapstate.AffectedByRefresh(st, updates) 712 c.Assert(err, IsNil) 713 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 714 "snap-a": { 715 Base: true, 716 AffectingSnaps: map[string]bool{ 717 "base-snap-a": true, 718 }}}) 719 } 720 721 func (s *autorefreshGatingSuite) TestAffectedByCore(c *C) { 722 restore := release.MockOnClassic(true) 723 defer restore() 724 725 st := s.state 726 727 st.Lock() 728 defer st.Unlock() 729 snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook) 730 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 731 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 732 733 c.Assert(s.repo.AddSnap(core), IsNil) 734 c.Assert(s.repo.AddSnap(snapB), IsNil) 735 c.Assert(s.repo.AddSnap(snapC), IsNil) 736 737 updates := []*snap.Info{core} 738 affected, err := snapstate.AffectedByRefresh(st, updates) 739 c.Assert(err, IsNil) 740 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 741 "snap-c": { 742 Base: true, 743 AffectingSnaps: map[string]bool{ 744 "core": true, 745 }}}) 746 } 747 748 func (s *autorefreshGatingSuite) TestAffectedByKernel(c *C) { 749 restore := release.MockOnClassic(true) 750 defer restore() 751 752 st := s.state 753 754 st.Lock() 755 defer st.Unlock() 756 kernel := mockInstalledSnap(c, s.state, kernelYaml, noHook) 757 mockInstalledSnap(c, s.state, snapCyaml, useHook) 758 mockInstalledSnap(c, s.state, snapByaml, noHook) 759 760 updates := []*snap.Info{kernel} 761 affected, err := snapstate.AffectedByRefresh(st, updates) 762 c.Assert(err, IsNil) 763 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 764 "snap-c": { 765 Restart: true, 766 AffectingSnaps: map[string]bool{ 767 "kernel": true, 768 }}}) 769 } 770 771 func (s *autorefreshGatingSuite) TestAffectedByGadget(c *C) { 772 restore := release.MockOnClassic(true) 773 defer restore() 774 775 st := s.state 776 777 st.Lock() 778 defer st.Unlock() 779 kernel := mockInstalledSnap(c, s.state, gadget1Yaml, noHook) 780 mockInstalledSnap(c, s.state, snapCyaml, useHook) 781 mockInstalledSnap(c, s.state, snapByaml, noHook) 782 783 updates := []*snap.Info{kernel} 784 affected, err := snapstate.AffectedByRefresh(st, updates) 785 c.Assert(err, IsNil) 786 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 787 "snap-c": { 788 Restart: true, 789 AffectingSnaps: map[string]bool{ 790 "gadget": true, 791 }}}) 792 } 793 794 func (s *autorefreshGatingSuite) TestAffectedBySlot(c *C) { 795 restore := release.MockOnClassic(true) 796 defer restore() 797 798 st := s.state 799 800 st.Lock() 801 defer st.Unlock() 802 803 snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook) 804 snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook) 805 // unrelated snap 806 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 807 808 c.Assert(s.repo.AddSnap(snapF), IsNil) 809 c.Assert(s.repo.AddSnap(snapD), IsNil) 810 c.Assert(s.repo.AddSnap(snapE), IsNil) 811 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 812 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 813 c.Assert(err, IsNil) 814 815 updates := []*snap.Info{snapD} 816 affected, err := snapstate.AffectedByRefresh(st, updates) 817 c.Assert(err, IsNil) 818 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 819 "snap-e": { 820 Restart: true, 821 AffectingSnaps: map[string]bool{ 822 "snap-d": true, 823 }}}) 824 } 825 826 func (s *autorefreshGatingSuite) TestNotAffectedByCoreOrSnapdSlot(c *C) { 827 restore := release.MockOnClassic(true) 828 defer restore() 829 830 st := s.state 831 832 st.Lock() 833 defer st.Unlock() 834 835 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 836 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 837 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 838 839 c.Assert(s.repo.AddSnap(snapG), IsNil) 840 c.Assert(s.repo.AddSnap(core), IsNil) 841 c.Assert(s.repo.AddSnap(snapB), IsNil) 842 843 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "mir"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "mir"}} 844 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 845 c.Assert(err, IsNil) 846 847 updates := []*snap.Info{core} 848 affected, err := snapstate.AffectedByRefresh(st, updates) 849 c.Assert(err, IsNil) 850 c.Check(affected, HasLen, 0) 851 } 852 853 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackend(c *C) { 854 restore := release.MockOnClassic(true) 855 defer restore() 856 857 st := s.state 858 859 st.Lock() 860 defer st.Unlock() 861 862 snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook) 863 snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook) 864 // unrelated snap 865 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 866 867 c.Assert(s.repo.AddSnap(snapF), IsNil) 868 c.Assert(s.repo.AddSnap(snapD), IsNil) 869 c.Assert(s.repo.AddSnap(snapE), IsNil) 870 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 871 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 872 c.Assert(err, IsNil) 873 874 // snapE has a plug using mount backend and is refreshed, this affects slot of snap-d. 875 updates := []*snap.Info{snapE} 876 affected, err := snapstate.AffectedByRefresh(st, updates) 877 c.Assert(err, IsNil) 878 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 879 "snap-d": { 880 Restart: true, 881 AffectingSnaps: map[string]bool{ 882 "snap-e": true, 883 }}}) 884 } 885 886 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendSnapdSlot(c *C) { 887 restore := release.MockOnClassic(true) 888 defer restore() 889 890 st := s.state 891 892 st.Lock() 893 defer st.Unlock() 894 895 snapdSnap := mockInstalledSnap(c, s.state, snapdYaml, useHook) 896 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 897 // unrelated snap 898 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 899 900 c.Assert(s.repo.AddSnap(snapF), IsNil) 901 c.Assert(s.repo.AddSnap(snapdSnap), IsNil) 902 c.Assert(s.repo.AddSnap(snapG), IsNil) 903 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "snapd", Name: "desktop"}} 904 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 905 c.Assert(err, IsNil) 906 907 // snapE has a plug using mount backend, refreshing snapd affects snapE. 908 updates := []*snap.Info{snapdSnap} 909 affected, err := snapstate.AffectedByRefresh(st, updates) 910 c.Assert(err, IsNil) 911 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 912 "snap-g": { 913 Restart: true, 914 AffectingSnaps: map[string]bool{ 915 "snapd": true, 916 }}}) 917 } 918 919 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendCoreSlot(c *C) { 920 restore := release.MockOnClassic(true) 921 defer restore() 922 923 st := s.state 924 925 st.Lock() 926 defer st.Unlock() 927 928 coreSnap := mockInstalledSnap(c, s.state, coreYaml, noHook) 929 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 930 931 c.Assert(s.repo.AddSnap(coreSnap), IsNil) 932 c.Assert(s.repo.AddSnap(snapG), IsNil) 933 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "desktop"}} 934 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 935 c.Assert(err, IsNil) 936 937 // snapG has a plug using mount backend, refreshing core affects snapE. 938 updates := []*snap.Info{coreSnap} 939 affected, err := snapstate.AffectedByRefresh(st, updates) 940 c.Assert(err, IsNil) 941 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 942 "snap-g": { 943 Restart: true, 944 AffectingSnaps: map[string]bool{ 945 "core": true, 946 }}}) 947 } 948 949 func (s *autorefreshGatingSuite) TestAffectedByBootBase(c *C) { 950 restore := release.MockOnClassic(false) 951 defer restore() 952 953 st := s.state 954 955 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 956 defer r() 957 958 st.Lock() 959 defer st.Unlock() 960 mockInstalledSnap(c, s.state, snapAyaml, useHook) 961 mockInstalledSnap(c, s.state, snapByaml, useHook) 962 mockInstalledSnap(c, s.state, snapDyaml, useHook) 963 mockInstalledSnap(c, s.state, snapEyaml, useHook) 964 core18 := mockInstalledSnap(c, s.state, core18Yaml, noHook) 965 966 updates := []*snap.Info{core18} 967 affected, err := snapstate.AffectedByRefresh(st, updates) 968 c.Assert(err, IsNil) 969 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 970 "snap-a": { 971 Base: false, 972 Restart: true, 973 AffectingSnaps: map[string]bool{ 974 "core18": true, 975 }, 976 }, 977 "snap-b": { 978 Base: false, 979 Restart: true, 980 AffectingSnaps: map[string]bool{ 981 "core18": true, 982 }, 983 }, 984 "snap-d": { 985 Base: false, 986 Restart: true, 987 AffectingSnaps: map[string]bool{ 988 "core18": true, 989 }, 990 }, 991 "snap-e": { 992 Base: false, 993 Restart: true, 994 AffectingSnaps: map[string]bool{ 995 "core18": true, 996 }}}) 997 } 998 999 func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) { 1000 st := s.state 1001 st.Lock() 1002 defer st.Unlock() 1003 1004 affected := map[string]*snapstate.AffectedSnapInfo{ 1005 "snap-a": { 1006 Base: true, 1007 Restart: true, 1008 AffectingSnaps: map[string]bool{ 1009 "snap-c": true, 1010 "snap-d": true, 1011 }, 1012 }, 1013 "snap-b": { 1014 AffectingSnaps: map[string]bool{ 1015 "snap-e": true, 1016 "snap-f": true, 1017 }, 1018 }, 1019 } 1020 1021 seenSnaps := make(map[string]bool) 1022 1023 ts := snapstate.CreateGateAutoRefreshHooks(st, affected) 1024 c.Assert(ts.Tasks(), HasLen, 2) 1025 1026 checkHook := func(t *state.Task) { 1027 c.Assert(t.Kind(), Equals, "run-hook") 1028 var hs hookstate.HookSetup 1029 c.Assert(t.Get("hook-setup", &hs), IsNil) 1030 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1031 c.Check(hs.Optional, Equals, true) 1032 seenSnaps[hs.Snap] = true 1033 1034 var data interface{} 1035 c.Assert(t.Get("hook-context", &data), IsNil) 1036 1037 // the order of hook tasks is not deterministic 1038 if hs.Snap == "snap-a" { 1039 c.Check(data, DeepEquals, map[string]interface{}{ 1040 "base": true, 1041 "restart": true, 1042 "affecting-snaps": []interface{}{"snap-c", "snap-d"}}) 1043 } else { 1044 c.Assert(hs.Snap, Equals, "snap-b") 1045 c.Check(data, DeepEquals, map[string]interface{}{ 1046 "base": false, 1047 "restart": false, 1048 "affecting-snaps": []interface{}{"snap-e", "snap-f"}}) 1049 } 1050 } 1051 1052 checkHook(ts.Tasks()[0]) 1053 checkHook(ts.Tasks()[1]) 1054 1055 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 1056 } 1057 1058 func (s *autorefreshGatingSuite) TestAutorefreshPhase1FeatureFlag(c *C) { 1059 st := s.state 1060 st.Lock() 1061 defer st.Unlock() 1062 1063 st.Set("seeded", true) 1064 1065 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1066 defer restore() 1067 1068 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 1069 return nil, nil 1070 } 1071 defer func() { snapstate.AutoAliases = nil }() 1072 1073 s.store.refreshedSnaps = []*snap.Info{{ 1074 Architectures: []string{"all"}, 1075 SnapType: snap.TypeApp, 1076 SideInfo: snap.SideInfo{ 1077 RealName: "snap-a", 1078 Revision: snap.R(8), 1079 }, 1080 }} 1081 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1082 1083 // gate-auto-refresh-hook feature not enabled, expect old-style refresh. 1084 _, tss, err := snapstate.AutoRefresh(context.TODO(), st) 1085 c.Check(err, IsNil) 1086 c.Assert(tss, HasLen, 2) 1087 c.Check(tss[0].Tasks()[0].Kind(), Equals, "prerequisites") 1088 c.Check(tss[0].Tasks()[1].Kind(), Equals, "download-snap") 1089 c.Check(tss[1].Tasks()[0].Kind(), Equals, "check-rerefresh") 1090 1091 // enable gate-auto-refresh-hook feature 1092 tr := config.NewTransaction(s.state) 1093 tr.Set("core", "experimental.gate-auto-refresh-hook", true) 1094 tr.Commit() 1095 1096 _, tss, err = snapstate.AutoRefresh(context.TODO(), st) 1097 c.Check(err, IsNil) 1098 c.Assert(tss, HasLen, 2) 1099 // TODO: verify conditional-auto-refresh task data 1100 c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh") 1101 c.Check(tss[1].Tasks()[0].Kind(), Equals, "run-hook") 1102 } 1103 1104 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) { 1105 s.store.refreshedSnaps = []*snap.Info{{ 1106 Architectures: []string{"all"}, 1107 SnapType: snap.TypeApp, 1108 SideInfo: snap.SideInfo{ 1109 RealName: "snap-a", 1110 Revision: snap.R(8), 1111 }, 1112 }, { 1113 Architectures: []string{"all"}, 1114 SnapType: snap.TypeBase, 1115 SideInfo: snap.SideInfo{ 1116 RealName: "base-snap-b", 1117 Revision: snap.R(3), 1118 }, 1119 }, { 1120 Architectures: []string{"all"}, 1121 SnapType: snap.TypeApp, 1122 SideInfo: snap.SideInfo{ 1123 RealName: "snap-c", 1124 Revision: snap.R(5), 1125 }, 1126 }} 1127 1128 st := s.state 1129 st.Lock() 1130 defer st.Unlock() 1131 1132 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1133 mockInstalledSnap(c, s.state, snapByaml, useHook) 1134 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1135 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1136 mockInstalledSnap(c, s.state, snapDyaml, noHook) 1137 1138 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1139 defer restore() 1140 1141 // pretend some snaps are held 1142 c.Assert(snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d"), IsNil) 1143 // sanity check 1144 heldSnaps, err := snapstate.HeldSnaps(st) 1145 c.Assert(err, IsNil) 1146 c.Check(heldSnaps, DeepEquals, map[string]bool{ 1147 "snap-a": true, 1148 "snap-d": true, 1149 }) 1150 1151 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1152 c.Assert(err, IsNil) 1153 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a", "snap-c"}) 1154 c.Assert(tss, HasLen, 2) 1155 1156 c.Assert(tss[0].Tasks(), HasLen, 1) 1157 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1158 "snap-a": { 1159 SnapSetup: snapstate.SnapSetup{ 1160 Type: "app", 1161 PlugsOnly: true, 1162 Flags: snapstate.Flags{ 1163 IsAutoRefresh: true, 1164 }, 1165 SideInfo: &snap.SideInfo{ 1166 RealName: "snap-a", 1167 Revision: snap.R(8), 1168 }, 1169 DownloadInfo: &snap.DownloadInfo{}, 1170 }, 1171 }, 1172 "base-snap-b": { 1173 SnapSetup: snapstate.SnapSetup{ 1174 Type: "base", 1175 PlugsOnly: true, 1176 Flags: snapstate.Flags{ 1177 IsAutoRefresh: true, 1178 }, 1179 SideInfo: &snap.SideInfo{ 1180 RealName: "base-snap-b", 1181 Revision: snap.R(3), 1182 }, 1183 DownloadInfo: &snap.DownloadInfo{}, 1184 }, 1185 }, 1186 "snap-c": { 1187 SnapSetup: snapstate.SnapSetup{ 1188 Type: "app", 1189 PlugsOnly: true, 1190 Flags: snapstate.Flags{ 1191 IsAutoRefresh: true, 1192 }, 1193 SideInfo: &snap.SideInfo{ 1194 RealName: "snap-c", 1195 Revision: snap.R(5), 1196 }, 1197 DownloadInfo: &snap.DownloadInfo{}, 1198 }, 1199 }, 1200 }) 1201 1202 c.Assert(tss[1].Tasks(), HasLen, 2) 1203 1204 // check hooks for affected snaps 1205 seenSnaps := make(map[string]bool) 1206 var hs hookstate.HookSetup 1207 c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil) 1208 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1209 seenSnaps[hs.Snap] = true 1210 1211 c.Assert(tss[1].Tasks()[1].Get("hook-setup", &hs), IsNil) 1212 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1213 seenSnaps[hs.Snap] = true 1214 1215 // hook for snap-a because it gets refreshed, for snap-b because its base 1216 // gets refreshed. snap-c is refreshed but doesn't have the hook. 1217 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 1218 1219 // check that refresh-candidates in the state were updated 1220 var candidates map[string]*snapstate.RefreshCandidate 1221 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1222 c.Assert(candidates, HasLen, 3) 1223 c.Check(candidates["snap-a"], NotNil) 1224 c.Check(candidates["base-snap-b"], NotNil) 1225 c.Check(candidates["snap-c"], NotNil) 1226 1227 // check that after autoRefreshPhase1 any held snaps that are not in refresh 1228 // candidates got removed. 1229 heldSnaps, err = snapstate.HeldSnaps(st) 1230 c.Assert(err, IsNil) 1231 // snap-d got removed from held snaps. 1232 c.Check(heldSnaps, DeepEquals, map[string]bool{ 1233 "snap-a": true, 1234 }) 1235 } 1236 1237 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1ConflictsFilteredOut(c *C) { 1238 s.store.refreshedSnaps = []*snap.Info{{ 1239 Architectures: []string{"all"}, 1240 SnapType: snap.TypeApp, 1241 SideInfo: snap.SideInfo{ 1242 RealName: "snap-a", 1243 Revision: snap.R(8), 1244 }, 1245 }, { 1246 Architectures: []string{"all"}, 1247 SnapType: snap.TypeBase, 1248 SideInfo: snap.SideInfo{ 1249 RealName: "snap-c", 1250 Revision: snap.R(5), 1251 }, 1252 }} 1253 1254 st := s.state 1255 st.Lock() 1256 defer st.Unlock() 1257 1258 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1259 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1260 1261 conflictChange := st.NewChange("conflicting change", "") 1262 conflictTask := st.NewTask("conflicting task", "") 1263 si := &snap.SideInfo{ 1264 RealName: "snap-c", 1265 Revision: snap.R(1), 1266 } 1267 sup := snapstate.SnapSetup{SideInfo: si} 1268 conflictTask.Set("snap-setup", sup) 1269 conflictChange.AddTask(conflictTask) 1270 1271 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1272 defer restore() 1273 1274 logbuf, restoreLogger := logger.MockLogger() 1275 defer restoreLogger() 1276 1277 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1278 c.Assert(err, IsNil) 1279 c.Check(names, DeepEquals, []string{"snap-a"}) 1280 c.Assert(tss, HasLen, 2) 1281 1282 c.Assert(tss[0].Tasks(), HasLen, 1) 1283 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1284 "snap-a": { 1285 SnapSetup: snapstate.SnapSetup{ 1286 Type: "app", 1287 PlugsOnly: true, 1288 Flags: snapstate.Flags{ 1289 IsAutoRefresh: true, 1290 }, 1291 SideInfo: &snap.SideInfo{ 1292 RealName: "snap-a", 1293 Revision: snap.R(8), 1294 }, 1295 DownloadInfo: &snap.DownloadInfo{}, 1296 }}}) 1297 1298 c.Assert(tss[1].Tasks(), HasLen, 1) 1299 1300 c.Assert(logbuf.String(), testutil.Contains, `cannot refresh snap "snap-c": snap "snap-c" has "conflicting change" change in progress`) 1301 1302 seenSnaps := make(map[string]bool) 1303 var hs hookstate.HookSetup 1304 c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil) 1305 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1306 seenSnaps[hs.Snap] = true 1307 1308 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true}) 1309 1310 // check that refresh-candidates in the state were updated 1311 var candidates map[string]*snapstate.RefreshCandidate 1312 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1313 c.Assert(candidates, HasLen, 2) 1314 c.Check(candidates["snap-a"], NotNil) 1315 c.Check(candidates["snap-c"], NotNil) 1316 } 1317 1318 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1NoHooks(c *C) { 1319 s.store.refreshedSnaps = []*snap.Info{{ 1320 Architectures: []string{"all"}, 1321 SnapType: snap.TypeBase, 1322 SideInfo: snap.SideInfo{ 1323 RealName: "base-snap-b", 1324 Revision: snap.R(3), 1325 }, 1326 }, { 1327 Architectures: []string{"all"}, 1328 SnapType: snap.TypeBase, 1329 SideInfo: snap.SideInfo{ 1330 RealName: "snap-c", 1331 Revision: snap.R(5), 1332 }, 1333 }} 1334 1335 st := s.state 1336 st.Lock() 1337 defer st.Unlock() 1338 1339 mockInstalledSnap(c, s.state, snapByaml, noHook) 1340 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1341 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1342 1343 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1344 defer restore() 1345 1346 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1347 c.Assert(err, IsNil) 1348 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-c"}) 1349 c.Assert(tss, HasLen, 1) 1350 1351 c.Assert(tss[0].Tasks(), HasLen, 1) 1352 c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh") 1353 } 1354 1355 func fakeReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) { 1356 info := &snap.Info{ 1357 SuggestedName: name, 1358 SideInfo: *si, 1359 Architectures: []string{"all"}, 1360 SnapType: snap.TypeApp, 1361 Epoch: snap.Epoch{}, 1362 } 1363 switch name { 1364 case "base-snap-b": 1365 info.SnapType = snap.TypeBase 1366 case "snap-a", "snap-b": 1367 info.Hooks = map[string]*snap.HookInfo{ 1368 "gate-auto-refresh": { 1369 Name: "gate-auto-refresh", 1370 Snap: info, 1371 }, 1372 } 1373 if name == "snap-b" { 1374 info.Base = "base-snap-b" 1375 } 1376 } 1377 return info, nil 1378 } 1379 1380 func (s *snapmgrTestSuite) testAutoRefreshPhase2(c *C, beforePhase1 func(), gateAutoRefreshHook func(snapName string), expected []string) { 1381 st := s.state 1382 st.Lock() 1383 defer st.Unlock() 1384 1385 s.o.TaskRunner().AddHandler("run-hook", func(t *state.Task, tomb *tomb.Tomb) error { 1386 var hsup hookstate.HookSetup 1387 t.State().Lock() 1388 defer t.State().Unlock() 1389 c.Assert(t.Get("hook-setup", &hsup), IsNil) 1390 if hsup.Hook == "gate-auto-refresh" && gateAutoRefreshHook != nil { 1391 gateAutoRefreshHook(hsup.Snap) 1392 } 1393 return nil 1394 }, nil) 1395 1396 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1397 c.Fatal("unexpected call to installSize") 1398 return 0, nil 1399 }) 1400 defer restoreInstallSize() 1401 1402 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1403 fakeStore: s.fakeStore, 1404 refreshedSnaps: []*snap.Info{{ 1405 Architectures: []string{"all"}, 1406 SnapType: snap.TypeApp, 1407 SideInfo: snap.SideInfo{ 1408 RealName: "snap-a", 1409 Revision: snap.R(8), 1410 }, 1411 }, { 1412 Architectures: []string{"all"}, 1413 SnapType: snap.TypeBase, 1414 SideInfo: snap.SideInfo{ 1415 RealName: "base-snap-b", 1416 Revision: snap.R(3), 1417 }, 1418 }}}) 1419 1420 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1421 mockInstalledSnap(c, s.state, snapByaml, useHook) 1422 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1423 1424 snapstate.MockSnapReadInfo(fakeReadInfo) 1425 1426 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1427 defer restore() 1428 1429 if beforePhase1 != nil { 1430 beforePhase1() 1431 } 1432 1433 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1434 c.Assert(err, IsNil) 1435 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1436 1437 chg := s.state.NewChange("refresh", "...") 1438 for _, ts := range tss { 1439 chg.AddAll(ts) 1440 } 1441 1442 s.state.Unlock() 1443 defer s.se.Stop() 1444 s.settle(c) 1445 s.state.Lock() 1446 1447 c.Check(chg.Status(), Equals, state.DoneStatus) 1448 c.Check(chg.Err(), IsNil) 1449 1450 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1451 } 1452 1453 func (s *snapmgrTestSuite) TestAutoRefreshPhase2(c *C) { 1454 expected := []string{ 1455 "conditional-auto-refresh", 1456 "run-hook [snap-a;gate-auto-refresh]", 1457 // snap-b hook is triggered because of base-snap-b refresh 1458 "run-hook [snap-b;gate-auto-refresh]", 1459 "prerequisites", 1460 "download-snap", 1461 "validate-snap", 1462 "mount-snap", 1463 "run-hook [base-snap-b;pre-refresh]", 1464 "stop-snap-services", 1465 "remove-aliases", 1466 "unlink-current-snap", 1467 "copy-snap-data", 1468 "setup-profiles", 1469 "link-snap", 1470 "auto-connect", 1471 "set-auto-aliases", 1472 "setup-aliases", 1473 "run-hook [base-snap-b;post-refresh]", 1474 "start-snap-services", 1475 "cleanup", 1476 "run-hook [base-snap-b;check-health]", 1477 "prerequisites", 1478 "download-snap", 1479 "validate-snap", 1480 "mount-snap", 1481 "run-hook [snap-a;pre-refresh]", 1482 "stop-snap-services", 1483 "remove-aliases", 1484 "unlink-current-snap", 1485 "copy-snap-data", 1486 "setup-profiles", 1487 "link-snap", 1488 "auto-connect", 1489 "set-auto-aliases", 1490 "setup-aliases", 1491 "run-hook [snap-a;post-refresh]", 1492 "start-snap-services", 1493 "cleanup", 1494 "run-hook [snap-a;configure]", 1495 "run-hook [snap-a;check-health]", 1496 "check-rerefresh", 1497 } 1498 1499 seenSnapsWithGateAutoRefreshHook := make(map[string]bool) 1500 1501 s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1502 seenSnapsWithGateAutoRefreshHook[snapName] = true 1503 }, expected) 1504 1505 c.Check(seenSnapsWithGateAutoRefreshHook, DeepEquals, map[string]bool{ 1506 "snap-a": true, 1507 "snap-b": true, 1508 }) 1509 } 1510 1511 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Held(c *C) { 1512 logbuf, restoreLogger := logger.MockLogger() 1513 defer restoreLogger() 1514 1515 expected := []string{ 1516 "conditional-auto-refresh", 1517 "run-hook [snap-a;gate-auto-refresh]", 1518 // snap-b hook is triggered because of base-snap-b refresh 1519 "run-hook [snap-b;gate-auto-refresh]", 1520 "prerequisites", 1521 "download-snap", 1522 "validate-snap", 1523 "mount-snap", 1524 "run-hook [snap-a;pre-refresh]", 1525 "stop-snap-services", 1526 "remove-aliases", 1527 "unlink-current-snap", 1528 "copy-snap-data", 1529 "setup-profiles", 1530 "link-snap", 1531 "auto-connect", 1532 "set-auto-aliases", 1533 "setup-aliases", 1534 "run-hook [snap-a;post-refresh]", 1535 "start-snap-services", 1536 "cleanup", 1537 "run-hook [snap-a;configure]", 1538 "run-hook [snap-a;check-health]", 1539 "check-rerefresh", 1540 } 1541 1542 s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1543 if snapName == "snap-b" { 1544 // pretend than snap-b calls snapctl --hold to hold refresh of base-snap-b 1545 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1546 } 1547 }, expected) 1548 1549 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`) 1550 } 1551 1552 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Proceed(c *C) { 1553 logbuf, restoreLogger := logger.MockLogger() 1554 defer restoreLogger() 1555 1556 expected := []string{ 1557 "conditional-auto-refresh", 1558 "run-hook [snap-a;gate-auto-refresh]", 1559 // snap-b hook is triggered because of base-snap-b refresh 1560 "run-hook [snap-b;gate-auto-refresh]", 1561 "prerequisites", 1562 "download-snap", 1563 "validate-snap", 1564 "mount-snap", 1565 "run-hook [snap-a;pre-refresh]", 1566 "stop-snap-services", 1567 "remove-aliases", 1568 "unlink-current-snap", 1569 "copy-snap-data", 1570 "setup-profiles", 1571 "link-snap", 1572 "auto-connect", 1573 "set-auto-aliases", 1574 "setup-aliases", 1575 "run-hook [snap-a;post-refresh]", 1576 "start-snap-services", 1577 "cleanup", 1578 "run-hook [snap-a;configure]", 1579 "run-hook [snap-a;check-health]", 1580 "check-rerefresh", 1581 } 1582 1583 s.testAutoRefreshPhase2(c, func() { 1584 // pretend that snap-a and base-snap-b are initially held 1585 c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) 1586 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1587 }, func(snapName string) { 1588 if snapName == "snap-a" { 1589 // pretend than snap-a calls snapctl --proceed 1590 c.Assert(snapstate.ProceedWithRefresh(s.state, "snap-a"), IsNil) 1591 } 1592 // note, do nothing about snap-b which just keeps its hold state in 1593 // the test, but if we were using real gate-auto-refresh hook 1594 // handler, the default behavior for snap-b if it doesn't call --hold 1595 // would be to proceed (hook handler would take care of that). 1596 }, expected) 1597 1598 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b`) 1599 } 1600 1601 func (s *snapmgrTestSuite) TestAutoRefreshPhase2AllHeld(c *C) { 1602 logbuf, restoreLogger := logger.MockLogger() 1603 defer restoreLogger() 1604 1605 expected := []string{ 1606 "conditional-auto-refresh", 1607 "run-hook [snap-a;gate-auto-refresh]", 1608 // snap-b hook is triggered because of base-snap-b refresh 1609 "run-hook [snap-b;gate-auto-refresh]", 1610 "check-rerefresh", 1611 } 1612 1613 s.testAutoRefreshPhase2(c, nil, func(snapName string) { 1614 switch snapName { 1615 case "snap-b": 1616 // pretend that snap-b calls snapctl --hold to hold refresh of base-snap-b 1617 c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) 1618 case "snap-a": 1619 // pretend that snap-a calls snapctl --hold to hold itself 1620 c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) 1621 default: 1622 c.Fatalf("unexpected snap %q", snapName) 1623 } 1624 }, expected) 1625 1626 c.Assert(logbuf.String(), testutil.Contains, `skipping refresh of held snaps: base-snap-b,snap-a`) 1627 } 1628 1629 func (s *snapmgrTestSuite) testAutoRefreshPhase2DiskSpaceCheck(c *C, fail bool) { 1630 st := s.state 1631 st.Lock() 1632 defer st.Unlock() 1633 1634 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 1635 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 1636 if fail { 1637 return &osutil.NotEnoughDiskSpaceError{} 1638 } 1639 return nil 1640 }) 1641 defer restore() 1642 1643 var installSizeCalled bool 1644 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1645 installSizeCalled = true 1646 seen := map[string]bool{} 1647 for _, sn := range snaps { 1648 seen[sn.InstanceName()] = true 1649 } 1650 c.Check(seen, DeepEquals, map[string]bool{ 1651 "base-snap-b": true, 1652 "snap-a": true, 1653 }) 1654 return 123, nil 1655 }) 1656 defer restoreInstallSize() 1657 1658 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 1659 defer restoreModel() 1660 1661 tr := config.NewTransaction(s.state) 1662 tr.Set("core", "experimental.check-disk-space-refresh", true) 1663 tr.Commit() 1664 1665 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1666 fakeStore: s.fakeStore, 1667 refreshedSnaps: []*snap.Info{{ 1668 Architectures: []string{"all"}, 1669 SnapType: snap.TypeApp, 1670 SideInfo: snap.SideInfo{ 1671 RealName: "snap-a", 1672 Revision: snap.R(8), 1673 }, 1674 }, { 1675 Architectures: []string{"all"}, 1676 SnapType: snap.TypeBase, 1677 SideInfo: snap.SideInfo{ 1678 RealName: "base-snap-b", 1679 Revision: snap.R(3), 1680 }, 1681 }}}) 1682 1683 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1684 mockInstalledSnap(c, s.state, snapByaml, useHook) 1685 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1686 1687 snapstate.MockSnapReadInfo(fakeReadInfo) 1688 1689 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1690 c.Assert(err, IsNil) 1691 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1692 1693 chg := s.state.NewChange("refresh", "...") 1694 for _, ts := range tss { 1695 chg.AddAll(ts) 1696 } 1697 1698 s.state.Unlock() 1699 defer s.se.Stop() 1700 s.settle(c) 1701 s.state.Lock() 1702 1703 c.Check(installSizeCalled, Equals, true) 1704 if fail { 1705 c.Check(chg.Status(), Equals, state.ErrorStatus) 1706 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- Run auto-refresh for ready snaps \(insufficient space.*`) 1707 } else { 1708 c.Check(chg.Status(), Equals, state.DoneStatus) 1709 c.Check(chg.Err(), IsNil) 1710 } 1711 } 1712 1713 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceError(c *C) { 1714 fail := true 1715 s.testAutoRefreshPhase2DiskSpaceCheck(c, fail) 1716 } 1717 1718 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceHappy(c *C) { 1719 var nofail bool 1720 s.testAutoRefreshPhase2DiskSpaceCheck(c, nofail) 1721 } 1722 1723 // XXX: this case is probably artificial; with proper conflict prevention 1724 // we shouldn't get conflicts from doInstall in phase2. 1725 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Conflict(c *C) { 1726 st := s.state 1727 st.Lock() 1728 defer st.Unlock() 1729 1730 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1731 fakeStore: s.fakeStore, 1732 refreshedSnaps: []*snap.Info{{ 1733 Architectures: []string{"all"}, 1734 SnapType: snap.TypeApp, 1735 SideInfo: snap.SideInfo{ 1736 RealName: "snap-a", 1737 Revision: snap.R(8), 1738 }, 1739 }, { 1740 Architectures: []string{"all"}, 1741 SnapType: snap.TypeBase, 1742 SideInfo: snap.SideInfo{ 1743 RealName: "base-snap-b", 1744 Revision: snap.R(3), 1745 }, 1746 }}}) 1747 1748 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1749 mockInstalledSnap(c, s.state, snapByaml, useHook) 1750 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1751 1752 snapstate.MockSnapReadInfo(fakeReadInfo) 1753 1754 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1755 defer restore() 1756 1757 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1758 c.Assert(err, IsNil) 1759 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1760 1761 chg := s.state.NewChange("refresh", "...") 1762 for _, ts := range tss { 1763 chg.AddAll(ts) 1764 } 1765 1766 conflictChange := st.NewChange("conflicting change", "") 1767 conflictTask := st.NewTask("conflicting task", "") 1768 si := &snap.SideInfo{ 1769 RealName: "snap-a", 1770 Revision: snap.R(1), 1771 } 1772 sup := snapstate.SnapSetup{SideInfo: si} 1773 conflictTask.Set("snap-setup", sup) 1774 conflictChange.AddTask(conflictTask) 1775 conflictTask.WaitFor(tss[0].Tasks()[0]) 1776 1777 s.state.Unlock() 1778 defer s.se.Stop() 1779 s.settle(c) 1780 s.state.Lock() 1781 1782 c.Assert(chg.Status(), Equals, state.DoneStatus) 1783 c.Check(chg.Err(), IsNil) 1784 1785 expected := []string{ 1786 "conditional-auto-refresh", 1787 "run-hook [snap-a;gate-auto-refresh]", 1788 // snap-b hook is triggered because of base-snap-b refresh 1789 "run-hook [snap-b;gate-auto-refresh]", 1790 "prerequisites", 1791 "download-snap", 1792 "validate-snap", 1793 "mount-snap", 1794 "run-hook [base-snap-b;pre-refresh]", 1795 "stop-snap-services", 1796 "remove-aliases", 1797 "unlink-current-snap", 1798 "copy-snap-data", 1799 "setup-profiles", 1800 "link-snap", 1801 "auto-connect", 1802 "set-auto-aliases", 1803 "setup-aliases", 1804 "run-hook [base-snap-b;post-refresh]", 1805 "start-snap-services", 1806 "cleanup", 1807 "run-hook [base-snap-b;check-health]", 1808 "check-rerefresh", 1809 } 1810 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1811 } 1812 1813 func (s *snapmgrTestSuite) TestAutoRefreshPhase2GatedSnaps(c *C) { 1814 st := s.state 1815 st.Lock() 1816 defer st.Unlock() 1817 1818 restore := snapstate.MockSnapsToRefresh(func(gatingTask *state.Task) ([]*snapstate.RefreshCandidate, error) { 1819 c.Assert(gatingTask.Kind(), Equals, "conditional-auto-refresh") 1820 var candidates map[string]*snapstate.RefreshCandidate 1821 c.Assert(gatingTask.Get("snaps", &candidates), IsNil) 1822 seenSnaps := make(map[string]bool) 1823 var filteredByGatingHooks []*snapstate.RefreshCandidate 1824 for _, cand := range candidates { 1825 seenSnaps[cand.InstanceName()] = true 1826 if cand.InstanceName() == "snap-a" { 1827 continue 1828 } 1829 filteredByGatingHooks = append(filteredByGatingHooks, cand) 1830 } 1831 c.Check(seenSnaps, DeepEquals, map[string]bool{ 1832 "snap-a": true, 1833 "base-snap-b": true, 1834 }) 1835 return filteredByGatingHooks, nil 1836 }) 1837 defer restore() 1838 1839 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1840 fakeStore: s.fakeStore, 1841 refreshedSnaps: []*snap.Info{ 1842 { 1843 Architectures: []string{"all"}, 1844 SnapType: snap.TypeApp, 1845 SideInfo: snap.SideInfo{ 1846 RealName: "snap-a", 1847 Revision: snap.R(8), 1848 }, 1849 }, { 1850 Architectures: []string{"all"}, 1851 SnapType: snap.TypeBase, 1852 SideInfo: snap.SideInfo{ 1853 RealName: "base-snap-b", 1854 Revision: snap.R(3), 1855 }, 1856 }, 1857 }}) 1858 1859 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1860 mockInstalledSnap(c, s.state, snapByaml, useHook) 1861 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1862 1863 snapstate.MockSnapReadInfo(fakeReadInfo) 1864 1865 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 1866 defer restoreModel() 1867 1868 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1869 c.Assert(err, IsNil) 1870 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1871 1872 chg := s.state.NewChange("refresh", "...") 1873 for _, ts := range tss { 1874 chg.AddAll(ts) 1875 } 1876 1877 s.state.Unlock() 1878 defer s.se.Stop() 1879 s.settle(c) 1880 s.state.Lock() 1881 1882 c.Assert(chg.Status(), Equals, state.DoneStatus) 1883 c.Check(chg.Err(), IsNil) 1884 1885 expected := []string{ 1886 "conditional-auto-refresh", 1887 "run-hook [snap-a;gate-auto-refresh]", 1888 // snap-b hook is triggered because of base-snap-b refresh 1889 "run-hook [snap-b;gate-auto-refresh]", 1890 "prerequisites", 1891 "download-snap", 1892 "validate-snap", 1893 "mount-snap", 1894 "run-hook [base-snap-b;pre-refresh]", 1895 "stop-snap-services", 1896 "remove-aliases", 1897 "unlink-current-snap", 1898 "copy-snap-data", 1899 "setup-profiles", 1900 "link-snap", 1901 "auto-connect", 1902 "set-auto-aliases", 1903 "setup-aliases", 1904 "run-hook [base-snap-b;post-refresh]", 1905 "start-snap-services", 1906 "cleanup", 1907 "run-hook [base-snap-b;check-health]", 1908 "check-rerefresh", 1909 } 1910 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1911 } 1912 1913 func verifyPhasedAutorefreshTasks(c *C, tasks []*state.Task, expected []string) { 1914 for i, t := range tasks { 1915 var got string 1916 if t.Kind() == "run-hook" { 1917 var hsup hookstate.HookSetup 1918 c.Assert(t.Get("hook-setup", &hsup), IsNil) 1919 got = fmt.Sprintf("%s [%s;%s]", t.Kind(), hsup.Snap, hsup.Hook) 1920 } else { 1921 got = t.Kind() 1922 } 1923 c.Assert(got, Equals, expected[i]) 1924 } 1925 }