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