github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/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 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/interfaces" 32 "github.com/snapcore/snapd/interfaces/builtin" 33 "github.com/snapcore/snapd/logger" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/overlord/auth" 36 "github.com/snapcore/snapd/overlord/configstate/config" 37 "github.com/snapcore/snapd/overlord/hookstate" 38 "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" 39 "github.com/snapcore/snapd/overlord/snapstate" 40 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 41 "github.com/snapcore/snapd/overlord/state" 42 "github.com/snapcore/snapd/release" 43 "github.com/snapcore/snapd/snap" 44 "github.com/snapcore/snapd/snap/snaptest" 45 "github.com/snapcore/snapd/store" 46 "github.com/snapcore/snapd/testutil" 47 48 . "gopkg.in/check.v1" 49 ) 50 51 type autoRefreshGatingStore struct { 52 *fakeStore 53 refreshedSnaps []*snap.Info 54 } 55 56 type autorefreshGatingSuite struct { 57 testutil.BaseTest 58 state *state.State 59 repo *interfaces.Repository 60 store *autoRefreshGatingStore 61 } 62 63 var _ = Suite(&autorefreshGatingSuite{}) 64 65 func (s *autorefreshGatingSuite) SetUpTest(c *C) { 66 s.BaseTest.SetUpTest(c) 67 dirs.SetRootDir(c.MkDir()) 68 s.AddCleanup(func() { 69 dirs.SetRootDir("/") 70 }) 71 s.state = state.New(nil) 72 73 s.repo = interfaces.NewRepository() 74 for _, iface := range builtin.Interfaces() { 75 c.Assert(s.repo.AddInterface(iface), IsNil) 76 } 77 78 s.state.Lock() 79 defer s.state.Unlock() 80 ifacerepo.Replace(s.state, s.repo) 81 82 s.store = &autoRefreshGatingStore{fakeStore: &fakeStore{}} 83 snapstate.ReplaceStore(s.state, s.store) 84 s.state.Set("refresh-privacy-key", "privacy-key") 85 } 86 87 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) { 88 if assertQuery != nil { 89 panic("no assertion query support") 90 } 91 if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 { 92 panic("expected in test one action for each current snaps, and at least one snap") 93 } 94 for _, a := range actions { 95 if a.Action != "refresh" { 96 panic("expected refresh actions") 97 } 98 } 99 100 res := []store.SnapActionResult{} 101 for _, rs := range r.refreshedSnaps { 102 res = append(res, store.SnapActionResult{Info: rs}) 103 } 104 105 return res, nil, nil 106 } 107 108 func mockInstalledSnap(c *C, st *state.State, snapYaml string, hasHook bool) *snap.Info { 109 snapInfo := snaptest.MockSnap(c, string(snapYaml), &snap.SideInfo{ 110 Revision: snap.R(1), 111 }) 112 113 snapName := snapInfo.SnapName() 114 si := &snap.SideInfo{RealName: snapName, SnapID: "id", Revision: snap.R(1)} 115 snapstate.Set(st, snapName, &snapstate.SnapState{ 116 Active: true, 117 Sequence: []*snap.SideInfo{si}, 118 Current: si.Revision, 119 SnapType: string(snapInfo.Type()), 120 }) 121 122 if hasHook { 123 c.Assert(os.MkdirAll(snapInfo.HooksDir(), 0775), IsNil) 124 err := ioutil.WriteFile(filepath.Join(snapInfo.HooksDir(), "gate-auto-refresh"), nil, 0755) 125 c.Assert(err, IsNil) 126 } 127 return snapInfo 128 } 129 130 func mockLastRefreshed(c *C, st *state.State, refreshedTime string, snaps ...string) { 131 refreshed, err := time.Parse(time.RFC3339, refreshedTime) 132 c.Assert(err, IsNil) 133 for _, snapName := range snaps { 134 var snapst snapstate.SnapState 135 c.Assert(snapstate.Get(st, snapName, &snapst), IsNil) 136 snapst.LastRefreshTime = &refreshed 137 snapstate.Set(st, snapName, &snapst) 138 } 139 } 140 141 const baseSnapAyaml = `name: base-snap-a 142 type: base 143 ` 144 145 const snapAyaml = `name: snap-a 146 type: app 147 base: base-snap-a 148 ` 149 150 const baseSnapByaml = `name: base-snap-b 151 type: base 152 ` 153 154 const snapByaml = `name: snap-b 155 type: app 156 base: base-snap-b 157 version: 1 158 ` 159 160 const kernelYaml = `name: kernel 161 type: kernel 162 version: 1 163 ` 164 165 const gadget1Yaml = `name: gadget 166 type: gadget 167 version: 1 168 ` 169 170 const snapCyaml = `name: snap-c 171 type: app 172 version: 1 173 ` 174 175 const snapDyaml = `name: snap-d 176 type: app 177 version: 1 178 slots: 179 slot: desktop 180 ` 181 182 const snapEyaml = `name: snap-e 183 type: app 184 version: 1 185 base: other-base 186 plugs: 187 plug: desktop 188 ` 189 190 const snapFyaml = `name: snap-f 191 type: app 192 version: 1 193 plugs: 194 plug: desktop 195 ` 196 197 const snapGyaml = `name: snap-g 198 type: app 199 version: 1 200 base: other-base 201 plugs: 202 desktop: 203 mir: 204 ` 205 206 const coreYaml = `name: core 207 type: os 208 version: 1 209 slots: 210 desktop: 211 mir: 212 ` 213 214 const core18Yaml = `name: core18 215 type: os 216 version: 1 217 ` 218 219 const snapdYaml = `name: snapd 220 version: 1 221 type: snapd 222 slots: 223 desktop: 224 ` 225 226 func (s *autorefreshGatingSuite) TestHoldDurationLeft(c *C) { 227 now, err := time.Parse(time.RFC3339, "2021-06-03T10:00:00Z") 228 c.Assert(err, IsNil) 229 maxPostponement := time.Hour * 24 * 90 230 231 for i, tc := range []struct { 232 lastRefresh, firstHeld string 233 maxDuration string 234 expected string 235 }{ 236 { 237 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 238 "2021-06-03T10:00:00Z", // first held now 239 "48h", // max duration 240 "48h", // expected 241 }, 242 { 243 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 244 "2021-06-02T10:00:00Z", // first held (1 day ago) 245 "48h", // max duration 246 "24h", // expected 247 }, 248 { 249 "2021-05-03T10:00:00Z", // last refreshed (1 month ago) 250 "2021-06-01T10:00:00Z", // first held (2 days ago) 251 "48h", // max duration 252 "00h", // expected 253 }, 254 { 255 "2021-03-08T10:00:00Z", // last refreshed (almost 3 months ago) 256 "2021-06-01T10:00:00Z", // first held 257 "2160h", // max duration (90 days) 258 "72h", // expected 259 }, 260 { 261 "2021-03-04T10:00:00Z", // last refreshed 262 "2021-06-01T10:00:00Z", // first held (2 days ago) 263 "2160h", // max duration (90 days) 264 "-24h", // expected (refresh is 1 day overdue) 265 }, 266 { 267 "2021-06-01T10:00:00Z", // last refreshed (2 days ago) 268 "2021-06-03T10:00:00Z", // first held now 269 "2160h", // max duration (90 days) 270 "2112h", // expected (max minus 2 days) 271 }, 272 } { 273 lastRefresh, err := time.Parse(time.RFC3339, tc.lastRefresh) 274 c.Assert(err, IsNil) 275 firstHeld, err := time.Parse(time.RFC3339, tc.firstHeld) 276 c.Assert(err, IsNil) 277 maxDuration, err := time.ParseDuration(tc.maxDuration) 278 c.Assert(err, IsNil) 279 expected, err := time.ParseDuration(tc.expected) 280 c.Assert(err, IsNil) 281 282 left := snapstate.HoldDurationLeft(now, lastRefresh, firstHeld, maxDuration, maxPostponement) 283 c.Check(left, Equals, expected, Commentf("case #%d", i)) 284 } 285 } 286 287 func (s *autorefreshGatingSuite) TestLastRefreshedHelper(c *C) { 288 st := s.state 289 st.Lock() 290 defer st.Unlock() 291 292 inf := mockInstalledSnap(c, st, snapAyaml, false) 293 stat, err := os.Stat(inf.MountFile()) 294 c.Assert(err, IsNil) 295 296 refreshed, err := snapstate.LastRefreshed(st, "snap-a") 297 c.Assert(err, IsNil) 298 c.Check(refreshed, DeepEquals, stat.ModTime()) 299 300 t, err := time.Parse(time.RFC3339, "2021-01-01T10:00:00Z") 301 c.Assert(err, IsNil) 302 303 var snapst snapstate.SnapState 304 c.Assert(snapstate.Get(st, "snap-a", &snapst), IsNil) 305 snapst.LastRefreshTime = &t 306 snapstate.Set(st, "snap-a", &snapst) 307 308 refreshed, err = snapstate.LastRefreshed(st, "snap-a") 309 c.Assert(err, IsNil) 310 c.Check(refreshed, DeepEquals, t) 311 } 312 313 func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) { 314 st := s.state 315 st.Lock() 316 defer st.Unlock() 317 318 restore := snapstate.MockTimeNow(func() time.Time { 319 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 320 c.Assert(err, IsNil) 321 return t 322 }) 323 defer restore() 324 325 mockInstalledSnap(c, st, snapAyaml, false) 326 mockInstalledSnap(c, st, snapByaml, false) 327 mockInstalledSnap(c, st, snapCyaml, false) 328 mockInstalledSnap(c, st, snapDyaml, false) 329 mockInstalledSnap(c, st, snapEyaml, false) 330 mockInstalledSnap(c, st, snapFyaml, false) 331 332 mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f") 333 334 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 335 // this could be merged with the above HoldRefresh call, but it's fine if 336 // done separately too. 337 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-e"), IsNil) 338 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-e"), IsNil) 339 c.Assert(snapstate.HoldRefresh(st, "snap-f", 0, "snap-f"), IsNil) 340 341 var gating map[string]map[string]*snapstate.HoldState 342 c.Assert(st.Get("snaps-hold", &gating), IsNil) 343 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 344 "snap-b": { 345 // holding of other snaps for maxOtherHoldDuration (48h) 346 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 347 }, 348 "snap-c": { 349 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 350 }, 351 "snap-e": { 352 "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 353 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), 354 }, 355 "snap-f": { 356 // holding self set for maxPostponement minus 1 day due to last refresh. 357 "snap-f": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-07T10:00:00Z"), 358 }, 359 }) 360 } 361 362 func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) { 363 st := s.state 364 st.Lock() 365 defer st.Unlock() 366 367 lastRefreshed := "2021-05-09T10:00:00Z" 368 now := "2021-05-10T10:00:00Z" 369 restore := snapstate.MockTimeNow(func() time.Time { 370 t, err := time.Parse(time.RFC3339, now) 371 c.Assert(err, IsNil) 372 return t 373 }) 374 defer restore() 375 376 mockInstalledSnap(c, st, snapAyaml, false) 377 mockInstalledSnap(c, st, snapByaml, false) 378 // snap-a was last refreshed yesterday 379 mockLastRefreshed(c, st, lastRefreshed, "snap-a") 380 381 // hold it for just a bit (10h) initially 382 hold := time.Hour * 10 383 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 384 var gating map[string]map[string]*snapstate.HoldState 385 c.Assert(st.Get("snaps-hold", &gating), IsNil) 386 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 387 "snap-a": { 388 "snap-b": snapstate.MockHoldState(now, "2021-05-10T20:00:00Z"), 389 }, 390 }) 391 392 // holding for a shorter time is fine too 393 hold = time.Hour * 5 394 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 395 c.Assert(st.Get("snaps-hold", &gating), IsNil) 396 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 397 "snap-a": { 398 "snap-b": snapstate.MockHoldState(now, "2021-05-10T15:00:00Z"), 399 }, 400 }) 401 402 oldNow := now 403 404 // a refresh on next day 405 now = "2021-05-11T08:00:00Z" 406 407 // default hold time requested 408 hold = 0 409 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 410 c.Assert(st.Get("snaps-hold", &gating), IsNil) 411 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 412 "snap-a": { 413 // maximum for holding other snaps, but taking into consideration 414 // firstHeld time = "2021-05-10T10:00:00". 415 "snap-b": snapstate.MockHoldState(oldNow, "2021-05-12T10:00:00Z"), 416 }, 417 }) 418 } 419 420 func (s *autorefreshGatingSuite) TestHoldRefreshHelperCloseToMaxPostponement(c *C) { 421 st := s.state 422 st.Lock() 423 defer st.Unlock() 424 425 lastRefreshedStr := "2021-01-01T10:00:00Z" 426 lastRefreshed, err := time.Parse(time.RFC3339, lastRefreshedStr) 427 c.Assert(err, IsNil) 428 // we are 1 day before maxPostponent 429 now := lastRefreshed.Add(89 * time.Hour * 24) 430 431 restore := snapstate.MockTimeNow(func() time.Time { return now }) 432 defer restore() 433 434 mockInstalledSnap(c, st, snapAyaml, false) 435 mockInstalledSnap(c, st, snapByaml, false) 436 mockLastRefreshed(c, st, lastRefreshedStr, "snap-a") 437 438 // request default hold time 439 var hold time.Duration 440 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 441 442 var gating map[string]map[string]*snapstate.HoldState 443 c.Assert(st.Get("snaps-hold", &gating), IsNil) 444 c.Assert(gating, HasLen, 1) 445 c.Check(gating["snap-a"]["snap-b"].HoldUntil.String(), DeepEquals, lastRefreshed.Add(90*time.Hour*24).String()) 446 } 447 448 func (s *autorefreshGatingSuite) TestHoldRefreshExplicitHoldTime(c *C) { 449 st := s.state 450 st.Lock() 451 defer st.Unlock() 452 453 now := "2021-05-10T10:00:00Z" 454 restore := snapstate.MockTimeNow(func() time.Time { 455 t, err := time.Parse(time.RFC3339, now) 456 c.Assert(err, IsNil) 457 return t 458 }) 459 defer restore() 460 461 mockInstalledSnap(c, st, snapAyaml, false) 462 mockInstalledSnap(c, st, snapByaml, false) 463 464 hold := time.Hour * 24 * 3 465 // holding self for 3 days 466 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), IsNil) 467 468 // snap-b holds snap-a for 1 day 469 hold = time.Hour * 24 470 c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) 471 472 var gating map[string]map[string]*snapstate.HoldState 473 c.Assert(st.Get("snaps-hold", &gating), IsNil) 474 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 475 "snap-a": { 476 "snap-a": snapstate.MockHoldState(now, "2021-05-13T10:00:00Z"), 477 "snap-b": snapstate.MockHoldState(now, "2021-05-11T10:00:00Z"), 478 }, 479 }) 480 } 481 482 func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) { 483 st := s.state 484 st.Lock() 485 defer st.Unlock() 486 487 now := "2021-05-10T10:00:00Z" 488 restore := snapstate.MockTimeNow(func() time.Time { 489 t, err := time.Parse(time.RFC3339, now) 490 c.Assert(err, IsNil) 491 return t 492 }) 493 defer restore() 494 495 mockInstalledSnap(c, st, snapAyaml, false) 496 mockInstalledSnap(c, st, snapByaml, false) 497 // snap-b was refreshed a few days ago 498 mockLastRefreshed(c, st, "2021-05-01T10:00:00Z", "snap-b") 499 500 // holding itself 501 hold := time.Hour * 24 * 96 502 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`) 503 504 // holding other snap 505 hold = time.Hour * 49 506 err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-b") 507 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`) 508 herr, ok := err.(*snapstate.HoldError) 509 c.Assert(ok, Equals, true) 510 c.Check(herr.SnapsInError, DeepEquals, map[string]snapstate.HoldDurationError{ 511 "snap-b": { 512 Err: fmt.Errorf(`requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`), 513 DurationLeft: 48 * time.Hour, 514 }, 515 }) 516 517 // hold for maximum allowed for other snaps 518 hold = time.Hour * 48 519 c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), IsNil) 520 // 2 days passed since it was first held 521 now = "2021-05-12T10:00:00Z" 522 hold = time.Minute * 2 523 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`) 524 525 // refreshed long time ago (> maxPostponement) 526 mockLastRefreshed(c, st, "2021-01-01T10:00:00Z", "snap-b") 527 hold = time.Hour * 2 528 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`) 529 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`) 530 } 531 532 func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) { 533 st := s.state 534 st.Lock() 535 defer st.Unlock() 536 537 mockInstalledSnap(c, st, snapAyaml, false) 538 mockInstalledSnap(c, st, snapByaml, false) 539 mockInstalledSnap(c, st, snapCyaml, false) 540 mockInstalledSnap(c, st, snapDyaml, false) 541 542 mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-b", "snap-c", "snap-d") 543 544 restore := snapstate.MockTimeNow(func() time.Time { 545 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 546 c.Assert(err, IsNil) 547 return t 548 }) 549 defer restore() 550 551 // nothing is held initially 552 held, err := snapstate.HeldSnaps(st) 553 c.Assert(err, IsNil) 554 c.Check(held, IsNil) 555 556 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 557 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil) 558 // holding self 559 c.Assert(snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d"), IsNil) 560 561 held, err = snapstate.HeldSnaps(st) 562 c.Assert(err, IsNil) 563 c.Check(held, DeepEquals, map[string]bool{"snap-b": true, "snap-c": true, "snap-d": true}) 564 565 c.Assert(snapstate.ProceedWithRefresh(st, "snap-a"), IsNil) 566 567 held, err = snapstate.HeldSnaps(st) 568 c.Assert(err, IsNil) 569 c.Check(held, DeepEquals, map[string]bool{"snap-c": true, "snap-d": true}) 570 571 c.Assert(snapstate.ProceedWithRefresh(st, "snap-d"), IsNil) 572 held, err = snapstate.HeldSnaps(st) 573 c.Assert(err, IsNil) 574 c.Check(held, IsNil) 575 } 576 577 func (s *autorefreshGatingSuite) TestResetGatingForRefreshedHelper(c *C) { 578 st := s.state 579 st.Lock() 580 defer st.Unlock() 581 582 restore := snapstate.MockTimeNow(func() time.Time { 583 t, err := time.Parse(time.RFC3339, "2021-05-10T10:00:00Z") 584 c.Assert(err, IsNil) 585 return t 586 }) 587 defer restore() 588 589 mockInstalledSnap(c, st, snapAyaml, false) 590 mockInstalledSnap(c, st, snapByaml, false) 591 mockInstalledSnap(c, st, snapCyaml, false) 592 mockInstalledSnap(c, st, snapDyaml, false) 593 594 c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) 595 c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil) 596 597 c.Assert(snapstate.ResetGatingForRefreshed(st, "snap-b", "snap-c"), IsNil) 598 var gating map[string]map[string]*snapstate.HoldState 599 c.Assert(st.Get("snaps-hold", &gating), IsNil) 600 c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ 601 "snap-d": { 602 // holding self set for maxPostponement (95 days - buffer = 90 days) 603 "snap-d": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-08T10:00:00Z"), 604 }, 605 }) 606 607 held, err := snapstate.HeldSnaps(st) 608 c.Assert(err, IsNil) 609 c.Check(held, DeepEquals, map[string]bool{"snap-d": true}) 610 } 611 612 const useHook = true 613 const noHook = false 614 615 func checkGatingTask(c *C, task *state.Task, expected map[string]*snapstate.RefreshCandidate) { 616 c.Assert(task.Kind(), Equals, "conditional-auto-refresh") 617 var snaps map[string]*snapstate.RefreshCandidate 618 c.Assert(task.Get("snaps", &snaps), IsNil) 619 c.Check(snaps, DeepEquals, expected) 620 } 621 622 func (s *autorefreshGatingSuite) TestAffectedByBase(c *C) { 623 restore := release.MockOnClassic(true) 624 defer restore() 625 626 st := s.state 627 628 st.Lock() 629 defer st.Unlock() 630 mockInstalledSnap(c, s.state, snapAyaml, useHook) 631 baseSnapA := mockInstalledSnap(c, s.state, baseSnapAyaml, noHook) 632 // unrelated snaps 633 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 634 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 635 636 c.Assert(s.repo.AddSnap(snapB), IsNil) 637 638 updates := []*snap.Info{baseSnapA} 639 affected, err := snapstate.AffectedByRefresh(st, updates) 640 c.Assert(err, IsNil) 641 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 642 "snap-a": { 643 Base: true, 644 AffectingSnaps: map[string]bool{ 645 "base-snap-a": true, 646 }}}) 647 } 648 649 func (s *autorefreshGatingSuite) TestAffectedByCore(c *C) { 650 restore := release.MockOnClassic(true) 651 defer restore() 652 653 st := s.state 654 655 st.Lock() 656 defer st.Unlock() 657 snapC := mockInstalledSnap(c, s.state, snapCyaml, useHook) 658 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 659 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 660 661 c.Assert(s.repo.AddSnap(core), IsNil) 662 c.Assert(s.repo.AddSnap(snapB), IsNil) 663 c.Assert(s.repo.AddSnap(snapC), IsNil) 664 665 updates := []*snap.Info{core} 666 affected, err := snapstate.AffectedByRefresh(st, updates) 667 c.Assert(err, IsNil) 668 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 669 "snap-c": { 670 Base: true, 671 AffectingSnaps: map[string]bool{ 672 "core": true, 673 }}}) 674 } 675 676 func (s *autorefreshGatingSuite) TestAffectedByKernel(c *C) { 677 restore := release.MockOnClassic(true) 678 defer restore() 679 680 st := s.state 681 682 st.Lock() 683 defer st.Unlock() 684 kernel := mockInstalledSnap(c, s.state, kernelYaml, noHook) 685 mockInstalledSnap(c, s.state, snapCyaml, useHook) 686 mockInstalledSnap(c, s.state, snapByaml, noHook) 687 688 updates := []*snap.Info{kernel} 689 affected, err := snapstate.AffectedByRefresh(st, updates) 690 c.Assert(err, IsNil) 691 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 692 "snap-c": { 693 Restart: true, 694 AffectingSnaps: map[string]bool{ 695 "kernel": true, 696 }}}) 697 } 698 699 func (s *autorefreshGatingSuite) TestAffectedByGadget(c *C) { 700 restore := release.MockOnClassic(true) 701 defer restore() 702 703 st := s.state 704 705 st.Lock() 706 defer st.Unlock() 707 kernel := mockInstalledSnap(c, s.state, gadget1Yaml, noHook) 708 mockInstalledSnap(c, s.state, snapCyaml, useHook) 709 mockInstalledSnap(c, s.state, snapByaml, noHook) 710 711 updates := []*snap.Info{kernel} 712 affected, err := snapstate.AffectedByRefresh(st, updates) 713 c.Assert(err, IsNil) 714 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 715 "snap-c": { 716 Restart: true, 717 AffectingSnaps: map[string]bool{ 718 "gadget": true, 719 }}}) 720 } 721 722 func (s *autorefreshGatingSuite) TestAffectedBySlot(c *C) { 723 restore := release.MockOnClassic(true) 724 defer restore() 725 726 st := s.state 727 728 st.Lock() 729 defer st.Unlock() 730 731 snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook) 732 snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook) 733 // unrelated snap 734 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 735 736 c.Assert(s.repo.AddSnap(snapF), IsNil) 737 c.Assert(s.repo.AddSnap(snapD), IsNil) 738 c.Assert(s.repo.AddSnap(snapE), IsNil) 739 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 740 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 741 c.Assert(err, IsNil) 742 743 updates := []*snap.Info{snapD} 744 affected, err := snapstate.AffectedByRefresh(st, updates) 745 c.Assert(err, IsNil) 746 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 747 "snap-e": { 748 Restart: true, 749 AffectingSnaps: map[string]bool{ 750 "snap-d": true, 751 }}}) 752 } 753 754 func (s *autorefreshGatingSuite) TestNotAffectedByCoreOrSnapdSlot(c *C) { 755 restore := release.MockOnClassic(true) 756 defer restore() 757 758 st := s.state 759 760 st.Lock() 761 defer st.Unlock() 762 763 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 764 core := mockInstalledSnap(c, s.state, coreYaml, noHook) 765 snapB := mockInstalledSnap(c, s.state, snapByaml, useHook) 766 767 c.Assert(s.repo.AddSnap(snapG), IsNil) 768 c.Assert(s.repo.AddSnap(core), IsNil) 769 c.Assert(s.repo.AddSnap(snapB), IsNil) 770 771 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "mir"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "mir"}} 772 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 773 c.Assert(err, IsNil) 774 775 updates := []*snap.Info{core} 776 affected, err := snapstate.AffectedByRefresh(st, updates) 777 c.Assert(err, IsNil) 778 c.Check(affected, HasLen, 0) 779 } 780 781 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackend(c *C) { 782 restore := release.MockOnClassic(true) 783 defer restore() 784 785 st := s.state 786 787 st.Lock() 788 defer st.Unlock() 789 790 snapD := mockInstalledSnap(c, s.state, snapDyaml, useHook) 791 snapE := mockInstalledSnap(c, s.state, snapEyaml, useHook) 792 // unrelated snap 793 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 794 795 c.Assert(s.repo.AddSnap(snapF), IsNil) 796 c.Assert(s.repo.AddSnap(snapD), IsNil) 797 c.Assert(s.repo.AddSnap(snapE), IsNil) 798 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-e", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "snap-d", Name: "slot"}} 799 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 800 c.Assert(err, IsNil) 801 802 // snapE has a plug using mount backend and is refreshed, this affects slot of snap-d. 803 updates := []*snap.Info{snapE} 804 affected, err := snapstate.AffectedByRefresh(st, updates) 805 c.Assert(err, IsNil) 806 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 807 "snap-d": { 808 Restart: true, 809 AffectingSnaps: map[string]bool{ 810 "snap-e": true, 811 }}}) 812 } 813 814 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendSnapdSlot(c *C) { 815 restore := release.MockOnClassic(true) 816 defer restore() 817 818 st := s.state 819 820 st.Lock() 821 defer st.Unlock() 822 823 snapdSnap := mockInstalledSnap(c, s.state, snapdYaml, useHook) 824 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 825 // unrelated snap 826 snapF := mockInstalledSnap(c, s.state, snapFyaml, useHook) 827 828 c.Assert(s.repo.AddSnap(snapF), IsNil) 829 c.Assert(s.repo.AddSnap(snapdSnap), IsNil) 830 c.Assert(s.repo.AddSnap(snapG), IsNil) 831 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "snapd", Name: "desktop"}} 832 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 833 c.Assert(err, IsNil) 834 835 // snapE has a plug using mount backend, refreshing snapd affects snapE. 836 updates := []*snap.Info{snapdSnap} 837 affected, err := snapstate.AffectedByRefresh(st, updates) 838 c.Assert(err, IsNil) 839 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 840 "snap-g": { 841 Restart: true, 842 AffectingSnaps: map[string]bool{ 843 "snapd": true, 844 }}}) 845 } 846 847 func (s *autorefreshGatingSuite) TestAffectedByPlugWithMountBackendCoreSlot(c *C) { 848 restore := release.MockOnClassic(true) 849 defer restore() 850 851 st := s.state 852 853 st.Lock() 854 defer st.Unlock() 855 856 coreSnap := mockInstalledSnap(c, s.state, coreYaml, noHook) 857 snapG := mockInstalledSnap(c, s.state, snapGyaml, useHook) 858 859 c.Assert(s.repo.AddSnap(coreSnap), IsNil) 860 c.Assert(s.repo.AddSnap(snapG), IsNil) 861 cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap-g", Name: "desktop"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "desktop"}} 862 _, err := s.repo.Connect(cref, nil, nil, nil, nil, nil) 863 c.Assert(err, IsNil) 864 865 // snapG has a plug using mount backend, refreshing core affects snapE. 866 updates := []*snap.Info{coreSnap} 867 affected, err := snapstate.AffectedByRefresh(st, updates) 868 c.Assert(err, IsNil) 869 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 870 "snap-g": { 871 Restart: true, 872 AffectingSnaps: map[string]bool{ 873 "core": true, 874 }}}) 875 } 876 877 func (s *autorefreshGatingSuite) TestAffectedByBootBase(c *C) { 878 restore := release.MockOnClassic(false) 879 defer restore() 880 881 st := s.state 882 883 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 884 defer r() 885 886 st.Lock() 887 defer st.Unlock() 888 mockInstalledSnap(c, s.state, snapAyaml, useHook) 889 mockInstalledSnap(c, s.state, snapByaml, useHook) 890 mockInstalledSnap(c, s.state, snapDyaml, useHook) 891 mockInstalledSnap(c, s.state, snapEyaml, useHook) 892 core18 := mockInstalledSnap(c, s.state, core18Yaml, noHook) 893 894 updates := []*snap.Info{core18} 895 affected, err := snapstate.AffectedByRefresh(st, updates) 896 c.Assert(err, IsNil) 897 c.Check(affected, DeepEquals, map[string]*snapstate.AffectedSnapInfo{ 898 "snap-a": { 899 Base: false, 900 Restart: true, 901 AffectingSnaps: map[string]bool{ 902 "core18": true, 903 }, 904 }, 905 "snap-b": { 906 Base: false, 907 Restart: true, 908 AffectingSnaps: map[string]bool{ 909 "core18": true, 910 }, 911 }, 912 "snap-d": { 913 Base: false, 914 Restart: true, 915 AffectingSnaps: map[string]bool{ 916 "core18": true, 917 }, 918 }, 919 "snap-e": { 920 Base: false, 921 Restart: true, 922 AffectingSnaps: map[string]bool{ 923 "core18": true, 924 }}}) 925 } 926 927 func (s *autorefreshGatingSuite) TestCreateAutoRefreshGateHooks(c *C) { 928 st := s.state 929 st.Lock() 930 defer st.Unlock() 931 932 affected := map[string]*snapstate.AffectedSnapInfo{ 933 "snap-a": { 934 Base: true, 935 Restart: true, 936 }, 937 "snap-b": {}, 938 } 939 940 seenSnaps := make(map[string]bool) 941 942 ts := snapstate.CreateGateAutoRefreshHooks(st, affected) 943 c.Assert(ts.Tasks(), HasLen, 2) 944 945 checkHook := func(t *state.Task) { 946 c.Assert(t.Kind(), Equals, "run-hook") 947 var hs hookstate.HookSetup 948 c.Assert(t.Get("hook-setup", &hs), IsNil) 949 c.Check(hs.Hook, Equals, "gate-auto-refresh") 950 c.Check(hs.Optional, Equals, true) 951 seenSnaps[hs.Snap] = true 952 953 var data interface{} 954 c.Assert(t.Get("hook-context", &data), IsNil) 955 956 // the order of hook tasks is not deterministic 957 if hs.Snap == "snap-a" { 958 c.Check(data, DeepEquals, map[string]interface{}{"base": true, "restart": true}) 959 } else { 960 c.Assert(hs.Snap, Equals, "snap-b") 961 c.Check(data, DeepEquals, map[string]interface{}{"base": false, "restart": false}) 962 } 963 } 964 965 checkHook(ts.Tasks()[0]) 966 checkHook(ts.Tasks()[1]) 967 968 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 969 } 970 971 func (s *autorefreshGatingSuite) TestAutorefreshPhase1FeatureFlag(c *C) { 972 st := s.state 973 st.Lock() 974 defer st.Unlock() 975 976 st.Set("seeded", true) 977 978 restore := snapstatetest.MockDeviceModel(DefaultModel()) 979 defer restore() 980 981 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 982 return nil, nil 983 } 984 defer func() { snapstate.AutoAliases = nil }() 985 986 s.store.refreshedSnaps = []*snap.Info{{ 987 Architectures: []string{"all"}, 988 SnapType: snap.TypeApp, 989 SideInfo: snap.SideInfo{ 990 RealName: "snap-a", 991 Revision: snap.R(8), 992 }, 993 }} 994 mockInstalledSnap(c, s.state, snapAyaml, useHook) 995 996 // gate-auto-refresh-hook feature not enabled, expect old-style refresh. 997 _, tss, err := snapstate.AutoRefresh(context.TODO(), st) 998 c.Check(err, IsNil) 999 c.Assert(tss, HasLen, 2) 1000 c.Check(tss[0].Tasks()[0].Kind(), Equals, "prerequisites") 1001 c.Check(tss[0].Tasks()[1].Kind(), Equals, "download-snap") 1002 c.Check(tss[1].Tasks()[0].Kind(), Equals, "check-rerefresh") 1003 1004 // enable gate-auto-refresh-hook feature 1005 tr := config.NewTransaction(s.state) 1006 tr.Set("core", "experimental.gate-auto-refresh-hook", true) 1007 tr.Commit() 1008 1009 _, tss, err = snapstate.AutoRefresh(context.TODO(), st) 1010 c.Check(err, IsNil) 1011 c.Assert(tss, HasLen, 2) 1012 // TODO: verify conditional-auto-refresh task data 1013 c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh") 1014 c.Check(tss[1].Tasks()[0].Kind(), Equals, "run-hook") 1015 } 1016 1017 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) { 1018 s.store.refreshedSnaps = []*snap.Info{{ 1019 Architectures: []string{"all"}, 1020 SnapType: snap.TypeApp, 1021 SideInfo: snap.SideInfo{ 1022 RealName: "snap-a", 1023 Revision: snap.R(8), 1024 }, 1025 }, { 1026 Architectures: []string{"all"}, 1027 SnapType: snap.TypeBase, 1028 SideInfo: snap.SideInfo{ 1029 RealName: "base-snap-b", 1030 Revision: snap.R(3), 1031 }, 1032 }, { 1033 Architectures: []string{"all"}, 1034 SnapType: snap.TypeApp, 1035 SideInfo: snap.SideInfo{ 1036 RealName: "snap-c", 1037 Revision: snap.R(5), 1038 }, 1039 }} 1040 1041 st := s.state 1042 st.Lock() 1043 defer st.Unlock() 1044 1045 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1046 mockInstalledSnap(c, s.state, snapByaml, useHook) 1047 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1048 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1049 1050 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1051 defer restore() 1052 1053 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1054 c.Assert(err, IsNil) 1055 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a", "snap-c"}) 1056 c.Assert(tss, HasLen, 2) 1057 1058 c.Assert(tss[0].Tasks(), HasLen, 1) 1059 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1060 "snap-a": { 1061 SnapSetup: snapstate.SnapSetup{ 1062 Type: "app", 1063 PlugsOnly: true, 1064 Flags: snapstate.Flags{ 1065 IsAutoRefresh: true, 1066 }, 1067 SideInfo: &snap.SideInfo{ 1068 RealName: "snap-a", 1069 Revision: snap.R(8), 1070 }, 1071 DownloadInfo: &snap.DownloadInfo{}, 1072 }, 1073 }, 1074 "base-snap-b": { 1075 SnapSetup: snapstate.SnapSetup{ 1076 Type: "base", 1077 PlugsOnly: true, 1078 Flags: snapstate.Flags{ 1079 IsAutoRefresh: true, 1080 }, 1081 SideInfo: &snap.SideInfo{ 1082 RealName: "base-snap-b", 1083 Revision: snap.R(3), 1084 }, 1085 DownloadInfo: &snap.DownloadInfo{}, 1086 }, 1087 }, 1088 "snap-c": { 1089 SnapSetup: snapstate.SnapSetup{ 1090 Type: "app", 1091 PlugsOnly: true, 1092 Flags: snapstate.Flags{ 1093 IsAutoRefresh: true, 1094 }, 1095 SideInfo: &snap.SideInfo{ 1096 RealName: "snap-c", 1097 Revision: snap.R(5), 1098 }, 1099 DownloadInfo: &snap.DownloadInfo{}, 1100 }, 1101 }, 1102 }) 1103 1104 c.Assert(tss[1].Tasks(), HasLen, 2) 1105 1106 // check hooks for affected snaps 1107 seenSnaps := make(map[string]bool) 1108 var hs hookstate.HookSetup 1109 c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil) 1110 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1111 seenSnaps[hs.Snap] = true 1112 1113 c.Assert(tss[1].Tasks()[1].Get("hook-setup", &hs), IsNil) 1114 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1115 seenSnaps[hs.Snap] = true 1116 1117 // hook for snap-a because it gets refreshed, for snap-b because its base 1118 // gets refreshed. snap-c is refreshed but doesn't have the hook. 1119 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true, "snap-b": true}) 1120 1121 // check that refresh-candidates in the state were updated 1122 var candidates map[string]*snapstate.RefreshCandidate 1123 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1124 c.Assert(candidates, HasLen, 3) 1125 c.Check(candidates["snap-a"], NotNil) 1126 c.Check(candidates["base-snap-b"], NotNil) 1127 c.Check(candidates["snap-c"], NotNil) 1128 } 1129 1130 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1ConflictsFilteredOut(c *C) { 1131 s.store.refreshedSnaps = []*snap.Info{{ 1132 Architectures: []string{"all"}, 1133 SnapType: snap.TypeApp, 1134 SideInfo: snap.SideInfo{ 1135 RealName: "snap-a", 1136 Revision: snap.R(8), 1137 }, 1138 }, { 1139 Architectures: []string{"all"}, 1140 SnapType: snap.TypeBase, 1141 SideInfo: snap.SideInfo{ 1142 RealName: "snap-c", 1143 Revision: snap.R(5), 1144 }, 1145 }} 1146 1147 st := s.state 1148 st.Lock() 1149 defer st.Unlock() 1150 1151 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1152 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1153 1154 conflictChange := st.NewChange("conflicting change", "") 1155 conflictTask := st.NewTask("conflicting task", "") 1156 si := &snap.SideInfo{ 1157 RealName: "snap-c", 1158 Revision: snap.R(1), 1159 } 1160 sup := snapstate.SnapSetup{SideInfo: si} 1161 conflictTask.Set("snap-setup", sup) 1162 conflictChange.AddTask(conflictTask) 1163 1164 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1165 defer restore() 1166 1167 logbuf, restoreLogger := logger.MockLogger() 1168 defer restoreLogger() 1169 1170 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1171 c.Assert(err, IsNil) 1172 c.Check(names, DeepEquals, []string{"snap-a"}) 1173 c.Assert(tss, HasLen, 2) 1174 1175 c.Assert(tss[0].Tasks(), HasLen, 1) 1176 checkGatingTask(c, tss[0].Tasks()[0], map[string]*snapstate.RefreshCandidate{ 1177 "snap-a": { 1178 SnapSetup: snapstate.SnapSetup{ 1179 Type: "app", 1180 PlugsOnly: true, 1181 Flags: snapstate.Flags{ 1182 IsAutoRefresh: true, 1183 }, 1184 SideInfo: &snap.SideInfo{ 1185 RealName: "snap-a", 1186 Revision: snap.R(8), 1187 }, 1188 DownloadInfo: &snap.DownloadInfo{}, 1189 }}}) 1190 1191 c.Assert(tss[1].Tasks(), HasLen, 1) 1192 1193 c.Assert(logbuf.String(), testutil.Contains, `cannot refresh snap "snap-c": snap "snap-c" has "conflicting change" change in progress`) 1194 1195 seenSnaps := make(map[string]bool) 1196 var hs hookstate.HookSetup 1197 c.Assert(tss[1].Tasks()[0].Get("hook-setup", &hs), IsNil) 1198 c.Check(hs.Hook, Equals, "gate-auto-refresh") 1199 seenSnaps[hs.Snap] = true 1200 1201 c.Check(seenSnaps, DeepEquals, map[string]bool{"snap-a": true}) 1202 1203 // check that refresh-candidates in the state were updated 1204 var candidates map[string]*snapstate.RefreshCandidate 1205 c.Assert(st.Get("refresh-candidates", &candidates), IsNil) 1206 c.Assert(candidates, HasLen, 2) 1207 c.Check(candidates["snap-a"], NotNil) 1208 c.Check(candidates["snap-c"], NotNil) 1209 } 1210 1211 func (s *autorefreshGatingSuite) TestAutoRefreshPhase1NoHooks(c *C) { 1212 s.store.refreshedSnaps = []*snap.Info{{ 1213 Architectures: []string{"all"}, 1214 SnapType: snap.TypeBase, 1215 SideInfo: snap.SideInfo{ 1216 RealName: "base-snap-b", 1217 Revision: snap.R(3), 1218 }, 1219 }, { 1220 Architectures: []string{"all"}, 1221 SnapType: snap.TypeBase, 1222 SideInfo: snap.SideInfo{ 1223 RealName: "snap-c", 1224 Revision: snap.R(5), 1225 }, 1226 }} 1227 1228 st := s.state 1229 st.Lock() 1230 defer st.Unlock() 1231 1232 mockInstalledSnap(c, s.state, snapByaml, noHook) 1233 mockInstalledSnap(c, s.state, snapCyaml, noHook) 1234 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1235 1236 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1237 defer restore() 1238 1239 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1240 c.Assert(err, IsNil) 1241 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-c"}) 1242 c.Assert(tss, HasLen, 1) 1243 1244 c.Assert(tss[0].Tasks(), HasLen, 1) 1245 c.Check(tss[0].Tasks()[0].Kind(), Equals, "conditional-auto-refresh") 1246 } 1247 1248 func fakeReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) { 1249 info := &snap.Info{ 1250 SuggestedName: name, 1251 SideInfo: *si, 1252 Architectures: []string{"all"}, 1253 SnapType: snap.TypeApp, 1254 Epoch: snap.Epoch{}, 1255 } 1256 switch name { 1257 case "base-snap-b": 1258 info.SnapType = snap.TypeBase 1259 case "snap-a", "snap-b": 1260 info.Hooks = map[string]*snap.HookInfo{ 1261 "gate-auto-refresh": { 1262 Name: "gate-auto-refresh", 1263 Snap: info, 1264 }, 1265 } 1266 if name == "snap-b" { 1267 info.Base = "base-snap-b" 1268 } 1269 } 1270 return info, nil 1271 } 1272 1273 func (s *snapmgrTestSuite) TestAutoRefreshPhase2(c *C) { 1274 st := s.state 1275 st.Lock() 1276 defer st.Unlock() 1277 1278 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1279 c.Fatal("unexpected call to installSize") 1280 return 0, nil 1281 }) 1282 defer restoreInstallSize() 1283 1284 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1285 fakeStore: s.fakeStore, 1286 refreshedSnaps: []*snap.Info{{ 1287 Architectures: []string{"all"}, 1288 SnapType: snap.TypeApp, 1289 SideInfo: snap.SideInfo{ 1290 RealName: "snap-a", 1291 Revision: snap.R(8), 1292 }, 1293 }, { 1294 Architectures: []string{"all"}, 1295 SnapType: snap.TypeBase, 1296 SideInfo: snap.SideInfo{ 1297 RealName: "base-snap-b", 1298 Revision: snap.R(3), 1299 }, 1300 }}}) 1301 1302 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1303 mockInstalledSnap(c, s.state, snapByaml, useHook) 1304 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1305 1306 snapstate.MockSnapReadInfo(fakeReadInfo) 1307 1308 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1309 defer restore() 1310 1311 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1312 c.Assert(err, IsNil) 1313 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1314 1315 chg := s.state.NewChange("refresh", "...") 1316 for _, ts := range tss { 1317 chg.AddAll(ts) 1318 } 1319 1320 s.state.Unlock() 1321 defer s.se.Stop() 1322 s.settle(c) 1323 s.state.Lock() 1324 1325 c.Check(chg.Status(), Equals, state.DoneStatus) 1326 c.Check(chg.Err(), IsNil) 1327 1328 expected := []string{ 1329 "conditional-auto-refresh", 1330 "run-hook [snap-a;gate-auto-refresh]", 1331 // snap-b hook is triggered because of base-snap-b refresh 1332 "run-hook [snap-b;gate-auto-refresh]", 1333 "prerequisites", 1334 "download-snap", 1335 "validate-snap", 1336 "mount-snap", 1337 "run-hook [base-snap-b;pre-refresh]", 1338 "stop-snap-services", 1339 "remove-aliases", 1340 "unlink-current-snap", 1341 "copy-snap-data", 1342 "setup-profiles", 1343 "link-snap", 1344 "auto-connect", 1345 "set-auto-aliases", 1346 "setup-aliases", 1347 "run-hook [base-snap-b;post-refresh]", 1348 "start-snap-services", 1349 "cleanup", 1350 "run-hook [base-snap-b;check-health]", 1351 "prerequisites", 1352 "download-snap", 1353 "validate-snap", 1354 "mount-snap", 1355 "run-hook [snap-a;pre-refresh]", 1356 "stop-snap-services", 1357 "remove-aliases", 1358 "unlink-current-snap", 1359 "copy-snap-data", 1360 "setup-profiles", 1361 "link-snap", 1362 "auto-connect", 1363 "set-auto-aliases", 1364 "setup-aliases", 1365 "run-hook [snap-a;post-refresh]", 1366 "start-snap-services", 1367 "cleanup", 1368 "run-hook [snap-a;configure]", 1369 "run-hook [snap-a;check-health]", 1370 "check-rerefresh", 1371 } 1372 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1373 } 1374 1375 func (s *snapmgrTestSuite) testAutoRefreshPhase2DiskSpaceCheck(c *C, fail bool) { 1376 st := s.state 1377 st.Lock() 1378 defer st.Unlock() 1379 1380 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 1381 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 1382 if fail { 1383 return &osutil.NotEnoughDiskSpaceError{} 1384 } 1385 return nil 1386 }) 1387 defer restore() 1388 1389 var installSizeCalled bool 1390 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 1391 installSizeCalled = true 1392 seen := map[string]bool{} 1393 for _, sn := range snaps { 1394 seen[sn.InstanceName()] = true 1395 } 1396 c.Check(seen, DeepEquals, map[string]bool{ 1397 "base-snap-b": true, 1398 "snap-a": true, 1399 }) 1400 return 123, nil 1401 }) 1402 defer restoreInstallSize() 1403 1404 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 1405 defer restoreModel() 1406 1407 tr := config.NewTransaction(s.state) 1408 tr.Set("core", "experimental.check-disk-space-refresh", true) 1409 tr.Commit() 1410 1411 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1412 fakeStore: s.fakeStore, 1413 refreshedSnaps: []*snap.Info{{ 1414 Architectures: []string{"all"}, 1415 SnapType: snap.TypeApp, 1416 SideInfo: snap.SideInfo{ 1417 RealName: "snap-a", 1418 Revision: snap.R(8), 1419 }, 1420 }, { 1421 Architectures: []string{"all"}, 1422 SnapType: snap.TypeBase, 1423 SideInfo: snap.SideInfo{ 1424 RealName: "base-snap-b", 1425 Revision: snap.R(3), 1426 }, 1427 }}}) 1428 1429 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1430 mockInstalledSnap(c, s.state, snapByaml, useHook) 1431 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1432 1433 snapstate.MockSnapReadInfo(fakeReadInfo) 1434 1435 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1436 c.Assert(err, IsNil) 1437 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1438 1439 chg := s.state.NewChange("refresh", "...") 1440 for _, ts := range tss { 1441 chg.AddAll(ts) 1442 } 1443 1444 s.state.Unlock() 1445 defer s.se.Stop() 1446 s.settle(c) 1447 s.state.Lock() 1448 1449 c.Check(installSizeCalled, Equals, true) 1450 if fail { 1451 c.Check(chg.Status(), Equals, state.ErrorStatus) 1452 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n- Run auto-refresh for ready snaps \(insufficient space.*`) 1453 } else { 1454 c.Check(chg.Status(), Equals, state.DoneStatus) 1455 c.Check(chg.Err(), IsNil) 1456 } 1457 } 1458 1459 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceError(c *C) { 1460 fail := true 1461 s.testAutoRefreshPhase2DiskSpaceCheck(c, fail) 1462 } 1463 1464 func (s *snapmgrTestSuite) TestAutoRefreshPhase2DiskSpaceHappy(c *C) { 1465 var nofail bool 1466 s.testAutoRefreshPhase2DiskSpaceCheck(c, nofail) 1467 } 1468 1469 // XXX: this case is probably artificial; with proper conflict prevention 1470 // we shouldn't get conflicts from doInstall in phase2. 1471 func (s *snapmgrTestSuite) TestAutoRefreshPhase2Conflict(c *C) { 1472 st := s.state 1473 st.Lock() 1474 defer st.Unlock() 1475 1476 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1477 fakeStore: s.fakeStore, 1478 refreshedSnaps: []*snap.Info{{ 1479 Architectures: []string{"all"}, 1480 SnapType: snap.TypeApp, 1481 SideInfo: snap.SideInfo{ 1482 RealName: "snap-a", 1483 Revision: snap.R(8), 1484 }, 1485 }, { 1486 Architectures: []string{"all"}, 1487 SnapType: snap.TypeBase, 1488 SideInfo: snap.SideInfo{ 1489 RealName: "base-snap-b", 1490 Revision: snap.R(3), 1491 }, 1492 }}}) 1493 1494 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1495 mockInstalledSnap(c, s.state, snapByaml, useHook) 1496 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1497 1498 snapstate.MockSnapReadInfo(fakeReadInfo) 1499 1500 restore := snapstatetest.MockDeviceModel(DefaultModel()) 1501 defer restore() 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 conflictChange := st.NewChange("conflicting change", "") 1513 conflictTask := st.NewTask("conflicting task", "") 1514 si := &snap.SideInfo{ 1515 RealName: "snap-a", 1516 Revision: snap.R(1), 1517 } 1518 sup := snapstate.SnapSetup{SideInfo: si} 1519 conflictTask.Set("snap-setup", sup) 1520 conflictChange.AddTask(conflictTask) 1521 conflictTask.WaitFor(tss[0].Tasks()[0]) 1522 1523 s.state.Unlock() 1524 defer s.se.Stop() 1525 s.settle(c) 1526 s.state.Lock() 1527 1528 c.Assert(chg.Status(), Equals, state.DoneStatus) 1529 c.Check(chg.Err(), IsNil) 1530 1531 expected := []string{ 1532 "conditional-auto-refresh", 1533 "run-hook [snap-a;gate-auto-refresh]", 1534 // snap-b hook is triggered because of base-snap-b refresh 1535 "run-hook [snap-b;gate-auto-refresh]", 1536 "prerequisites", 1537 "download-snap", 1538 "validate-snap", 1539 "mount-snap", 1540 "run-hook [base-snap-b;pre-refresh]", 1541 "stop-snap-services", 1542 "remove-aliases", 1543 "unlink-current-snap", 1544 "copy-snap-data", 1545 "setup-profiles", 1546 "link-snap", 1547 "auto-connect", 1548 "set-auto-aliases", 1549 "setup-aliases", 1550 "run-hook [base-snap-b;post-refresh]", 1551 "start-snap-services", 1552 "cleanup", 1553 "run-hook [base-snap-b;check-health]", 1554 "check-rerefresh", 1555 } 1556 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1557 } 1558 1559 func (s *snapmgrTestSuite) TestAutoRefreshPhase2GatedSnaps(c *C) { 1560 st := s.state 1561 st.Lock() 1562 defer st.Unlock() 1563 1564 restore := snapstate.MockSnapsToRefresh(func(gatingTask *state.Task) ([]*snapstate.RefreshCandidate, error) { 1565 c.Assert(gatingTask.Kind(), Equals, "conditional-auto-refresh") 1566 var candidates map[string]*snapstate.RefreshCandidate 1567 c.Assert(gatingTask.Get("snaps", &candidates), IsNil) 1568 seenSnaps := make(map[string]bool) 1569 var filteredByGatingHooks []*snapstate.RefreshCandidate 1570 for _, cand := range candidates { 1571 seenSnaps[cand.InstanceName()] = true 1572 if cand.InstanceName() == "snap-a" { 1573 continue 1574 } 1575 filteredByGatingHooks = append(filteredByGatingHooks, cand) 1576 } 1577 c.Check(seenSnaps, DeepEquals, map[string]bool{ 1578 "snap-a": true, 1579 "base-snap-b": true, 1580 }) 1581 return filteredByGatingHooks, nil 1582 }) 1583 defer restore() 1584 1585 snapstate.ReplaceStore(s.state, &autoRefreshGatingStore{ 1586 fakeStore: s.fakeStore, 1587 refreshedSnaps: []*snap.Info{ 1588 { 1589 Architectures: []string{"all"}, 1590 SnapType: snap.TypeApp, 1591 SideInfo: snap.SideInfo{ 1592 RealName: "snap-a", 1593 Revision: snap.R(8), 1594 }, 1595 }, { 1596 Architectures: []string{"all"}, 1597 SnapType: snap.TypeBase, 1598 SideInfo: snap.SideInfo{ 1599 RealName: "base-snap-b", 1600 Revision: snap.R(3), 1601 }, 1602 }, 1603 }}) 1604 1605 mockInstalledSnap(c, s.state, snapAyaml, useHook) 1606 mockInstalledSnap(c, s.state, snapByaml, useHook) 1607 mockInstalledSnap(c, s.state, baseSnapByaml, noHook) 1608 1609 snapstate.MockSnapReadInfo(fakeReadInfo) 1610 1611 restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) 1612 defer restoreModel() 1613 1614 names, tss, err := snapstate.AutoRefreshPhase1(context.TODO(), st) 1615 c.Assert(err, IsNil) 1616 c.Check(names, DeepEquals, []string{"base-snap-b", "snap-a"}) 1617 1618 chg := s.state.NewChange("refresh", "...") 1619 for _, ts := range tss { 1620 chg.AddAll(ts) 1621 } 1622 1623 s.state.Unlock() 1624 defer s.se.Stop() 1625 s.settle(c) 1626 s.state.Lock() 1627 1628 c.Assert(chg.Status(), Equals, state.DoneStatus) 1629 c.Check(chg.Err(), IsNil) 1630 1631 expected := []string{ 1632 "conditional-auto-refresh", 1633 "run-hook [snap-a;gate-auto-refresh]", 1634 // snap-b hook is triggered because of base-snap-b refresh 1635 "run-hook [snap-b;gate-auto-refresh]", 1636 "prerequisites", 1637 "download-snap", 1638 "validate-snap", 1639 "mount-snap", 1640 "run-hook [base-snap-b;pre-refresh]", 1641 "stop-snap-services", 1642 "remove-aliases", 1643 "unlink-current-snap", 1644 "copy-snap-data", 1645 "setup-profiles", 1646 "link-snap", 1647 "auto-connect", 1648 "set-auto-aliases", 1649 "setup-aliases", 1650 "run-hook [base-snap-b;post-refresh]", 1651 "start-snap-services", 1652 "cleanup", 1653 "run-hook [base-snap-b;check-health]", 1654 "check-rerefresh", 1655 } 1656 verifyPhasedAutorefreshTasks(c, chg.Tasks(), expected) 1657 } 1658 1659 func verifyPhasedAutorefreshTasks(c *C, tasks []*state.Task, expected []string) { 1660 for i, t := range tasks { 1661 var got string 1662 if t.Kind() == "run-hook" { 1663 var hsup hookstate.HookSetup 1664 c.Assert(t.Get("hook-setup", &hsup), IsNil) 1665 got = fmt.Sprintf("%s [%s;%s]", t.Kind(), hsup.Snap, hsup.Hook) 1666 } else { 1667 got = t.Kind() 1668 } 1669 c.Assert(got, Equals, expected[i]) 1670 } 1671 }