github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/overlord/snapstate/snapstate_update_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2020 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 "errors" 25 "fmt" 26 "os" 27 "path/filepath" 28 "sort" 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/osutil" 36 "github.com/snapcore/snapd/overlord/auth" 37 "github.com/snapcore/snapd/overlord/configstate/config" 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 // So it registers Configure. 49 _ "github.com/snapcore/snapd/overlord/configstate" 50 ) 51 52 func verifyUpdateTasks(c *C, opts, discards int, ts *state.TaskSet, st *state.State) { 53 kinds := taskKinds(ts.Tasks()) 54 55 expected := []string{ 56 "prerequisites", 57 "download-snap", 58 "validate-snap", 59 "mount-snap", 60 } 61 expected = append(expected, "run-hook[pre-refresh]") 62 if opts&unlinkBefore != 0 { 63 expected = append(expected, 64 "stop-snap-services", 65 ) 66 } 67 if opts&unlinkBefore != 0 { 68 expected = append(expected, 69 "remove-aliases", 70 "unlink-current-snap", 71 ) 72 } 73 if opts&updatesGadget != 0 { 74 expected = append(expected, "update-gadget-assets") 75 } 76 expected = append(expected, 77 "copy-snap-data", 78 "setup-profiles", 79 "link-snap", 80 ) 81 if opts&maybeCore != 0 { 82 expected = append(expected, "setup-profiles") 83 } 84 expected = append(expected, 85 "auto-connect", 86 "set-auto-aliases", 87 "setup-aliases", 88 "run-hook[post-refresh]", 89 "start-snap-services") 90 91 c.Assert(ts.Tasks()[len(expected)-2].Summary(), Matches, `Run post-refresh hook of .*`) 92 for i := 0; i < discards; i++ { 93 expected = append(expected, 94 "clear-snap", 95 "discard-snap", 96 ) 97 } 98 if opts&cleanupAfter != 0 { 99 expected = append(expected, 100 "cleanup", 101 ) 102 } 103 expected = append(expected, 104 "run-hook[configure]", 105 "run-hook[check-health]", 106 ) 107 if opts&doesReRefresh != 0 { 108 expected = append(expected, "check-rerefresh") 109 } 110 111 c.Assert(kinds, DeepEquals, expected) 112 } 113 114 func (s *snapmgrTestSuite) TestUpdateDoesGC(c *C) { 115 s.state.Lock() 116 defer s.state.Unlock() 117 restore := release.MockOnClassic(false) 118 defer restore() 119 120 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 121 Active: true, 122 Sequence: []*snap.SideInfo{ 123 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 124 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 125 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 126 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 127 }, 128 Current: snap.R(4), 129 SnapType: "app", 130 }) 131 132 chg := s.state.NewChange("update", "update a snap") 133 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 134 c.Assert(err, IsNil) 135 chg.AddAll(ts) 136 137 s.state.Unlock() 138 defer s.se.Stop() 139 s.settle(c) 140 s.state.Lock() 141 142 // ensure garbage collection runs as the last tasks 143 expectedTail := fakeOps{ 144 { 145 op: "link-snap", 146 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 147 }, 148 { 149 op: "auto-connect:Doing", 150 name: "some-snap", 151 revno: snap.R(11), 152 }, 153 { 154 op: "update-aliases", 155 }, 156 { 157 op: "remove-snap-data", 158 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 159 }, 160 { 161 op: "remove-snap-files", 162 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 163 stype: "app", 164 }, 165 { 166 op: "remove-snap-data", 167 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 168 }, 169 { 170 op: "remove-snap-files", 171 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 172 stype: "app", 173 }, 174 { 175 op: "cleanup-trash", 176 name: "some-snap", 177 revno: snap.R(11), 178 }, 179 } 180 181 opsTail := s.fakeBackend.ops[len(s.fakeBackend.ops)-len(expectedTail):] 182 c.Assert(opsTail.Ops(), DeepEquals, expectedTail.Ops()) 183 c.Check(opsTail, DeepEquals, expectedTail) 184 } 185 186 func (s *snapmgrTestSuite) TestUpdateScenarios(c *C) { 187 // TODO: also use channel-for-7 or equiv to check updates that are switches 188 for k, t := range switchScenarios { 189 s.testUpdateScenario(c, k, t) 190 } 191 } 192 193 func (s *snapmgrTestSuite) testUpdateScenario(c *C, desc string, t switchScenario) { 194 // reset 195 s.fakeBackend.ops = nil 196 197 comment := Commentf("%q (%+v)", desc, t) 198 si := snap.SideInfo{ 199 RealName: "some-snap", 200 Revision: snap.R(7), 201 Channel: t.chanFrom, 202 SnapID: "some-snap-id", 203 } 204 205 s.state.Lock() 206 defer s.state.Unlock() 207 208 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 209 Sequence: []*snap.SideInfo{&si}, 210 Active: true, 211 Current: si.Revision, 212 TrackingChannel: t.chanFrom, 213 CohortKey: t.cohFrom, 214 }) 215 216 chg := s.state.NewChange("update-snap", t.summary) 217 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{ 218 Channel: t.chanTo, 219 CohortKey: t.cohTo, 220 LeaveCohort: t.cohFrom != "" && t.cohTo == "", 221 }, 0, snapstate.Flags{}) 222 c.Assert(err, IsNil, comment) 223 chg.AddAll(ts) 224 225 s.state.Unlock() 226 s.settle(c) 227 s.state.Lock() 228 229 // switch is not really really doing anything backend related 230 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, []string{ 231 "storesvc-snap-action", 232 "storesvc-snap-action:action", 233 "storesvc-download", 234 "validate-snap:Doing", 235 "current", 236 "open-snap-file", 237 "setup-snap", 238 "remove-snap-aliases", 239 "unlink-snap", 240 "copy-data", 241 "setup-profiles:Doing", 242 "candidate", 243 "link-snap", 244 "auto-connect:Doing", 245 "update-aliases", 246 "cleanup-trash", 247 }, comment) 248 249 expectedChanTo := t.chanTo 250 if t.chanTo == "" { 251 expectedChanTo = t.chanFrom 252 } 253 expectedCohTo := t.cohTo 254 255 // ensure the desired channel/cohort has changed 256 var snapst snapstate.SnapState 257 err = snapstate.Get(s.state, "some-snap", &snapst) 258 c.Assert(err, IsNil, comment) 259 c.Assert(snapst.TrackingChannel, Equals, expectedChanTo, comment) 260 c.Assert(snapst.CohortKey, Equals, expectedCohTo, comment) 261 262 // ensure the current info *has* changed 263 info, err := snapst.CurrentInfo() 264 c.Assert(err, IsNil, comment) 265 c.Assert(info.Channel, Equals, expectedChanTo, comment) 266 } 267 268 func (s *snapmgrTestSuite) TestUpdateTasksWithOldCurrent(c *C) { 269 s.state.Lock() 270 defer s.state.Unlock() 271 restore := release.MockOnClassic(false) 272 defer restore() 273 274 si1 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)} 275 si2 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)} 276 si3 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)} 277 si4 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)} 278 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 279 Active: true, 280 TrackingChannel: "latest/edge", 281 Sequence: []*snap.SideInfo{si1, si2, si3, si4}, 282 Current: snap.R(2), 283 SnapType: "app", 284 }) 285 286 // run the update 287 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 288 c.Assert(err, IsNil) 289 290 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 2, ts, s.state) 291 292 // and ensure that it will remove the revisions after "current" 293 // (si3, si4) 294 var snapsup snapstate.SnapSetup 295 tasks := ts.Tasks() 296 297 i := len(tasks) - 8 298 c.Check(tasks[i].Kind(), Equals, "clear-snap") 299 err = tasks[i].Get("snap-setup", &snapsup) 300 c.Assert(err, IsNil) 301 c.Check(snapsup.Revision(), Equals, si3.Revision) 302 303 i = len(tasks) - 6 304 c.Check(tasks[i].Kind(), Equals, "clear-snap") 305 err = tasks[i].Get("snap-setup", &snapsup) 306 c.Assert(err, IsNil) 307 c.Check(snapsup.Revision(), Equals, si4.Revision) 308 } 309 310 func (s *snapmgrTestSuite) TestUpdateCanDoBackwards(c *C) { 311 si7 := snap.SideInfo{ 312 RealName: "some-snap", 313 SnapID: "some-snap-id", 314 Revision: snap.R(7), 315 } 316 si11 := snap.SideInfo{ 317 RealName: "some-snap", 318 SnapID: "some-snap-id", 319 Revision: snap.R(11), 320 } 321 322 s.state.Lock() 323 defer s.state.Unlock() 324 325 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 326 Active: true, 327 Sequence: []*snap.SideInfo{&si7, &si11}, 328 Current: si11.Revision, 329 SnapType: "app", 330 }) 331 332 chg := s.state.NewChange("refresh", "refresh a snap") 333 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Revision: snap.R(7)}, s.user.ID, snapstate.Flags{}) 334 c.Assert(err, IsNil) 335 chg.AddAll(ts) 336 337 s.state.Unlock() 338 defer s.se.Stop() 339 s.settle(c) 340 s.state.Lock() 341 expected := fakeOps{ 342 { 343 op: "remove-snap-aliases", 344 name: "some-snap", 345 }, 346 { 347 op: "unlink-snap", 348 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 349 }, 350 { 351 op: "copy-data", 352 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 353 old: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 354 }, 355 { 356 op: "setup-profiles:Doing", 357 name: "some-snap", 358 revno: snap.R(7), 359 }, 360 { 361 op: "candidate", 362 sinfo: snap.SideInfo{ 363 RealName: "some-snap", 364 SnapID: "some-snap-id", 365 Channel: "", 366 Revision: snap.R(7), 367 }, 368 }, 369 { 370 op: "link-snap", 371 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 372 }, 373 { 374 op: "auto-connect:Doing", 375 name: "some-snap", 376 revno: snap.R(7), 377 }, 378 { 379 op: "update-aliases", 380 }, 381 { 382 op: "cleanup-trash", 383 name: "some-snap", 384 revno: snap.R(7), 385 }, 386 } 387 // start with an easier-to-read error if this fails: 388 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 389 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 390 } 391 392 func revs(seq []*snap.SideInfo) []int { 393 revs := make([]int, len(seq)) 394 for i, si := range seq { 395 revs[i] = si.Revision.N 396 } 397 398 return revs 399 } 400 401 type opSeqOpts struct { 402 revert bool 403 fail bool 404 before []int 405 current int 406 via int 407 after []int 408 } 409 410 // build a SnapState with a revision sequence given by `before` and a 411 // current revision of `current`. Then refresh --revision via. Then 412 // check the revision sequence is as in `after`. 413 func (s *snapmgrTestSuite) testOpSequence(c *C, opts *opSeqOpts) (*snapstate.SnapState, *state.TaskSet) { 414 s.state.Lock() 415 defer s.state.Unlock() 416 417 seq := make([]*snap.SideInfo, len(opts.before)) 418 for i, n := range opts.before { 419 seq[i] = &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(n)} 420 } 421 422 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 423 Active: true, 424 TrackingChannel: "latest/edge", 425 Sequence: seq, 426 Current: snap.R(opts.current), 427 SnapType: "app", 428 }) 429 430 var chg *state.Change 431 var ts *state.TaskSet 432 var err error 433 if opts.revert { 434 chg = s.state.NewChange("revert", "revert a snap") 435 ts, err = snapstate.RevertToRevision(s.state, "some-snap", snap.R(opts.via), snapstate.Flags{}) 436 } else { 437 chg = s.state.NewChange("refresh", "refresh a snap") 438 ts, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Revision: snap.R(opts.via)}, s.user.ID, snapstate.Flags{}) 439 } 440 c.Assert(err, IsNil) 441 if opts.fail { 442 tasks := ts.Tasks() 443 var last *state.Task 444 // don't make a task wait on rerefresh, that's bad 445 for i := len(tasks) - 1; i > 0; i-- { 446 last = tasks[i] 447 if last.Kind() != "check-rerefresh" { 448 break 449 } 450 } 451 terr := s.state.NewTask("error-trigger", "provoking total undo") 452 terr.WaitFor(last) 453 if len(last.Lanes()) > 0 { 454 lanes := last.Lanes() 455 // sanity 456 c.Assert(lanes, HasLen, 1) 457 terr.JoinLane(lanes[0]) 458 } 459 chg.AddTask(terr) 460 } 461 chg.AddAll(ts) 462 463 s.state.Unlock() 464 defer s.se.Stop() 465 s.settle(c) 466 s.state.Lock() 467 468 var snapst snapstate.SnapState 469 err = snapstate.Get(s.state, "some-snap", &snapst) 470 c.Assert(err, IsNil) 471 c.Check(revs(snapst.Sequence), DeepEquals, opts.after) 472 473 return &snapst, ts 474 } 475 476 func (s *snapmgrTestSuite) testUpdateSequence(c *C, opts *opSeqOpts) *state.TaskSet { 477 restore := release.MockOnClassic(false) 478 defer restore() 479 480 opts.revert = false 481 snapst, ts := s.testOpSequence(c, opts) 482 // update always ends with current==seq[-1]==via: 483 c.Check(snapst.Current.N, Equals, opts.after[len(opts.after)-1]) 484 c.Check(snapst.Current.N, Equals, opts.via) 485 486 c.Check(s.fakeBackend.ops.Count("copy-data"), Equals, 1) 487 c.Check(s.fakeBackend.ops.First("copy-data"), DeepEquals, &fakeOp{ 488 op: "copy-data", 489 path: fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.via), 490 old: fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.current), 491 }) 492 493 return ts 494 } 495 496 func (s *snapmgrTestSuite) testUpdateFailureSequence(c *C, opts *opSeqOpts) *state.TaskSet { 497 restore := release.MockOnClassic(false) 498 defer restore() 499 500 opts.revert = false 501 opts.after = opts.before 502 s.fakeBackend.linkSnapFailTrigger = fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.via) 503 snapst, ts := s.testOpSequence(c, opts) 504 // a failed update will always end with current unchanged 505 c.Check(snapst.Current.N, Equals, opts.current) 506 507 ops := s.fakeBackend.ops 508 c.Check(ops.Count("copy-data"), Equals, 1) 509 do := ops.First("copy-data") 510 511 c.Check(ops.Count("undo-copy-snap-data"), Equals, 1) 512 undo := ops.First("undo-copy-snap-data") 513 514 do.op = undo.op 515 c.Check(do, DeepEquals, undo) // i.e. they only differed in the op 516 517 return ts 518 } 519 520 // testTotal*Failure fails *after* link-snap 521 func (s *snapmgrTestSuite) testTotalUpdateFailureSequence(c *C, opts *opSeqOpts) *state.TaskSet { 522 restore := release.MockOnClassic(false) 523 defer restore() 524 525 opts.revert = false 526 opts.fail = true 527 snapst, ts := s.testOpSequence(c, opts) 528 // a failed update will always end with current unchanged 529 c.Check(snapst.Current.N, Equals, opts.current) 530 531 ops := s.fakeBackend.ops 532 c.Check(ops.Count("copy-data"), Equals, 1) 533 do := ops.First("copy-data") 534 535 c.Check(ops.Count("undo-copy-snap-data"), Equals, 1) 536 undo := ops.First("undo-copy-snap-data") 537 538 do.op = undo.op 539 c.Check(do, DeepEquals, undo) // i.e. they only differed in the op 540 541 return ts 542 } 543 544 func (s *snapmgrTestSuite) TestUpdateLayoutsChecksFeatureFlag(c *C) { 545 s.state.Lock() 546 defer s.state.Unlock() 547 548 // When layouts are disabled we cannot refresh to a snap depending on the feature. 549 tr := config.NewTransaction(s.state) 550 tr.Set("core", "experimental.layouts", false) 551 tr.Commit() 552 553 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 554 Active: true, 555 Sequence: []*snap.SideInfo{ 556 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 557 }, 558 Current: snap.R(1), 559 SnapType: "app", 560 }) 561 562 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-layout/stable"}, s.user.ID, snapstate.Flags{}) 563 c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true") 564 565 // When layouts are enabled we can refresh to a snap depending on the feature. 566 tr = config.NewTransaction(s.state) 567 tr.Set("core", "experimental.layouts", true) 568 tr.Commit() 569 570 _, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-layout/stable"}, s.user.ID, snapstate.Flags{}) 571 c.Assert(err, IsNil) 572 } 573 574 func (s *snapmgrTestSuite) TestUpdateManyExplicitLayoutsChecksFeatureFlag(c *C) { 575 s.state.Lock() 576 defer s.state.Unlock() 577 578 // When layouts are disabled we cannot refresh multiple snaps if one of them depends on the feature. 579 tr := config.NewTransaction(s.state) 580 tr.Set("core", "experimental.layouts", false) 581 tr.Commit() 582 583 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 584 Active: true, 585 TrackingChannel: "channel-for-layout/stable", 586 Sequence: []*snap.SideInfo{ 587 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 588 }, 589 Current: snap.R(1), 590 SnapType: "app", 591 }) 592 593 _, _, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 594 c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true") 595 596 // When layouts are enabled we can refresh multiple snaps if one of them depends on the feature. 597 tr = config.NewTransaction(s.state) 598 tr.Set("core", "experimental.layouts", true) 599 tr.Commit() 600 601 _, _, err = snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 602 c.Assert(err, IsNil) 603 } 604 605 func (s *snapmgrTestSuite) TestUpdateManyLayoutsChecksFeatureFlag(c *C) { 606 s.state.Lock() 607 defer s.state.Unlock() 608 609 // When layouts are disabled we cannot refresh multiple snaps if one of them depends on the feature. 610 tr := config.NewTransaction(s.state) 611 tr.Set("core", "experimental.layouts", false) 612 tr.Commit() 613 614 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 615 Active: true, 616 TrackingChannel: "channel-for-layout/stable", 617 Sequence: []*snap.SideInfo{ 618 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 619 }, 620 Current: snap.R(1), 621 SnapType: "app", 622 }) 623 624 refreshes, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, s.user.ID, nil) 625 c.Assert(err, IsNil) 626 c.Assert(refreshes, HasLen, 0) 627 628 // When layouts are enabled we can refresh multiple snaps if one of them depends on the feature. 629 tr = config.NewTransaction(s.state) 630 tr.Set("core", "experimental.layouts", true) 631 tr.Commit() 632 633 refreshes, _, err = snapstate.UpdateMany(context.Background(), s.state, nil, s.user.ID, nil) 634 c.Assert(err, IsNil) 635 c.Assert(refreshes, DeepEquals, []string{"some-snap"}) 636 } 637 638 func (s *snapmgrTestSuite) TestUpdateFailsEarlyOnEpochMismatch(c *C) { 639 s.state.Lock() 640 defer s.state.Unlock() 641 642 snapstate.Set(s.state, "some-epoch-snap", &snapstate.SnapState{ 643 Active: true, 644 Sequence: []*snap.SideInfo{ 645 {RealName: "some-epoch-snap", SnapID: "some-epoch-snap-id", Revision: snap.R(1)}, 646 }, 647 Current: snap.R(1), 648 SnapType: "app", 649 }) 650 651 _, err := snapstate.Update(s.state, "some-epoch-snap", nil, 0, snapstate.Flags{}) 652 c.Assert(err, ErrorMatches, `cannot refresh "some-epoch-snap" to new revision 11 with epoch 42, because it can't read the current epoch of 13`) 653 } 654 655 func (s *snapmgrTestSuite) TestUpdateTasksPropagatesErrors(c *C) { 656 s.state.Lock() 657 defer s.state.Unlock() 658 659 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 660 Active: true, 661 TrackingChannel: "latest/edge", 662 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "fakestore-please-error-on-refresh", Revision: snap.R(7)}}, 663 Current: snap.R(7), 664 }) 665 666 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 667 c.Assert(err, ErrorMatches, `failing as requested`) 668 } 669 670 func (s *snapmgrTestSuite) TestUpdateTasks(c *C) { 671 s.state.Lock() 672 defer s.state.Unlock() 673 674 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 675 Active: true, 676 TrackingChannel: "latest/edge", 677 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 678 Current: snap.R(7), 679 SnapType: "app", 680 }) 681 682 validateCalled := false 683 happyValidateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 684 validateCalled = true 685 return refreshes, nil 686 } 687 // hook it up 688 snapstate.ValidateRefreshes = happyValidateRefreshes 689 690 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 691 c.Assert(err, IsNil) 692 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 0, ts, s.state) 693 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 694 695 c.Check(validateCalled, Equals, true) 696 697 var snapsup snapstate.SnapSetup 698 err = ts.Tasks()[0].Get("snap-setup", &snapsup) 699 c.Assert(err, IsNil) 700 701 c.Check(snapsup.Channel, Equals, "some-channel") 702 } 703 704 func (s *snapmgrTestSuite) TestUpdateAmendRunThrough(c *C) { 705 si := snap.SideInfo{ 706 RealName: "some-snap", 707 Revision: snap.R(-42), 708 } 709 snaptest.MockSnap(c, `name: some-snap`, &si) 710 711 s.state.Lock() 712 defer s.state.Unlock() 713 714 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 715 Active: true, 716 Sequence: []*snap.SideInfo{&si}, 717 Current: si.Revision, 718 SnapType: "app", 719 TrackingChannel: "latest/stable", 720 }) 721 722 chg := s.state.NewChange("refresh", "refresh a snap") 723 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 724 c.Assert(err, IsNil) 725 chg.AddAll(ts) 726 727 s.state.Unlock() 728 defer s.se.Stop() 729 s.settle(c) 730 s.state.Lock() 731 732 // ensure all our tasks ran 733 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 734 macaroon: s.user.StoreMacaroon, 735 name: "some-snap", 736 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 737 }}) 738 c.Check(s.fakeStore.seenPrivacyKeys["privacy-key"], Equals, true, Commentf("salts seen: %v", s.fakeStore.seenPrivacyKeys)) 739 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, []string{ 740 "storesvc-snap-action", 741 "storesvc-snap-action:action", 742 "storesvc-download", 743 "validate-snap:Doing", 744 "current", 745 "open-snap-file", 746 "setup-snap", 747 "remove-snap-aliases", 748 "unlink-snap", 749 "copy-data", 750 "setup-profiles:Doing", 751 "candidate", 752 "link-snap", 753 "auto-connect:Doing", 754 "update-aliases", 755 "cleanup-trash", 756 }) 757 // just check the interesting op 758 c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{ 759 op: "storesvc-snap-action:action", 760 action: store.SnapAction{ 761 Action: "install", // we asked for an Update, but an amend is actually an Install 762 InstanceName: "some-snap", 763 Channel: "some-channel", 764 Epoch: snap.E("1*"), // in amend, epoch in the action is not nil! 765 Flags: store.SnapActionEnforceValidation, 766 }, 767 revno: snap.R(11), 768 userID: 1, 769 }) 770 771 task := ts.Tasks()[1] 772 // verify snapSetup info 773 var snapsup snapstate.SnapSetup 774 err = task.Get("snap-setup", &snapsup) 775 c.Assert(err, IsNil) 776 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 777 Channel: "some-channel", 778 UserID: s.user.ID, 779 780 SnapPath: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 781 DownloadInfo: &snap.DownloadInfo{ 782 DownloadURL: "https://some-server.com/some/path.snap", 783 Size: 5, 784 }, 785 SideInfo: snapsup.SideInfo, 786 Type: snap.TypeApp, 787 PlugsOnly: true, 788 Flags: snapstate.Flags{Amend: true}, 789 }) 790 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 791 RealName: "some-snap", 792 Revision: snap.R(11), 793 Channel: "some-channel", 794 SnapID: "some-snap-id", 795 }) 796 797 // verify services stop reason 798 verifyStopReason(c, ts, "refresh") 799 800 // check post-refresh hook 801 task = ts.Tasks()[14] 802 c.Assert(task.Kind(), Equals, "run-hook") 803 c.Assert(task.Summary(), Matches, `Run post-refresh hook of "some-snap" snap if present`) 804 805 // verify snaps in the system state 806 var snapst snapstate.SnapState 807 err = snapstate.Get(s.state, "some-snap", &snapst) 808 c.Assert(err, IsNil) 809 810 c.Assert(snapst.Active, Equals, true) 811 c.Assert(snapst.Sequence, HasLen, 2) 812 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 813 RealName: "some-snap", 814 Channel: "", 815 Revision: snap.R(-42), 816 }) 817 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 818 RealName: "some-snap", 819 Channel: "some-channel", 820 SnapID: "some-snap-id", 821 Revision: snap.R(11), 822 }) 823 } 824 825 func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) { 826 // we start without the auxiliary store info (or with an older one) 827 c.Check(snapstate.AuxStoreInfoFilename("services-snap-id"), testutil.FileAbsent) 828 829 // use services-snap here to make sure services would be stopped/started appropriately 830 si := snap.SideInfo{ 831 RealName: "services-snap", 832 Revision: snap.R(7), 833 SnapID: "services-snap-id", 834 } 835 snaptest.MockSnap(c, `name: services-snap`, &si) 836 fi, err := os.Stat(snap.MountFile("services-snap", si.Revision)) 837 c.Assert(err, IsNil) 838 refreshedDate := fi.ModTime() 839 // look at disk 840 r := snapstate.MockRevisionDate(nil) 841 defer r() 842 843 s.state.Lock() 844 defer s.state.Unlock() 845 846 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 847 Active: true, 848 Sequence: []*snap.SideInfo{&si}, 849 Current: si.Revision, 850 SnapType: "app", 851 TrackingChannel: "latest/stable", 852 CohortKey: "embattled", 853 }) 854 855 chg := s.state.NewChange("refresh", "refresh a snap") 856 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{ 857 Channel: "some-channel", 858 CohortKey: "some-cohort", 859 }, s.user.ID, snapstate.Flags{}) 860 c.Assert(err, IsNil) 861 chg.AddAll(ts) 862 863 s.state.Unlock() 864 defer s.se.Stop() 865 s.settle(c) 866 s.state.Lock() 867 868 expected := fakeOps{ 869 { 870 op: "storesvc-snap-action", 871 curSnaps: []store.CurrentSnap{{ 872 InstanceName: "services-snap", 873 SnapID: "services-snap-id", 874 Revision: snap.R(7), 875 TrackingChannel: "latest/stable", 876 RefreshedDate: refreshedDate, 877 Epoch: snap.E("0"), 878 CohortKey: "embattled", 879 }}, 880 userID: 1, 881 }, 882 { 883 op: "storesvc-snap-action:action", 884 action: store.SnapAction{ 885 Action: "refresh", 886 InstanceName: "services-snap", 887 SnapID: "services-snap-id", 888 Channel: "some-channel", 889 CohortKey: "some-cohort", 890 Flags: store.SnapActionEnforceValidation, 891 }, 892 revno: snap.R(11), 893 userID: 1, 894 }, 895 { 896 op: "storesvc-download", 897 name: "services-snap", 898 }, 899 { 900 op: "validate-snap:Doing", 901 name: "services-snap", 902 revno: snap.R(11), 903 }, 904 { 905 op: "current", 906 old: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 907 }, 908 { 909 op: "open-snap-file", 910 path: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 911 sinfo: snap.SideInfo{ 912 RealName: "services-snap", 913 SnapID: "services-snap-id", 914 Channel: "some-channel", 915 Revision: snap.R(11), 916 }, 917 }, 918 { 919 op: "setup-snap", 920 name: "services-snap", 921 path: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 922 revno: snap.R(11), 923 }, 924 { 925 op: "stop-snap-services:refresh", 926 path: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 927 }, 928 { 929 op: "current-snap-service-states", 930 }, 931 { 932 op: "remove-snap-aliases", 933 name: "services-snap", 934 }, 935 { 936 op: "unlink-snap", 937 path: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 938 }, 939 { 940 op: "copy-data", 941 path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), 942 old: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 943 }, 944 { 945 op: "setup-profiles:Doing", 946 name: "services-snap", 947 revno: snap.R(11), 948 }, 949 { 950 op: "candidate", 951 sinfo: snap.SideInfo{ 952 RealName: "services-snap", 953 SnapID: "services-snap-id", 954 Channel: "some-channel", 955 Revision: snap.R(11), 956 }, 957 }, 958 { 959 op: "link-snap", 960 path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), 961 }, 962 { 963 op: "auto-connect:Doing", 964 name: "services-snap", 965 revno: snap.R(11), 966 }, 967 { 968 op: "update-aliases", 969 }, 970 { 971 op: "start-snap-services", 972 path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), 973 services: []string{"svc1", "svc3", "svc2"}, 974 }, 975 { 976 op: "cleanup-trash", 977 name: "services-snap", 978 revno: snap.R(11), 979 }, 980 } 981 982 // ensure all our tasks ran 983 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 984 macaroon: s.user.StoreMacaroon, 985 name: "services-snap", 986 target: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 987 }}) 988 c.Check(s.fakeStore.seenPrivacyKeys["privacy-key"], Equals, true, Commentf("salts seen: %v", s.fakeStore.seenPrivacyKeys)) 989 // start with an easier-to-read error if this fails: 990 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 991 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 992 993 // check progress 994 task := ts.Tasks()[1] 995 _, cur, total := task.Progress() 996 c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress) 997 c.Assert(total, Equals, s.fakeStore.fakeTotalProgress) 998 999 // verify snapSetup info 1000 var snapsup snapstate.SnapSetup 1001 err = task.Get("snap-setup", &snapsup) 1002 c.Assert(err, IsNil) 1003 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 1004 Channel: "some-channel", 1005 CohortKey: "some-cohort", 1006 UserID: s.user.ID, 1007 1008 SnapPath: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 1009 DownloadInfo: &snap.DownloadInfo{ 1010 DownloadURL: "https://some-server.com/some/path.snap", 1011 }, 1012 SideInfo: snapsup.SideInfo, 1013 Type: snap.TypeApp, 1014 PlugsOnly: true, 1015 }) 1016 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 1017 RealName: "services-snap", 1018 Revision: snap.R(11), 1019 Channel: "some-channel", 1020 SnapID: "services-snap-id", 1021 }) 1022 1023 // verify services stop reason 1024 verifyStopReason(c, ts, "refresh") 1025 1026 // check post-refresh hook 1027 task = ts.Tasks()[14] 1028 c.Assert(task.Kind(), Equals, "run-hook") 1029 c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap" snap if present`) 1030 1031 // verify snaps in the system state 1032 var snapst snapstate.SnapState 1033 err = snapstate.Get(s.state, "services-snap", &snapst) 1034 c.Assert(err, IsNil) 1035 1036 c.Assert(snapst.Active, Equals, true) 1037 c.Assert(snapst.Sequence, HasLen, 2) 1038 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 1039 RealName: "services-snap", 1040 SnapID: "services-snap-id", 1041 Channel: "", 1042 Revision: snap.R(7), 1043 }) 1044 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 1045 RealName: "services-snap", 1046 Channel: "some-channel", 1047 SnapID: "services-snap-id", 1048 Revision: snap.R(11), 1049 }) 1050 c.Check(snapst.CohortKey, Equals, "some-cohort") 1051 1052 // we end up with the auxiliary store info 1053 c.Check(snapstate.AuxStoreInfoFilename("services-snap-id"), testutil.FilePresent) 1054 } 1055 1056 func (s *snapmgrTestSuite) TestParallelInstanceUpdateRunThrough(c *C) { 1057 // use services-snap here to make sure services would be stopped/started appropriately 1058 si := snap.SideInfo{ 1059 RealName: "services-snap", 1060 Revision: snap.R(7), 1061 SnapID: "services-snap-id", 1062 } 1063 snaptest.MockSnapInstance(c, "services-snap_instance", `name: services-snap`, &si) 1064 fi, err := os.Stat(snap.MountFile("services-snap_instance", si.Revision)) 1065 c.Assert(err, IsNil) 1066 refreshedDate := fi.ModTime() 1067 // look at disk 1068 r := snapstate.MockRevisionDate(nil) 1069 defer r() 1070 1071 s.state.Lock() 1072 defer s.state.Unlock() 1073 1074 tr := config.NewTransaction(s.state) 1075 tr.Set("core", "experimental.parallel-instances", true) 1076 tr.Commit() 1077 1078 snapstate.Set(s.state, "services-snap_instance", &snapstate.SnapState{ 1079 Active: true, 1080 Sequence: []*snap.SideInfo{&si}, 1081 Current: si.Revision, 1082 SnapType: "app", 1083 TrackingChannel: "latest/stable", 1084 InstanceKey: "instance", 1085 }) 1086 1087 chg := s.state.NewChange("refresh", "refresh a snap") 1088 ts, err := snapstate.Update(s.state, "services-snap_instance", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 1089 c.Assert(err, IsNil) 1090 chg.AddAll(ts) 1091 1092 s.state.Unlock() 1093 s.settle(c) 1094 s.state.Lock() 1095 1096 expected := fakeOps{ 1097 { 1098 op: "storesvc-snap-action", 1099 curSnaps: []store.CurrentSnap{{ 1100 InstanceName: "services-snap_instance", 1101 SnapID: "services-snap-id", 1102 Revision: snap.R(7), 1103 TrackingChannel: "latest/stable", 1104 RefreshedDate: refreshedDate, 1105 Epoch: snap.E("0"), 1106 }}, 1107 userID: 1, 1108 }, 1109 { 1110 op: "storesvc-snap-action:action", 1111 action: store.SnapAction{ 1112 Action: "refresh", 1113 SnapID: "services-snap-id", 1114 InstanceName: "services-snap_instance", 1115 Channel: "some-channel", 1116 Flags: store.SnapActionEnforceValidation, 1117 }, 1118 revno: snap.R(11), 1119 userID: 1, 1120 }, 1121 { 1122 op: "storesvc-download", 1123 name: "services-snap", 1124 }, 1125 { 1126 op: "validate-snap:Doing", 1127 name: "services-snap_instance", 1128 revno: snap.R(11), 1129 }, 1130 { 1131 op: "current", 1132 old: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1133 }, 1134 { 1135 op: "open-snap-file", 1136 path: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1137 sinfo: snap.SideInfo{ 1138 RealName: "services-snap", 1139 SnapID: "services-snap-id", 1140 Channel: "some-channel", 1141 Revision: snap.R(11), 1142 }, 1143 }, 1144 { 1145 op: "setup-snap", 1146 name: "services-snap_instance", 1147 path: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1148 revno: snap.R(11), 1149 }, 1150 { 1151 op: "stop-snap-services:refresh", 1152 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1153 }, 1154 { 1155 op: "current-snap-service-states", 1156 }, 1157 { 1158 op: "remove-snap-aliases", 1159 name: "services-snap_instance", 1160 }, 1161 { 1162 op: "unlink-snap", 1163 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1164 }, 1165 { 1166 op: "copy-data", 1167 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"), 1168 old: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1169 }, 1170 { 1171 op: "setup-profiles:Doing", 1172 name: "services-snap_instance", 1173 revno: snap.R(11), 1174 }, 1175 { 1176 op: "candidate", 1177 sinfo: snap.SideInfo{ 1178 RealName: "services-snap", 1179 SnapID: "services-snap-id", 1180 Channel: "some-channel", 1181 Revision: snap.R(11), 1182 }, 1183 }, 1184 { 1185 op: "link-snap", 1186 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"), 1187 }, 1188 { 1189 op: "auto-connect:Doing", 1190 name: "services-snap_instance", 1191 revno: snap.R(11), 1192 }, 1193 { 1194 op: "update-aliases", 1195 }, 1196 { 1197 op: "start-snap-services", 1198 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"), 1199 services: []string{"svc1", "svc3", "svc2"}, 1200 }, 1201 { 1202 op: "cleanup-trash", 1203 name: "services-snap_instance", 1204 revno: snap.R(11), 1205 }, 1206 } 1207 1208 // ensure all our tasks ran 1209 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 1210 macaroon: s.user.StoreMacaroon, 1211 name: "services-snap", 1212 target: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1213 }}) 1214 c.Check(s.fakeStore.seenPrivacyKeys["privacy-key"], Equals, true, Commentf("salts seen: %v", s.fakeStore.seenPrivacyKeys)) 1215 // start with an easier-to-read error if this fails: 1216 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 1217 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 1218 1219 // check progress 1220 task := ts.Tasks()[1] 1221 _, cur, total := task.Progress() 1222 c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress) 1223 c.Assert(total, Equals, s.fakeStore.fakeTotalProgress) 1224 1225 // verify snapSetup info 1226 var snapsup snapstate.SnapSetup 1227 err = task.Get("snap-setup", &snapsup) 1228 c.Assert(err, IsNil) 1229 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 1230 Channel: "some-channel", 1231 UserID: s.user.ID, 1232 1233 SnapPath: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1234 DownloadInfo: &snap.DownloadInfo{ 1235 DownloadURL: "https://some-server.com/some/path.snap", 1236 }, 1237 SideInfo: snapsup.SideInfo, 1238 Type: snap.TypeApp, 1239 PlugsOnly: true, 1240 InstanceKey: "instance", 1241 }) 1242 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 1243 RealName: "services-snap", 1244 Revision: snap.R(11), 1245 Channel: "some-channel", 1246 SnapID: "services-snap-id", 1247 }) 1248 1249 // verify services stop reason 1250 verifyStopReason(c, ts, "refresh") 1251 1252 // check post-refresh hook 1253 task = ts.Tasks()[14] 1254 c.Assert(task.Kind(), Equals, "run-hook") 1255 c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap_instance" snap if present`) 1256 1257 // verify snaps in the system state 1258 var snapst snapstate.SnapState 1259 err = snapstate.Get(s.state, "services-snap_instance", &snapst) 1260 c.Assert(err, IsNil) 1261 1262 c.Assert(snapst.InstanceKey, Equals, "instance") 1263 c.Assert(snapst.Active, Equals, true) 1264 c.Assert(snapst.Sequence, HasLen, 2) 1265 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 1266 RealName: "services-snap", 1267 SnapID: "services-snap-id", 1268 Channel: "", 1269 Revision: snap.R(7), 1270 }) 1271 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 1272 RealName: "services-snap", 1273 Channel: "some-channel", 1274 SnapID: "services-snap-id", 1275 Revision: snap.R(11), 1276 }) 1277 } 1278 1279 func (s *snapmgrTestSuite) TestUpdateWithNewBase(c *C) { 1280 si := &snap.SideInfo{ 1281 RealName: "some-snap", 1282 SnapID: "some-snap-id", 1283 Revision: snap.R(7), 1284 } 1285 snaptest.MockSnap(c, `name: some-snap`, si) 1286 1287 s.state.Lock() 1288 defer s.state.Unlock() 1289 1290 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1291 Active: true, 1292 TrackingChannel: "latest/edge", 1293 Sequence: []*snap.SideInfo{si}, 1294 Current: snap.R(7), 1295 SnapType: "app", 1296 }) 1297 1298 chg := s.state.NewChange("refresh", "refresh a snap") 1299 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-base/stable"}, s.user.ID, snapstate.Flags{}) 1300 c.Assert(err, IsNil) 1301 chg.AddAll(ts) 1302 1303 s.state.Unlock() 1304 defer s.se.Stop() 1305 s.settle(c) 1306 s.state.Lock() 1307 1308 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1309 {macaroon: s.user.StoreMacaroon, name: "some-base", target: filepath.Join(dirs.SnapBlobDir, "some-base_11.snap")}, 1310 {macaroon: s.user.StoreMacaroon, name: "some-snap", target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap")}, 1311 }) 1312 } 1313 1314 func (s *snapmgrTestSuite) TestUpdateWithAlreadyInstalledBase(c *C) { 1315 si := &snap.SideInfo{ 1316 RealName: "some-snap", 1317 SnapID: "some-snap-id", 1318 Revision: snap.R(7), 1319 } 1320 snaptest.MockSnap(c, `name: some-snap`, si) 1321 1322 s.state.Lock() 1323 defer s.state.Unlock() 1324 1325 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1326 Active: true, 1327 TrackingChannel: "latest/edge", 1328 Sequence: []*snap.SideInfo{si}, 1329 Current: snap.R(7), 1330 SnapType: "app", 1331 }) 1332 snapstate.Set(s.state, "some-base", &snapstate.SnapState{ 1333 Active: true, 1334 TrackingChannel: "latest/stable", 1335 Sequence: []*snap.SideInfo{{ 1336 RealName: "some-base", 1337 SnapID: "some-base-id", 1338 Revision: snap.R(1), 1339 }}, 1340 Current: snap.R(1), 1341 SnapType: "base", 1342 }) 1343 1344 chg := s.state.NewChange("refresh", "refresh a snap") 1345 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-base"}, s.user.ID, snapstate.Flags{}) 1346 c.Assert(err, IsNil) 1347 chg.AddAll(ts) 1348 1349 s.state.Unlock() 1350 defer s.se.Stop() 1351 s.settle(c) 1352 s.state.Lock() 1353 1354 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1355 {macaroon: s.user.StoreMacaroon, name: "some-snap", target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap")}, 1356 }) 1357 } 1358 1359 func (s *snapmgrTestSuite) TestUpdateWithNewDefaultProvider(c *C) { 1360 s.state.Lock() 1361 defer s.state.Unlock() 1362 1363 snapstate.ReplaceStore(s.state, contentStore{fakeStore: s.fakeStore, state: s.state}) 1364 repo := interfaces.NewRepository() 1365 ifacerepo.Replace(s.state, repo) 1366 1367 si := &snap.SideInfo{ 1368 RealName: "snap-content-plug", 1369 SnapID: "snap-content-plug-id", 1370 Revision: snap.R(7), 1371 } 1372 snaptest.MockSnap(c, `name: snap-content-plug`, si) 1373 snapstate.Set(s.state, "snap-content-plug", &snapstate.SnapState{ 1374 Active: true, 1375 TrackingChannel: "latest/edge", 1376 Sequence: []*snap.SideInfo{si}, 1377 Current: snap.R(7), 1378 SnapType: "app", 1379 }) 1380 1381 chg := s.state.NewChange("refresh", "refresh a snap") 1382 ts, err := snapstate.Update(s.state, "snap-content-plug", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{}) 1383 c.Assert(err, IsNil) 1384 chg.AddAll(ts) 1385 1386 s.state.Unlock() 1387 defer s.se.Stop() 1388 s.settle(c) 1389 s.state.Lock() 1390 1391 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1392 {macaroon: s.user.StoreMacaroon, name: "snap-content-plug", target: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_11.snap")}, 1393 {macaroon: s.user.StoreMacaroon, name: "snap-content-slot", target: filepath.Join(dirs.SnapBlobDir, "snap-content-slot_11.snap")}, 1394 }) 1395 } 1396 1397 func (s *snapmgrTestSuite) TestUpdateWithInstalledDefaultProvider(c *C) { 1398 s.state.Lock() 1399 defer s.state.Unlock() 1400 1401 snapstate.ReplaceStore(s.state, contentStore{fakeStore: s.fakeStore, state: s.state}) 1402 repo := interfaces.NewRepository() 1403 ifacerepo.Replace(s.state, repo) 1404 1405 si := &snap.SideInfo{ 1406 RealName: "snap-content-plug", 1407 SnapID: "snap-content-plug-id", 1408 Revision: snap.R(7), 1409 } 1410 snaptest.MockSnap(c, `name: snap-content-plug`, si) 1411 snapstate.Set(s.state, "snap-content-plug", &snapstate.SnapState{ 1412 Active: true, 1413 TrackingChannel: "latest/edge", 1414 Sequence: []*snap.SideInfo{si}, 1415 Current: snap.R(7), 1416 SnapType: "app", 1417 }) 1418 snapstate.Set(s.state, "snap-content-slot", &snapstate.SnapState{ 1419 Active: true, 1420 TrackingChannel: "latest/stable", 1421 Sequence: []*snap.SideInfo{{ 1422 RealName: "snap-content-slot", 1423 SnapID: "snap-content-slot-id", 1424 Revision: snap.R(1), 1425 }}, 1426 Current: snap.R(1), 1427 SnapType: "app", 1428 }) 1429 1430 chg := s.state.NewChange("refresh", "refresh a snap") 1431 ts, err := snapstate.Update(s.state, "snap-content-plug", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{}) 1432 c.Assert(err, IsNil) 1433 chg.AddAll(ts) 1434 1435 s.state.Unlock() 1436 defer s.se.Stop() 1437 s.settle(c) 1438 s.state.Lock() 1439 1440 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1441 {macaroon: s.user.StoreMacaroon, name: "snap-content-plug", target: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_11.snap")}, 1442 }) 1443 } 1444 1445 func (s *snapmgrTestSuite) TestUpdateRememberedUserRunThrough(c *C) { 1446 s.state.Lock() 1447 defer s.state.Unlock() 1448 1449 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1450 Active: true, 1451 Sequence: []*snap.SideInfo{ 1452 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1453 }, 1454 Current: snap.R(5), 1455 SnapType: "app", 1456 UserID: 1, 1457 }) 1458 1459 chg := s.state.NewChange("refresh", "refresh a snap") 1460 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, 0, snapstate.Flags{}) 1461 c.Assert(err, IsNil) 1462 chg.AddAll(ts) 1463 1464 s.state.Unlock() 1465 defer s.se.Stop() 1466 s.settle(c) 1467 s.state.Lock() 1468 1469 c.Assert(chg.Status(), Equals, state.DoneStatus) 1470 c.Assert(chg.Err(), IsNil) 1471 1472 for _, op := range s.fakeBackend.ops { 1473 switch op.op { 1474 case "storesvc-snap-action": 1475 c.Check(op.userID, Equals, 1) 1476 case "storesvc-download": 1477 snapName := op.name 1478 c.Check(s.fakeStore.downloads[0], DeepEquals, fakeDownload{ 1479 macaroon: "macaroon", 1480 name: "some-snap", 1481 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 1482 }, Commentf(snapName)) 1483 } 1484 } 1485 } 1486 1487 func (s *snapmgrTestSuite) TestUpdateModelKernelSwitchTrackRunThrough(c *C) { 1488 // use services-snap here to make sure services would be stopped/started appropriately 1489 si := snap.SideInfo{ 1490 RealName: "kernel", 1491 Revision: snap.R(7), 1492 SnapID: "kernel-id", 1493 } 1494 snaptest.MockSnap(c, `name: kernel`, &si) 1495 fi, err := os.Stat(snap.MountFile("kernel", si.Revision)) 1496 c.Assert(err, IsNil) 1497 refreshedDate := fi.ModTime() 1498 // look at disk 1499 r := snapstate.MockRevisionDate(nil) 1500 defer r() 1501 1502 s.state.Lock() 1503 defer s.state.Unlock() 1504 1505 r1 := snapstatetest.MockDeviceModel(ModelWithKernelTrack("18")) 1506 defer r1() 1507 snapstate.Set(s.state, "kernel", &snapstate.SnapState{ 1508 Active: true, 1509 Sequence: []*snap.SideInfo{&si}, 1510 Current: si.Revision, 1511 TrackingChannel: "18/stable", 1512 }) 1513 1514 chg := s.state.NewChange("refresh", "refresh a snap") 1515 ts, err := snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "edge"}, s.user.ID, snapstate.Flags{}) 1516 c.Assert(err, IsNil) 1517 chg.AddAll(ts) 1518 1519 s.state.Unlock() 1520 defer s.se.Stop() 1521 s.settle(c) 1522 s.state.Lock() 1523 1524 c.Check(chg.Status(), Equals, state.DoneStatus) 1525 1526 c.Assert(len(s.fakeBackend.ops) > 2, Equals, true) 1527 c.Assert(s.fakeBackend.ops[:2], DeepEquals, fakeOps{ 1528 { 1529 op: "storesvc-snap-action", 1530 curSnaps: []store.CurrentSnap{{ 1531 InstanceName: "kernel", 1532 SnapID: "kernel-id", 1533 Revision: snap.R(7), 1534 TrackingChannel: "18/stable", 1535 RefreshedDate: refreshedDate, 1536 Epoch: snap.E("1*"), 1537 }}, 1538 userID: 1, 1539 }, { 1540 op: "storesvc-snap-action:action", 1541 action: store.SnapAction{ 1542 Action: "refresh", 1543 InstanceName: "kernel", 1544 SnapID: "kernel-id", 1545 Channel: "18/edge", 1546 Flags: store.SnapActionEnforceValidation, 1547 }, 1548 revno: snap.R(11), 1549 userID: 1, 1550 }, 1551 }) 1552 1553 // check progress 1554 task := ts.Tasks()[1] 1555 _, cur, total := task.Progress() 1556 c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress) 1557 c.Assert(total, Equals, s.fakeStore.fakeTotalProgress) 1558 1559 // verify snapSetup info 1560 var snapsup snapstate.SnapSetup 1561 err = task.Get("snap-setup", &snapsup) 1562 c.Assert(err, IsNil) 1563 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 1564 Channel: "18/edge", 1565 UserID: s.user.ID, 1566 1567 SnapPath: filepath.Join(dirs.SnapBlobDir, "kernel_11.snap"), 1568 DownloadInfo: &snap.DownloadInfo{ 1569 DownloadURL: "https://some-server.com/some/path.snap", 1570 }, 1571 SideInfo: snapsup.SideInfo, 1572 Type: snap.TypeKernel, 1573 PlugsOnly: true, 1574 }) 1575 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 1576 RealName: "kernel", 1577 Revision: snap.R(11), 1578 Channel: "18/edge", 1579 SnapID: "kernel-id", 1580 }) 1581 1582 // verify snaps in the system state 1583 var snapst snapstate.SnapState 1584 err = snapstate.Get(s.state, "kernel", &snapst) 1585 c.Assert(err, IsNil) 1586 1587 c.Assert(snapst.Active, Equals, true) 1588 c.Assert(snapst.TrackingChannel, Equals, "18/edge") 1589 c.Assert(snapst.Sequence, HasLen, 2) 1590 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 1591 RealName: "kernel", 1592 SnapID: "kernel-id", 1593 Channel: "", 1594 Revision: snap.R(7), 1595 }) 1596 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 1597 RealName: "kernel", 1598 Channel: "18/edge", 1599 SnapID: "kernel-id", 1600 Revision: snap.R(11), 1601 }) 1602 } 1603 1604 func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsNoUserRunThrough(c *C) { 1605 s.state.Lock() 1606 defer s.state.Unlock() 1607 1608 snapstate.Set(s.state, "core", &snapstate.SnapState{ 1609 Active: true, 1610 Sequence: []*snap.SideInfo{ 1611 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 1612 }, 1613 Current: snap.R(1), 1614 SnapType: "os", 1615 }) 1616 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1617 Active: true, 1618 Sequence: []*snap.SideInfo{ 1619 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1620 }, 1621 Current: snap.R(5), 1622 SnapType: "app", 1623 UserID: 1, 1624 }) 1625 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 1626 Active: true, 1627 Sequence: []*snap.SideInfo{ 1628 {RealName: "services-snap", Revision: snap.R(2), SnapID: "services-snap-id"}, 1629 }, 1630 Current: snap.R(2), 1631 SnapType: "app", 1632 UserID: 2, 1633 }) 1634 1635 chg := s.state.NewChange("refresh", "refresh all snaps") 1636 // no user is passed to use for UpdateMany 1637 updated, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 1638 c.Assert(err, IsNil) 1639 for _, ts := range tts { 1640 chg.AddAll(ts) 1641 } 1642 c.Check(updated, HasLen, 3) 1643 1644 s.state.Unlock() 1645 defer s.se.Stop() 1646 s.settle(c) 1647 s.state.Lock() 1648 1649 c.Assert(chg.Status(), Equals, state.DoneStatus) 1650 c.Assert(chg.Err(), IsNil) 1651 1652 macaroonMap := map[string]string{ 1653 "core": "", 1654 "some-snap": "macaroon", 1655 "services-snap": "macaroon2", 1656 } 1657 1658 seen := make(map[string]int) 1659 ir := 0 1660 di := 0 1661 for _, op := range s.fakeBackend.ops { 1662 switch op.op { 1663 case "storesvc-snap-action": 1664 ir++ 1665 c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{ 1666 { 1667 InstanceName: "core", 1668 SnapID: "core-snap-id", 1669 Revision: snap.R(1), 1670 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1), 1671 Epoch: snap.E("1*"), 1672 }, 1673 { 1674 InstanceName: "services-snap", 1675 SnapID: "services-snap-id", 1676 Revision: snap.R(2), 1677 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2), 1678 Epoch: snap.E("0"), 1679 }, 1680 { 1681 InstanceName: "some-snap", 1682 SnapID: "some-snap-id", 1683 Revision: snap.R(5), 1684 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5), 1685 Epoch: snap.E("1*"), 1686 }, 1687 }) 1688 case "storesvc-snap-action:action": 1689 snapID := op.action.SnapID 1690 seen[snapID] = op.userID 1691 case "storesvc-download": 1692 snapName := op.name 1693 fakeDl := s.fakeStore.downloads[di] 1694 // check target path separately and clear it 1695 c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName))) 1696 fakeDl.target = "" 1697 c.Check(fakeDl, DeepEquals, fakeDownload{ 1698 macaroon: macaroonMap[snapName], 1699 name: snapName, 1700 }, Commentf(snapName)) 1701 di++ 1702 } 1703 } 1704 c.Check(ir, Equals, 2) 1705 // we check all snaps with each user 1706 c.Check(seen["some-snap-id"], Equals, 1) 1707 c.Check(seen["services-snap-id"], Equals, 2) 1708 // coalesced with one of the others 1709 c.Check(seen["core-snap-id"] > 0, Equals, true) 1710 } 1711 1712 func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsUserRunThrough(c *C) { 1713 s.state.Lock() 1714 defer s.state.Unlock() 1715 1716 snapstate.Set(s.state, "core", &snapstate.SnapState{ 1717 Active: true, 1718 Sequence: []*snap.SideInfo{ 1719 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 1720 }, 1721 Current: snap.R(1), 1722 SnapType: "os", 1723 }) 1724 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1725 Active: true, 1726 Sequence: []*snap.SideInfo{ 1727 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1728 }, 1729 Current: snap.R(5), 1730 SnapType: "app", 1731 UserID: 1, 1732 }) 1733 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 1734 Active: true, 1735 Sequence: []*snap.SideInfo{ 1736 {RealName: "services-snap", Revision: snap.R(2), SnapID: "services-snap-id"}, 1737 }, 1738 Current: snap.R(2), 1739 SnapType: "app", 1740 UserID: 2, 1741 }) 1742 1743 chg := s.state.NewChange("refresh", "refresh all snaps") 1744 // do UpdateMany using user 2 as fallback 1745 updated, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 2, nil) 1746 c.Assert(err, IsNil) 1747 for _, ts := range tts { 1748 chg.AddAll(ts) 1749 } 1750 c.Check(updated, HasLen, 3) 1751 1752 s.state.Unlock() 1753 defer s.se.Stop() 1754 s.settle(c) 1755 s.state.Lock() 1756 1757 c.Assert(chg.Status(), Equals, state.DoneStatus) 1758 c.Assert(chg.Err(), IsNil) 1759 1760 macaroonMap := map[string]string{ 1761 "core": "macaroon2", 1762 "some-snap": "macaroon", 1763 "services-snap": "macaroon2", 1764 } 1765 1766 type snapIDuserID struct { 1767 snapID string 1768 userID int 1769 } 1770 seen := make(map[snapIDuserID]bool) 1771 ir := 0 1772 di := 0 1773 for _, op := range s.fakeBackend.ops { 1774 switch op.op { 1775 case "storesvc-snap-action": 1776 ir++ 1777 c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{ 1778 { 1779 InstanceName: "core", 1780 SnapID: "core-snap-id", 1781 Revision: snap.R(1), 1782 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1), 1783 Epoch: snap.E("1*"), 1784 }, 1785 { 1786 InstanceName: "services-snap", 1787 SnapID: "services-snap-id", 1788 Revision: snap.R(2), 1789 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2), 1790 Epoch: snap.E("0"), 1791 }, 1792 { 1793 InstanceName: "some-snap", 1794 SnapID: "some-snap-id", 1795 Revision: snap.R(5), 1796 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5), 1797 Epoch: snap.E("1*"), 1798 }, 1799 }) 1800 case "storesvc-snap-action:action": 1801 snapID := op.action.SnapID 1802 seen[snapIDuserID{snapID: snapID, userID: op.userID}] = true 1803 case "storesvc-download": 1804 snapName := op.name 1805 fakeDl := s.fakeStore.downloads[di] 1806 // check target path separately and clear it 1807 c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName))) 1808 fakeDl.target = "" 1809 c.Check(fakeDl, DeepEquals, fakeDownload{ 1810 macaroon: macaroonMap[snapName], 1811 name: snapName, 1812 }, Commentf(snapName)) 1813 di++ 1814 } 1815 } 1816 c.Check(ir, Equals, 2) 1817 // we check all snaps with each user 1818 c.Check(seen, DeepEquals, map[snapIDuserID]bool{ 1819 {snapID: "core-snap-id", userID: 2}: true, 1820 {snapID: "some-snap-id", userID: 1}: true, 1821 {snapID: "services-snap-id", userID: 2}: true, 1822 }) 1823 1824 var coreState, snapState snapstate.SnapState 1825 // user in SnapState was preserved 1826 err = snapstate.Get(s.state, "some-snap", &snapState) 1827 c.Assert(err, IsNil) 1828 c.Check(snapState.UserID, Equals, 1) 1829 c.Check(snapState.Current, DeepEquals, snap.R(11)) 1830 1831 // user in SnapState was set 1832 err = snapstate.Get(s.state, "core", &coreState) 1833 c.Assert(err, IsNil) 1834 c.Check(coreState.UserID, Equals, 2) 1835 c.Check(coreState.Current, DeepEquals, snap.R(11)) 1836 1837 } 1838 1839 func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsUserWithNoStoreAuthRunThrough(c *C) { 1840 s.state.Lock() 1841 defer s.state.Unlock() 1842 1843 snapstate.Set(s.state, "core", &snapstate.SnapState{ 1844 Active: true, 1845 Sequence: []*snap.SideInfo{ 1846 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 1847 }, 1848 Current: snap.R(1), 1849 SnapType: "os", 1850 }) 1851 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1852 Active: true, 1853 Sequence: []*snap.SideInfo{ 1854 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1855 }, 1856 Current: snap.R(5), 1857 SnapType: "app", 1858 UserID: 1, 1859 }) 1860 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 1861 Active: true, 1862 Sequence: []*snap.SideInfo{ 1863 {RealName: "services-snap", Revision: snap.R(2), SnapID: "services-snap-id"}, 1864 }, 1865 Current: snap.R(2), 1866 SnapType: "app", 1867 UserID: 3, 1868 }) 1869 1870 chg := s.state.NewChange("refresh", "refresh all snaps") 1871 // no user is passed to use for UpdateMany 1872 updated, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 1873 c.Assert(err, IsNil) 1874 for _, ts := range tts { 1875 chg.AddAll(ts) 1876 } 1877 c.Check(updated, HasLen, 3) 1878 1879 s.state.Unlock() 1880 defer s.se.Stop() 1881 s.settle(c) 1882 s.state.Lock() 1883 1884 c.Assert(chg.Status(), Equals, state.DoneStatus) 1885 c.Assert(chg.Err(), IsNil) 1886 1887 macaroonMap := map[string]string{ 1888 "core": "", 1889 "some-snap": "macaroon", 1890 "services-snap": "", 1891 } 1892 1893 seen := make(map[string]int) 1894 ir := 0 1895 di := 0 1896 for _, op := range s.fakeBackend.ops { 1897 switch op.op { 1898 case "storesvc-snap-action": 1899 ir++ 1900 c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{ 1901 { 1902 InstanceName: "core", 1903 SnapID: "core-snap-id", 1904 Revision: snap.R(1), 1905 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1), 1906 Epoch: snap.E("1*"), 1907 }, 1908 { 1909 InstanceName: "services-snap", 1910 SnapID: "services-snap-id", 1911 Revision: snap.R(2), 1912 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2), 1913 Epoch: snap.E("0"), 1914 }, 1915 { 1916 InstanceName: "some-snap", 1917 SnapID: "some-snap-id", 1918 Revision: snap.R(5), 1919 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5), 1920 Epoch: snap.E("1*"), 1921 }, 1922 }) 1923 case "storesvc-snap-action:action": 1924 snapID := op.action.SnapID 1925 if _, ok := seen[snapID]; !ok { 1926 seen[snapID] = op.userID 1927 } 1928 case "storesvc-download": 1929 snapName := op.name 1930 fakeDl := s.fakeStore.downloads[di] 1931 // check target path separately and clear it 1932 c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName))) 1933 fakeDl.target = "" 1934 c.Check(fakeDl, DeepEquals, fakeDownload{ 1935 macaroon: macaroonMap[snapName], 1936 name: snapName, 1937 }, Commentf(snapName)) 1938 di++ 1939 } 1940 } 1941 c.Check(ir, Equals, 1) 1942 // we check all snaps with each user 1943 c.Check(seen["some-snap-id"], Equals, 1) 1944 // coalesced with request for 1 1945 c.Check(seen["services-snap-id"], Equals, 1) 1946 c.Check(seen["core-snap-id"], Equals, 1) 1947 } 1948 1949 func (s *snapmgrTestSuite) TestUpdateUndoRunThrough(c *C) { 1950 si := snap.SideInfo{ 1951 RealName: "some-snap", 1952 SnapID: "some-snap-id", 1953 Revision: snap.R(7), 1954 } 1955 1956 s.state.Lock() 1957 defer s.state.Unlock() 1958 1959 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1960 Active: true, 1961 Sequence: []*snap.SideInfo{&si}, 1962 Current: si.Revision, 1963 SnapType: "app", 1964 }) 1965 1966 chg := s.state.NewChange("install", "install a snap") 1967 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 1968 c.Assert(err, IsNil) 1969 chg.AddAll(ts) 1970 1971 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/some-snap/11") 1972 1973 s.state.Unlock() 1974 defer s.se.Stop() 1975 s.settle(c) 1976 s.state.Lock() 1977 1978 expected := fakeOps{ 1979 { 1980 op: "storesvc-snap-action", 1981 curSnaps: []store.CurrentSnap{{ 1982 InstanceName: "some-snap", 1983 SnapID: "some-snap-id", 1984 Revision: snap.R(7), 1985 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 1986 Epoch: snap.E("1*"), 1987 }}, 1988 userID: 1, 1989 }, 1990 { 1991 op: "storesvc-snap-action:action", 1992 action: store.SnapAction{ 1993 Action: "refresh", 1994 InstanceName: "some-snap", 1995 SnapID: "some-snap-id", 1996 Channel: "some-channel", 1997 Flags: store.SnapActionEnforceValidation, 1998 }, 1999 revno: snap.R(11), 2000 userID: 1, 2001 }, 2002 { 2003 op: "storesvc-download", 2004 name: "some-snap", 2005 }, 2006 { 2007 op: "validate-snap:Doing", 2008 name: "some-snap", 2009 revno: snap.R(11), 2010 }, 2011 { 2012 op: "current", 2013 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2014 }, 2015 { 2016 op: "open-snap-file", 2017 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2018 sinfo: snap.SideInfo{ 2019 RealName: "some-snap", 2020 SnapID: "some-snap-id", 2021 Channel: "some-channel", 2022 Revision: snap.R(11), 2023 }, 2024 }, 2025 { 2026 op: "setup-snap", 2027 name: "some-snap", 2028 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2029 revno: snap.R(11), 2030 }, 2031 { 2032 op: "remove-snap-aliases", 2033 name: "some-snap", 2034 }, 2035 { 2036 op: "unlink-snap", 2037 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2038 }, 2039 { 2040 op: "copy-data", 2041 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2042 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2043 }, 2044 { 2045 op: "setup-profiles:Doing", 2046 name: "some-snap", 2047 revno: snap.R(11), 2048 }, 2049 { 2050 op: "candidate", 2051 sinfo: snap.SideInfo{ 2052 RealName: "some-snap", 2053 SnapID: "some-snap-id", 2054 Channel: "some-channel", 2055 Revision: snap.R(11), 2056 }, 2057 }, 2058 { 2059 op: "link-snap.failed", 2060 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2061 }, 2062 { 2063 op: "unlink-snap", 2064 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2065 }, 2066 { 2067 op: "setup-profiles:Undoing", 2068 name: "some-snap", 2069 revno: snap.R(11), 2070 }, 2071 { 2072 op: "undo-copy-snap-data", 2073 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2074 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2075 }, 2076 { 2077 op: "link-snap", 2078 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2079 }, 2080 { 2081 op: "update-aliases", 2082 }, 2083 { 2084 op: "undo-setup-snap", 2085 name: "some-snap", 2086 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2087 stype: "app", 2088 }, 2089 { 2090 op: "remove-snap-dir", 2091 name: "some-snap", 2092 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 2093 }, 2094 } 2095 2096 // ensure all our tasks ran 2097 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 2098 macaroon: s.user.StoreMacaroon, 2099 name: "some-snap", 2100 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2101 }}) 2102 // start with an easier-to-read error if this fails: 2103 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 2104 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 2105 2106 // verify snaps in the system state 2107 var snapst snapstate.SnapState 2108 err = snapstate.Get(s.state, "some-snap", &snapst) 2109 c.Assert(err, IsNil) 2110 2111 c.Assert(snapst.Active, Equals, true) 2112 c.Assert(snapst.Sequence, HasLen, 1) 2113 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 2114 RealName: "some-snap", 2115 SnapID: "some-snap-id", 2116 Channel: "", 2117 Revision: snap.R(7), 2118 }) 2119 } 2120 2121 func lastWithLane(tasks []*state.Task) *state.Task { 2122 for i := len(tasks) - 1; i >= 0; i-- { 2123 if lanes := tasks[i].Lanes(); len(lanes) == 1 && lanes[0] != 0 { 2124 return tasks[i] 2125 } 2126 } 2127 return nil 2128 } 2129 2130 func (s *snapmgrTestSuite) TestUpdateUndoRestoresRevisionConfig(c *C) { 2131 var errorTaskExecuted bool 2132 2133 // overwrite error-trigger task handler with custom one for this test 2134 erroringHandler := func(task *state.Task, _ *tomb.Tomb) error { 2135 st := task.State() 2136 st.Lock() 2137 defer st.Unlock() 2138 2139 // modify current config of some-snap 2140 tr := config.NewTransaction(st) 2141 tr.Set("some-snap", "foo", "canary") 2142 tr.Commit() 2143 2144 errorTaskExecuted = true 2145 return errors.New("error out") 2146 } 2147 s.o.TaskRunner().AddHandler("error-trigger", erroringHandler, nil) 2148 2149 si := snap.SideInfo{ 2150 RealName: "some-snap", 2151 SnapID: "some-snap-id", 2152 Revision: snap.R(7), 2153 } 2154 si2 := snap.SideInfo{ 2155 RealName: "some-snap", 2156 SnapID: "some-snap-id", 2157 Revision: snap.R(6), 2158 } 2159 2160 s.state.Lock() 2161 defer s.state.Unlock() 2162 2163 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2164 Active: true, 2165 Sequence: []*snap.SideInfo{&si2, &si}, 2166 TrackingChannel: "latest/stable", 2167 Current: si.Revision, 2168 SnapType: "app", 2169 }) 2170 2171 // set some configuration 2172 tr := config.NewTransaction(s.state) 2173 tr.Set("some-snap", "foo", "revision 7 value") 2174 tr.Commit() 2175 config.SaveRevisionConfig(s.state, "some-snap", snap.R(7)) 2176 2177 chg := s.state.NewChange("install", "install a snap") 2178 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 2179 c.Assert(err, IsNil) 2180 chg.AddAll(ts) 2181 2182 last := lastWithLane(ts.Tasks()) 2183 c.Assert(last, NotNil) 2184 2185 terr := s.state.NewTask("error-trigger", "provoking total undo") 2186 terr.WaitFor(last) 2187 terr.JoinLane(last.Lanes()[0]) 2188 chg.AddTask(terr) 2189 2190 s.state.Unlock() 2191 defer s.se.Stop() 2192 s.settle(c) 2193 s.state.Lock() 2194 2195 c.Check(chg.Status(), Equals, state.ErrorStatus) 2196 c.Check(errorTaskExecuted, Equals, true) 2197 2198 // after undoing the update some-snap config should be restored to that of rev.7 2199 var val string 2200 tr = config.NewTransaction(s.state) 2201 c.Assert(tr.Get("some-snap", "foo", &val), IsNil) 2202 c.Check(val, Equals, "revision 7 value") 2203 } 2204 2205 func (s *snapmgrTestSuite) TestUpdateMakesConfigSnapshot(c *C) { 2206 s.state.Lock() 2207 defer s.state.Unlock() 2208 2209 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2210 Active: true, 2211 Sequence: []*snap.SideInfo{ 2212 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 2213 }, 2214 Current: snap.R(1), 2215 SnapType: "app", 2216 }) 2217 2218 tr := config.NewTransaction(s.state) 2219 tr.Set("some-snap", "foo", "bar") 2220 tr.Commit() 2221 2222 var cfgs map[string]interface{} 2223 // we don't have config snapshots yet 2224 c.Assert(s.state.Get("revision-config", &cfgs), Equals, state.ErrNoState) 2225 2226 chg := s.state.NewChange("update", "update a snap") 2227 opts := &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(2)} 2228 ts, err := snapstate.Update(s.state, "some-snap", opts, s.user.ID, snapstate.Flags{}) 2229 c.Assert(err, IsNil) 2230 chg.AddAll(ts) 2231 2232 s.state.Unlock() 2233 defer s.se.Stop() 2234 s.settle(c) 2235 2236 s.state.Lock() 2237 cfgs = nil 2238 // config copy of rev. 1 has been made 2239 c.Assert(s.state.Get("revision-config", &cfgs), IsNil) 2240 c.Assert(cfgs["some-snap"], DeepEquals, map[string]interface{}{ 2241 "1": map[string]interface{}{ 2242 "foo": "bar", 2243 }, 2244 }) 2245 } 2246 2247 func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) { 2248 si := snap.SideInfo{ 2249 RealName: "some-snap", 2250 SnapID: "some-snap-id", 2251 Revision: snap.R(7), 2252 } 2253 2254 s.state.Lock() 2255 defer s.state.Unlock() 2256 2257 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2258 Active: true, 2259 Sequence: []*snap.SideInfo{&si}, 2260 TrackingChannel: "latest/stable", 2261 Current: si.Revision, 2262 SnapType: "app", 2263 }) 2264 2265 chg := s.state.NewChange("install", "install a snap") 2266 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 2267 c.Assert(err, IsNil) 2268 chg.AddAll(ts) 2269 2270 // We need to make it not be rerefresh, and we could do just 2271 // that but instead we do the 'right' thing and attach it to 2272 // the last task that's on a lane. 2273 last := lastWithLane(ts.Tasks()) 2274 c.Assert(last, NotNil) 2275 2276 terr := s.state.NewTask("error-trigger", "provoking total undo") 2277 terr.WaitFor(last) 2278 terr.JoinLane(last.Lanes()[0]) 2279 chg.AddTask(terr) 2280 2281 s.state.Unlock() 2282 defer s.se.Stop() 2283 s.settle(c) 2284 s.state.Lock() 2285 2286 expected := fakeOps{ 2287 { 2288 op: "storesvc-snap-action", 2289 curSnaps: []store.CurrentSnap{{ 2290 InstanceName: "some-snap", 2291 SnapID: "some-snap-id", 2292 Revision: snap.R(7), 2293 TrackingChannel: "latest/stable", 2294 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 2295 Epoch: snap.E("1*"), 2296 }}, 2297 userID: 1, 2298 }, 2299 { 2300 op: "storesvc-snap-action:action", 2301 action: store.SnapAction{ 2302 Action: "refresh", 2303 InstanceName: "some-snap", 2304 SnapID: "some-snap-id", 2305 Channel: "some-channel", 2306 Flags: store.SnapActionEnforceValidation, 2307 }, 2308 revno: snap.R(11), 2309 userID: 1, 2310 }, 2311 { 2312 op: "storesvc-download", 2313 name: "some-snap", 2314 }, 2315 { 2316 op: "validate-snap:Doing", 2317 name: "some-snap", 2318 revno: snap.R(11), 2319 }, 2320 { 2321 op: "current", 2322 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2323 }, 2324 { 2325 op: "open-snap-file", 2326 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2327 sinfo: snap.SideInfo{ 2328 RealName: "some-snap", 2329 SnapID: "some-snap-id", 2330 Channel: "some-channel", 2331 Revision: snap.R(11), 2332 }, 2333 }, 2334 { 2335 op: "setup-snap", 2336 name: "some-snap", 2337 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2338 revno: snap.R(11), 2339 }, 2340 { 2341 op: "remove-snap-aliases", 2342 name: "some-snap", 2343 }, 2344 { 2345 op: "unlink-snap", 2346 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2347 }, 2348 { 2349 op: "copy-data", 2350 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2351 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2352 }, 2353 { 2354 op: "setup-profiles:Doing", 2355 name: "some-snap", 2356 revno: snap.R(11), 2357 }, 2358 { 2359 op: "candidate", 2360 sinfo: snap.SideInfo{ 2361 RealName: "some-snap", 2362 SnapID: "some-snap-id", 2363 Channel: "some-channel", 2364 Revision: snap.R(11), 2365 }, 2366 }, 2367 { 2368 op: "link-snap", 2369 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2370 }, 2371 { 2372 op: "auto-connect:Doing", 2373 name: "some-snap", 2374 revno: snap.R(11), 2375 }, 2376 { 2377 op: "update-aliases", 2378 }, 2379 // undoing everything from here down... 2380 { 2381 op: "remove-snap-aliases", 2382 name: "some-snap", 2383 }, 2384 { 2385 op: "current-snap-service-states", 2386 }, 2387 { 2388 op: "unlink-snap", 2389 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2390 }, 2391 { 2392 op: "setup-profiles:Undoing", 2393 name: "some-snap", 2394 revno: snap.R(11), 2395 }, 2396 { 2397 op: "undo-copy-snap-data", 2398 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2399 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2400 }, 2401 { 2402 op: "link-snap", 2403 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2404 }, 2405 { 2406 op: "update-aliases", 2407 }, 2408 { 2409 op: "undo-setup-snap", 2410 name: "some-snap", 2411 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2412 stype: "app", 2413 }, 2414 { 2415 op: "remove-snap-dir", 2416 name: "some-snap", 2417 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 2418 }, 2419 } 2420 2421 // ensure all our tasks ran 2422 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 2423 macaroon: s.user.StoreMacaroon, 2424 name: "some-snap", 2425 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2426 }}) 2427 // friendlier failure first 2428 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 2429 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 2430 2431 // verify snaps in the system state 2432 var snapst snapstate.SnapState 2433 err = snapstate.Get(s.state, "some-snap", &snapst) 2434 c.Assert(err, IsNil) 2435 2436 c.Assert(snapst.Active, Equals, true) 2437 c.Assert(snapst.TrackingChannel, Equals, "latest/stable") 2438 c.Assert(snapst.Sequence, HasLen, 1) 2439 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 2440 RealName: "some-snap", 2441 SnapID: "some-snap-id", 2442 Channel: "", 2443 Revision: snap.R(7), 2444 }) 2445 } 2446 2447 func (s *snapmgrTestSuite) TestUpdateSameRevision(c *C) { 2448 si := snap.SideInfo{ 2449 RealName: "some-snap", 2450 SnapID: "some-snap-id", 2451 Revision: snap.R(7), 2452 } 2453 2454 s.state.Lock() 2455 defer s.state.Unlock() 2456 2457 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2458 Active: true, 2459 Sequence: []*snap.SideInfo{&si}, 2460 TrackingChannel: "channel-for-7/stable", 2461 Current: si.Revision, 2462 }) 2463 2464 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7/stable"}, s.user.ID, snapstate.Flags{}) 2465 c.Assert(err, Equals, store.ErrNoUpdateAvailable) 2466 } 2467 2468 func (s *snapmgrTestSuite) TestUpdateToRevisionRememberedUserRunThrough(c *C) { 2469 s.state.Lock() 2470 defer s.state.Unlock() 2471 2472 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2473 Active: true, 2474 Sequence: []*snap.SideInfo{ 2475 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 2476 }, 2477 Current: snap.R(5), 2478 SnapType: "app", 2479 UserID: 1, 2480 }) 2481 2482 chg := s.state.NewChange("refresh", "refresh a snap") 2483 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(11)}, 0, snapstate.Flags{}) 2484 c.Assert(err, IsNil) 2485 chg.AddAll(ts) 2486 2487 s.state.Unlock() 2488 defer s.se.Stop() 2489 s.settle(c) 2490 s.state.Lock() 2491 2492 c.Assert(chg.Status(), Equals, state.DoneStatus) 2493 c.Assert(chg.Err(), IsNil) 2494 2495 for _, op := range s.fakeBackend.ops { 2496 switch op.op { 2497 case "storesvc-snap-action:action": 2498 c.Check(op.userID, Equals, 1) 2499 case "storesvc-download": 2500 snapName := op.name 2501 c.Check(s.fakeStore.downloads[0], DeepEquals, fakeDownload{ 2502 macaroon: "macaroon", 2503 name: "some-snap", 2504 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2505 }, Commentf(snapName)) 2506 } 2507 } 2508 } 2509 2510 // A noResultsStore returns no results for install/refresh requests 2511 type noResultsStore struct { 2512 *fakeStore 2513 } 2514 2515 func (n noResultsStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) { 2516 if assertQuery != nil { 2517 panic("no assertion query support") 2518 } 2519 return nil, nil, &store.SnapActionError{NoResults: true} 2520 } 2521 2522 func (s *snapmgrTestSuite) TestUpdateNoStoreResults(c *C) { 2523 s.state.Lock() 2524 defer s.state.Unlock() 2525 2526 snapstate.ReplaceStore(s.state, noResultsStore{fakeStore: s.fakeStore}) 2527 2528 // this is an atypical case in which the store didn't return 2529 // an error nor a result, we are defensive and return 2530 // a reasonable error 2531 si := snap.SideInfo{ 2532 RealName: "some-snap", 2533 SnapID: "some-snap-id", 2534 Revision: snap.R(7), 2535 } 2536 2537 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2538 Active: true, 2539 Sequence: []*snap.SideInfo{&si}, 2540 TrackingChannel: "channel-for-7/stable", 2541 Current: si.Revision, 2542 }) 2543 2544 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{}) 2545 c.Assert(err, Equals, snapstate.ErrMissingExpectedResult) 2546 } 2547 2548 func (s *snapmgrTestSuite) TestUpdateNoStoreResultsWithChannelChange(c *C) { 2549 s.state.Lock() 2550 defer s.state.Unlock() 2551 2552 snapstate.ReplaceStore(s.state, noResultsStore{fakeStore: s.fakeStore}) 2553 2554 // this is an atypical case in which the store didn't return 2555 // an error nor a result, we are defensive and return 2556 // a reasonable error 2557 si := snap.SideInfo{ 2558 RealName: "some-snap", 2559 SnapID: "some-snap-id", 2560 Revision: snap.R(7), 2561 } 2562 2563 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2564 Active: true, 2565 Sequence: []*snap.SideInfo{&si}, 2566 TrackingChannel: "channel-for-9/stable", 2567 Current: si.Revision, 2568 }) 2569 2570 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{}) 2571 c.Assert(err, Equals, snapstate.ErrMissingExpectedResult) 2572 } 2573 2574 func (s *snapmgrTestSuite) TestUpdateSameRevisionSwitchesChannel(c *C) { 2575 si := snap.SideInfo{ 2576 RealName: "some-snap", 2577 SnapID: "some-snap-id", 2578 Revision: snap.R(7), 2579 } 2580 2581 s.state.Lock() 2582 defer s.state.Unlock() 2583 2584 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2585 Active: true, 2586 Sequence: []*snap.SideInfo{&si}, 2587 TrackingChannel: "other-chanenl/stable", 2588 Current: si.Revision, 2589 }) 2590 2591 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7/stable"}, s.user.ID, snapstate.Flags{}) 2592 c.Assert(err, IsNil) 2593 c.Check(ts.Tasks(), HasLen, 1) 2594 c.Check(ts.Tasks()[0].Kind(), Equals, "switch-snap-channel") 2595 } 2596 2597 func (s *snapmgrTestSuite) TestUpdateSameRevisionSwitchesChannelConflict(c *C) { 2598 si := snap.SideInfo{ 2599 RealName: "some-snap", 2600 SnapID: "some-snap-id", 2601 Revision: snap.R(7), 2602 } 2603 2604 s.state.Lock() 2605 defer s.state.Unlock() 2606 2607 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2608 Active: true, 2609 Sequence: []*snap.SideInfo{&si}, 2610 TrackingChannel: "other-channel/stable", 2611 Current: si.Revision, 2612 }) 2613 2614 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{}) 2615 c.Assert(err, IsNil) 2616 // make it visible 2617 s.state.NewChange("refresh", "refresh a snap").AddAll(ts) 2618 2619 _, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{}) 2620 c.Check(err, ErrorMatches, `snap "some-snap" has "refresh" change in progress`) 2621 } 2622 2623 func (s *snapmgrTestSuite) TestUpdateSameRevisionSwitchChannelRunThrough(c *C) { 2624 si := snap.SideInfo{ 2625 RealName: "some-snap", 2626 SnapID: "some-snap-id", 2627 Channel: "other-channel", 2628 Revision: snap.R(7), 2629 } 2630 2631 s.state.Lock() 2632 defer s.state.Unlock() 2633 2634 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2635 Active: true, 2636 Sequence: []*snap.SideInfo{&si}, 2637 TrackingChannel: "other-channel/stable", 2638 Current: si.Revision, 2639 }) 2640 2641 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7/stable"}, s.user.ID, snapstate.Flags{}) 2642 c.Assert(err, IsNil) 2643 chg := s.state.NewChange("refresh", "refresh a snap") 2644 chg.AddAll(ts) 2645 2646 s.state.Unlock() 2647 defer s.se.Stop() 2648 s.settle(c) 2649 s.state.Lock() 2650 2651 expected := fakeOps{ 2652 // we just expect the "storesvc-snap-action" ops, we 2653 // don't have a fakeOp for switchChannel because it has 2654 // not a backend method, it just manipulates the state 2655 { 2656 op: "storesvc-snap-action", 2657 curSnaps: []store.CurrentSnap{{ 2658 InstanceName: "some-snap", 2659 SnapID: "some-snap-id", 2660 Revision: snap.R(7), 2661 TrackingChannel: "other-channel/stable", 2662 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 2663 Epoch: snap.E("1*"), 2664 }}, 2665 userID: 1, 2666 }, 2667 2668 { 2669 op: "storesvc-snap-action:action", 2670 action: store.SnapAction{ 2671 Action: "refresh", 2672 InstanceName: "some-snap", 2673 SnapID: "some-snap-id", 2674 Channel: "channel-for-7/stable", 2675 Flags: store.SnapActionEnforceValidation, 2676 }, 2677 userID: 1, 2678 }, 2679 } 2680 2681 // start with an easier-to-read error if this fails: 2682 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 2683 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 2684 2685 // verify snapSetup info 2686 var snapsup snapstate.SnapSetup 2687 task := ts.Tasks()[0] 2688 err = task.Get("snap-setup", &snapsup) 2689 c.Assert(err, IsNil) 2690 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 2691 Channel: "channel-for-7/stable", 2692 SideInfo: snapsup.SideInfo, 2693 }) 2694 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 2695 RealName: "some-snap", 2696 SnapID: "some-snap-id", 2697 Revision: snap.R(7), 2698 Channel: "channel-for-7/stable", 2699 }) 2700 2701 // verify snaps in the system state 2702 var snapst snapstate.SnapState 2703 err = snapstate.Get(s.state, "some-snap", &snapst) 2704 c.Assert(err, IsNil) 2705 2706 c.Assert(snapst.Active, Equals, true) 2707 c.Assert(snapst.Sequence, HasLen, 1) 2708 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 2709 RealName: "some-snap", 2710 SnapID: "some-snap-id", 2711 Channel: "channel-for-7/stable", 2712 Revision: snap.R(7), 2713 }) 2714 } 2715 2716 func (s *snapmgrTestSuite) TestUpdateSameRevisionToggleIgnoreValidation(c *C) { 2717 si := snap.SideInfo{ 2718 RealName: "some-snap", 2719 SnapID: "some-snap-id", 2720 Revision: snap.R(7), 2721 } 2722 2723 s.state.Lock() 2724 defer s.state.Unlock() 2725 2726 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2727 Active: true, 2728 Sequence: []*snap.SideInfo{&si}, 2729 TrackingChannel: "channel-for-7/stable", 2730 Current: si.Revision, 2731 }) 2732 2733 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7/stable"}, s.user.ID, snapstate.Flags{IgnoreValidation: true}) 2734 c.Assert(err, IsNil) 2735 c.Check(ts.Tasks(), HasLen, 1) 2736 c.Check(ts.Tasks()[0].Kind(), Equals, "toggle-snap-flags") 2737 } 2738 2739 func (s *snapmgrTestSuite) TestUpdateSameRevisionToggleIgnoreValidationConflict(c *C) { 2740 si := snap.SideInfo{ 2741 RealName: "some-snap", 2742 SnapID: "some-snap-id", 2743 Revision: snap.R(7), 2744 } 2745 2746 s.state.Lock() 2747 defer s.state.Unlock() 2748 2749 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2750 Active: true, 2751 Sequence: []*snap.SideInfo{&si}, 2752 TrackingChannel: "channel-for-7/stable", 2753 Current: si.Revision, 2754 }) 2755 2756 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{IgnoreValidation: true}) 2757 c.Assert(err, IsNil) 2758 // make it visible 2759 s.state.NewChange("refresh", "refresh a snap").AddAll(ts) 2760 2761 _, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{IgnoreValidation: true}) 2762 c.Check(err, ErrorMatches, `snap "some-snap" has "refresh" change in progress`) 2763 2764 } 2765 2766 func (s *snapmgrTestSuite) TestUpdateSameRevisionToggleIgnoreValidationRunThrough(c *C) { 2767 si := snap.SideInfo{ 2768 RealName: "some-snap", 2769 SnapID: "some-snap-id", 2770 Revision: snap.R(7), 2771 Channel: "channel-for-7", 2772 } 2773 2774 s.state.Lock() 2775 defer s.state.Unlock() 2776 2777 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2778 Active: true, 2779 Sequence: []*snap.SideInfo{&si}, 2780 TrackingChannel: "channel-for-7/stable", 2781 Current: si.Revision, 2782 }) 2783 2784 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7/stable"}, s.user.ID, snapstate.Flags{IgnoreValidation: true}) 2785 c.Assert(err, IsNil) 2786 2787 chg := s.state.NewChange("refresh", "refresh a snap") 2788 chg.AddAll(ts) 2789 2790 s.state.Unlock() 2791 defer s.se.Stop() 2792 s.settle(c) 2793 s.state.Lock() 2794 2795 // verify snapSetup info 2796 var snapsup snapstate.SnapSetup 2797 task := ts.Tasks()[0] 2798 err = task.Get("snap-setup", &snapsup) 2799 c.Assert(err, IsNil) 2800 c.Check(snapsup, DeepEquals, snapstate.SnapSetup{ 2801 SideInfo: snapsup.SideInfo, 2802 Flags: snapstate.Flags{ 2803 IgnoreValidation: true, 2804 }, 2805 }) 2806 c.Check(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 2807 RealName: "some-snap", 2808 SnapID: "some-snap-id", 2809 Revision: snap.R(7), 2810 Channel: "channel-for-7", 2811 }) 2812 2813 // verify snaps in the system state 2814 var snapst snapstate.SnapState 2815 err = snapstate.Get(s.state, "some-snap", &snapst) 2816 c.Assert(err, IsNil) 2817 2818 c.Check(snapst.Active, Equals, true) 2819 c.Check(snapst.Sequence, HasLen, 1) 2820 c.Check(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 2821 RealName: "some-snap", 2822 SnapID: "some-snap-id", 2823 Channel: "channel-for-7", 2824 Revision: snap.R(7), 2825 }) 2826 c.Check(snapst.IgnoreValidation, Equals, true) 2827 } 2828 2829 func (s *snapmgrTestSuite) TestUpdateValidateRefreshesSaysNo(c *C) { 2830 si := snap.SideInfo{ 2831 RealName: "some-snap", 2832 SnapID: "some-snap-id", 2833 Revision: snap.R(7), 2834 } 2835 2836 s.state.Lock() 2837 defer s.state.Unlock() 2838 2839 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2840 Active: true, 2841 Sequence: []*snap.SideInfo{&si}, 2842 Current: si.Revision, 2843 }) 2844 2845 validateErr := errors.New("refresh control error") 2846 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 2847 c.Check(refreshes, HasLen, 1) 2848 c.Check(refreshes[0].SnapID, Equals, "some-snap-id") 2849 c.Check(refreshes[0].Revision, Equals, snap.R(11)) 2850 c.Check(ignoreValidation, HasLen, 0) 2851 return nil, validateErr 2852 } 2853 // hook it up 2854 snapstate.ValidateRefreshes = validateRefreshes 2855 2856 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{}) 2857 c.Assert(err, Equals, validateErr) 2858 } 2859 2860 func (s *snapmgrTestSuite) TestUpdateValidateRefreshesSaysNoButIgnoreValidationIsSet(c *C) { 2861 si := snap.SideInfo{ 2862 RealName: "some-snap", 2863 SnapID: "some-snap-id", 2864 Revision: snap.R(7), 2865 } 2866 2867 s.state.Lock() 2868 defer s.state.Unlock() 2869 2870 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2871 Active: true, 2872 Sequence: []*snap.SideInfo{&si}, 2873 Current: si.Revision, 2874 SnapType: "app", 2875 }) 2876 2877 validateErr := errors.New("refresh control error") 2878 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 2879 return nil, validateErr 2880 } 2881 // hook it up 2882 snapstate.ValidateRefreshes = validateRefreshes 2883 2884 flags := snapstate.Flags{JailMode: true, IgnoreValidation: true} 2885 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, flags) 2886 c.Assert(err, IsNil) 2887 2888 var snapsup snapstate.SnapSetup 2889 err = ts.Tasks()[0].Get("snap-setup", &snapsup) 2890 c.Assert(err, IsNil) 2891 c.Check(snapsup.Flags, DeepEquals, flags.ForSnapSetup()) 2892 } 2893 2894 func (s *snapmgrTestSuite) TestUpdateIgnoreValidationSticky(c *C) { 2895 si := snap.SideInfo{ 2896 RealName: "some-snap", 2897 SnapID: "some-snap-id", 2898 Revision: snap.R(7), 2899 } 2900 2901 s.state.Lock() 2902 defer s.state.Unlock() 2903 2904 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2905 Active: true, 2906 Sequence: []*snap.SideInfo{&si}, 2907 Current: si.Revision, 2908 SnapType: "app", 2909 }) 2910 2911 validateErr := errors.New("refresh control error") 2912 validateRefreshesFail := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 2913 c.Check(refreshes, HasLen, 1) 2914 if len(ignoreValidation) == 0 { 2915 return nil, validateErr 2916 } 2917 c.Check(ignoreValidation, DeepEquals, map[string]bool{ 2918 "some-snap": true, 2919 }) 2920 return refreshes, nil 2921 } 2922 // hook it up 2923 snapstate.ValidateRefreshes = validateRefreshesFail 2924 2925 flags := snapstate.Flags{IgnoreValidation: true} 2926 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, flags) 2927 c.Assert(err, IsNil) 2928 2929 c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{ 2930 op: "storesvc-snap-action", 2931 curSnaps: []store.CurrentSnap{{ 2932 InstanceName: "some-snap", 2933 SnapID: "some-snap-id", 2934 Revision: snap.R(7), 2935 IgnoreValidation: false, 2936 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 2937 Epoch: snap.E("1*"), 2938 }}, 2939 userID: 1, 2940 }) 2941 c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{ 2942 op: "storesvc-snap-action:action", 2943 revno: snap.R(11), 2944 action: store.SnapAction{ 2945 Action: "refresh", 2946 InstanceName: "some-snap", 2947 SnapID: "some-snap-id", 2948 Channel: "stable", 2949 Flags: store.SnapActionIgnoreValidation, 2950 }, 2951 userID: 1, 2952 }) 2953 2954 chg := s.state.NewChange("refresh", "refresh snap") 2955 chg.AddAll(ts) 2956 2957 s.state.Unlock() 2958 defer s.se.Stop() 2959 s.settle(c) 2960 s.state.Lock() 2961 2962 // verify snap has IgnoreValidation set 2963 var snapst snapstate.SnapState 2964 err = snapstate.Get(s.state, "some-snap", &snapst) 2965 c.Assert(err, IsNil) 2966 c.Check(snapst.IgnoreValidation, Equals, true) 2967 c.Check(snapst.Current, Equals, snap.R(11)) 2968 2969 s.fakeBackend.ops = nil 2970 s.fakeStore.refreshRevnos = map[string]snap.Revision{ 2971 "some-snap-id": snap.R(12), 2972 } 2973 _, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 2974 c.Assert(err, IsNil) 2975 c.Check(tts, HasLen, 2) 2976 verifyLastTasksetIsReRefresh(c, tts) 2977 2978 c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{ 2979 op: "storesvc-snap-action", 2980 curSnaps: []store.CurrentSnap{{ 2981 InstanceName: "some-snap", 2982 SnapID: "some-snap-id", 2983 Revision: snap.R(11), 2984 TrackingChannel: "latest/stable", 2985 IgnoreValidation: true, 2986 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 11), 2987 Epoch: snap.E("1*"), 2988 }}, 2989 userID: 1, 2990 }) 2991 c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{ 2992 op: "storesvc-snap-action:action", 2993 revno: snap.R(12), 2994 action: store.SnapAction{ 2995 Action: "refresh", 2996 InstanceName: "some-snap", 2997 SnapID: "some-snap-id", 2998 Flags: 0, 2999 }, 3000 userID: 1, 3001 }) 3002 3003 chg = s.state.NewChange("refresh", "refresh snaps") 3004 chg.AddAll(tts[0]) 3005 3006 s.state.Unlock() 3007 defer s.se.Stop() 3008 s.settle(c) 3009 s.state.Lock() 3010 3011 snapst = snapstate.SnapState{} 3012 err = snapstate.Get(s.state, "some-snap", &snapst) 3013 c.Assert(err, IsNil) 3014 c.Check(snapst.IgnoreValidation, Equals, true) 3015 c.Check(snapst.Current, Equals, snap.R(12)) 3016 3017 // reset ignore validation 3018 s.fakeBackend.ops = nil 3019 s.fakeStore.refreshRevnos = map[string]snap.Revision{ 3020 "some-snap-id": snap.R(11), 3021 } 3022 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 3023 return refreshes, nil 3024 } 3025 // hook it up 3026 snapstate.ValidateRefreshes = validateRefreshes 3027 flags = snapstate.Flags{} 3028 ts, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, flags) 3029 c.Assert(err, IsNil) 3030 3031 c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{ 3032 op: "storesvc-snap-action", 3033 curSnaps: []store.CurrentSnap{{ 3034 InstanceName: "some-snap", 3035 SnapID: "some-snap-id", 3036 Revision: snap.R(12), 3037 TrackingChannel: "latest/stable", 3038 IgnoreValidation: true, 3039 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 12), 3040 Epoch: snap.E("1*"), 3041 }}, 3042 userID: 1, 3043 }) 3044 c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{ 3045 op: "storesvc-snap-action:action", 3046 revno: snap.R(11), 3047 action: store.SnapAction{ 3048 Action: "refresh", 3049 InstanceName: "some-snap", 3050 SnapID: "some-snap-id", 3051 Channel: "latest/stable", 3052 Flags: store.SnapActionEnforceValidation, 3053 }, 3054 userID: 1, 3055 }) 3056 3057 chg = s.state.NewChange("refresh", "refresh snap") 3058 chg.AddAll(ts) 3059 3060 s.state.Unlock() 3061 defer s.se.Stop() 3062 s.settle(c) 3063 s.state.Lock() 3064 3065 snapst = snapstate.SnapState{} 3066 err = snapstate.Get(s.state, "some-snap", &snapst) 3067 c.Assert(err, IsNil) 3068 c.Check(snapst.IgnoreValidation, Equals, false) 3069 c.Check(snapst.Current, Equals, snap.R(11)) 3070 } 3071 3072 func (s *snapmgrTestSuite) TestParallelInstanceUpdateIgnoreValidationSticky(c *C) { 3073 si := snap.SideInfo{ 3074 RealName: "some-snap", 3075 SnapID: "some-snap-id", 3076 Revision: snap.R(7), 3077 } 3078 3079 s.state.Lock() 3080 defer s.state.Unlock() 3081 3082 tr := config.NewTransaction(s.state) 3083 tr.Set("core", "experimental.parallel-instances", true) 3084 tr.Commit() 3085 3086 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3087 Active: true, 3088 Sequence: []*snap.SideInfo{&si}, 3089 Current: si.Revision, 3090 SnapType: "app", 3091 }) 3092 snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{ 3093 Active: true, 3094 Sequence: []*snap.SideInfo{&si}, 3095 Current: si.Revision, 3096 SnapType: "app", 3097 InstanceKey: "instance", 3098 }) 3099 3100 validateErr := errors.New("refresh control error") 3101 validateRefreshesFail := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 3102 c.Check(refreshes, HasLen, 2) 3103 if len(ignoreValidation) == 0 { 3104 return nil, validateErr 3105 } 3106 c.Check(ignoreValidation, DeepEquals, map[string]bool{ 3107 "some-snap_instance": true, 3108 }) 3109 return refreshes, nil 3110 } 3111 // hook it up 3112 snapstate.ValidateRefreshes = validateRefreshesFail 3113 3114 flags := snapstate.Flags{IgnoreValidation: true} 3115 ts, err := snapstate.Update(s.state, "some-snap_instance", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, flags) 3116 c.Assert(err, IsNil) 3117 3118 c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{ 3119 op: "storesvc-snap-action", 3120 curSnaps: []store.CurrentSnap{{ 3121 InstanceName: "some-snap", 3122 SnapID: "some-snap-id", 3123 Revision: snap.R(7), 3124 IgnoreValidation: false, 3125 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 3126 Epoch: snap.E("1*"), 3127 }, { 3128 InstanceName: "some-snap_instance", 3129 SnapID: "some-snap-id", 3130 Revision: snap.R(7), 3131 IgnoreValidation: false, 3132 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 3133 Epoch: snap.E("1*"), 3134 }}, 3135 userID: 1, 3136 }) 3137 c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{ 3138 op: "storesvc-snap-action:action", 3139 revno: snap.R(11), 3140 action: store.SnapAction{ 3141 Action: "refresh", 3142 InstanceName: "some-snap_instance", 3143 SnapID: "some-snap-id", 3144 Channel: "stable", 3145 Flags: store.SnapActionIgnoreValidation, 3146 }, 3147 userID: 1, 3148 }) 3149 3150 chg := s.state.NewChange("refresh", "refresh snaps") 3151 chg.AddAll(ts) 3152 3153 s.state.Unlock() 3154 defer s.se.Stop() 3155 s.settle(c) 3156 s.state.Lock() 3157 3158 // ensure all our tasks ran 3159 c.Assert(chg.Err(), IsNil) 3160 c.Assert(chg.IsReady(), Equals, true) 3161 3162 // verify snap 'instance' has IgnoreValidation set and the snap was 3163 // updated 3164 var snapst snapstate.SnapState 3165 err = snapstate.Get(s.state, "some-snap_instance", &snapst) 3166 c.Assert(err, IsNil) 3167 c.Check(snapst.IgnoreValidation, Equals, true) 3168 c.Check(snapst.Current, Equals, snap.R(11)) 3169 // and the other snap does not 3170 err = snapstate.Get(s.state, "some-snap", &snapst) 3171 c.Assert(err, IsNil) 3172 c.Check(snapst.Current, Equals, snap.R(7)) 3173 c.Check(snapst.IgnoreValidation, Equals, false) 3174 3175 s.fakeBackend.ops = nil 3176 s.fakeStore.refreshRevnos = map[string]snap.Revision{ 3177 "some-snap-id": snap.R(12), 3178 } 3179 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap", "some-snap_instance"}, s.user.ID, nil) 3180 c.Assert(err, IsNil) 3181 c.Check(tts, HasLen, 3) 3182 verifyLastTasksetIsReRefresh(c, tts) 3183 sort.Strings(updates) 3184 c.Check(updates, DeepEquals, []string{"some-snap", "some-snap_instance"}) 3185 3186 chg = s.state.NewChange("refresh", "refresh snaps") 3187 for _, ts := range tts[:len(tts)-1] { 3188 chg.AddAll(ts) 3189 } 3190 3191 s.state.Unlock() 3192 s.settle(c) 3193 s.state.Lock() 3194 3195 // ensure all our tasks ran 3196 c.Assert(chg.Err(), IsNil) 3197 c.Assert(chg.IsReady(), Equals, true) 3198 3199 err = snapstate.Get(s.state, "some-snap", &snapst) 3200 c.Assert(err, IsNil) 3201 c.Check(snapst.IgnoreValidation, Equals, false) 3202 c.Check(snapst.Current, Equals, snap.R(12)) 3203 3204 err = snapstate.Get(s.state, "some-snap_instance", &snapst) 3205 c.Assert(err, IsNil) 3206 c.Check(snapst.IgnoreValidation, Equals, true) 3207 c.Check(snapst.Current, Equals, snap.R(12)) 3208 3209 for i := 0; i < 2; i++ { 3210 op := s.fakeBackend.ops[i] 3211 switch op.op { 3212 case "storesvc-snap-action": 3213 c.Check(op, DeepEquals, fakeOp{ 3214 op: "storesvc-snap-action", 3215 curSnaps: []store.CurrentSnap{{ 3216 InstanceName: "some-snap", 3217 SnapID: "some-snap-id", 3218 Revision: snap.R(7), 3219 IgnoreValidation: false, 3220 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 3221 Epoch: snap.E("1*"), 3222 }, { 3223 InstanceName: "some-snap_instance", 3224 SnapID: "some-snap-id", 3225 Revision: snap.R(11), 3226 TrackingChannel: "latest/stable", 3227 IgnoreValidation: true, 3228 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 11), 3229 Epoch: snap.E("1*"), 3230 }}, 3231 userID: 1, 3232 }) 3233 case "storesvc-snap-action:action": 3234 switch op.action.InstanceName { 3235 case "some-snap": 3236 c.Check(op, DeepEquals, fakeOp{ 3237 op: "storesvc-snap-action:action", 3238 revno: snap.R(12), 3239 action: store.SnapAction{ 3240 Action: "refresh", 3241 InstanceName: "some-snap", 3242 SnapID: "some-snap-id", 3243 Flags: 0, 3244 }, 3245 userID: 1, 3246 }) 3247 case "some-snap_instance": 3248 c.Check(op, DeepEquals, fakeOp{ 3249 op: "storesvc-snap-action:action", 3250 revno: snap.R(12), 3251 action: store.SnapAction{ 3252 Action: "refresh", 3253 InstanceName: "some-snap_instance", 3254 SnapID: "some-snap-id", 3255 Flags: 0, 3256 }, 3257 userID: 1, 3258 }) 3259 default: 3260 c.Fatalf("unexpected instance name %q", op.action.InstanceName) 3261 } 3262 default: 3263 c.Fatalf("unexpected action %q", op.op) 3264 } 3265 } 3266 3267 } 3268 3269 func (s *snapmgrTestSuite) TestUpdateFromLocal(c *C) { 3270 si := snap.SideInfo{ 3271 RealName: "some-snap", 3272 Revision: snap.R("x1"), 3273 } 3274 3275 s.state.Lock() 3276 defer s.state.Unlock() 3277 3278 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3279 Active: true, 3280 Sequence: []*snap.SideInfo{&si}, 3281 TrackingChannel: "channel-for-7/stable", 3282 Current: si.Revision, 3283 }) 3284 3285 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{}) 3286 c.Assert(err, Equals, store.ErrLocalSnap) 3287 } 3288 3289 func (s *snapmgrTestSuite) TestUpdateAmend(c *C) { 3290 si := snap.SideInfo{ 3291 RealName: "some-snap", 3292 Revision: snap.R("x1"), 3293 } 3294 3295 s.state.Lock() 3296 defer s.state.Unlock() 3297 3298 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3299 Active: true, 3300 Sequence: []*snap.SideInfo{&si}, 3301 TrackingChannel: "channel-for-7/stable", 3302 Current: si.Revision, 3303 }) 3304 3305 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-7"}, s.user.ID, snapstate.Flags{Amend: true}) 3306 c.Assert(err, IsNil) 3307 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 0, ts, s.state) 3308 3309 // ensure we go from local to store revision-7 3310 var snapsup snapstate.SnapSetup 3311 tasks := ts.Tasks() 3312 c.Check(tasks[1].Kind(), Equals, "download-snap") 3313 err = tasks[1].Get("snap-setup", &snapsup) 3314 c.Assert(err, IsNil) 3315 c.Check(snapsup.Revision(), Equals, snap.R(7)) 3316 } 3317 3318 func (s *snapmgrTestSuite) TestUpdateAmendSnapNotFound(c *C) { 3319 si := snap.SideInfo{ 3320 RealName: "snap-unknown", 3321 Revision: snap.R("x1"), 3322 } 3323 3324 s.state.Lock() 3325 defer s.state.Unlock() 3326 3327 snapstate.Set(s.state, "snap-unknown", &snapstate.SnapState{ 3328 Active: true, 3329 Sequence: []*snap.SideInfo{&si}, 3330 TrackingChannel: "latest/stable", 3331 Current: si.Revision, 3332 }) 3333 3334 _, err := snapstate.Update(s.state, "snap-unknown", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{Amend: true}) 3335 c.Assert(err, Equals, store.ErrSnapNotFound) 3336 } 3337 3338 func (s *snapmgrTestSuite) TestSingleUpdateBlockedRevision(c *C) { 3339 // single updates should *not* set the block list 3340 si7 := snap.SideInfo{ 3341 RealName: "some-snap", 3342 SnapID: "some-snap-id", 3343 Revision: snap.R(7), 3344 } 3345 si11 := snap.SideInfo{ 3346 RealName: "some-snap", 3347 SnapID: "some-snap-id", 3348 Revision: snap.R(11), 3349 } 3350 3351 s.state.Lock() 3352 defer s.state.Unlock() 3353 3354 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3355 Active: true, 3356 Sequence: []*snap.SideInfo{&si7, &si11}, 3357 Current: si7.Revision, 3358 SnapType: "app", 3359 }) 3360 3361 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 3362 c.Assert(err, IsNil) 3363 3364 c.Assert(s.fakeBackend.ops, HasLen, 2) 3365 c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{ 3366 op: "storesvc-snap-action", 3367 curSnaps: []store.CurrentSnap{{ 3368 InstanceName: "some-snap", 3369 SnapID: "some-snap-id", 3370 Revision: snap.R(7), 3371 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 3372 Epoch: snap.E("1*"), 3373 }}, 3374 userID: 1, 3375 }) 3376 } 3377 3378 func (s *snapmgrTestSuite) TestMultiUpdateBlockedRevision(c *C) { 3379 // multi-updates should *not* set the block list 3380 si7 := snap.SideInfo{ 3381 RealName: "some-snap", 3382 SnapID: "some-snap-id", 3383 Revision: snap.R(7), 3384 } 3385 si11 := snap.SideInfo{ 3386 RealName: "some-snap", 3387 SnapID: "some-snap-id", 3388 Revision: snap.R(11), 3389 } 3390 3391 s.state.Lock() 3392 defer s.state.Unlock() 3393 3394 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3395 Active: true, 3396 Sequence: []*snap.SideInfo{&si7, &si11}, 3397 Current: si7.Revision, 3398 SnapType: "app", 3399 }) 3400 3401 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 3402 c.Assert(err, IsNil) 3403 c.Check(updates, DeepEquals, []string{"some-snap"}) 3404 3405 c.Assert(s.fakeBackend.ops, HasLen, 2) 3406 c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{ 3407 op: "storesvc-snap-action", 3408 curSnaps: []store.CurrentSnap{{ 3409 InstanceName: "some-snap", 3410 SnapID: "some-snap-id", 3411 Revision: snap.R(7), 3412 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 3413 Epoch: snap.E("1*"), 3414 }}, 3415 userID: 1, 3416 }) 3417 } 3418 3419 func (s *snapmgrTestSuite) TestAllUpdateBlockedRevision(c *C) { 3420 // update-all *should* set the block list 3421 si7 := snap.SideInfo{ 3422 RealName: "some-snap", 3423 SnapID: "some-snap-id", 3424 Revision: snap.R(7), 3425 } 3426 si11 := snap.SideInfo{ 3427 RealName: "some-snap", 3428 SnapID: "some-snap-id", 3429 Revision: snap.R(11), 3430 } 3431 3432 s.state.Lock() 3433 defer s.state.Unlock() 3434 3435 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3436 Active: true, 3437 Sequence: []*snap.SideInfo{&si7, &si11}, 3438 Current: si7.Revision, 3439 }) 3440 3441 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, s.user.ID, nil) 3442 c.Check(err, IsNil) 3443 c.Check(updates, HasLen, 0) 3444 3445 c.Assert(s.fakeBackend.ops, HasLen, 2) 3446 c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{ 3447 op: "storesvc-snap-action", 3448 curSnaps: []store.CurrentSnap{{ 3449 InstanceName: "some-snap", 3450 SnapID: "some-snap-id", 3451 Revision: snap.R(7), 3452 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 3453 Block: []snap.Revision{snap.R(11)}, 3454 Epoch: snap.E("1*"), 3455 }}, 3456 userID: 1, 3457 }) 3458 } 3459 3460 var orthogonalAutoAliasesScenarios = []struct { 3461 aliasesBefore map[string][]string 3462 names []string 3463 prune []string 3464 update bool 3465 new bool 3466 }{ 3467 {nil, nil, nil, true, true}, 3468 {nil, []string{"some-snap"}, nil, true, false}, 3469 {nil, []string{"other-snap"}, nil, false, true}, 3470 {map[string][]string{"some-snap": {"aliasA", "aliasC"}}, []string{"some-snap"}, nil, true, false}, 3471 {map[string][]string{"other-snap": {"aliasB", "aliasC"}}, []string{"other-snap"}, []string{"other-snap"}, false, false}, 3472 {map[string][]string{"other-snap": {"aliasB", "aliasC"}}, nil, []string{"other-snap"}, true, false}, 3473 {map[string][]string{"other-snap": {"aliasB", "aliasC"}}, []string{"some-snap"}, nil, true, false}, 3474 {map[string][]string{"other-snap": {"aliasC"}}, []string{"other-snap"}, []string{"other-snap"}, false, true}, 3475 {map[string][]string{"other-snap": {"aliasC"}}, nil, []string{"other-snap"}, true, true}, 3476 {map[string][]string{"other-snap": {"aliasC"}}, []string{"some-snap"}, nil, true, false}, 3477 {map[string][]string{"some-snap": {"aliasB"}, "other-snap": {"aliasA"}}, []string{"some-snap"}, []string{"other-snap"}, true, false}, 3478 {map[string][]string{"some-snap": {"aliasB"}, "other-snap": {"aliasA"}}, nil, []string{"other-snap", "some-snap"}, true, true}, 3479 {map[string][]string{"some-snap": {"aliasB"}, "other-snap": {"aliasA"}}, []string{"other-snap"}, []string{"other-snap", "some-snap"}, false, true}, 3480 {map[string][]string{"some-snap": {"aliasB"}}, nil, []string{"some-snap"}, true, true}, 3481 {map[string][]string{"some-snap": {"aliasB"}}, []string{"other-snap"}, []string{"some-snap"}, false, true}, 3482 {map[string][]string{"some-snap": {"aliasB"}}, []string{"some-snap"}, nil, true, false}, 3483 {map[string][]string{"other-snap": {"aliasA"}}, nil, []string{"other-snap"}, true, true}, 3484 {map[string][]string{"other-snap": {"aliasA"}}, []string{"other-snap"}, []string{"other-snap"}, false, true}, 3485 {map[string][]string{"other-snap": {"aliasA"}}, []string{"some-snap"}, []string{"other-snap"}, true, false}, 3486 } 3487 3488 func (s *snapmgrTestSuite) TestUpdateManyAutoAliasesScenarios(c *C) { 3489 s.state.Lock() 3490 defer s.state.Unlock() 3491 3492 snapstate.Set(s.state, "other-snap", &snapstate.SnapState{ 3493 Active: true, 3494 Sequence: []*snap.SideInfo{ 3495 {RealName: "other-snap", SnapID: "other-snap-id", Revision: snap.R(2)}, 3496 }, 3497 Current: snap.R(2), 3498 SnapType: "app", 3499 }) 3500 3501 snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) { 3502 switch info.InstanceName() { 3503 case "some-snap": 3504 return map[string]string{"aliasA": "cmdA"}, nil 3505 case "other-snap": 3506 return map[string]string{"aliasB": "cmdB"}, nil 3507 } 3508 return nil, nil 3509 } 3510 3511 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3512 Active: true, 3513 Sequence: []*snap.SideInfo{ 3514 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 3515 }, 3516 Current: snap.R(4), 3517 SnapType: "app", 3518 }) 3519 3520 expectedSet := func(aliases []string) map[string]bool { 3521 res := make(map[string]bool, len(aliases)) 3522 for _, alias := range aliases { 3523 res[alias] = true 3524 } 3525 return res 3526 } 3527 3528 for _, scenario := range orthogonalAutoAliasesScenarios { 3529 for _, instanceName := range []string{"some-snap", "other-snap"} { 3530 var snapst snapstate.SnapState 3531 err := snapstate.Get(s.state, instanceName, &snapst) 3532 c.Assert(err, IsNil) 3533 snapst.Aliases = nil 3534 snapst.AutoAliasesDisabled = false 3535 if autoAliases := scenario.aliasesBefore[instanceName]; autoAliases != nil { 3536 targets := make(map[string]*snapstate.AliasTarget) 3537 for _, alias := range autoAliases { 3538 targets[alias] = &snapstate.AliasTarget{Auto: "cmd" + alias[len(alias)-1:]} 3539 } 3540 3541 snapst.Aliases = targets 3542 } 3543 snapstate.Set(s.state, instanceName, &snapst) 3544 } 3545 3546 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, scenario.names, s.user.ID, nil) 3547 c.Check(err, IsNil) 3548 if scenario.update { 3549 verifyLastTasksetIsReRefresh(c, tts) 3550 } 3551 3552 _, dropped, err := snapstate.AutoAliasesDelta(s.state, []string{"some-snap", "other-snap"}) 3553 c.Assert(err, IsNil) 3554 3555 j := 0 3556 expectedUpdatesSet := make(map[string]bool) 3557 var expectedPruned map[string]map[string]bool 3558 var pruneTs *state.TaskSet 3559 if len(scenario.prune) != 0 { 3560 pruneTs = tts[0] 3561 j++ 3562 taskAliases := make(map[string]map[string]bool) 3563 for _, aliasTask := range pruneTs.Tasks() { 3564 c.Check(aliasTask.Kind(), Equals, "prune-auto-aliases") 3565 var aliases []string 3566 err := aliasTask.Get("aliases", &aliases) 3567 c.Assert(err, IsNil) 3568 snapsup, err := snapstate.TaskSnapSetup(aliasTask) 3569 c.Assert(err, IsNil) 3570 taskAliases[snapsup.InstanceName()] = expectedSet(aliases) 3571 } 3572 expectedPruned = make(map[string]map[string]bool) 3573 for _, instanceName := range scenario.prune { 3574 expectedPruned[instanceName] = expectedSet(dropped[instanceName]) 3575 if instanceName == "other-snap" && !scenario.new && !scenario.update { 3576 expectedUpdatesSet["other-snap"] = true 3577 } 3578 } 3579 c.Check(taskAliases, DeepEquals, expectedPruned) 3580 } 3581 if scenario.update { 3582 updateTs := tts[j] 3583 j++ 3584 expectedUpdatesSet["some-snap"] = true 3585 first := updateTs.Tasks()[0] 3586 c.Check(first.Kind(), Equals, "prerequisites") 3587 wait := false 3588 if expectedPruned["other-snap"]["aliasA"] { 3589 wait = true 3590 } else if expectedPruned["some-snap"] != nil { 3591 wait = true 3592 } 3593 if wait { 3594 c.Check(first.WaitTasks(), DeepEquals, pruneTs.Tasks()) 3595 } else { 3596 c.Check(first.WaitTasks(), HasLen, 0) 3597 } 3598 } 3599 if scenario.new { 3600 newTs := tts[j] 3601 j++ 3602 expectedUpdatesSet["other-snap"] = true 3603 tasks := newTs.Tasks() 3604 c.Check(tasks, HasLen, 1) 3605 aliasTask := tasks[0] 3606 c.Check(aliasTask.Kind(), Equals, "refresh-aliases") 3607 3608 wait := false 3609 if expectedPruned["some-snap"]["aliasB"] { 3610 wait = true 3611 } else if expectedPruned["other-snap"] != nil { 3612 wait = true 3613 } 3614 if wait { 3615 c.Check(aliasTask.WaitTasks(), DeepEquals, pruneTs.Tasks()) 3616 } else { 3617 c.Check(aliasTask.WaitTasks(), HasLen, 0) 3618 } 3619 } 3620 l := len(tts) 3621 if scenario.update { 3622 l-- 3623 } 3624 c.Assert(j, Equals, l, Commentf("%#v", scenario)) 3625 3626 // check reported updated names 3627 c.Check(len(updates) > 0, Equals, true) 3628 sort.Strings(updates) 3629 expectedUpdates := make([]string, 0, len(expectedUpdatesSet)) 3630 for x := range expectedUpdatesSet { 3631 expectedUpdates = append(expectedUpdates, x) 3632 } 3633 sort.Strings(expectedUpdates) 3634 c.Check(updates, DeepEquals, expectedUpdates) 3635 } 3636 } 3637 3638 func (s *snapmgrTestSuite) TestUpdateOneAutoAliasesScenarios(c *C) { 3639 s.state.Lock() 3640 defer s.state.Unlock() 3641 3642 snapstate.Set(s.state, "other-snap", &snapstate.SnapState{ 3643 Active: true, 3644 Sequence: []*snap.SideInfo{ 3645 {RealName: "other-snap", SnapID: "other-snap-id", Revision: snap.R(2)}, 3646 }, 3647 Current: snap.R(2), 3648 SnapType: "app", 3649 }) 3650 3651 snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) { 3652 switch info.InstanceName() { 3653 case "some-snap": 3654 return map[string]string{"aliasA": "cmdA"}, nil 3655 case "other-snap": 3656 return map[string]string{"aliasB": "cmdB"}, nil 3657 } 3658 return nil, nil 3659 } 3660 3661 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3662 Active: true, 3663 Sequence: []*snap.SideInfo{ 3664 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 3665 }, 3666 Current: snap.R(4), 3667 SnapType: "app", 3668 }) 3669 3670 expectedSet := func(aliases []string) map[string]bool { 3671 res := make(map[string]bool, len(aliases)) 3672 for _, alias := range aliases { 3673 res[alias] = true 3674 } 3675 return res 3676 } 3677 3678 for _, scenario := range orthogonalAutoAliasesScenarios { 3679 if len(scenario.names) != 1 { 3680 continue 3681 } 3682 3683 for _, instanceName := range []string{"some-snap", "other-snap"} { 3684 var snapst snapstate.SnapState 3685 err := snapstate.Get(s.state, instanceName, &snapst) 3686 c.Assert(err, IsNil) 3687 snapst.Aliases = nil 3688 snapst.AutoAliasesDisabled = false 3689 if autoAliases := scenario.aliasesBefore[instanceName]; autoAliases != nil { 3690 targets := make(map[string]*snapstate.AliasTarget) 3691 for _, alias := range autoAliases { 3692 targets[alias] = &snapstate.AliasTarget{Auto: "cmd" + alias[len(alias)-1:]} 3693 } 3694 3695 snapst.Aliases = targets 3696 } 3697 snapstate.Set(s.state, instanceName, &snapst) 3698 } 3699 3700 ts, err := snapstate.Update(s.state, scenario.names[0], nil, s.user.ID, snapstate.Flags{}) 3701 c.Assert(err, IsNil) 3702 _, dropped, err := snapstate.AutoAliasesDelta(s.state, []string{"some-snap", "other-snap"}) 3703 c.Assert(err, IsNil) 3704 3705 j := 0 3706 3707 tasks := ts.Tasks() 3708 // make sure the last task from Update is the rerefresh 3709 if scenario.update { 3710 reRefresh := tasks[len(tasks)-1] 3711 c.Check(reRefresh.Kind(), Equals, "check-rerefresh") 3712 // nothing should wait on it 3713 c.Check(reRefresh.NumHaltTasks(), Equals, 0) 3714 tasks = tasks[:len(tasks)-1] // and now forget about it 3715 } 3716 3717 var expectedPruned map[string]map[string]bool 3718 var pruneTasks []*state.Task 3719 if len(scenario.prune) != 0 { 3720 nprune := len(scenario.prune) 3721 pruneTasks = tasks[:nprune] 3722 j += nprune 3723 taskAliases := make(map[string]map[string]bool) 3724 for _, aliasTask := range pruneTasks { 3725 c.Check(aliasTask.Kind(), Equals, "prune-auto-aliases") 3726 var aliases []string 3727 err := aliasTask.Get("aliases", &aliases) 3728 c.Assert(err, IsNil) 3729 snapsup, err := snapstate.TaskSnapSetup(aliasTask) 3730 c.Assert(err, IsNil) 3731 taskAliases[snapsup.InstanceName()] = expectedSet(aliases) 3732 } 3733 expectedPruned = make(map[string]map[string]bool) 3734 for _, instanceName := range scenario.prune { 3735 expectedPruned[instanceName] = expectedSet(dropped[instanceName]) 3736 } 3737 c.Check(taskAliases, DeepEquals, expectedPruned) 3738 } 3739 if scenario.update { 3740 first := tasks[j] 3741 j += 19 3742 c.Check(first.Kind(), Equals, "prerequisites") 3743 wait := false 3744 if expectedPruned["other-snap"]["aliasA"] { 3745 wait = true 3746 } else if expectedPruned["some-snap"] != nil { 3747 wait = true 3748 } 3749 if wait { 3750 c.Check(first.WaitTasks(), DeepEquals, pruneTasks) 3751 } else { 3752 c.Check(first.WaitTasks(), HasLen, 0) 3753 } 3754 } 3755 if scenario.new { 3756 aliasTask := tasks[j] 3757 j++ 3758 c.Check(aliasTask.Kind(), Equals, "refresh-aliases") 3759 wait := false 3760 if expectedPruned["some-snap"]["aliasB"] { 3761 wait = true 3762 } else if expectedPruned["other-snap"] != nil { 3763 wait = true 3764 } 3765 if wait { 3766 c.Check(aliasTask.WaitTasks(), DeepEquals, pruneTasks) 3767 } else { 3768 c.Check(aliasTask.WaitTasks(), HasLen, 0) 3769 } 3770 } 3771 c.Assert(len(tasks), Equals, j, Commentf("%#v", scenario)) 3772 3773 // conflict checks are triggered 3774 chg := s.state.NewChange("update", "...") 3775 chg.AddAll(ts) 3776 err = snapstate.CheckChangeConflict(s.state, scenario.names[0], nil) 3777 c.Check(err, ErrorMatches, `.* has "update" change in progress`) 3778 chg.SetStatus(state.DoneStatus) 3779 } 3780 } 3781 3782 func (s *snapmgrTestSuite) TestUpdateLocalSnapFails(c *C) { 3783 si := snap.SideInfo{ 3784 RealName: "some-snap", 3785 Revision: snap.R(7), 3786 } 3787 3788 s.state.Lock() 3789 defer s.state.Unlock() 3790 3791 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3792 Active: true, 3793 Sequence: []*snap.SideInfo{&si}, 3794 Current: si.Revision, 3795 }) 3796 3797 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 3798 c.Assert(err, Equals, store.ErrLocalSnap) 3799 } 3800 3801 func (s *snapmgrTestSuite) TestUpdateDisabledUnsupported(c *C) { 3802 si := snap.SideInfo{ 3803 RealName: "some-snap", 3804 SnapID: "some-snap-id", 3805 Revision: snap.R(7), 3806 } 3807 3808 s.state.Lock() 3809 defer s.state.Unlock() 3810 3811 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3812 Active: false, 3813 Sequence: []*snap.SideInfo{&si}, 3814 Current: si.Revision, 3815 }) 3816 3817 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 3818 c.Assert(err, ErrorMatches, `refreshing disabled snap "some-snap" not supported`) 3819 } 3820 3821 func (s *snapmgrTestSuite) TestUpdateKernelTrackChecksSwitchingTracks(c *C) { 3822 si := snap.SideInfo{ 3823 RealName: "kernel", 3824 SnapID: "kernel-id", 3825 Revision: snap.R(7), 3826 } 3827 3828 s.state.Lock() 3829 defer s.state.Unlock() 3830 3831 r := snapstatetest.MockDeviceModel(ModelWithKernelTrack("18")) 3832 defer r() 3833 snapstate.Set(s.state, "kernel", &snapstate.SnapState{ 3834 Active: true, 3835 Sequence: []*snap.SideInfo{&si}, 3836 Current: si.Revision, 3837 TrackingChannel: "18/stable", 3838 }) 3839 3840 // switching tracks is not ok 3841 _, err := snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "new-channel"}, s.user.ID, snapstate.Flags{}) 3842 c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "new-channel"`) 3843 3844 // no change to the channel is ok 3845 _, err = snapstate.Update(s.state, "kernel", nil, s.user.ID, snapstate.Flags{}) 3846 c.Assert(err, IsNil) 3847 3848 // switching risk level is ok 3849 _, err = snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "18/beta"}, s.user.ID, snapstate.Flags{}) 3850 c.Assert(err, IsNil) 3851 3852 // switching just risk within the pinned track is ok 3853 _, err = snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "beta"}, s.user.ID, snapstate.Flags{}) 3854 c.Assert(err, IsNil) 3855 } 3856 3857 func (s *snapmgrTestSuite) TestUpdateGadgetTrackChecksSwitchingTracks(c *C) { 3858 si := snap.SideInfo{ 3859 RealName: "brand-gadget", 3860 SnapID: "brand-gadget-id", 3861 Revision: snap.R(7), 3862 } 3863 3864 s.state.Lock() 3865 defer s.state.Unlock() 3866 3867 r := snapstatetest.MockDeviceModel(ModelWithGadgetTrack("18")) 3868 defer r() 3869 snapstate.Set(s.state, "brand-gadget", &snapstate.SnapState{ 3870 Active: true, 3871 Sequence: []*snap.SideInfo{&si}, 3872 Current: si.Revision, 3873 TrackingChannel: "18/stable", 3874 }) 3875 3876 // switching tracks is not ok 3877 _, err := snapstate.Update(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "new-channel"}, s.user.ID, snapstate.Flags{}) 3878 c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "new-channel"`) 3879 3880 // no change to the channel is ok 3881 _, err = snapstate.Update(s.state, "brand-gadget", nil, s.user.ID, snapstate.Flags{}) 3882 c.Assert(err, IsNil) 3883 3884 // switching risk level is ok 3885 _, err = snapstate.Update(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "18/beta"}, s.user.ID, snapstate.Flags{}) 3886 c.Assert(err, IsNil) 3887 3888 // switching just risk within the pinned track is ok 3889 _, err = snapstate.Update(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "beta"}, s.user.ID, snapstate.Flags{}) 3890 c.Assert(err, IsNil) 3891 3892 } 3893 3894 func (s *snapmgrTestSuite) TestUpdateWithDeviceContext(c *C) { 3895 s.state.Lock() 3896 defer s.state.Unlock() 3897 3898 // unset the global store, it will need to come via the device context 3899 snapstate.ReplaceStore(s.state, nil) 3900 3901 deviceCtx := &snapstatetest.TrivialDeviceContext{ 3902 DeviceModel: DefaultModel(), 3903 CtxStore: s.fakeStore, 3904 } 3905 3906 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3907 Active: true, 3908 TrackingChannel: "latest/edge", 3909 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 3910 Current: snap.R(7), 3911 SnapType: "app", 3912 }) 3913 3914 validateCalled := false 3915 happyValidateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx1 snapstate.DeviceContext) ([]*snap.Info, error) { 3916 c.Check(deviceCtx1, Equals, deviceCtx) 3917 validateCalled = true 3918 return refreshes, nil 3919 } 3920 // hook it up 3921 snapstate.ValidateRefreshes = happyValidateRefreshes 3922 3923 ts, err := snapstate.UpdateWithDeviceContext(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}, deviceCtx, "") 3924 c.Assert(err, IsNil) 3925 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 0, ts, s.state) 3926 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 3927 3928 c.Check(validateCalled, Equals, true) 3929 } 3930 3931 func (s *snapmgrTestSuite) TestUpdateWithDeviceContextToRevision(c *C) { 3932 s.state.Lock() 3933 defer s.state.Unlock() 3934 3935 // unset the global store, it will need to come via the device context 3936 snapstate.ReplaceStore(s.state, nil) 3937 3938 deviceCtx := &snapstatetest.TrivialDeviceContext{ 3939 DeviceModel: DefaultModel(), 3940 CtxStore: s.fakeStore, 3941 } 3942 3943 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3944 Active: true, 3945 Sequence: []*snap.SideInfo{ 3946 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 3947 }, 3948 Current: snap.R(5), 3949 SnapType: "app", 3950 UserID: 1, 3951 }) 3952 3953 opts := &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(11)} 3954 ts, err := snapstate.UpdateWithDeviceContext(s.state, "some-snap", opts, 0, snapstate.Flags{}, deviceCtx, "") 3955 c.Assert(err, IsNil) 3956 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 0, ts, s.state) 3957 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 3958 } 3959 3960 func (s *snapmgrTestSuite) TestUpdateTasksCoreSetsIgnoreOnConfigure(c *C) { 3961 s.state.Lock() 3962 defer s.state.Unlock() 3963 3964 snapstate.Set(s.state, "core", &snapstate.SnapState{ 3965 Active: true, 3966 TrackingChannel: "latest/edge", 3967 Sequence: []*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(7)}}, 3968 Current: snap.R(7), 3969 SnapType: "os", 3970 }) 3971 3972 oldConfigure := snapstate.Configure 3973 defer func() { snapstate.Configure = oldConfigure }() 3974 3975 var configureFlags int 3976 snapstate.Configure = func(st *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet { 3977 configureFlags = flags 3978 return state.NewTaskSet() 3979 } 3980 3981 _, err := snapstate.Update(s.state, "core", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 3982 c.Assert(err, IsNil) 3983 3984 // ensure the core snap sets the "ignore-hook-error" flag 3985 c.Check(configureFlags&snapstate.IgnoreHookError, Equals, 1) 3986 } 3987 3988 func (s *snapmgrTestSuite) TestUpdateDevModeConfinementFiltering(c *C) { 3989 restore := maybeMockClassicSupport(c) 3990 defer restore() 3991 3992 s.state.Lock() 3993 defer s.state.Unlock() 3994 3995 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3996 Active: true, 3997 TrackingChannel: "channel-for-devmode/stable", 3998 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 3999 Current: snap.R(7), 4000 SnapType: "app", 4001 }) 4002 4003 // updated snap is devmode, refresh without --devmode, do nothing 4004 // TODO: better error message here 4005 _, err := snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{}) 4006 c.Assert(err, ErrorMatches, `.* requires devmode or confinement override`) 4007 4008 // updated snap is devmode, refresh with --devmode 4009 _, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{DevMode: true}) 4010 c.Assert(err, IsNil) 4011 } 4012 4013 func (s *snapmgrTestSuite) TestUpdateClassicConfinementFiltering(c *C) { 4014 restore := maybeMockClassicSupport(c) 4015 defer restore() 4016 4017 s.state.Lock() 4018 defer s.state.Unlock() 4019 4020 snapstate.Set(s.state, "some-snap-now-classic", &snapstate.SnapState{ 4021 Active: true, 4022 Sequence: []*snap.SideInfo{{RealName: "some-snap-now-classic", SnapID: "some-snap-now-classic-id", Revision: snap.R(7)}}, 4023 Current: snap.R(7), 4024 SnapType: "app", 4025 }) 4026 4027 // updated snap is classic, refresh without --classic, do nothing 4028 // TODO: better error message here 4029 _, err := snapstate.Update(s.state, "some-snap-now-classic", nil, s.user.ID, snapstate.Flags{}) 4030 c.Assert(err, ErrorMatches, `.* requires classic confinement`) 4031 4032 // updated snap is classic, refresh with --classic 4033 ts, err := snapstate.Update(s.state, "some-snap-now-classic", nil, s.user.ID, snapstate.Flags{Classic: true}) 4034 c.Assert(err, IsNil) 4035 4036 chg := s.state.NewChange("refresh", "refresh snap") 4037 chg.AddAll(ts) 4038 4039 s.state.Unlock() 4040 defer s.se.Stop() 4041 s.settle(c) 4042 s.state.Lock() 4043 4044 c.Assert(chg.Err(), IsNil) 4045 c.Assert(chg.IsReady(), Equals, true) 4046 4047 // verify snap is in classic 4048 var snapst snapstate.SnapState 4049 err = snapstate.Get(s.state, "some-snap-now-classic", &snapst) 4050 c.Assert(err, IsNil) 4051 c.Check(snapst.Classic, Equals, true) 4052 } 4053 4054 func (s *snapmgrTestSuite) TestUpdateClassicFromClassic(c *C) { 4055 restore := maybeMockClassicSupport(c) 4056 defer restore() 4057 4058 s.state.Lock() 4059 defer s.state.Unlock() 4060 4061 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4062 Active: true, 4063 TrackingChannel: "channel-for-classic/stable", 4064 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4065 Current: snap.R(7), 4066 SnapType: "app", 4067 Flags: snapstate.Flags{Classic: true}, 4068 }) 4069 4070 // snap installed with --classic, update needs classic, refresh with --classic works 4071 ts, err := snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{Classic: true}) 4072 c.Assert(err, IsNil) 4073 c.Assert(ts.Tasks(), Not(HasLen), 0) 4074 snapsup, err := snapstate.TaskSnapSetup(ts.Tasks()[0]) 4075 c.Assert(err, IsNil) 4076 c.Check(snapsup.Flags.Classic, Equals, true) 4077 4078 // devmode overrides the snapsetup classic flag 4079 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{DevMode: true}) 4080 c.Assert(err, IsNil) 4081 c.Assert(ts.Tasks(), Not(HasLen), 0) 4082 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4083 c.Assert(err, IsNil) 4084 c.Check(snapsup.Flags.Classic, Equals, false) 4085 4086 // jailmode overrides it too (you need to provide both) 4087 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{JailMode: true}) 4088 c.Assert(err, IsNil) 4089 c.Assert(ts.Tasks(), Not(HasLen), 0) 4090 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4091 c.Assert(err, IsNil) 4092 c.Check(snapsup.Flags.Classic, Equals, false) 4093 4094 // jailmode and classic together gets you both 4095 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{JailMode: true, Classic: true}) 4096 c.Assert(err, IsNil) 4097 c.Assert(ts.Tasks(), Not(HasLen), 0) 4098 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4099 c.Assert(err, IsNil) 4100 c.Check(snapsup.Flags.Classic, Equals, true) 4101 4102 // snap installed with --classic, update needs classic, refresh without --classic works 4103 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{}) 4104 c.Assert(err, IsNil) 4105 c.Assert(ts.Tasks(), Not(HasLen), 0) 4106 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4107 c.Assert(err, IsNil) 4108 c.Check(snapsup.Flags.Classic, Equals, true) 4109 4110 chg := s.state.NewChange("refresh", "refresh snap") 4111 chg.AddAll(ts) 4112 4113 s.state.Unlock() 4114 defer s.se.Stop() 4115 s.settle(c) 4116 s.state.Lock() 4117 4118 // verify snap is in classic 4119 var snapst snapstate.SnapState 4120 err = snapstate.Get(s.state, "some-snap", &snapst) 4121 c.Assert(err, IsNil) 4122 c.Check(snapst.Classic, Equals, true) 4123 } 4124 4125 func (s *snapmgrTestSuite) TestUpdateStrictFromClassic(c *C) { 4126 restore := maybeMockClassicSupport(c) 4127 defer restore() 4128 4129 s.state.Lock() 4130 defer s.state.Unlock() 4131 4132 snapstate.Set(s.state, "some-snap-was-classic", &snapstate.SnapState{ 4133 Active: true, 4134 TrackingChannel: "channel/stable", 4135 Sequence: []*snap.SideInfo{{RealName: "some-snap-was-classic", SnapID: "some-snap-was-classic-id", Revision: snap.R(7)}}, 4136 Current: snap.R(7), 4137 SnapType: "app", 4138 Flags: snapstate.Flags{Classic: true}, 4139 }) 4140 4141 // snap installed with --classic, update does not need classic, refresh works without --classic 4142 _, err := snapstate.Update(s.state, "some-snap-was-classic", nil, s.user.ID, snapstate.Flags{}) 4143 c.Assert(err, IsNil) 4144 4145 // snap installed with --classic, update does not need classic, refresh works with --classic 4146 _, err = snapstate.Update(s.state, "some-snap-was-classic", nil, s.user.ID, snapstate.Flags{Classic: true}) 4147 c.Assert(err, IsNil) 4148 } 4149 4150 func (s *snapmgrTestSuite) TestUpdateChannelFallback(c *C) { 4151 s.state.Lock() 4152 defer s.state.Unlock() 4153 4154 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4155 Active: true, 4156 TrackingChannel: "latest/edge", 4157 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4158 Current: snap.R(7), 4159 SnapType: "app", 4160 }) 4161 4162 ts, err := snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{}) 4163 c.Assert(err, IsNil) 4164 4165 var snapsup snapstate.SnapSetup 4166 err = ts.Tasks()[0].Get("snap-setup", &snapsup) 4167 c.Assert(err, IsNil) 4168 4169 c.Check(snapsup.Channel, Equals, "latest/edge") 4170 } 4171 4172 func (s *snapmgrTestSuite) TestUpdateTooEarly(c *C) { 4173 s.state.Lock() 4174 defer s.state.Unlock() 4175 4176 s.state.Set("seeded", nil) 4177 4178 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4179 Active: true, 4180 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4181 Current: snap.R(7), 4182 SnapType: "app", 4183 }) 4184 4185 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 4186 c.Check(err, FitsTypeOf, &snapstate.ChangeConflictError{}) 4187 c.Assert(err, ErrorMatches, `too early for operation, device not yet seeded or device model not acknowledged`) 4188 } 4189 4190 func (s *snapmgrTestSuite) TestUpdateConflict(c *C) { 4191 s.state.Lock() 4192 defer s.state.Unlock() 4193 4194 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4195 Active: true, 4196 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4197 Current: snap.R(7), 4198 SnapType: "app", 4199 }) 4200 4201 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 4202 c.Assert(err, IsNil) 4203 // need a change to make the tasks visible 4204 s.state.NewChange("refresh", "...").AddAll(ts) 4205 4206 _, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 4207 c.Assert(err, ErrorMatches, `snap "some-snap" has "refresh" change in progress`) 4208 } 4209 4210 func (s *snapmgrTestSuite) TestUpdateCreatesGCTasks(c *C) { 4211 restore := release.MockOnClassic(false) 4212 defer restore() 4213 4214 s.testUpdateCreatesGCTasks(c, 2) 4215 } 4216 4217 func (s *snapmgrTestSuite) TestUpdateCreatesGCTasksOnClassic(c *C) { 4218 restore := release.MockOnClassic(true) 4219 defer restore() 4220 4221 s.testUpdateCreatesGCTasks(c, 3) 4222 } 4223 4224 func (s *snapmgrTestSuite) testUpdateCreatesGCTasks(c *C, expectedDiscards int) { 4225 s.state.Lock() 4226 defer s.state.Unlock() 4227 4228 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4229 Active: true, 4230 Sequence: []*snap.SideInfo{ 4231 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4232 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4233 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4234 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4235 }, 4236 Current: snap.R(4), 4237 SnapType: "app", 4238 }) 4239 4240 ts, err := snapstate.Update(s.state, "some-snap", nil, 0, snapstate.Flags{}) 4241 c.Assert(err, IsNil) 4242 4243 // ensure edges information is still there 4244 te, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge) 4245 c.Assert(te, NotNil) 4246 c.Assert(err, IsNil) 4247 4248 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, expectedDiscards, ts, s.state) 4249 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 4250 } 4251 4252 func (s *snapmgrTestSuite) TestUpdateCreatesDiscardAfterCurrentTasks(c *C) { 4253 s.state.Lock() 4254 defer s.state.Unlock() 4255 4256 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4257 Active: true, 4258 Sequence: []*snap.SideInfo{ 4259 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4260 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4261 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4262 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4263 }, 4264 Current: snap.R(1), 4265 SnapType: "app", 4266 }) 4267 4268 ts, err := snapstate.Update(s.state, "some-snap", nil, 0, snapstate.Flags{}) 4269 c.Assert(err, IsNil) 4270 4271 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 3, ts, s.state) 4272 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 4273 } 4274 4275 func (s *snapmgrTestSuite) TestUpdateManyTooEarly(c *C) { 4276 s.state.Lock() 4277 defer s.state.Unlock() 4278 4279 s.state.Set("seeded", nil) 4280 4281 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4282 Active: true, 4283 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4284 Current: snap.R(7), 4285 SnapType: "app", 4286 }) 4287 4288 _, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4289 c.Check(err, FitsTypeOf, &snapstate.ChangeConflictError{}) 4290 c.Assert(err, ErrorMatches, `too early for operation, device not yet seeded or device model not acknowledged`) 4291 } 4292 4293 func (s *snapmgrTestSuite) TestUpdateMany(c *C) { 4294 s.state.Lock() 4295 defer s.state.Unlock() 4296 4297 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4298 Active: true, 4299 Sequence: []*snap.SideInfo{ 4300 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4301 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4302 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4303 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4304 }, 4305 Current: snap.R(1), 4306 SnapType: "app", 4307 }) 4308 4309 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4310 c.Assert(err, IsNil) 4311 c.Assert(tts, HasLen, 2) 4312 verifyLastTasksetIsReRefresh(c, tts) 4313 c.Check(updates, DeepEquals, []string{"some-snap"}) 4314 4315 ts := tts[0] 4316 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 3, ts, s.state) 4317 4318 // check that the tasks are in non-default lane 4319 for _, t := range ts.Tasks() { 4320 c.Assert(t.Lanes(), DeepEquals, []int{1}) 4321 } 4322 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())+1) // 1==rerefresh 4323 4324 // ensure edges information is still there 4325 te, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge) 4326 c.Assert(te, NotNil) 4327 c.Assert(err, IsNil) 4328 4329 checkIsAutoRefresh(c, ts.Tasks(), false) 4330 } 4331 4332 func (s *snapmgrTestSuite) TestUpdateManyDevModeConfinementFiltering(c *C) { 4333 s.state.Lock() 4334 defer s.state.Unlock() 4335 4336 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4337 Active: true, 4338 TrackingChannel: "channel-for-devmode/stable", 4339 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4340 Current: snap.R(7), 4341 SnapType: "app", 4342 }) 4343 4344 // updated snap is devmode, updatemany doesn't update it 4345 _, tts, _ := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 4346 // FIXME: UpdateMany will not error out in this case (daemon catches this case, with a weird error) 4347 c.Assert(tts, HasLen, 0) 4348 } 4349 4350 func (s *snapmgrTestSuite) TestUpdateManyClassicConfinementFiltering(c *C) { 4351 restore := maybeMockClassicSupport(c) 4352 defer restore() 4353 4354 s.state.Lock() 4355 defer s.state.Unlock() 4356 4357 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4358 Active: true, 4359 TrackingChannel: "channel-for-classic/stable", 4360 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4361 Current: snap.R(7), 4362 SnapType: "app", 4363 }) 4364 4365 // if a snap installed without --classic gets a classic update it isn't installed 4366 _, tts, _ := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 4367 // FIXME: UpdateMany will not error out in this case (daemon catches this case, with a weird error) 4368 c.Assert(tts, HasLen, 0) 4369 } 4370 4371 func (s *snapmgrTestSuite) TestUpdateManyClassic(c *C) { 4372 restore := maybeMockClassicSupport(c) 4373 defer restore() 4374 4375 s.state.Lock() 4376 defer s.state.Unlock() 4377 4378 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4379 Active: true, 4380 TrackingChannel: "channel-for-classic/stable", 4381 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4382 Current: snap.R(7), 4383 SnapType: "app", 4384 Flags: snapstate.Flags{Classic: true}, 4385 }) 4386 4387 // snap installed with classic: refresh gets classic 4388 _, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 4389 c.Assert(err, IsNil) 4390 c.Assert(tts, HasLen, 2) 4391 verifyLastTasksetIsReRefresh(c, tts) 4392 } 4393 4394 func (s *snapmgrTestSuite) TestUpdateManyClassicToStrict(c *C) { 4395 restore := maybeMockClassicSupport(c) 4396 defer restore() 4397 4398 s.state.Lock() 4399 defer s.state.Unlock() 4400 4401 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4402 Active: true, 4403 TrackingChannel: "stable", 4404 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4405 Current: snap.R(7), 4406 SnapType: "app", 4407 Flags: snapstate.Flags{Classic: true}, 4408 }) 4409 4410 // snap installed with classic: refresh gets classic 4411 _, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, &snapstate.Flags{Classic: true}) 4412 c.Assert(err, IsNil) 4413 c.Assert(tts, HasLen, 2) 4414 // ensure we clear the classic flag 4415 snapsup, err := snapstate.TaskSnapSetup(tts[0].Tasks()[0]) 4416 c.Assert(err, IsNil) 4417 c.Assert(snapsup.Flags.Classic, Equals, false) 4418 4419 verifyLastTasksetIsReRefresh(c, tts) 4420 } 4421 4422 func (s *snapmgrTestSuite) TestUpdateManyDevMode(c *C) { 4423 s.state.Lock() 4424 defer s.state.Unlock() 4425 4426 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4427 Active: true, 4428 Flags: snapstate.Flags{DevMode: true}, 4429 Sequence: []*snap.SideInfo{ 4430 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4431 }, 4432 Current: snap.R(1), 4433 SnapType: "app", 4434 }) 4435 4436 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, 0, nil) 4437 c.Assert(err, IsNil) 4438 c.Check(updates, HasLen, 1) 4439 } 4440 4441 func (s *snapmgrTestSuite) TestUpdateAllDevMode(c *C) { 4442 s.state.Lock() 4443 defer s.state.Unlock() 4444 4445 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4446 Active: true, 4447 Flags: snapstate.Flags{DevMode: true}, 4448 Sequence: []*snap.SideInfo{ 4449 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4450 }, 4451 Current: snap.R(1), 4452 SnapType: "app", 4453 }) 4454 4455 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4456 c.Assert(err, IsNil) 4457 c.Check(updates, HasLen, 0) 4458 } 4459 4460 func (s *snapmgrTestSuite) TestUpdateManyWaitForBasesUC16(c *C) { 4461 s.state.Lock() 4462 defer s.state.Unlock() 4463 4464 snapstate.Set(s.state, "core", &snapstate.SnapState{ 4465 Active: true, 4466 Sequence: []*snap.SideInfo{ 4467 {RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1)}, 4468 }, 4469 Current: snap.R(1), 4470 SnapType: "os", 4471 }) 4472 4473 snapstate.Set(s.state, "some-base", &snapstate.SnapState{ 4474 Active: true, 4475 Sequence: []*snap.SideInfo{ 4476 {RealName: "some-base", SnapID: "some-base-id", Revision: snap.R(1)}, 4477 }, 4478 Current: snap.R(1), 4479 SnapType: "base", 4480 }) 4481 4482 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4483 Active: true, 4484 Sequence: []*snap.SideInfo{ 4485 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4486 }, 4487 Current: snap.R(1), 4488 SnapType: "app", 4489 TrackingChannel: "channel-for-base/stable", 4490 }) 4491 4492 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap", "core", "some-base"}, 0, nil) 4493 c.Assert(err, IsNil) 4494 c.Assert(tts, HasLen, 4) 4495 verifyLastTasksetIsReRefresh(c, tts) 4496 c.Check(updates, HasLen, 3) 4497 4498 // to make TaskSnapSetup work 4499 chg := s.state.NewChange("refresh", "...") 4500 for _, ts := range tts { 4501 chg.AddAll(ts) 4502 } 4503 4504 prereqTotal := len(tts[0].Tasks()) + len(tts[1].Tasks()) 4505 prereqs := map[string]bool{} 4506 for i, task := range tts[2].Tasks() { 4507 waitTasks := task.WaitTasks() 4508 if i == 0 { 4509 c.Check(len(waitTasks), Equals, prereqTotal) 4510 } else if task.Kind() == "link-snap" { 4511 c.Check(len(waitTasks), Equals, prereqTotal+1) 4512 for _, pre := range waitTasks { 4513 if pre.Kind() == "link-snap" { 4514 snapsup, err := snapstate.TaskSnapSetup(pre) 4515 c.Assert(err, IsNil) 4516 prereqs[snapsup.InstanceName()] = true 4517 } 4518 } 4519 } 4520 } 4521 4522 c.Check(prereqs, DeepEquals, map[string]bool{ 4523 "core": true, 4524 "some-base": true, 4525 }) 4526 } 4527 4528 func (s *snapmgrTestSuite) TestUpdateManyWaitForBasesUC18(c *C) { 4529 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 4530 defer r() 4531 4532 s.state.Lock() 4533 defer s.state.Unlock() 4534 4535 snapstate.Set(s.state, "core18", &snapstate.SnapState{ 4536 Active: true, 4537 Sequence: []*snap.SideInfo{ 4538 {RealName: "core18", SnapID: "core18-snap-id", Revision: snap.R(1)}, 4539 }, 4540 Current: snap.R(1), 4541 SnapType: "base", 4542 }) 4543 4544 snapstate.Set(s.state, "some-base", &snapstate.SnapState{ 4545 Active: true, 4546 Sequence: []*snap.SideInfo{ 4547 {RealName: "some-base", SnapID: "some-base-id", Revision: snap.R(1)}, 4548 }, 4549 Current: snap.R(1), 4550 SnapType: "base", 4551 }) 4552 4553 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 4554 Active: true, 4555 Sequence: []*snap.SideInfo{ 4556 {RealName: "snapd", SnapID: "snapd-snap-id", Revision: snap.R(1)}, 4557 }, 4558 Current: snap.R(1), 4559 SnapType: "app", 4560 }) 4561 4562 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4563 Active: true, 4564 Sequence: []*snap.SideInfo{ 4565 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4566 }, 4567 Current: snap.R(1), 4568 SnapType: "app", 4569 TrackingChannel: "channel-for-base/stable", 4570 }) 4571 4572 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap", "core18", "some-base", "snapd"}, 0, nil) 4573 c.Assert(err, IsNil) 4574 c.Assert(tts, HasLen, 5) 4575 verifyLastTasksetIsReRefresh(c, tts) 4576 c.Check(updates, HasLen, 4) 4577 4578 // to make TaskSnapSetup work 4579 chg := s.state.NewChange("refresh", "...") 4580 for _, ts := range tts { 4581 chg.AddAll(ts) 4582 } 4583 4584 // Note that some-app only waits for snapd+some-base. The core18 4585 // base is not special to this snap and not waited for 4586 prereqTotal := len(tts[0].Tasks()) + len(tts[1].Tasks()) 4587 prereqs := map[string]bool{} 4588 for i, task := range tts[3].Tasks() { 4589 waitTasks := task.WaitTasks() 4590 if i == 0 { 4591 c.Check(len(waitTasks), Equals, prereqTotal) 4592 } else if task.Kind() == "link-snap" { 4593 c.Check(len(waitTasks), Equals, prereqTotal+1) 4594 for _, pre := range waitTasks { 4595 if pre.Kind() == "link-snap" { 4596 snapsup, err := snapstate.TaskSnapSetup(pre) 4597 c.Assert(err, IsNil) 4598 prereqs[snapsup.InstanceName()] = true 4599 } 4600 } 4601 } 4602 } 4603 4604 // Note that "core18" is not part of the prereqs for some-app 4605 // as it does not use this base. 4606 c.Check(prereqs, DeepEquals, map[string]bool{ 4607 "some-base": true, 4608 "snapd": true, 4609 }) 4610 } 4611 4612 func (s *snapmgrTestSuite) TestUpdateManyValidateRefreshes(c *C) { 4613 s.state.Lock() 4614 defer s.state.Unlock() 4615 4616 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4617 Active: true, 4618 Sequence: []*snap.SideInfo{ 4619 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4620 }, 4621 Current: snap.R(1), 4622 SnapType: "app", 4623 }) 4624 4625 validateCalled := false 4626 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 4627 validateCalled = true 4628 c.Check(refreshes, HasLen, 1) 4629 c.Check(refreshes[0].InstanceName(), Equals, "some-snap") 4630 c.Check(refreshes[0].SnapID, Equals, "some-snap-id") 4631 c.Check(refreshes[0].Revision, Equals, snap.R(11)) 4632 c.Check(ignoreValidation, HasLen, 0) 4633 return refreshes, nil 4634 } 4635 // hook it up 4636 snapstate.ValidateRefreshes = validateRefreshes 4637 4638 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4639 c.Assert(err, IsNil) 4640 c.Assert(tts, HasLen, 2) 4641 verifyLastTasksetIsReRefresh(c, tts) 4642 c.Check(updates, DeepEquals, []string{"some-snap"}) 4643 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 0, tts[0], s.state) 4644 4645 c.Check(validateCalled, Equals, true) 4646 } 4647 4648 func (s *snapmgrTestSuite) TestParallelInstanceUpdateMany(c *C) { 4649 restore := release.MockOnClassic(false) 4650 defer restore() 4651 4652 s.state.Lock() 4653 defer s.state.Unlock() 4654 4655 tr := config.NewTransaction(s.state) 4656 tr.Set("core", "experimental.parallel-instances", true) 4657 tr.Commit() 4658 4659 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4660 Active: true, 4661 Sequence: []*snap.SideInfo{ 4662 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4663 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4664 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4665 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4666 }, 4667 Current: snap.R(1), 4668 SnapType: "app", 4669 }) 4670 snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{ 4671 Active: true, 4672 Sequence: []*snap.SideInfo{ 4673 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4674 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4675 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4676 }, 4677 Current: snap.R(3), 4678 SnapType: "app", 4679 InstanceKey: "instance", 4680 }) 4681 4682 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4683 c.Assert(err, IsNil) 4684 c.Assert(tts, HasLen, 3) 4685 verifyLastTasksetIsReRefresh(c, tts) 4686 // ensure stable ordering of updates list 4687 if updates[0] != "some-snap" { 4688 updates[1], updates[0] = updates[0], updates[1] 4689 } 4690 4691 c.Check(updates, DeepEquals, []string{"some-snap", "some-snap_instance"}) 4692 4693 var snapsup, snapsupInstance *snapstate.SnapSetup 4694 4695 // ensure stable ordering of task sets list 4696 snapsup, err = snapstate.TaskSnapSetup(tts[0].Tasks()[0]) 4697 c.Assert(err, IsNil) 4698 if snapsup.InstanceName() != "some-snap" { 4699 tts[0], tts[1] = tts[1], tts[0] 4700 snapsup, err = snapstate.TaskSnapSetup(tts[0].Tasks()[0]) 4701 c.Assert(err, IsNil) 4702 } 4703 snapsupInstance, err = snapstate.TaskSnapSetup(tts[1].Tasks()[0]) 4704 c.Assert(err, IsNil) 4705 4706 c.Assert(snapsup.InstanceName(), Equals, "some-snap") 4707 c.Assert(snapsupInstance.InstanceName(), Equals, "some-snap_instance") 4708 4709 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 3, tts[0], s.state) 4710 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 1, tts[1], s.state) 4711 } 4712 4713 func (s *snapmgrTestSuite) TestParallelInstanceUpdateManyValidateRefreshes(c *C) { 4714 s.state.Lock() 4715 defer s.state.Unlock() 4716 4717 tr := config.NewTransaction(s.state) 4718 tr.Set("core", "experimental.parallel-instances", true) 4719 tr.Commit() 4720 4721 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4722 Active: true, 4723 Sequence: []*snap.SideInfo{ 4724 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4725 }, 4726 Current: snap.R(1), 4727 SnapType: "app", 4728 }) 4729 snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{ 4730 Active: true, 4731 Sequence: []*snap.SideInfo{ 4732 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4733 }, 4734 Current: snap.R(1), 4735 SnapType: "app", 4736 InstanceKey: "instance", 4737 }) 4738 4739 validateCalled := false 4740 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 4741 validateCalled = true 4742 c.Check(refreshes, HasLen, 2) 4743 instanceIdx := 0 4744 someIdx := 1 4745 if refreshes[0].InstanceName() != "some-snap_instance" { 4746 instanceIdx = 1 4747 someIdx = 0 4748 } 4749 c.Check(refreshes[someIdx].InstanceName(), Equals, "some-snap") 4750 c.Check(refreshes[instanceIdx].InstanceName(), Equals, "some-snap_instance") 4751 c.Check(refreshes[0].SnapID, Equals, "some-snap-id") 4752 c.Check(refreshes[0].Revision, Equals, snap.R(11)) 4753 c.Check(refreshes[1].SnapID, Equals, "some-snap-id") 4754 c.Check(refreshes[1].Revision, Equals, snap.R(11)) 4755 c.Check(ignoreValidation, HasLen, 0) 4756 return refreshes, nil 4757 } 4758 // hook it up 4759 snapstate.ValidateRefreshes = validateRefreshes 4760 4761 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4762 c.Assert(err, IsNil) 4763 c.Assert(tts, HasLen, 3) 4764 verifyLastTasksetIsReRefresh(c, tts) 4765 sort.Strings(updates) 4766 c.Check(updates, DeepEquals, []string{"some-snap", "some-snap_instance"}) 4767 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 0, tts[0], s.state) 4768 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 0, tts[1], s.state) 4769 4770 c.Check(validateCalled, Equals, true) 4771 } 4772 4773 func (s *snapmgrTestSuite) TestUpdateManyValidateRefreshesUnhappy(c *C) { 4774 s.state.Lock() 4775 defer s.state.Unlock() 4776 4777 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4778 Active: true, 4779 Sequence: []*snap.SideInfo{ 4780 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4781 }, 4782 Current: snap.R(1), 4783 }) 4784 4785 validateErr := errors.New("refresh control error") 4786 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 4787 c.Check(refreshes, HasLen, 1) 4788 c.Check(refreshes[0].SnapID, Equals, "some-snap-id") 4789 c.Check(refreshes[0].Revision, Equals, snap.R(11)) 4790 c.Check(ignoreValidation, HasLen, 0) 4791 return nil, validateErr 4792 } 4793 // hook it up 4794 snapstate.ValidateRefreshes = validateRefreshes 4795 4796 // refresh all => no error 4797 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4798 c.Assert(err, IsNil) 4799 c.Check(tts, HasLen, 0) 4800 c.Check(updates, HasLen, 0) 4801 4802 // refresh some-snap => report error 4803 updates, tts, err = snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, 0, nil) 4804 c.Assert(err, Equals, validateErr) 4805 c.Check(tts, HasLen, 0) 4806 c.Check(updates, HasLen, 0) 4807 4808 } 4809 4810 func (s *snapmgrTestSuite) testUpdateManyDiskSpaceCheck(c *C, featureFlag, failDiskCheck, failInstallSize bool) error { 4811 var diskCheckCalled, installSizeCalled bool 4812 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 4813 diskCheckCalled = true 4814 c.Check(path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 4815 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 4816 if failDiskCheck { 4817 return &osutil.NotEnoughDiskSpaceError{} 4818 } 4819 return nil 4820 }) 4821 defer restore() 4822 4823 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []*snap.Info, userID int) (uint64, error) { 4824 installSizeCalled = true 4825 if failInstallSize { 4826 return 0, fmt.Errorf("boom") 4827 } 4828 c.Assert(snaps, HasLen, 2) 4829 c.Check(snaps[0].InstanceName(), Equals, "snapd") 4830 c.Check(snaps[1].InstanceName(), Equals, "some-snap") 4831 return 123, nil 4832 }) 4833 defer restoreInstallSize() 4834 4835 s.state.Lock() 4836 defer s.state.Unlock() 4837 4838 tr := config.NewTransaction(s.state) 4839 tr.Set("core", "experimental.check-disk-space-refresh", featureFlag) 4840 tr.Commit() 4841 4842 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4843 Active: true, 4844 Sequence: []*snap.SideInfo{ 4845 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4846 }, 4847 Current: snap.R(1), 4848 SnapType: "app", 4849 }) 4850 4851 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 4852 Active: true, 4853 Sequence: []*snap.SideInfo{ 4854 {RealName: "snapd", SnapID: "snapd-snap-id", Revision: snap.R(1)}, 4855 }, 4856 Current: snap.R(1), 4857 SnapType: "app", 4858 }) 4859 4860 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4861 if featureFlag { 4862 c.Check(installSizeCalled, Equals, true) 4863 if failInstallSize { 4864 c.Check(diskCheckCalled, Equals, false) 4865 } else { 4866 c.Check(diskCheckCalled, Equals, true) 4867 if failDiskCheck { 4868 c.Check(updates, HasLen, 0) 4869 } else { 4870 c.Check(updates, HasLen, 2) 4871 } 4872 } 4873 } else { 4874 c.Check(installSizeCalled, Equals, false) 4875 c.Check(diskCheckCalled, Equals, false) 4876 } 4877 4878 return err 4879 } 4880 4881 func (s *snapmgrTestSuite) TestUpdateManyDiskSpaceCheckError(c *C) { 4882 featureFlag := true 4883 failDiskCheck := true 4884 failInstallSize := false 4885 err := s.testUpdateManyDiskSpaceCheck(c, featureFlag, failDiskCheck, failInstallSize) 4886 diskSpaceErr := err.(*snapstate.InsufficientSpaceError) 4887 c.Assert(diskSpaceErr, ErrorMatches, `insufficient space in .* to perform "refresh" change for the following snaps: snapd, some-snap`) 4888 c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 4889 c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"snapd", "some-snap"}) 4890 } 4891 4892 func (s *snapmgrTestSuite) TestUpdateManyDiskSpaceSkippedIfFeatureDisabled(c *C) { 4893 featureFlag := false 4894 failDiskCheck := true 4895 failInstallSize := false 4896 err := s.testUpdateManyDiskSpaceCheck(c, featureFlag, failDiskCheck, failInstallSize) 4897 c.Assert(err, IsNil) 4898 } 4899 4900 func (s *snapmgrTestSuite) TestUpdateManyDiskSpaceFailInstallSize(c *C) { 4901 featureFlag := true 4902 failDiskCheck := false 4903 failInstallSize := true 4904 err := s.testUpdateManyDiskSpaceCheck(c, featureFlag, failDiskCheck, failInstallSize) 4905 c.Assert(err, ErrorMatches, "boom") 4906 } 4907 4908 func (s *snapmgrTestSuite) TestUnlinkCurrentSnapLastActiveDisabledServicesSet(c *C) { 4909 si := snap.SideInfo{ 4910 RealName: "services-snap", 4911 Revision: snap.R(-42), 4912 } 4913 snaptest.MockSnap(c, `name: services-snap`, &si) 4914 4915 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 4916 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1", "svc2"} 4917 4918 // reset the services to what they were before after the test is done 4919 defer func() { 4920 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 4921 }() 4922 4923 s.state.Lock() 4924 defer s.state.Unlock() 4925 4926 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 4927 Active: true, 4928 Sequence: []*snap.SideInfo{&si}, 4929 Current: si.Revision, 4930 SnapType: "app", 4931 TrackingChannel: "stable", 4932 LastActiveDisabledServices: []string{}, 4933 }) 4934 4935 chg := s.state.NewChange("refresh", "refresh a snap") 4936 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 4937 4938 c.Assert(err, IsNil) 4939 // only add up to unlink-current-snap task 4940 for _, t := range ts.Tasks() { 4941 chg.AddTask(t) 4942 if t.Kind() == "unlink-current-snap" { 4943 // don't add any more from this point on 4944 break 4945 } 4946 } 4947 4948 s.state.Unlock() 4949 defer s.se.Stop() 4950 s.settle(c) 4951 s.state.Lock() 4952 4953 c.Assert(chg.Err(), IsNil) 4954 c.Assert(chg.IsReady(), Equals, true) 4955 4956 // get the snap state 4957 var snapst snapstate.SnapState 4958 err = snapstate.Get(s.state, "services-snap", &snapst) 4959 c.Assert(err, IsNil) 4960 4961 // make sure that the disabled services in this snap's state is what we 4962 // provided 4963 sort.Strings(snapst.LastActiveDisabledServices) 4964 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"svc1", "svc2"}) 4965 } 4966 4967 func (s *snapmgrTestSuite) TestUnlinkCurrentSnapMergedLastActiveDisabledServicesSet(c *C) { 4968 si := snap.SideInfo{ 4969 RealName: "services-snap", 4970 Revision: snap.R(-42), 4971 } 4972 snaptest.MockSnap(c, `name: services-snap`, &si) 4973 4974 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 4975 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1", "svc2"} 4976 4977 // reset the services to what they were before after the test is done 4978 defer func() { 4979 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 4980 }() 4981 4982 s.state.Lock() 4983 defer s.state.Unlock() 4984 4985 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 4986 Active: true, 4987 Sequence: []*snap.SideInfo{&si}, 4988 Current: si.Revision, 4989 SnapType: "app", 4990 TrackingChannel: "stable", 4991 LastActiveDisabledServices: []string{"missing-svc3"}, 4992 }) 4993 4994 chg := s.state.NewChange("refresh", "refresh a snap") 4995 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 4996 4997 c.Assert(err, IsNil) 4998 // only add up to unlink-current-snap task 4999 for _, t := range ts.Tasks() { 5000 chg.AddTask(t) 5001 if t.Kind() == "unlink-current-snap" { 5002 // don't add any more from this point on 5003 break 5004 } 5005 } 5006 5007 s.state.Unlock() 5008 defer s.se.Stop() 5009 s.settle(c) 5010 s.state.Lock() 5011 5012 c.Assert(chg.Err(), IsNil) 5013 c.Assert(chg.IsReady(), Equals, true) 5014 5015 // get the snap state 5016 var snapst snapstate.SnapState 5017 err = snapstate.Get(s.state, "services-snap", &snapst) 5018 c.Assert(err, IsNil) 5019 5020 // make sure that the disabled services in this snap's state is what we 5021 // provided 5022 sort.Strings(snapst.LastActiveDisabledServices) 5023 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"missing-svc3", "svc1", "svc2"}) 5024 } 5025 5026 func (s *snapmgrTestSuite) TestUnlinkCurrentSnapPassthroughLastActiveDisabledServicesSet(c *C) { 5027 si := snap.SideInfo{ 5028 RealName: "services-snap", 5029 Revision: snap.R(-42), 5030 } 5031 snaptest.MockSnap(c, `name: services-snap`, &si) 5032 5033 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5034 s.fakeBackend.servicesCurrentlyDisabled = []string{} 5035 5036 // reset the services to what they were before after the test is done 5037 defer func() { 5038 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5039 }() 5040 5041 s.state.Lock() 5042 defer s.state.Unlock() 5043 5044 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5045 Active: true, 5046 Sequence: []*snap.SideInfo{&si}, 5047 Current: si.Revision, 5048 SnapType: "app", 5049 TrackingChannel: "stable", 5050 LastActiveDisabledServices: []string{"missing-svc3"}, 5051 }) 5052 5053 chg := s.state.NewChange("refresh", "refresh a snap") 5054 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 5055 5056 c.Assert(err, IsNil) 5057 // only add up to unlink-current-snap task 5058 for _, t := range ts.Tasks() { 5059 chg.AddTask(t) 5060 if t.Kind() == "unlink-current-snap" { 5061 // don't add any more from this point on 5062 break 5063 } 5064 } 5065 5066 s.state.Unlock() 5067 defer s.se.Stop() 5068 s.settle(c) 5069 s.state.Lock() 5070 5071 c.Assert(chg.Err(), IsNil) 5072 c.Assert(chg.IsReady(), Equals, true) 5073 5074 // get the snap state 5075 var snapst snapstate.SnapState 5076 err = snapstate.Get(s.state, "services-snap", &snapst) 5077 c.Assert(err, IsNil) 5078 5079 // make sure that the disabled services in this snap's state is what we 5080 // provided 5081 sort.Strings(snapst.LastActiveDisabledServices) 5082 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"missing-svc3"}) 5083 } 5084 5085 func (s *snapmgrTestSuite) TestStopSnapServicesSavesSnapSetupLastActiveDisabledServices(c *C) { 5086 s.state.Lock() 5087 defer s.state.Unlock() 5088 5089 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5090 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1", "svc2"} 5091 5092 // reset the services to what they were before after the test is done 5093 defer func() { 5094 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5095 }() 5096 5097 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5098 Sequence: []*snap.SideInfo{ 5099 {RealName: "services-snap", Revision: snap.R(11)}, 5100 }, 5101 Current: snap.R(11), 5102 Active: true, 5103 }) 5104 5105 snapsup := &snapstate.SnapSetup{ 5106 SideInfo: &snap.SideInfo{ 5107 RealName: "services-snap", 5108 Revision: snap.R(11), 5109 SnapID: "services-snap-id", 5110 }, 5111 } 5112 5113 chg := s.state.NewChange("stop-services", "stop the services") 5114 t1 := s.state.NewTask("prerequisites", "...") 5115 t1.Set("snap-setup", snapsup) 5116 t2 := s.state.NewTask("stop-snap-services", "...") 5117 t2.Set("stop-reason", snap.StopReasonDisable) 5118 t2.Set("snap-setup-task", t1.ID()) 5119 t2.WaitFor(t1) 5120 chg.AddTask(t1) 5121 chg.AddTask(t2) 5122 5123 s.state.Unlock() 5124 defer s.se.Stop() 5125 s.settle(c) 5126 s.state.Lock() 5127 5128 c.Assert(chg.Err(), IsNil) 5129 c.Assert(chg.IsReady(), Equals, true) 5130 5131 // get the snap setup from the task from state 5132 endT := s.state.Task(t1.ID()) 5133 finalsnapsup := &snapstate.SnapSetup{} 5134 endT.Get("snap-setup", finalsnapsup) 5135 5136 // make sure that the disabled services in this snap's state is what we 5137 // provided 5138 sort.Strings(finalsnapsup.LastActiveDisabledServices) 5139 c.Assert(finalsnapsup.LastActiveDisabledServices, DeepEquals, []string{"svc1", "svc2"}) 5140 5141 } 5142 5143 func (s *snapmgrTestSuite) TestStopSnapServicesFirstSavesSnapSetupLastActiveDisabledServices(c *C) { 5144 s.state.Lock() 5145 defer s.state.Unlock() 5146 5147 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5148 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1", "svc2"} 5149 5150 // reset the services to what they were before after the test is done 5151 defer func() { 5152 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5153 }() 5154 5155 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5156 Sequence: []*snap.SideInfo{ 5157 {RealName: "services-snap", Revision: snap.R(11)}, 5158 }, 5159 Current: snap.R(11), 5160 Active: true, 5161 }) 5162 5163 snapsup := &snapstate.SnapSetup{ 5164 SideInfo: &snap.SideInfo{ 5165 RealName: "services-snap", 5166 Revision: snap.R(11), 5167 SnapID: "services-snap-id", 5168 }, 5169 } 5170 5171 chg := s.state.NewChange("stop-services", "stop the services") 5172 t := s.state.NewTask("stop-snap-services", "...") 5173 t.Set("stop-reason", snap.StopReasonDisable) 5174 t.Set("snap-setup", snapsup) 5175 chg.AddTask(t) 5176 5177 s.state.Unlock() 5178 defer s.se.Stop() 5179 s.settle(c) 5180 s.state.Lock() 5181 5182 c.Assert(chg.Err(), IsNil) 5183 c.Assert(chg.IsReady(), Equals, true) 5184 5185 // get the snap setup from the task from state 5186 endT := s.state.Task(t.ID()) 5187 finalsnapsup := &snapstate.SnapSetup{} 5188 endT.Get("snap-setup", finalsnapsup) 5189 5190 // make sure that the disabled services in this snap's state is what we 5191 // provided 5192 sort.Strings(finalsnapsup.LastActiveDisabledServices) 5193 c.Assert(finalsnapsup.LastActiveDisabledServices, DeepEquals, []string{"svc1", "svc2"}) 5194 } 5195 5196 func (s *snapmgrTestSuite) TestRefreshDoesntRestoreRevisionConfig(c *C) { 5197 restore := release.MockOnClassic(false) 5198 defer restore() 5199 5200 s.state.Lock() 5201 defer s.state.Unlock() 5202 5203 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5204 Active: true, 5205 Sequence: []*snap.SideInfo{ 5206 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 5207 }, 5208 Current: snap.R(1), 5209 SnapType: "app", 5210 }) 5211 5212 // set global configuration (affecting current snap) 5213 tr := config.NewTransaction(s.state) 5214 tr.Set("some-snap", "foo", "100") 5215 tr.Commit() 5216 5217 // set per-revision config for the upcoming rev. 2, we don't expect it restored though 5218 // since only revert restores revision configs. 5219 s.state.Set("revision-config", map[string]interface{}{ 5220 "some-snap": map[string]interface{}{ 5221 "2": map[string]interface{}{"foo": "200"}, 5222 }, 5223 }) 5224 5225 // simulate a refresh to rev. 2 5226 chg := s.state.NewChange("update", "update some-snap") 5227 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(2)}, s.user.ID, snapstate.Flags{}) 5228 c.Assert(err, IsNil) 5229 chg.AddAll(ts) 5230 5231 s.state.Unlock() 5232 defer s.se.Stop() 5233 s.settle(c) 5234 5235 s.state.Lock() 5236 // config of rev. 1 has been stored in per-revision map 5237 var cfgs map[string]interface{} 5238 c.Assert(s.state.Get("revision-config", &cfgs), IsNil) 5239 c.Assert(cfgs["some-snap"], DeepEquals, map[string]interface{}{ 5240 "1": map[string]interface{}{"foo": "100"}, 5241 "2": map[string]interface{}{"foo": "200"}, 5242 }) 5243 5244 // config of rev. 2 hasn't been restored by refresh, old value returned 5245 tr = config.NewTransaction(s.state) 5246 var res string 5247 c.Assert(tr.Get("some-snap", "foo", &res), IsNil) 5248 c.Assert(res, Equals, "100") 5249 } 5250 5251 func (s *snapmgrTestSuite) TestRefreshFailureCausesErrorReport(c *C) { 5252 var errSnap, errMsg, errSig string 5253 var errExtra map[string]string 5254 var n int 5255 restore := snapstate.MockErrtrackerReport(func(aSnap, aErrMsg, aDupSig string, extra map[string]string) (string, error) { 5256 errSnap = aSnap 5257 errMsg = aErrMsg 5258 errSig = aDupSig 5259 errExtra = extra 5260 n += 1 5261 return "oopsid", nil 5262 }) 5263 defer restore() 5264 5265 si := snap.SideInfo{ 5266 RealName: "some-snap", 5267 SnapID: "some-snap-id", 5268 Revision: snap.R(7), 5269 } 5270 5271 s.state.Lock() 5272 defer s.state.Unlock() 5273 5274 s.state.Set("ubuntu-core-transition-retry", 7) 5275 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5276 Active: true, 5277 Sequence: []*snap.SideInfo{&si}, 5278 Current: si.Revision, 5279 SnapType: "app", 5280 }) 5281 5282 chg := s.state.NewChange("install", "install a snap") 5283 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 5284 c.Assert(err, IsNil) 5285 chg.AddAll(ts) 5286 5287 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "some-snap/11") 5288 5289 s.state.Unlock() 5290 defer s.se.Stop() 5291 s.settle(c) 5292 s.state.Lock() 5293 5294 // verify we generated a failure report 5295 c.Check(n, Equals, 1) 5296 c.Check(errSnap, Equals, "some-snap") 5297 c.Check(errExtra, DeepEquals, map[string]string{ 5298 "UbuntuCoreTransitionCount": "7", 5299 "Channel": "some-channel", 5300 "Revision": "11", 5301 }) 5302 c.Check(errMsg, Matches, `(?sm)change "install": "install a snap" 5303 prerequisites: Undo 5304 snap-setup: "some-snap" \(11\) "some-channel" 5305 download-snap: Undoing 5306 validate-snap: Done 5307 .* 5308 link-snap: Error 5309 INFO unlink 5310 ERROR fail 5311 auto-connect: Hold 5312 set-auto-aliases: Hold 5313 setup-aliases: Hold 5314 run-hook: Hold 5315 start-snap-services: Hold 5316 cleanup: Hold 5317 run-hook: Hold`) 5318 c.Check(errSig, Matches, `(?sm)snap-install: 5319 prerequisites: Undo 5320 snap-setup: "some-snap" 5321 download-snap: Undoing 5322 validate-snap: Done 5323 .* 5324 link-snap: Error 5325 INFO unlink 5326 ERROR fail 5327 auto-connect: Hold 5328 set-auto-aliases: Hold 5329 setup-aliases: Hold 5330 run-hook: Hold 5331 start-snap-services: Hold 5332 cleanup: Hold 5333 run-hook: Hold`) 5334 5335 // run again with empty "ubuntu-core-transition-retry" 5336 s.state.Set("ubuntu-core-transition-retry", 0) 5337 chg = s.state.NewChange("install", "install a snap") 5338 ts, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 5339 c.Assert(err, IsNil) 5340 chg.AddAll(ts) 5341 s.state.Unlock() 5342 defer s.se.Stop() 5343 s.settle(c) 5344 s.state.Lock() 5345 // verify that we excluded this field from the bugreport 5346 c.Check(n, Equals, 2) 5347 c.Check(errExtra, DeepEquals, map[string]string{ 5348 "Channel": "some-channel", 5349 "Revision": "11", 5350 }) 5351 } 5352 5353 func (s *snapmgrTestSuite) TestNoReRefreshInUpdate(c *C) { 5354 s.state.Lock() 5355 defer s.state.Unlock() 5356 5357 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5358 Active: true, 5359 Sequence: []*snap.SideInfo{ 5360 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 5361 }, 5362 Current: snap.R(1), 5363 SnapType: "app", 5364 }) 5365 5366 ts, err := snapstate.Update(s.state, "some-snap", nil, 0, snapstate.Flags{NoReRefresh: true}) 5367 c.Assert(err, IsNil) 5368 5369 // ensure we have no re-refresh task 5370 for _, t := range ts.Tasks() { 5371 c.Assert(t.Kind(), Not(Equals), "check-rerefresh") 5372 } 5373 5374 snapsup, err := snapstate.TaskSnapSetup(ts.Tasks()[0]) 5375 c.Assert(err, IsNil) 5376 // NoReRefresh is consumed and consulted when creating the taskset 5377 // but is not copied into SnapSetup 5378 c.Check(snapsup.Flags.NoReRefresh, Equals, false) 5379 } 5380 5381 func (s *snapmgrTestSuite) TestEmptyUpdateWithChannelChangeAndAutoAlias(c *C) { 5382 // this reproduces the cause behind lp:1860324, 5383 // namely an empty refresh with a channel change on a snap 5384 // with changed aliases 5385 5386 s.state.Lock() 5387 defer s.state.Unlock() 5388 5389 n := 0 5390 snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) { 5391 if info.InstanceName() == "alias-snap" { 5392 if n > 0 { 5393 return map[string]string{ 5394 "alias1": "cmd1", 5395 "alias2": "cmd2", 5396 }, nil 5397 } 5398 n++ 5399 } 5400 return nil, nil 5401 } 5402 5403 snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{ 5404 TrackingChannel: "latest/stable", 5405 Sequence: []*snap.SideInfo{ 5406 {RealName: "alias-snap", Revision: snap.R(11), SnapID: "alias-snap-id"}, 5407 }, 5408 Current: snap.R(11), 5409 Active: true, 5410 }) 5411 5412 s.state.Set("aliases", map[string]map[string]string{ 5413 "alias-snap": { 5414 "alias1": "auto", 5415 }, 5416 }) 5417 5418 s.state.Unlock() 5419 err := s.snapmgr.Ensure() 5420 s.state.Lock() 5421 c.Assert(err, IsNil) 5422 5423 ts, err := snapstate.Update(s.state, "alias-snap", &snapstate.RevisionOptions{Channel: "latest/candidate"}, s.user.ID, snapstate.Flags{}) 5424 c.Assert(err, IsNil) 5425 5426 chg := s.state.NewChange("refresh", "refresh snap") 5427 chg.AddAll(ts) 5428 5429 s.state.Unlock() 5430 defer s.se.Stop() 5431 s.settle(c) 5432 s.state.Lock() 5433 5434 c.Assert(chg.Err(), IsNil) 5435 c.Assert(chg.IsReady(), Equals, true) 5436 } 5437 5438 func (s *snapmgrTestSuite) testUpdateDiskSpaceCheck(c *C, featureFlag, failInstallSize, failDiskCheck bool) error { 5439 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 5440 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 5441 if failDiskCheck { 5442 return &osutil.NotEnoughDiskSpaceError{} 5443 } 5444 return nil 5445 }) 5446 defer restore() 5447 5448 var installSizeCalled bool 5449 5450 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []*snap.Info, userID int) (uint64, error) { 5451 installSizeCalled = true 5452 if failInstallSize { 5453 return 0, fmt.Errorf("boom") 5454 } 5455 c.Assert(snaps, HasLen, 1) 5456 c.Check(snaps[0].InstanceName(), Equals, "some-snap") 5457 return 123, nil 5458 }) 5459 defer restoreInstallSize() 5460 5461 s.state.Lock() 5462 defer s.state.Unlock() 5463 5464 tr := config.NewTransaction(s.state) 5465 tr.Set("core", "experimental.check-disk-space-refresh", featureFlag) 5466 tr.Commit() 5467 5468 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5469 Active: true, 5470 Sequence: []*snap.SideInfo{ 5471 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 5472 }, 5473 Current: snap.R(4), 5474 SnapType: "app", 5475 }) 5476 5477 opts := &snapstate.RevisionOptions{Channel: "some-channel"} 5478 _, err := snapstate.Update(s.state, "some-snap", opts, s.user.ID, snapstate.Flags{}) 5479 5480 if featureFlag { 5481 c.Check(installSizeCalled, Equals, true) 5482 } else { 5483 c.Check(installSizeCalled, Equals, false) 5484 } 5485 5486 return err 5487 } 5488 5489 func (s *snapmgrTestSuite) TestUpdateDiskSpaceError(c *C) { 5490 featureFlag := true 5491 failInstallSize := false 5492 failDiskCheck := true 5493 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5494 diskSpaceErr := err.(*snapstate.InsufficientSpaceError) 5495 c.Assert(diskSpaceErr, ErrorMatches, `insufficient space in .* to perform "refresh" change for the following snaps: some-snap`) 5496 c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 5497 c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"some-snap"}) 5498 } 5499 5500 func (s *snapmgrTestSuite) TestUpdateDiskCheckSkippedIfDisabled(c *C) { 5501 featureFlag := false 5502 failInstallSize := false 5503 failDiskCheck := true 5504 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5505 c.Check(err, IsNil) 5506 } 5507 5508 func (s *snapmgrTestSuite) TestUpdateDiskCheckInstallSizeError(c *C) { 5509 featureFlag := true 5510 failInstallSize := true 5511 failDiskCheck := false 5512 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5513 c.Check(err, ErrorMatches, "boom") 5514 } 5515 5516 func (s *snapmgrTestSuite) TestUpdateDiskCheckHappy(c *C) { 5517 featureFlag := true 5518 failInstallSize := false 5519 failDiskCheck := false 5520 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5521 c.Check(err, IsNil) 5522 }