github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/snapstate_update_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package snapstate_test 21 22 import ( 23 "context" 24 "errors" 25 "fmt" 26 "os" 27 "path/filepath" 28 "sort" 29 30 . "gopkg.in/check.v1" 31 "gopkg.in/tomb.v2" 32 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/interfaces" 35 "github.com/snapcore/snapd/osutil" 36 "github.com/snapcore/snapd/overlord/auth" 37 38 // So it registers Configure. 39 _ "github.com/snapcore/snapd/overlord/configstate" 40 "github.com/snapcore/snapd/overlord/configstate/config" 41 "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 44 "github.com/snapcore/snapd/overlord/state" 45 "github.com/snapcore/snapd/release" 46 "github.com/snapcore/snapd/snap" 47 "github.com/snapcore/snapd/snap/snaptest" 48 "github.com/snapcore/snapd/store" 49 "github.com/snapcore/snapd/testutil" 50 ) 51 52 func verifyUpdateTasks(c *C, opts, discards int, ts *state.TaskSet, st *state.State) { 53 kinds := taskKinds(ts.Tasks()) 54 55 expected := []string{ 56 "prerequisites", 57 "download-snap", 58 "validate-snap", 59 "mount-snap", 60 } 61 expected = append(expected, "run-hook[pre-refresh]") 62 if opts&unlinkBefore != 0 { 63 expected = append(expected, 64 "stop-snap-services", 65 ) 66 } 67 if opts&unlinkBefore != 0 { 68 expected = append(expected, 69 "remove-aliases", 70 "unlink-current-snap", 71 ) 72 } 73 if opts&updatesGadget != 0 { 74 expected = append(expected, 75 "update-gadget-assets", 76 "update-gadget-cmdline") 77 } 78 expected = append(expected, 79 "copy-snap-data", 80 "setup-profiles", 81 "link-snap", 82 ) 83 if opts&maybeCore != 0 { 84 expected = append(expected, "setup-profiles") 85 } 86 expected = append(expected, 87 "auto-connect", 88 "set-auto-aliases", 89 "setup-aliases", 90 "run-hook[post-refresh]", 91 "start-snap-services") 92 93 c.Assert(ts.Tasks()[len(expected)-2].Summary(), Matches, `Run post-refresh hook of .*`) 94 for i := 0; i < discards; i++ { 95 expected = append(expected, 96 "clear-snap", 97 "discard-snap", 98 ) 99 } 100 if opts&cleanupAfter != 0 { 101 expected = append(expected, 102 "cleanup", 103 ) 104 } 105 expected = append(expected, 106 "run-hook[configure]", 107 "run-hook[check-health]", 108 ) 109 if opts&doesReRefresh != 0 { 110 expected = append(expected, "check-rerefresh") 111 } 112 113 c.Assert(kinds, DeepEquals, expected) 114 } 115 116 func (s *snapmgrTestSuite) TestUpdateDoesGC(c *C) { 117 s.state.Lock() 118 defer s.state.Unlock() 119 restore := release.MockOnClassic(false) 120 defer restore() 121 122 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 123 Active: true, 124 Sequence: []*snap.SideInfo{ 125 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 126 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 127 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 128 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 129 }, 130 Current: snap.R(4), 131 SnapType: "app", 132 }) 133 134 chg := s.state.NewChange("update", "update a snap") 135 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 136 c.Assert(err, IsNil) 137 chg.AddAll(ts) 138 139 s.state.Unlock() 140 defer s.se.Stop() 141 s.settle(c) 142 s.state.Lock() 143 144 // ensure garbage collection runs as the last tasks 145 expectedTail := fakeOps{ 146 { 147 op: "link-snap", 148 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 149 }, 150 { 151 op: "auto-connect:Doing", 152 name: "some-snap", 153 revno: snap.R(11), 154 }, 155 { 156 op: "update-aliases", 157 }, 158 { 159 op: "remove-snap-data", 160 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 161 }, 162 { 163 op: "remove-snap-files", 164 path: filepath.Join(dirs.SnapMountDir, "some-snap/1"), 165 stype: "app", 166 }, 167 { 168 op: "remove-snap-data", 169 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 170 }, 171 { 172 op: "remove-snap-files", 173 path: filepath.Join(dirs.SnapMountDir, "some-snap/2"), 174 stype: "app", 175 }, 176 { 177 op: "cleanup-trash", 178 name: "some-snap", 179 revno: snap.R(11), 180 }, 181 } 182 183 opsTail := s.fakeBackend.ops[len(s.fakeBackend.ops)-len(expectedTail):] 184 c.Assert(opsTail.Ops(), DeepEquals, expectedTail.Ops()) 185 c.Check(opsTail, DeepEquals, expectedTail) 186 } 187 188 func (s *snapmgrTestSuite) TestUpdateScenarios(c *C) { 189 // TODO: also use channel-for-7 or equiv to check updates that are switches 190 for k, t := range switchScenarios { 191 s.testUpdateScenario(c, k, t) 192 } 193 } 194 195 func (s *snapmgrTestSuite) testUpdateScenario(c *C, desc string, t switchScenario) { 196 // reset 197 s.fakeBackend.ops = nil 198 199 comment := Commentf("%q (%+v)", desc, t) 200 si := snap.SideInfo{ 201 RealName: "some-snap", 202 Revision: snap.R(7), 203 Channel: t.chanFrom, 204 SnapID: "some-snap-id", 205 } 206 207 s.state.Lock() 208 defer s.state.Unlock() 209 210 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 211 Sequence: []*snap.SideInfo{&si}, 212 Active: true, 213 Current: si.Revision, 214 TrackingChannel: t.chanFrom, 215 CohortKey: t.cohFrom, 216 }) 217 218 chg := s.state.NewChange("update-snap", t.summary) 219 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{ 220 Channel: t.chanTo, 221 CohortKey: t.cohTo, 222 LeaveCohort: t.cohFrom != "" && t.cohTo == "", 223 }, 0, snapstate.Flags{}) 224 c.Assert(err, IsNil, comment) 225 chg.AddAll(ts) 226 227 s.state.Unlock() 228 s.settle(c) 229 s.state.Lock() 230 231 // switch is not really really doing anything backend related 232 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, []string{ 233 "storesvc-snap-action", 234 "storesvc-snap-action:action", 235 "storesvc-download", 236 "validate-snap:Doing", 237 "current", 238 "open-snap-file", 239 "setup-snap", 240 "remove-snap-aliases", 241 "unlink-snap", 242 "copy-data", 243 "setup-profiles:Doing", 244 "candidate", 245 "link-snap", 246 "auto-connect:Doing", 247 "update-aliases", 248 "cleanup-trash", 249 }, comment) 250 251 expectedChanTo := t.chanTo 252 if t.chanTo == "" { 253 expectedChanTo = t.chanFrom 254 } 255 expectedCohTo := t.cohTo 256 257 // ensure the desired channel/cohort has changed 258 var snapst snapstate.SnapState 259 err = snapstate.Get(s.state, "some-snap", &snapst) 260 c.Assert(err, IsNil, comment) 261 c.Assert(snapst.TrackingChannel, Equals, expectedChanTo, comment) 262 c.Assert(snapst.CohortKey, Equals, expectedCohTo, comment) 263 264 // ensure the current info *has* changed 265 info, err := snapst.CurrentInfo() 266 c.Assert(err, IsNil, comment) 267 c.Assert(info.Channel, Equals, expectedChanTo, comment) 268 } 269 270 func (s *snapmgrTestSuite) TestUpdateTasksWithOldCurrent(c *C) { 271 s.state.Lock() 272 defer s.state.Unlock() 273 restore := release.MockOnClassic(false) 274 defer restore() 275 276 si1 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)} 277 si2 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)} 278 si3 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)} 279 si4 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)} 280 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 281 Active: true, 282 TrackingChannel: "latest/edge", 283 Sequence: []*snap.SideInfo{si1, si2, si3, si4}, 284 Current: snap.R(2), 285 SnapType: "app", 286 }) 287 288 // run the update 289 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 290 c.Assert(err, IsNil) 291 292 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 2, ts, s.state) 293 294 // and ensure that it will remove the revisions after "current" 295 // (si3, si4) 296 var snapsup snapstate.SnapSetup 297 tasks := ts.Tasks() 298 299 i := len(tasks) - 8 300 c.Check(tasks[i].Kind(), Equals, "clear-snap") 301 err = tasks[i].Get("snap-setup", &snapsup) 302 c.Assert(err, IsNil) 303 c.Check(snapsup.Revision(), Equals, si3.Revision) 304 305 i = len(tasks) - 6 306 c.Check(tasks[i].Kind(), Equals, "clear-snap") 307 err = tasks[i].Get("snap-setup", &snapsup) 308 c.Assert(err, IsNil) 309 c.Check(snapsup.Revision(), Equals, si4.Revision) 310 } 311 312 func (s *snapmgrTestSuite) TestUpdateCanDoBackwards(c *C) { 313 si7 := snap.SideInfo{ 314 RealName: "some-snap", 315 SnapID: "some-snap-id", 316 Revision: snap.R(7), 317 } 318 si11 := snap.SideInfo{ 319 RealName: "some-snap", 320 SnapID: "some-snap-id", 321 Revision: snap.R(11), 322 } 323 324 s.state.Lock() 325 defer s.state.Unlock() 326 327 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 328 Active: true, 329 Sequence: []*snap.SideInfo{&si7, &si11}, 330 Current: si11.Revision, 331 SnapType: "app", 332 }) 333 334 chg := s.state.NewChange("refresh", "refresh a snap") 335 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Revision: snap.R(7)}, s.user.ID, snapstate.Flags{}) 336 c.Assert(err, IsNil) 337 chg.AddAll(ts) 338 339 s.state.Unlock() 340 defer s.se.Stop() 341 s.settle(c) 342 s.state.Lock() 343 expected := fakeOps{ 344 { 345 op: "remove-snap-aliases", 346 name: "some-snap", 347 }, 348 { 349 op: "unlink-snap", 350 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 351 }, 352 { 353 op: "copy-data", 354 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 355 old: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 356 }, 357 { 358 op: "setup-profiles:Doing", 359 name: "some-snap", 360 revno: snap.R(7), 361 }, 362 { 363 op: "candidate", 364 sinfo: snap.SideInfo{ 365 RealName: "some-snap", 366 SnapID: "some-snap-id", 367 Channel: "", 368 Revision: snap.R(7), 369 }, 370 }, 371 { 372 op: "link-snap", 373 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 374 }, 375 { 376 op: "auto-connect:Doing", 377 name: "some-snap", 378 revno: snap.R(7), 379 }, 380 { 381 op: "update-aliases", 382 }, 383 { 384 op: "cleanup-trash", 385 name: "some-snap", 386 revno: snap.R(7), 387 }, 388 } 389 // start with an easier-to-read error if this fails: 390 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 391 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 392 } 393 394 func revs(seq []*snap.SideInfo) []int { 395 revs := make([]int, len(seq)) 396 for i, si := range seq { 397 revs[i] = si.Revision.N 398 } 399 400 return revs 401 } 402 403 type opSeqOpts struct { 404 revert bool 405 fail bool 406 before []int 407 current int 408 via int 409 after []int 410 } 411 412 // build a SnapState with a revision sequence given by `before` and a 413 // current revision of `current`. Then refresh --revision via. Then 414 // check the revision sequence is as in `after`. 415 func (s *snapmgrTestSuite) testOpSequence(c *C, opts *opSeqOpts) (*snapstate.SnapState, *state.TaskSet) { 416 s.state.Lock() 417 defer s.state.Unlock() 418 419 seq := make([]*snap.SideInfo, len(opts.before)) 420 for i, n := range opts.before { 421 seq[i] = &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(n)} 422 } 423 424 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 425 Active: true, 426 TrackingChannel: "latest/edge", 427 Sequence: seq, 428 Current: snap.R(opts.current), 429 SnapType: "app", 430 }) 431 432 var chg *state.Change 433 var ts *state.TaskSet 434 var err error 435 if opts.revert { 436 chg = s.state.NewChange("revert", "revert a snap") 437 ts, err = snapstate.RevertToRevision(s.state, "some-snap", snap.R(opts.via), snapstate.Flags{}) 438 } else { 439 chg = s.state.NewChange("refresh", "refresh a snap") 440 ts, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Revision: snap.R(opts.via)}, s.user.ID, snapstate.Flags{}) 441 } 442 c.Assert(err, IsNil) 443 if opts.fail { 444 tasks := ts.Tasks() 445 var last *state.Task 446 // don't make a task wait on rerefresh, that's bad 447 for i := len(tasks) - 1; i > 0; i-- { 448 last = tasks[i] 449 if last.Kind() != "check-rerefresh" { 450 break 451 } 452 } 453 terr := s.state.NewTask("error-trigger", "provoking total undo") 454 terr.WaitFor(last) 455 if len(last.Lanes()) > 0 { 456 lanes := last.Lanes() 457 // sanity 458 c.Assert(lanes, HasLen, 1) 459 terr.JoinLane(lanes[0]) 460 } 461 chg.AddTask(terr) 462 } 463 chg.AddAll(ts) 464 465 s.state.Unlock() 466 defer s.se.Stop() 467 s.settle(c) 468 s.state.Lock() 469 470 var snapst snapstate.SnapState 471 err = snapstate.Get(s.state, "some-snap", &snapst) 472 c.Assert(err, IsNil) 473 c.Check(revs(snapst.Sequence), DeepEquals, opts.after) 474 475 return &snapst, ts 476 } 477 478 func (s *snapmgrTestSuite) testUpdateSequence(c *C, opts *opSeqOpts) *state.TaskSet { 479 restore := release.MockOnClassic(false) 480 defer restore() 481 482 opts.revert = false 483 snapst, ts := s.testOpSequence(c, opts) 484 // update always ends with current==seq[-1]==via: 485 c.Check(snapst.Current.N, Equals, opts.after[len(opts.after)-1]) 486 c.Check(snapst.Current.N, Equals, opts.via) 487 488 c.Check(s.fakeBackend.ops.Count("copy-data"), Equals, 1) 489 c.Check(s.fakeBackend.ops.First("copy-data"), DeepEquals, &fakeOp{ 490 op: "copy-data", 491 path: fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.via), 492 old: fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.current), 493 }) 494 495 return ts 496 } 497 498 func (s *snapmgrTestSuite) testUpdateFailureSequence(c *C, opts *opSeqOpts) *state.TaskSet { 499 restore := release.MockOnClassic(false) 500 defer restore() 501 502 opts.revert = false 503 opts.after = opts.before 504 s.fakeBackend.linkSnapFailTrigger = fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.via) 505 snapst, ts := s.testOpSequence(c, opts) 506 // a failed update will always end with current unchanged 507 c.Check(snapst.Current.N, Equals, opts.current) 508 509 ops := s.fakeBackend.ops 510 c.Check(ops.Count("copy-data"), Equals, 1) 511 do := ops.First("copy-data") 512 513 c.Check(ops.Count("undo-copy-snap-data"), Equals, 1) 514 undo := ops.First("undo-copy-snap-data") 515 516 do.op = undo.op 517 c.Check(do, DeepEquals, undo) // i.e. they only differed in the op 518 519 return ts 520 } 521 522 // testTotal*Failure fails *after* link-snap 523 func (s *snapmgrTestSuite) testTotalUpdateFailureSequence(c *C, opts *opSeqOpts) *state.TaskSet { 524 restore := release.MockOnClassic(false) 525 defer restore() 526 527 opts.revert = false 528 opts.fail = true 529 snapst, ts := s.testOpSequence(c, opts) 530 // a failed update will always end with current unchanged 531 c.Check(snapst.Current.N, Equals, opts.current) 532 533 ops := s.fakeBackend.ops 534 c.Check(ops.Count("copy-data"), Equals, 1) 535 do := ops.First("copy-data") 536 537 c.Check(ops.Count("undo-copy-snap-data"), Equals, 1) 538 undo := ops.First("undo-copy-snap-data") 539 540 do.op = undo.op 541 c.Check(do, DeepEquals, undo) // i.e. they only differed in the op 542 543 return ts 544 } 545 546 func (s *snapmgrTestSuite) TestUpdateLayoutsChecksFeatureFlag(c *C) { 547 s.state.Lock() 548 defer s.state.Unlock() 549 550 // When layouts are disabled we cannot refresh to a snap depending on the feature. 551 tr := config.NewTransaction(s.state) 552 tr.Set("core", "experimental.layouts", false) 553 tr.Commit() 554 555 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 556 Active: true, 557 Sequence: []*snap.SideInfo{ 558 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 559 }, 560 Current: snap.R(1), 561 SnapType: "app", 562 }) 563 564 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-layout/stable"}, s.user.ID, snapstate.Flags{}) 565 c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true") 566 567 // When layouts are enabled we can refresh to a snap depending on the feature. 568 tr = config.NewTransaction(s.state) 569 tr.Set("core", "experimental.layouts", true) 570 tr.Commit() 571 572 _, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-layout/stable"}, s.user.ID, snapstate.Flags{}) 573 c.Assert(err, IsNil) 574 } 575 576 func (s *snapmgrTestSuite) TestUpdateManyExplicitLayoutsChecksFeatureFlag(c *C) { 577 s.state.Lock() 578 defer s.state.Unlock() 579 580 // When layouts are disabled we cannot refresh multiple snaps if one of them depends on the feature. 581 tr := config.NewTransaction(s.state) 582 tr.Set("core", "experimental.layouts", false) 583 tr.Commit() 584 585 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 586 Active: true, 587 TrackingChannel: "channel-for-layout/stable", 588 Sequence: []*snap.SideInfo{ 589 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 590 }, 591 Current: snap.R(1), 592 SnapType: "app", 593 }) 594 595 _, _, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 596 c.Assert(err, ErrorMatches, "experimental feature disabled - test it by setting 'experimental.layouts' to true") 597 598 // When layouts are enabled we can refresh multiple snaps if one of them depends on the feature. 599 tr = config.NewTransaction(s.state) 600 tr.Set("core", "experimental.layouts", true) 601 tr.Commit() 602 603 _, _, err = snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 604 c.Assert(err, IsNil) 605 } 606 607 func (s *snapmgrTestSuite) TestUpdateManyLayoutsChecksFeatureFlag(c *C) { 608 s.state.Lock() 609 defer s.state.Unlock() 610 611 // When layouts are disabled we cannot refresh multiple snaps if one of them depends on the feature. 612 tr := config.NewTransaction(s.state) 613 tr.Set("core", "experimental.layouts", false) 614 tr.Commit() 615 616 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 617 Active: true, 618 TrackingChannel: "channel-for-layout/stable", 619 Sequence: []*snap.SideInfo{ 620 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 621 }, 622 Current: snap.R(1), 623 SnapType: "app", 624 }) 625 626 refreshes, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, s.user.ID, nil) 627 c.Assert(err, IsNil) 628 c.Assert(refreshes, HasLen, 0) 629 630 // When layouts are enabled we can refresh multiple snaps if one of them depends on the feature. 631 tr = config.NewTransaction(s.state) 632 tr.Set("core", "experimental.layouts", true) 633 tr.Commit() 634 635 refreshes, _, err = snapstate.UpdateMany(context.Background(), s.state, nil, s.user.ID, nil) 636 c.Assert(err, IsNil) 637 c.Assert(refreshes, DeepEquals, []string{"some-snap"}) 638 } 639 640 func (s *snapmgrTestSuite) TestUpdateFailsEarlyOnEpochMismatch(c *C) { 641 s.state.Lock() 642 defer s.state.Unlock() 643 644 snapstate.Set(s.state, "some-epoch-snap", &snapstate.SnapState{ 645 Active: true, 646 Sequence: []*snap.SideInfo{ 647 {RealName: "some-epoch-snap", SnapID: "some-epoch-snap-id", Revision: snap.R(1)}, 648 }, 649 Current: snap.R(1), 650 SnapType: "app", 651 }) 652 653 _, err := snapstate.Update(s.state, "some-epoch-snap", nil, 0, snapstate.Flags{}) 654 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`) 655 } 656 657 func (s *snapmgrTestSuite) TestUpdateTasksPropagatesErrors(c *C) { 658 s.state.Lock() 659 defer s.state.Unlock() 660 661 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 662 Active: true, 663 TrackingChannel: "latest/edge", 664 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "fakestore-please-error-on-refresh", Revision: snap.R(7)}}, 665 Current: snap.R(7), 666 }) 667 668 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 669 c.Assert(err, ErrorMatches, `failing as requested`) 670 } 671 672 func (s *snapmgrTestSuite) TestUpdateTasks(c *C) { 673 s.state.Lock() 674 defer s.state.Unlock() 675 676 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 677 Active: true, 678 TrackingChannel: "latest/edge", 679 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 680 Current: snap.R(7), 681 SnapType: "app", 682 }) 683 684 validateCalled := false 685 happyValidateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 686 validateCalled = true 687 return refreshes, nil 688 } 689 // hook it up 690 snapstate.ValidateRefreshes = happyValidateRefreshes 691 692 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 693 c.Assert(err, IsNil) 694 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 0, ts, s.state) 695 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 696 697 c.Check(validateCalled, Equals, true) 698 699 var snapsup snapstate.SnapSetup 700 err = ts.Tasks()[0].Get("snap-setup", &snapsup) 701 c.Assert(err, IsNil) 702 703 c.Check(snapsup.Channel, Equals, "some-channel") 704 } 705 706 func (s *snapmgrTestSuite) TestUpdateAmendRunThrough(c *C) { 707 si := snap.SideInfo{ 708 RealName: "some-snap", 709 Revision: snap.R(-42), 710 } 711 snaptest.MockSnap(c, `name: some-snap`, &si) 712 713 s.state.Lock() 714 defer s.state.Unlock() 715 716 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 717 Active: true, 718 Sequence: []*snap.SideInfo{&si}, 719 Current: si.Revision, 720 SnapType: "app", 721 TrackingChannel: "latest/stable", 722 }) 723 724 chg := s.state.NewChange("refresh", "refresh a snap") 725 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 726 c.Assert(err, IsNil) 727 chg.AddAll(ts) 728 729 s.state.Unlock() 730 defer s.se.Stop() 731 s.settle(c) 732 s.state.Lock() 733 734 // ensure all our tasks ran 735 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 736 macaroon: s.user.StoreMacaroon, 737 name: "some-snap", 738 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 739 }}) 740 c.Check(s.fakeStore.seenPrivacyKeys["privacy-key"], Equals, true, Commentf("salts seen: %v", s.fakeStore.seenPrivacyKeys)) 741 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, []string{ 742 "storesvc-snap-action", 743 "storesvc-snap-action:action", 744 "storesvc-download", 745 "validate-snap:Doing", 746 "current", 747 "open-snap-file", 748 "setup-snap", 749 "remove-snap-aliases", 750 "unlink-snap", 751 "copy-data", 752 "setup-profiles:Doing", 753 "candidate", 754 "link-snap", 755 "auto-connect:Doing", 756 "update-aliases", 757 "cleanup-trash", 758 }) 759 // just check the interesting op 760 c.Check(s.fakeBackend.ops[1], DeepEquals, fakeOp{ 761 op: "storesvc-snap-action:action", 762 action: store.SnapAction{ 763 Action: "install", // we asked for an Update, but an amend is actually an Install 764 InstanceName: "some-snap", 765 Channel: "some-channel", 766 Epoch: snap.E("1*"), // in amend, epoch in the action is not nil! 767 Flags: store.SnapActionEnforceValidation, 768 }, 769 revno: snap.R(11), 770 userID: 1, 771 }) 772 773 task := ts.Tasks()[1] 774 // verify snapSetup info 775 var snapsup snapstate.SnapSetup 776 err = task.Get("snap-setup", &snapsup) 777 c.Assert(err, IsNil) 778 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 779 Channel: "some-channel", 780 UserID: s.user.ID, 781 782 SnapPath: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 783 DownloadInfo: &snap.DownloadInfo{ 784 DownloadURL: "https://some-server.com/some/path.snap", 785 Size: 5, 786 }, 787 SideInfo: snapsup.SideInfo, 788 Type: snap.TypeApp, 789 PlugsOnly: true, 790 Flags: snapstate.Flags{Amend: true}, 791 }) 792 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 793 RealName: "some-snap", 794 Revision: snap.R(11), 795 Channel: "some-channel", 796 SnapID: "some-snap-id", 797 }) 798 799 // verify services stop reason 800 verifyStopReason(c, ts, "refresh") 801 802 // check post-refresh hook 803 task = ts.Tasks()[14] 804 c.Assert(task.Kind(), Equals, "run-hook") 805 c.Assert(task.Summary(), Matches, `Run post-refresh hook of "some-snap" snap if present`) 806 807 // verify snaps in the system state 808 var snapst snapstate.SnapState 809 err = snapstate.Get(s.state, "some-snap", &snapst) 810 c.Assert(err, IsNil) 811 812 c.Assert(snapst.Active, Equals, true) 813 c.Assert(snapst.Sequence, HasLen, 2) 814 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 815 RealName: "some-snap", 816 Channel: "", 817 Revision: snap.R(-42), 818 }) 819 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 820 RealName: "some-snap", 821 Channel: "some-channel", 822 SnapID: "some-snap-id", 823 Revision: snap.R(11), 824 }) 825 } 826 827 func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) { 828 // we start without the auxiliary store info (or with an older one) 829 c.Check(snapstate.AuxStoreInfoFilename("services-snap-id"), testutil.FileAbsent) 830 831 // use services-snap here to make sure services would be stopped/started appropriately 832 si := snap.SideInfo{ 833 RealName: "services-snap", 834 Revision: snap.R(7), 835 SnapID: "services-snap-id", 836 } 837 snaptest.MockSnap(c, `name: services-snap`, &si) 838 fi, err := os.Stat(snap.MountFile("services-snap", si.Revision)) 839 c.Assert(err, IsNil) 840 refreshedDate := fi.ModTime() 841 // look at disk 842 r := snapstate.MockRevisionDate(nil) 843 defer r() 844 845 s.state.Lock() 846 defer s.state.Unlock() 847 848 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 849 Active: true, 850 Sequence: []*snap.SideInfo{&si}, 851 Current: si.Revision, 852 SnapType: "app", 853 TrackingChannel: "latest/stable", 854 CohortKey: "embattled", 855 }) 856 857 chg := s.state.NewChange("refresh", "refresh a snap") 858 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{ 859 Channel: "some-channel", 860 CohortKey: "some-cohort", 861 }, s.user.ID, snapstate.Flags{}) 862 c.Assert(err, IsNil) 863 chg.AddAll(ts) 864 865 s.state.Unlock() 866 defer s.se.Stop() 867 s.settle(c) 868 s.state.Lock() 869 870 expected := fakeOps{ 871 { 872 op: "storesvc-snap-action", 873 curSnaps: []store.CurrentSnap{{ 874 InstanceName: "services-snap", 875 SnapID: "services-snap-id", 876 Revision: snap.R(7), 877 TrackingChannel: "latest/stable", 878 RefreshedDate: refreshedDate, 879 Epoch: snap.E("0"), 880 CohortKey: "embattled", 881 }}, 882 userID: 1, 883 }, 884 { 885 op: "storesvc-snap-action:action", 886 action: store.SnapAction{ 887 Action: "refresh", 888 InstanceName: "services-snap", 889 SnapID: "services-snap-id", 890 Channel: "some-channel", 891 CohortKey: "some-cohort", 892 Flags: store.SnapActionEnforceValidation, 893 }, 894 revno: snap.R(11), 895 userID: 1, 896 }, 897 { 898 op: "storesvc-download", 899 name: "services-snap", 900 }, 901 { 902 op: "validate-snap:Doing", 903 name: "services-snap", 904 revno: snap.R(11), 905 }, 906 { 907 op: "current", 908 old: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 909 }, 910 { 911 op: "open-snap-file", 912 path: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 913 sinfo: snap.SideInfo{ 914 RealName: "services-snap", 915 SnapID: "services-snap-id", 916 Channel: "some-channel", 917 Revision: snap.R(11), 918 }, 919 }, 920 { 921 op: "setup-snap", 922 name: "services-snap", 923 path: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 924 revno: snap.R(11), 925 }, 926 { 927 op: "stop-snap-services:refresh", 928 path: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 929 }, 930 { 931 op: "current-snap-service-states", 932 }, 933 { 934 op: "remove-snap-aliases", 935 name: "services-snap", 936 }, 937 { 938 op: "unlink-snap", 939 path: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 940 }, 941 { 942 op: "copy-data", 943 path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), 944 old: filepath.Join(dirs.SnapMountDir, "services-snap/7"), 945 }, 946 { 947 op: "setup-profiles:Doing", 948 name: "services-snap", 949 revno: snap.R(11), 950 }, 951 { 952 op: "candidate", 953 sinfo: snap.SideInfo{ 954 RealName: "services-snap", 955 SnapID: "services-snap-id", 956 Channel: "some-channel", 957 Revision: snap.R(11), 958 }, 959 }, 960 { 961 op: "link-snap", 962 path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), 963 }, 964 { 965 op: "auto-connect:Doing", 966 name: "services-snap", 967 revno: snap.R(11), 968 }, 969 { 970 op: "update-aliases", 971 }, 972 { 973 op: "start-snap-services", 974 path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), 975 services: []string{"svc1", "svc3", "svc2"}, 976 }, 977 { 978 op: "cleanup-trash", 979 name: "services-snap", 980 revno: snap.R(11), 981 }, 982 } 983 984 // ensure all our tasks ran 985 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 986 macaroon: s.user.StoreMacaroon, 987 name: "services-snap", 988 target: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 989 }}) 990 c.Check(s.fakeStore.seenPrivacyKeys["privacy-key"], Equals, true, Commentf("salts seen: %v", s.fakeStore.seenPrivacyKeys)) 991 // start with an easier-to-read error if this fails: 992 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 993 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 994 995 // check progress 996 task := ts.Tasks()[1] 997 _, cur, total := task.Progress() 998 c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress) 999 c.Assert(total, Equals, s.fakeStore.fakeTotalProgress) 1000 1001 // verify snapSetup info 1002 var snapsup snapstate.SnapSetup 1003 err = task.Get("snap-setup", &snapsup) 1004 c.Assert(err, IsNil) 1005 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 1006 Channel: "some-channel", 1007 CohortKey: "some-cohort", 1008 UserID: s.user.ID, 1009 1010 SnapPath: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), 1011 DownloadInfo: &snap.DownloadInfo{ 1012 DownloadURL: "https://some-server.com/some/path.snap", 1013 }, 1014 SideInfo: snapsup.SideInfo, 1015 Type: snap.TypeApp, 1016 PlugsOnly: true, 1017 }) 1018 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 1019 RealName: "services-snap", 1020 Revision: snap.R(11), 1021 Channel: "some-channel", 1022 SnapID: "services-snap-id", 1023 }) 1024 1025 // verify services stop reason 1026 verifyStopReason(c, ts, "refresh") 1027 1028 // check post-refresh hook 1029 task = ts.Tasks()[14] 1030 c.Assert(task.Kind(), Equals, "run-hook") 1031 c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap" snap if present`) 1032 1033 // verify snaps in the system state 1034 var snapst snapstate.SnapState 1035 err = snapstate.Get(s.state, "services-snap", &snapst) 1036 c.Assert(err, IsNil) 1037 1038 c.Assert(snapst.Active, Equals, true) 1039 c.Assert(snapst.Sequence, HasLen, 2) 1040 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 1041 RealName: "services-snap", 1042 SnapID: "services-snap-id", 1043 Channel: "", 1044 Revision: snap.R(7), 1045 }) 1046 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 1047 RealName: "services-snap", 1048 Channel: "some-channel", 1049 SnapID: "services-snap-id", 1050 Revision: snap.R(11), 1051 }) 1052 c.Check(snapst.CohortKey, Equals, "some-cohort") 1053 1054 // we end up with the auxiliary store info 1055 c.Check(snapstate.AuxStoreInfoFilename("services-snap-id"), testutil.FilePresent) 1056 } 1057 1058 func (s *snapmgrTestSuite) TestParallelInstanceUpdateRunThrough(c *C) { 1059 // use services-snap here to make sure services would be stopped/started appropriately 1060 si := snap.SideInfo{ 1061 RealName: "services-snap", 1062 Revision: snap.R(7), 1063 SnapID: "services-snap-id", 1064 } 1065 snaptest.MockSnapInstance(c, "services-snap_instance", `name: services-snap`, &si) 1066 fi, err := os.Stat(snap.MountFile("services-snap_instance", si.Revision)) 1067 c.Assert(err, IsNil) 1068 refreshedDate := fi.ModTime() 1069 // look at disk 1070 r := snapstate.MockRevisionDate(nil) 1071 defer r() 1072 1073 s.state.Lock() 1074 defer s.state.Unlock() 1075 1076 tr := config.NewTransaction(s.state) 1077 tr.Set("core", "experimental.parallel-instances", true) 1078 tr.Commit() 1079 1080 snapstate.Set(s.state, "services-snap_instance", &snapstate.SnapState{ 1081 Active: true, 1082 Sequence: []*snap.SideInfo{&si}, 1083 Current: si.Revision, 1084 SnapType: "app", 1085 TrackingChannel: "latest/stable", 1086 InstanceKey: "instance", 1087 }) 1088 1089 chg := s.state.NewChange("refresh", "refresh a snap") 1090 ts, err := snapstate.Update(s.state, "services-snap_instance", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 1091 c.Assert(err, IsNil) 1092 chg.AddAll(ts) 1093 1094 s.state.Unlock() 1095 s.settle(c) 1096 s.state.Lock() 1097 1098 expected := fakeOps{ 1099 { 1100 op: "storesvc-snap-action", 1101 curSnaps: []store.CurrentSnap{{ 1102 InstanceName: "services-snap_instance", 1103 SnapID: "services-snap-id", 1104 Revision: snap.R(7), 1105 TrackingChannel: "latest/stable", 1106 RefreshedDate: refreshedDate, 1107 Epoch: snap.E("0"), 1108 }}, 1109 userID: 1, 1110 }, 1111 { 1112 op: "storesvc-snap-action:action", 1113 action: store.SnapAction{ 1114 Action: "refresh", 1115 SnapID: "services-snap-id", 1116 InstanceName: "services-snap_instance", 1117 Channel: "some-channel", 1118 Flags: store.SnapActionEnforceValidation, 1119 }, 1120 revno: snap.R(11), 1121 userID: 1, 1122 }, 1123 { 1124 op: "storesvc-download", 1125 name: "services-snap", 1126 }, 1127 { 1128 op: "validate-snap:Doing", 1129 name: "services-snap_instance", 1130 revno: snap.R(11), 1131 }, 1132 { 1133 op: "current", 1134 old: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1135 }, 1136 { 1137 op: "open-snap-file", 1138 path: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1139 sinfo: snap.SideInfo{ 1140 RealName: "services-snap", 1141 SnapID: "services-snap-id", 1142 Channel: "some-channel", 1143 Revision: snap.R(11), 1144 }, 1145 }, 1146 { 1147 op: "setup-snap", 1148 name: "services-snap_instance", 1149 path: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1150 revno: snap.R(11), 1151 }, 1152 { 1153 op: "stop-snap-services:refresh", 1154 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1155 }, 1156 { 1157 op: "current-snap-service-states", 1158 }, 1159 { 1160 op: "remove-snap-aliases", 1161 name: "services-snap_instance", 1162 }, 1163 { 1164 op: "unlink-snap", 1165 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1166 }, 1167 { 1168 op: "copy-data", 1169 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"), 1170 old: filepath.Join(dirs.SnapMountDir, "services-snap_instance/7"), 1171 }, 1172 { 1173 op: "setup-profiles:Doing", 1174 name: "services-snap_instance", 1175 revno: snap.R(11), 1176 }, 1177 { 1178 op: "candidate", 1179 sinfo: snap.SideInfo{ 1180 RealName: "services-snap", 1181 SnapID: "services-snap-id", 1182 Channel: "some-channel", 1183 Revision: snap.R(11), 1184 }, 1185 }, 1186 { 1187 op: "link-snap", 1188 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"), 1189 }, 1190 { 1191 op: "auto-connect:Doing", 1192 name: "services-snap_instance", 1193 revno: snap.R(11), 1194 }, 1195 { 1196 op: "update-aliases", 1197 }, 1198 { 1199 op: "start-snap-services", 1200 path: filepath.Join(dirs.SnapMountDir, "services-snap_instance/11"), 1201 services: []string{"svc1", "svc3", "svc2"}, 1202 }, 1203 { 1204 op: "cleanup-trash", 1205 name: "services-snap_instance", 1206 revno: snap.R(11), 1207 }, 1208 } 1209 1210 // ensure all our tasks ran 1211 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 1212 macaroon: s.user.StoreMacaroon, 1213 name: "services-snap", 1214 target: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1215 }}) 1216 c.Check(s.fakeStore.seenPrivacyKeys["privacy-key"], Equals, true, Commentf("salts seen: %v", s.fakeStore.seenPrivacyKeys)) 1217 // start with an easier-to-read error if this fails: 1218 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 1219 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 1220 1221 // check progress 1222 task := ts.Tasks()[1] 1223 _, cur, total := task.Progress() 1224 c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress) 1225 c.Assert(total, Equals, s.fakeStore.fakeTotalProgress) 1226 1227 // verify snapSetup info 1228 var snapsup snapstate.SnapSetup 1229 err = task.Get("snap-setup", &snapsup) 1230 c.Assert(err, IsNil) 1231 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 1232 Channel: "some-channel", 1233 UserID: s.user.ID, 1234 1235 SnapPath: filepath.Join(dirs.SnapBlobDir, "services-snap_instance_11.snap"), 1236 DownloadInfo: &snap.DownloadInfo{ 1237 DownloadURL: "https://some-server.com/some/path.snap", 1238 }, 1239 SideInfo: snapsup.SideInfo, 1240 Type: snap.TypeApp, 1241 PlugsOnly: true, 1242 InstanceKey: "instance", 1243 }) 1244 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 1245 RealName: "services-snap", 1246 Revision: snap.R(11), 1247 Channel: "some-channel", 1248 SnapID: "services-snap-id", 1249 }) 1250 1251 // verify services stop reason 1252 verifyStopReason(c, ts, "refresh") 1253 1254 // check post-refresh hook 1255 task = ts.Tasks()[14] 1256 c.Assert(task.Kind(), Equals, "run-hook") 1257 c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap_instance" snap if present`) 1258 1259 // verify snaps in the system state 1260 var snapst snapstate.SnapState 1261 err = snapstate.Get(s.state, "services-snap_instance", &snapst) 1262 c.Assert(err, IsNil) 1263 1264 c.Assert(snapst.InstanceKey, Equals, "instance") 1265 c.Assert(snapst.Active, Equals, true) 1266 c.Assert(snapst.Sequence, HasLen, 2) 1267 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 1268 RealName: "services-snap", 1269 SnapID: "services-snap-id", 1270 Channel: "", 1271 Revision: snap.R(7), 1272 }) 1273 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 1274 RealName: "services-snap", 1275 Channel: "some-channel", 1276 SnapID: "services-snap-id", 1277 Revision: snap.R(11), 1278 }) 1279 } 1280 1281 func (s *snapmgrTestSuite) TestUpdateWithNewBase(c *C) { 1282 si := &snap.SideInfo{ 1283 RealName: "some-snap", 1284 SnapID: "some-snap-id", 1285 Revision: snap.R(7), 1286 } 1287 snaptest.MockSnap(c, `name: some-snap`, si) 1288 1289 s.state.Lock() 1290 defer s.state.Unlock() 1291 1292 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1293 Active: true, 1294 TrackingChannel: "latest/edge", 1295 Sequence: []*snap.SideInfo{si}, 1296 Current: snap.R(7), 1297 SnapType: "app", 1298 }) 1299 1300 chg := s.state.NewChange("refresh", "refresh a snap") 1301 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-base/stable"}, s.user.ID, snapstate.Flags{}) 1302 c.Assert(err, IsNil) 1303 chg.AddAll(ts) 1304 1305 s.state.Unlock() 1306 defer s.se.Stop() 1307 s.settle(c) 1308 s.state.Lock() 1309 1310 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1311 {macaroon: s.user.StoreMacaroon, name: "some-base", target: filepath.Join(dirs.SnapBlobDir, "some-base_11.snap")}, 1312 {macaroon: s.user.StoreMacaroon, name: "some-snap", target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap")}, 1313 }) 1314 } 1315 1316 func (s *snapmgrTestSuite) TestUpdateWithAlreadyInstalledBase(c *C) { 1317 si := &snap.SideInfo{ 1318 RealName: "some-snap", 1319 SnapID: "some-snap-id", 1320 Revision: snap.R(7), 1321 } 1322 snaptest.MockSnap(c, `name: some-snap`, si) 1323 1324 s.state.Lock() 1325 defer s.state.Unlock() 1326 1327 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1328 Active: true, 1329 TrackingChannel: "latest/edge", 1330 Sequence: []*snap.SideInfo{si}, 1331 Current: snap.R(7), 1332 SnapType: "app", 1333 }) 1334 snapstate.Set(s.state, "some-base", &snapstate.SnapState{ 1335 Active: true, 1336 TrackingChannel: "latest/stable", 1337 Sequence: []*snap.SideInfo{{ 1338 RealName: "some-base", 1339 SnapID: "some-base-id", 1340 Revision: snap.R(1), 1341 }}, 1342 Current: snap.R(1), 1343 SnapType: "base", 1344 }) 1345 1346 chg := s.state.NewChange("refresh", "refresh a snap") 1347 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "channel-for-base"}, s.user.ID, snapstate.Flags{}) 1348 c.Assert(err, IsNil) 1349 chg.AddAll(ts) 1350 1351 s.state.Unlock() 1352 defer s.se.Stop() 1353 s.settle(c) 1354 s.state.Lock() 1355 1356 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1357 {macaroon: s.user.StoreMacaroon, name: "some-snap", target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap")}, 1358 }) 1359 } 1360 1361 func (s *snapmgrTestSuite) TestUpdateWithNewDefaultProvider(c *C) { 1362 s.state.Lock() 1363 defer s.state.Unlock() 1364 1365 snapstate.ReplaceStore(s.state, contentStore{fakeStore: s.fakeStore, state: s.state}) 1366 repo := interfaces.NewRepository() 1367 ifacerepo.Replace(s.state, repo) 1368 1369 si := &snap.SideInfo{ 1370 RealName: "snap-content-plug", 1371 SnapID: "snap-content-plug-id", 1372 Revision: snap.R(7), 1373 } 1374 snaptest.MockSnap(c, `name: snap-content-plug`, si) 1375 snapstate.Set(s.state, "snap-content-plug", &snapstate.SnapState{ 1376 Active: true, 1377 TrackingChannel: "latest/edge", 1378 Sequence: []*snap.SideInfo{si}, 1379 Current: snap.R(7), 1380 SnapType: "app", 1381 }) 1382 1383 chg := s.state.NewChange("refresh", "refresh a snap") 1384 ts, err := snapstate.Update(s.state, "snap-content-plug", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{}) 1385 c.Assert(err, IsNil) 1386 chg.AddAll(ts) 1387 1388 s.state.Unlock() 1389 defer s.se.Stop() 1390 s.settle(c) 1391 s.state.Lock() 1392 1393 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1394 {macaroon: s.user.StoreMacaroon, name: "snap-content-plug", target: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_11.snap")}, 1395 {macaroon: s.user.StoreMacaroon, name: "snap-content-slot", target: filepath.Join(dirs.SnapBlobDir, "snap-content-slot_11.snap")}, 1396 }) 1397 } 1398 1399 func (s *snapmgrTestSuite) TestUpdateWithInstalledDefaultProvider(c *C) { 1400 s.state.Lock() 1401 defer s.state.Unlock() 1402 1403 snapstate.ReplaceStore(s.state, contentStore{fakeStore: s.fakeStore, state: s.state}) 1404 repo := interfaces.NewRepository() 1405 ifacerepo.Replace(s.state, repo) 1406 1407 si := &snap.SideInfo{ 1408 RealName: "snap-content-plug", 1409 SnapID: "snap-content-plug-id", 1410 Revision: snap.R(7), 1411 } 1412 snaptest.MockSnap(c, `name: snap-content-plug`, si) 1413 snapstate.Set(s.state, "snap-content-plug", &snapstate.SnapState{ 1414 Active: true, 1415 TrackingChannel: "latest/edge", 1416 Sequence: []*snap.SideInfo{si}, 1417 Current: snap.R(7), 1418 SnapType: "app", 1419 }) 1420 snapstate.Set(s.state, "snap-content-slot", &snapstate.SnapState{ 1421 Active: true, 1422 TrackingChannel: "latest/stable", 1423 Sequence: []*snap.SideInfo{{ 1424 RealName: "snap-content-slot", 1425 SnapID: "snap-content-slot-id", 1426 Revision: snap.R(1), 1427 }}, 1428 Current: snap.R(1), 1429 SnapType: "app", 1430 }) 1431 1432 chg := s.state.NewChange("refresh", "refresh a snap") 1433 ts, err := snapstate.Update(s.state, "snap-content-plug", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{}) 1434 c.Assert(err, IsNil) 1435 chg.AddAll(ts) 1436 1437 s.state.Unlock() 1438 defer s.se.Stop() 1439 s.settle(c) 1440 s.state.Lock() 1441 1442 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{ 1443 {macaroon: s.user.StoreMacaroon, name: "snap-content-plug", target: filepath.Join(dirs.SnapBlobDir, "snap-content-plug_11.snap")}, 1444 }) 1445 } 1446 1447 func (s *snapmgrTestSuite) TestUpdateRememberedUserRunThrough(c *C) { 1448 s.state.Lock() 1449 defer s.state.Unlock() 1450 1451 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1452 Active: true, 1453 Sequence: []*snap.SideInfo{ 1454 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1455 }, 1456 Current: snap.R(5), 1457 SnapType: "app", 1458 UserID: 1, 1459 }) 1460 1461 chg := s.state.NewChange("refresh", "refresh a snap") 1462 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, 0, snapstate.Flags{}) 1463 c.Assert(err, IsNil) 1464 chg.AddAll(ts) 1465 1466 s.state.Unlock() 1467 defer s.se.Stop() 1468 s.settle(c) 1469 s.state.Lock() 1470 1471 c.Assert(chg.Status(), Equals, state.DoneStatus) 1472 c.Assert(chg.Err(), IsNil) 1473 1474 for _, op := range s.fakeBackend.ops { 1475 switch op.op { 1476 case "storesvc-snap-action": 1477 c.Check(op.userID, Equals, 1) 1478 case "storesvc-download": 1479 snapName := op.name 1480 c.Check(s.fakeStore.downloads[0], DeepEquals, fakeDownload{ 1481 macaroon: "macaroon", 1482 name: "some-snap", 1483 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 1484 }, Commentf(snapName)) 1485 } 1486 } 1487 } 1488 1489 func (s *snapmgrTestSuite) TestUpdateModelKernelSwitchTrackRunThrough(c *C) { 1490 // use services-snap here to make sure services would be stopped/started appropriately 1491 si := snap.SideInfo{ 1492 RealName: "kernel", 1493 Revision: snap.R(7), 1494 SnapID: "kernel-id", 1495 } 1496 snaptest.MockSnap(c, `name: kernel`, &si) 1497 fi, err := os.Stat(snap.MountFile("kernel", si.Revision)) 1498 c.Assert(err, IsNil) 1499 refreshedDate := fi.ModTime() 1500 // look at disk 1501 r := snapstate.MockRevisionDate(nil) 1502 defer r() 1503 1504 s.state.Lock() 1505 defer s.state.Unlock() 1506 1507 r1 := snapstatetest.MockDeviceModel(ModelWithKernelTrack("18")) 1508 defer r1() 1509 snapstate.Set(s.state, "kernel", &snapstate.SnapState{ 1510 Active: true, 1511 Sequence: []*snap.SideInfo{&si}, 1512 Current: si.Revision, 1513 TrackingChannel: "18/stable", 1514 }) 1515 1516 chg := s.state.NewChange("refresh", "refresh a snap") 1517 ts, err := snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "edge"}, s.user.ID, snapstate.Flags{}) 1518 c.Assert(err, IsNil) 1519 chg.AddAll(ts) 1520 1521 s.state.Unlock() 1522 defer s.se.Stop() 1523 s.settle(c) 1524 s.state.Lock() 1525 1526 c.Check(chg.Status(), Equals, state.DoneStatus) 1527 1528 c.Assert(len(s.fakeBackend.ops) > 2, Equals, true) 1529 c.Assert(s.fakeBackend.ops[:2], DeepEquals, fakeOps{ 1530 { 1531 op: "storesvc-snap-action", 1532 curSnaps: []store.CurrentSnap{{ 1533 InstanceName: "kernel", 1534 SnapID: "kernel-id", 1535 Revision: snap.R(7), 1536 TrackingChannel: "18/stable", 1537 RefreshedDate: refreshedDate, 1538 Epoch: snap.E("1*"), 1539 }}, 1540 userID: 1, 1541 }, { 1542 op: "storesvc-snap-action:action", 1543 action: store.SnapAction{ 1544 Action: "refresh", 1545 InstanceName: "kernel", 1546 SnapID: "kernel-id", 1547 Channel: "18/edge", 1548 Flags: store.SnapActionEnforceValidation, 1549 }, 1550 revno: snap.R(11), 1551 userID: 1, 1552 }, 1553 }) 1554 1555 // check progress 1556 task := ts.Tasks()[1] 1557 _, cur, total := task.Progress() 1558 c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress) 1559 c.Assert(total, Equals, s.fakeStore.fakeTotalProgress) 1560 1561 // verify snapSetup info 1562 var snapsup snapstate.SnapSetup 1563 err = task.Get("snap-setup", &snapsup) 1564 c.Assert(err, IsNil) 1565 c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ 1566 Channel: "18/edge", 1567 UserID: s.user.ID, 1568 1569 SnapPath: filepath.Join(dirs.SnapBlobDir, "kernel_11.snap"), 1570 DownloadInfo: &snap.DownloadInfo{ 1571 DownloadURL: "https://some-server.com/some/path.snap", 1572 }, 1573 SideInfo: snapsup.SideInfo, 1574 Type: snap.TypeKernel, 1575 PlugsOnly: true, 1576 }) 1577 c.Assert(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ 1578 RealName: "kernel", 1579 Revision: snap.R(11), 1580 Channel: "18/edge", 1581 SnapID: "kernel-id", 1582 }) 1583 1584 // verify snaps in the system state 1585 var snapst snapstate.SnapState 1586 err = snapstate.Get(s.state, "kernel", &snapst) 1587 c.Assert(err, IsNil) 1588 1589 c.Assert(snapst.Active, Equals, true) 1590 c.Assert(snapst.TrackingChannel, Equals, "18/edge") 1591 c.Assert(snapst.Sequence, HasLen, 2) 1592 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 1593 RealName: "kernel", 1594 SnapID: "kernel-id", 1595 Channel: "", 1596 Revision: snap.R(7), 1597 }) 1598 c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ 1599 RealName: "kernel", 1600 Channel: "18/edge", 1601 SnapID: "kernel-id", 1602 Revision: snap.R(11), 1603 }) 1604 } 1605 1606 func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsNoUserRunThrough(c *C) { 1607 s.state.Lock() 1608 defer s.state.Unlock() 1609 1610 snapstate.Set(s.state, "core", &snapstate.SnapState{ 1611 Active: true, 1612 Sequence: []*snap.SideInfo{ 1613 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 1614 }, 1615 Current: snap.R(1), 1616 SnapType: "os", 1617 }) 1618 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1619 Active: true, 1620 Sequence: []*snap.SideInfo{ 1621 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1622 }, 1623 Current: snap.R(5), 1624 SnapType: "app", 1625 UserID: 1, 1626 }) 1627 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 1628 Active: true, 1629 Sequence: []*snap.SideInfo{ 1630 {RealName: "services-snap", Revision: snap.R(2), SnapID: "services-snap-id"}, 1631 }, 1632 Current: snap.R(2), 1633 SnapType: "app", 1634 UserID: 2, 1635 }) 1636 1637 chg := s.state.NewChange("refresh", "refresh all snaps") 1638 // no user is passed to use for UpdateMany 1639 updated, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 1640 c.Assert(err, IsNil) 1641 for _, ts := range tts { 1642 chg.AddAll(ts) 1643 } 1644 c.Check(updated, HasLen, 3) 1645 1646 s.state.Unlock() 1647 defer s.se.Stop() 1648 s.settle(c) 1649 s.state.Lock() 1650 1651 c.Assert(chg.Status(), Equals, state.DoneStatus) 1652 c.Assert(chg.Err(), IsNil) 1653 1654 macaroonMap := map[string]string{ 1655 "core": "", 1656 "some-snap": "macaroon", 1657 "services-snap": "macaroon2", 1658 } 1659 1660 seen := make(map[string]int) 1661 ir := 0 1662 di := 0 1663 for _, op := range s.fakeBackend.ops { 1664 switch op.op { 1665 case "storesvc-snap-action": 1666 ir++ 1667 c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{ 1668 { 1669 InstanceName: "core", 1670 SnapID: "core-snap-id", 1671 Revision: snap.R(1), 1672 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1), 1673 Epoch: snap.E("1*"), 1674 }, 1675 { 1676 InstanceName: "services-snap", 1677 SnapID: "services-snap-id", 1678 Revision: snap.R(2), 1679 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2), 1680 Epoch: snap.E("0"), 1681 }, 1682 { 1683 InstanceName: "some-snap", 1684 SnapID: "some-snap-id", 1685 Revision: snap.R(5), 1686 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5), 1687 Epoch: snap.E("1*"), 1688 }, 1689 }) 1690 case "storesvc-snap-action:action": 1691 snapID := op.action.SnapID 1692 seen[snapID] = op.userID 1693 case "storesvc-download": 1694 snapName := op.name 1695 fakeDl := s.fakeStore.downloads[di] 1696 // check target path separately and clear it 1697 c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName))) 1698 fakeDl.target = "" 1699 c.Check(fakeDl, DeepEquals, fakeDownload{ 1700 macaroon: macaroonMap[snapName], 1701 name: snapName, 1702 }, Commentf(snapName)) 1703 di++ 1704 } 1705 } 1706 c.Check(ir, Equals, 2) 1707 // we check all snaps with each user 1708 c.Check(seen["some-snap-id"], Equals, 1) 1709 c.Check(seen["services-snap-id"], Equals, 2) 1710 // coalesced with one of the others 1711 c.Check(seen["core-snap-id"] > 0, Equals, true) 1712 } 1713 1714 func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsUserRunThrough(c *C) { 1715 s.state.Lock() 1716 defer s.state.Unlock() 1717 1718 snapstate.Set(s.state, "core", &snapstate.SnapState{ 1719 Active: true, 1720 Sequence: []*snap.SideInfo{ 1721 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 1722 }, 1723 Current: snap.R(1), 1724 SnapType: "os", 1725 }) 1726 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1727 Active: true, 1728 Sequence: []*snap.SideInfo{ 1729 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1730 }, 1731 Current: snap.R(5), 1732 SnapType: "app", 1733 UserID: 1, 1734 }) 1735 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 1736 Active: true, 1737 Sequence: []*snap.SideInfo{ 1738 {RealName: "services-snap", Revision: snap.R(2), SnapID: "services-snap-id"}, 1739 }, 1740 Current: snap.R(2), 1741 SnapType: "app", 1742 UserID: 2, 1743 }) 1744 1745 chg := s.state.NewChange("refresh", "refresh all snaps") 1746 // do UpdateMany using user 2 as fallback 1747 updated, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 2, nil) 1748 c.Assert(err, IsNil) 1749 for _, ts := range tts { 1750 chg.AddAll(ts) 1751 } 1752 c.Check(updated, HasLen, 3) 1753 1754 s.state.Unlock() 1755 defer s.se.Stop() 1756 s.settle(c) 1757 s.state.Lock() 1758 1759 c.Assert(chg.Status(), Equals, state.DoneStatus) 1760 c.Assert(chg.Err(), IsNil) 1761 1762 macaroonMap := map[string]string{ 1763 "core": "macaroon2", 1764 "some-snap": "macaroon", 1765 "services-snap": "macaroon2", 1766 } 1767 1768 type snapIDuserID struct { 1769 snapID string 1770 userID int 1771 } 1772 seen := make(map[snapIDuserID]bool) 1773 ir := 0 1774 di := 0 1775 for _, op := range s.fakeBackend.ops { 1776 switch op.op { 1777 case "storesvc-snap-action": 1778 ir++ 1779 c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{ 1780 { 1781 InstanceName: "core", 1782 SnapID: "core-snap-id", 1783 Revision: snap.R(1), 1784 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1), 1785 Epoch: snap.E("1*"), 1786 }, 1787 { 1788 InstanceName: "services-snap", 1789 SnapID: "services-snap-id", 1790 Revision: snap.R(2), 1791 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2), 1792 Epoch: snap.E("0"), 1793 }, 1794 { 1795 InstanceName: "some-snap", 1796 SnapID: "some-snap-id", 1797 Revision: snap.R(5), 1798 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5), 1799 Epoch: snap.E("1*"), 1800 }, 1801 }) 1802 case "storesvc-snap-action:action": 1803 snapID := op.action.SnapID 1804 seen[snapIDuserID{snapID: snapID, userID: op.userID}] = true 1805 case "storesvc-download": 1806 snapName := op.name 1807 fakeDl := s.fakeStore.downloads[di] 1808 // check target path separately and clear it 1809 c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName))) 1810 fakeDl.target = "" 1811 c.Check(fakeDl, DeepEquals, fakeDownload{ 1812 macaroon: macaroonMap[snapName], 1813 name: snapName, 1814 }, Commentf(snapName)) 1815 di++ 1816 } 1817 } 1818 c.Check(ir, Equals, 2) 1819 // we check all snaps with each user 1820 c.Check(seen, DeepEquals, map[snapIDuserID]bool{ 1821 {snapID: "core-snap-id", userID: 2}: true, 1822 {snapID: "some-snap-id", userID: 1}: true, 1823 {snapID: "services-snap-id", userID: 2}: true, 1824 }) 1825 1826 var coreState, snapState snapstate.SnapState 1827 // user in SnapState was preserved 1828 err = snapstate.Get(s.state, "some-snap", &snapState) 1829 c.Assert(err, IsNil) 1830 c.Check(snapState.UserID, Equals, 1) 1831 c.Check(snapState.Current, DeepEquals, snap.R(11)) 1832 1833 // user in SnapState was set 1834 err = snapstate.Get(s.state, "core", &coreState) 1835 c.Assert(err, IsNil) 1836 c.Check(coreState.UserID, Equals, 2) 1837 c.Check(coreState.Current, DeepEquals, snap.R(11)) 1838 1839 } 1840 1841 func (s *snapmgrTestSuite) TestUpdateManyMultipleCredsUserWithNoStoreAuthRunThrough(c *C) { 1842 s.state.Lock() 1843 defer s.state.Unlock() 1844 1845 snapstate.Set(s.state, "core", &snapstate.SnapState{ 1846 Active: true, 1847 Sequence: []*snap.SideInfo{ 1848 {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"}, 1849 }, 1850 Current: snap.R(1), 1851 SnapType: "os", 1852 }) 1853 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1854 Active: true, 1855 Sequence: []*snap.SideInfo{ 1856 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 1857 }, 1858 Current: snap.R(5), 1859 SnapType: "app", 1860 UserID: 1, 1861 }) 1862 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 1863 Active: true, 1864 Sequence: []*snap.SideInfo{ 1865 {RealName: "services-snap", Revision: snap.R(2), SnapID: "services-snap-id"}, 1866 }, 1867 Current: snap.R(2), 1868 SnapType: "app", 1869 UserID: 3, 1870 }) 1871 1872 chg := s.state.NewChange("refresh", "refresh all snaps") 1873 // no user is passed to use for UpdateMany 1874 updated, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 1875 c.Assert(err, IsNil) 1876 for _, ts := range tts { 1877 chg.AddAll(ts) 1878 } 1879 c.Check(updated, HasLen, 3) 1880 1881 s.state.Unlock() 1882 defer s.se.Stop() 1883 s.settle(c) 1884 s.state.Lock() 1885 1886 c.Assert(chg.Status(), Equals, state.DoneStatus) 1887 c.Assert(chg.Err(), IsNil) 1888 1889 macaroonMap := map[string]string{ 1890 "core": "", 1891 "some-snap": "macaroon", 1892 "services-snap": "", 1893 } 1894 1895 seen := make(map[string]int) 1896 ir := 0 1897 di := 0 1898 for _, op := range s.fakeBackend.ops { 1899 switch op.op { 1900 case "storesvc-snap-action": 1901 ir++ 1902 c.Check(op.curSnaps, DeepEquals, []store.CurrentSnap{ 1903 { 1904 InstanceName: "core", 1905 SnapID: "core-snap-id", 1906 Revision: snap.R(1), 1907 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 1), 1908 Epoch: snap.E("1*"), 1909 }, 1910 { 1911 InstanceName: "services-snap", 1912 SnapID: "services-snap-id", 1913 Revision: snap.R(2), 1914 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 2), 1915 Epoch: snap.E("0"), 1916 }, 1917 { 1918 InstanceName: "some-snap", 1919 SnapID: "some-snap-id", 1920 Revision: snap.R(5), 1921 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 5), 1922 Epoch: snap.E("1*"), 1923 }, 1924 }) 1925 case "storesvc-snap-action:action": 1926 snapID := op.action.SnapID 1927 if _, ok := seen[snapID]; !ok { 1928 seen[snapID] = op.userID 1929 } 1930 case "storesvc-download": 1931 snapName := op.name 1932 fakeDl := s.fakeStore.downloads[di] 1933 // check target path separately and clear it 1934 c.Check(fakeDl.target, Matches, filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_[0-9]+.snap", snapName))) 1935 fakeDl.target = "" 1936 c.Check(fakeDl, DeepEquals, fakeDownload{ 1937 macaroon: macaroonMap[snapName], 1938 name: snapName, 1939 }, Commentf(snapName)) 1940 di++ 1941 } 1942 } 1943 c.Check(ir, Equals, 1) 1944 // we check all snaps with each user 1945 c.Check(seen["some-snap-id"], Equals, 1) 1946 // coalesced with request for 1 1947 c.Check(seen["services-snap-id"], Equals, 1) 1948 c.Check(seen["core-snap-id"], Equals, 1) 1949 } 1950 1951 func (s *snapmgrTestSuite) TestUpdateUndoRunThrough(c *C) { 1952 si := snap.SideInfo{ 1953 RealName: "some-snap", 1954 SnapID: "some-snap-id", 1955 Revision: snap.R(7), 1956 } 1957 1958 s.state.Lock() 1959 defer s.state.Unlock() 1960 1961 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1962 Active: true, 1963 Sequence: []*snap.SideInfo{&si}, 1964 Current: si.Revision, 1965 SnapType: "app", 1966 }) 1967 1968 chg := s.state.NewChange("install", "install a snap") 1969 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 1970 c.Assert(err, IsNil) 1971 chg.AddAll(ts) 1972 1973 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/some-snap/11") 1974 1975 s.state.Unlock() 1976 defer s.se.Stop() 1977 s.settle(c) 1978 s.state.Lock() 1979 1980 expected := fakeOps{ 1981 { 1982 op: "storesvc-snap-action", 1983 curSnaps: []store.CurrentSnap{{ 1984 InstanceName: "some-snap", 1985 SnapID: "some-snap-id", 1986 Revision: snap.R(7), 1987 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 1988 Epoch: snap.E("1*"), 1989 }}, 1990 userID: 1, 1991 }, 1992 { 1993 op: "storesvc-snap-action:action", 1994 action: store.SnapAction{ 1995 Action: "refresh", 1996 InstanceName: "some-snap", 1997 SnapID: "some-snap-id", 1998 Channel: "some-channel", 1999 Flags: store.SnapActionEnforceValidation, 2000 }, 2001 revno: snap.R(11), 2002 userID: 1, 2003 }, 2004 { 2005 op: "storesvc-download", 2006 name: "some-snap", 2007 }, 2008 { 2009 op: "validate-snap:Doing", 2010 name: "some-snap", 2011 revno: snap.R(11), 2012 }, 2013 { 2014 op: "current", 2015 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2016 }, 2017 { 2018 op: "open-snap-file", 2019 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2020 sinfo: snap.SideInfo{ 2021 RealName: "some-snap", 2022 SnapID: "some-snap-id", 2023 Channel: "some-channel", 2024 Revision: snap.R(11), 2025 }, 2026 }, 2027 { 2028 op: "setup-snap", 2029 name: "some-snap", 2030 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2031 revno: snap.R(11), 2032 }, 2033 { 2034 op: "remove-snap-aliases", 2035 name: "some-snap", 2036 }, 2037 { 2038 op: "unlink-snap", 2039 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2040 }, 2041 { 2042 op: "copy-data", 2043 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2044 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2045 }, 2046 { 2047 op: "setup-profiles:Doing", 2048 name: "some-snap", 2049 revno: snap.R(11), 2050 }, 2051 { 2052 op: "candidate", 2053 sinfo: snap.SideInfo{ 2054 RealName: "some-snap", 2055 SnapID: "some-snap-id", 2056 Channel: "some-channel", 2057 Revision: snap.R(11), 2058 }, 2059 }, 2060 { 2061 op: "link-snap.failed", 2062 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2063 }, 2064 { 2065 op: "unlink-snap", 2066 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2067 }, 2068 { 2069 op: "setup-profiles:Undoing", 2070 name: "some-snap", 2071 revno: snap.R(11), 2072 }, 2073 { 2074 op: "undo-copy-snap-data", 2075 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2076 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2077 }, 2078 { 2079 op: "link-snap", 2080 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2081 }, 2082 { 2083 op: "update-aliases", 2084 }, 2085 { 2086 op: "undo-setup-snap", 2087 name: "some-snap", 2088 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2089 stype: "app", 2090 }, 2091 { 2092 op: "remove-snap-dir", 2093 name: "some-snap", 2094 path: filepath.Join(dirs.SnapMountDir, "some-snap"), 2095 }, 2096 } 2097 2098 // ensure all our tasks ran 2099 c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ 2100 macaroon: s.user.StoreMacaroon, 2101 name: "some-snap", 2102 target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2103 }}) 2104 // start with an easier-to-read error if this fails: 2105 c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 2106 c.Assert(s.fakeBackend.ops, DeepEquals, expected) 2107 2108 // verify snaps in the system state 2109 var snapst snapstate.SnapState 2110 err = snapstate.Get(s.state, "some-snap", &snapst) 2111 c.Assert(err, IsNil) 2112 2113 c.Assert(snapst.Active, Equals, true) 2114 c.Assert(snapst.Sequence, HasLen, 1) 2115 c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ 2116 RealName: "some-snap", 2117 SnapID: "some-snap-id", 2118 Channel: "", 2119 Revision: snap.R(7), 2120 }) 2121 } 2122 2123 func lastWithLane(tasks []*state.Task) *state.Task { 2124 for i := len(tasks) - 1; i >= 0; i-- { 2125 if lanes := tasks[i].Lanes(); len(lanes) == 1 && lanes[0] != 0 { 2126 return tasks[i] 2127 } 2128 } 2129 return nil 2130 } 2131 2132 func (s *snapmgrTestSuite) TestUpdateUndoRestoresRevisionConfig(c *C) { 2133 var errorTaskExecuted bool 2134 2135 // overwrite error-trigger task handler with custom one for this test 2136 erroringHandler := func(task *state.Task, _ *tomb.Tomb) error { 2137 st := task.State() 2138 st.Lock() 2139 defer st.Unlock() 2140 2141 // modify current config of some-snap 2142 tr := config.NewTransaction(st) 2143 tr.Set("some-snap", "foo", "canary") 2144 tr.Commit() 2145 2146 errorTaskExecuted = true 2147 return errors.New("error out") 2148 } 2149 s.o.TaskRunner().AddHandler("error-trigger", erroringHandler, nil) 2150 2151 si := snap.SideInfo{ 2152 RealName: "some-snap", 2153 SnapID: "some-snap-id", 2154 Revision: snap.R(7), 2155 } 2156 si2 := snap.SideInfo{ 2157 RealName: "some-snap", 2158 SnapID: "some-snap-id", 2159 Revision: snap.R(6), 2160 } 2161 2162 s.state.Lock() 2163 defer s.state.Unlock() 2164 2165 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2166 Active: true, 2167 Sequence: []*snap.SideInfo{&si2, &si}, 2168 TrackingChannel: "latest/stable", 2169 Current: si.Revision, 2170 SnapType: "app", 2171 }) 2172 2173 // set some configuration 2174 tr := config.NewTransaction(s.state) 2175 tr.Set("some-snap", "foo", "revision 7 value") 2176 tr.Commit() 2177 config.SaveRevisionConfig(s.state, "some-snap", snap.R(7)) 2178 2179 chg := s.state.NewChange("install", "install a snap") 2180 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 2181 c.Assert(err, IsNil) 2182 chg.AddAll(ts) 2183 2184 last := lastWithLane(ts.Tasks()) 2185 c.Assert(last, NotNil) 2186 2187 terr := s.state.NewTask("error-trigger", "provoking total undo") 2188 terr.WaitFor(last) 2189 terr.JoinLane(last.Lanes()[0]) 2190 chg.AddTask(terr) 2191 2192 s.state.Unlock() 2193 defer s.se.Stop() 2194 s.settle(c) 2195 s.state.Lock() 2196 2197 c.Check(chg.Status(), Equals, state.ErrorStatus) 2198 c.Check(errorTaskExecuted, Equals, true) 2199 2200 // after undoing the update some-snap config should be restored to that of rev.7 2201 var val string 2202 tr = config.NewTransaction(s.state) 2203 c.Assert(tr.Get("some-snap", "foo", &val), IsNil) 2204 c.Check(val, Equals, "revision 7 value") 2205 } 2206 2207 func (s *snapmgrTestSuite) TestUpdateMakesConfigSnapshot(c *C) { 2208 s.state.Lock() 2209 defer s.state.Unlock() 2210 2211 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2212 Active: true, 2213 Sequence: []*snap.SideInfo{ 2214 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 2215 }, 2216 Current: snap.R(1), 2217 SnapType: "app", 2218 }) 2219 2220 tr := config.NewTransaction(s.state) 2221 tr.Set("some-snap", "foo", "bar") 2222 tr.Commit() 2223 2224 var cfgs map[string]interface{} 2225 // we don't have config snapshots yet 2226 c.Assert(s.state.Get("revision-config", &cfgs), Equals, state.ErrNoState) 2227 2228 chg := s.state.NewChange("update", "update a snap") 2229 opts := &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(2)} 2230 ts, err := snapstate.Update(s.state, "some-snap", opts, s.user.ID, snapstate.Flags{}) 2231 c.Assert(err, IsNil) 2232 chg.AddAll(ts) 2233 2234 s.state.Unlock() 2235 defer s.se.Stop() 2236 s.settle(c) 2237 2238 s.state.Lock() 2239 cfgs = nil 2240 // config copy of rev. 1 has been made 2241 c.Assert(s.state.Get("revision-config", &cfgs), IsNil) 2242 c.Assert(cfgs["some-snap"], DeepEquals, map[string]interface{}{ 2243 "1": map[string]interface{}{ 2244 "foo": "bar", 2245 }, 2246 }) 2247 } 2248 2249 func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) { 2250 si := snap.SideInfo{ 2251 RealName: "some-snap", 2252 SnapID: "some-snap-id", 2253 Revision: snap.R(7), 2254 } 2255 2256 s.state.Lock() 2257 defer s.state.Unlock() 2258 2259 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 2260 Active: true, 2261 Sequence: []*snap.SideInfo{&si}, 2262 TrackingChannel: "latest/stable", 2263 Current: si.Revision, 2264 SnapType: "app", 2265 }) 2266 2267 chg := s.state.NewChange("install", "install a snap") 2268 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 2269 c.Assert(err, IsNil) 2270 chg.AddAll(ts) 2271 2272 // We need to make it not be rerefresh, and we could do just 2273 // that but instead we do the 'right' thing and attach it to 2274 // the last task that's on a lane. 2275 last := lastWithLane(ts.Tasks()) 2276 c.Assert(last, NotNil) 2277 2278 terr := s.state.NewTask("error-trigger", "provoking total undo") 2279 terr.WaitFor(last) 2280 terr.JoinLane(last.Lanes()[0]) 2281 chg.AddTask(terr) 2282 2283 s.state.Unlock() 2284 defer s.se.Stop() 2285 s.settle(c) 2286 s.state.Lock() 2287 2288 expected := fakeOps{ 2289 { 2290 op: "storesvc-snap-action", 2291 curSnaps: []store.CurrentSnap{{ 2292 InstanceName: "some-snap", 2293 SnapID: "some-snap-id", 2294 Revision: snap.R(7), 2295 TrackingChannel: "latest/stable", 2296 RefreshedDate: fakeRevDateEpoch.AddDate(0, 0, 7), 2297 Epoch: snap.E("1*"), 2298 }}, 2299 userID: 1, 2300 }, 2301 { 2302 op: "storesvc-snap-action:action", 2303 action: store.SnapAction{ 2304 Action: "refresh", 2305 InstanceName: "some-snap", 2306 SnapID: "some-snap-id", 2307 Channel: "some-channel", 2308 Flags: store.SnapActionEnforceValidation, 2309 }, 2310 revno: snap.R(11), 2311 userID: 1, 2312 }, 2313 { 2314 op: "storesvc-download", 2315 name: "some-snap", 2316 }, 2317 { 2318 op: "validate-snap:Doing", 2319 name: "some-snap", 2320 revno: snap.R(11), 2321 }, 2322 { 2323 op: "current", 2324 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2325 }, 2326 { 2327 op: "open-snap-file", 2328 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2329 sinfo: snap.SideInfo{ 2330 RealName: "some-snap", 2331 SnapID: "some-snap-id", 2332 Channel: "some-channel", 2333 Revision: snap.R(11), 2334 }, 2335 }, 2336 { 2337 op: "setup-snap", 2338 name: "some-snap", 2339 path: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), 2340 revno: snap.R(11), 2341 }, 2342 { 2343 op: "remove-snap-aliases", 2344 name: "some-snap", 2345 }, 2346 { 2347 op: "unlink-snap", 2348 path: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2349 }, 2350 { 2351 op: "copy-data", 2352 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2353 old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), 2354 }, 2355 { 2356 op: "setup-profiles:Doing", 2357 name: "some-snap", 2358 revno: snap.R(11), 2359 }, 2360 { 2361 op: "candidate", 2362 sinfo: snap.SideInfo{ 2363 RealName: "some-snap", 2364 SnapID: "some-snap-id", 2365 Channel: "some-channel", 2366 Revision: snap.R(11), 2367 }, 2368 }, 2369 { 2370 op: "link-snap", 2371 path: filepath.Join(dirs.SnapMountDir, "some-snap/11"), 2372 }, 2373 { 2374 op: "auto-connect:Doing", 2375 name: "some-snap", 2376 revno: snap.R(11), 2377 }, 2378 { 2379 op: "update-aliases", 2380 }, 2381 // undoing everything from here down... 2382 { 2383 op: "remove-snap-aliases", 2384 name: "some-snap", 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 func (s *snapmgrTestSuite) TestUpdateManyPartialFailureCheckRerefreshDone(c *C) { 3460 s.state.Lock() 3461 defer s.state.Unlock() 3462 3463 snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil } 3464 makeTestRefreshConfig(s.state) 3465 3466 var someSnapValidation bool 3467 3468 // override validate-snap handler set by AddForeignTaskHandlers. 3469 s.o.TaskRunner().AddHandler("validate-snap", func(t *state.Task, _ *tomb.Tomb) error { 3470 t.State().Lock() 3471 defer t.State().Unlock() 3472 snapsup, err := snapstate.TaskSnapSetup(t) 3473 c.Assert(err, IsNil) 3474 if snapsup.SnapName() == "some-snap" { 3475 someSnapValidation = true 3476 return fmt.Errorf("boom") 3477 } 3478 return nil 3479 }, nil) 3480 3481 snapstate.Set(s.state, "some-other-snap", &snapstate.SnapState{ 3482 Active: true, 3483 Sequence: []*snap.SideInfo{ 3484 {RealName: "some-other-snap", SnapID: "some-other-snap-id", Revision: snap.R(1)}, 3485 }, 3486 Current: snap.R(1), 3487 SnapType: "app", 3488 }) 3489 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3490 Active: true, 3491 Sequence: []*snap.SideInfo{ 3492 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 3493 }, 3494 Current: snap.R(1), 3495 SnapType: "app", 3496 }) 3497 3498 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 3499 c.Check(refreshes, HasLen, 2) 3500 c.Check(ignoreValidation, HasLen, 0) 3501 return refreshes, nil 3502 } 3503 // hook it up 3504 snapstate.ValidateRefreshes = validateRefreshes 3505 3506 s.state.Unlock() 3507 s.snapmgr.Ensure() 3508 s.state.Lock() 3509 3510 c.Assert(s.state.Changes(), HasLen, 1) 3511 chg := s.state.Changes()[0] 3512 c.Check(chg.Kind(), Equals, "auto-refresh") 3513 c.Check(chg.IsReady(), Equals, false) 3514 s.verifyRefreshLast(c) 3515 3516 checkIsAutoRefresh(c, chg.Tasks(), true) 3517 3518 s.state.Unlock() 3519 defer s.se.Stop() 3520 s.settle(c) 3521 s.state.Lock() 3522 3523 // not updated 3524 var snapst snapstate.SnapState 3525 c.Assert(snapstate.Get(s.state, "some-snap", &snapst), IsNil) 3526 c.Check(snapst.Current, Equals, snap.Revision{N: 1}) 3527 3528 // updated 3529 c.Assert(snapstate.Get(s.state, "some-other-snap", &snapst), IsNil) 3530 c.Check(snapst.Current, Equals, snap.Revision{N: 11}) 3531 3532 c.Assert(chg.Err(), ErrorMatches, "cannot perform the following tasks:\n.*Fetch and check assertions for snap \"some-snap\" \\(11\\) \\(boom\\)") 3533 c.Assert(chg.IsReady(), Equals, true) 3534 3535 // check-rerefresh is last 3536 tasks := chg.Tasks() 3537 checkRerefresh := tasks[len(tasks)-1] 3538 c.Check(checkRerefresh.Kind(), Equals, "check-rerefresh") 3539 c.Check(checkRerefresh.Status(), Equals, state.DoneStatus) 3540 3541 // sanity 3542 c.Check(someSnapValidation, Equals, true) 3543 } 3544 3545 var orthogonalAutoAliasesScenarios = []struct { 3546 aliasesBefore map[string][]string 3547 names []string 3548 prune []string 3549 update bool 3550 new bool 3551 }{ 3552 {nil, nil, nil, true, true}, 3553 {nil, []string{"some-snap"}, nil, true, false}, 3554 {nil, []string{"other-snap"}, nil, false, true}, 3555 {map[string][]string{"some-snap": {"aliasA", "aliasC"}}, []string{"some-snap"}, nil, true, false}, 3556 {map[string][]string{"other-snap": {"aliasB", "aliasC"}}, []string{"other-snap"}, []string{"other-snap"}, false, false}, 3557 {map[string][]string{"other-snap": {"aliasB", "aliasC"}}, nil, []string{"other-snap"}, true, false}, 3558 {map[string][]string{"other-snap": {"aliasB", "aliasC"}}, []string{"some-snap"}, nil, true, false}, 3559 {map[string][]string{"other-snap": {"aliasC"}}, []string{"other-snap"}, []string{"other-snap"}, false, true}, 3560 {map[string][]string{"other-snap": {"aliasC"}}, nil, []string{"other-snap"}, true, true}, 3561 {map[string][]string{"other-snap": {"aliasC"}}, []string{"some-snap"}, nil, true, false}, 3562 {map[string][]string{"some-snap": {"aliasB"}, "other-snap": {"aliasA"}}, []string{"some-snap"}, []string{"other-snap"}, true, false}, 3563 {map[string][]string{"some-snap": {"aliasB"}, "other-snap": {"aliasA"}}, nil, []string{"other-snap", "some-snap"}, true, true}, 3564 {map[string][]string{"some-snap": {"aliasB"}, "other-snap": {"aliasA"}}, []string{"other-snap"}, []string{"other-snap", "some-snap"}, false, true}, 3565 {map[string][]string{"some-snap": {"aliasB"}}, nil, []string{"some-snap"}, true, true}, 3566 {map[string][]string{"some-snap": {"aliasB"}}, []string{"other-snap"}, []string{"some-snap"}, false, true}, 3567 {map[string][]string{"some-snap": {"aliasB"}}, []string{"some-snap"}, nil, true, false}, 3568 {map[string][]string{"other-snap": {"aliasA"}}, nil, []string{"other-snap"}, true, true}, 3569 {map[string][]string{"other-snap": {"aliasA"}}, []string{"other-snap"}, []string{"other-snap"}, false, true}, 3570 {map[string][]string{"other-snap": {"aliasA"}}, []string{"some-snap"}, []string{"other-snap"}, true, false}, 3571 } 3572 3573 func (s *snapmgrTestSuite) TestUpdateManyAutoAliasesScenarios(c *C) { 3574 s.state.Lock() 3575 defer s.state.Unlock() 3576 3577 snapstate.Set(s.state, "other-snap", &snapstate.SnapState{ 3578 Active: true, 3579 Sequence: []*snap.SideInfo{ 3580 {RealName: "other-snap", SnapID: "other-snap-id", Revision: snap.R(2)}, 3581 }, 3582 Current: snap.R(2), 3583 SnapType: "app", 3584 }) 3585 3586 snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) { 3587 switch info.InstanceName() { 3588 case "some-snap": 3589 return map[string]string{"aliasA": "cmdA"}, nil 3590 case "other-snap": 3591 return map[string]string{"aliasB": "cmdB"}, nil 3592 } 3593 return nil, nil 3594 } 3595 3596 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3597 Active: true, 3598 Sequence: []*snap.SideInfo{ 3599 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 3600 }, 3601 Current: snap.R(4), 3602 SnapType: "app", 3603 }) 3604 3605 expectedSet := func(aliases []string) map[string]bool { 3606 res := make(map[string]bool, len(aliases)) 3607 for _, alias := range aliases { 3608 res[alias] = true 3609 } 3610 return res 3611 } 3612 3613 for _, scenario := range orthogonalAutoAliasesScenarios { 3614 for _, instanceName := range []string{"some-snap", "other-snap"} { 3615 var snapst snapstate.SnapState 3616 err := snapstate.Get(s.state, instanceName, &snapst) 3617 c.Assert(err, IsNil) 3618 snapst.Aliases = nil 3619 snapst.AutoAliasesDisabled = false 3620 if autoAliases := scenario.aliasesBefore[instanceName]; autoAliases != nil { 3621 targets := make(map[string]*snapstate.AliasTarget) 3622 for _, alias := range autoAliases { 3623 targets[alias] = &snapstate.AliasTarget{Auto: "cmd" + alias[len(alias)-1:]} 3624 } 3625 3626 snapst.Aliases = targets 3627 } 3628 snapstate.Set(s.state, instanceName, &snapst) 3629 } 3630 3631 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, scenario.names, s.user.ID, nil) 3632 c.Check(err, IsNil) 3633 if scenario.update { 3634 verifyLastTasksetIsReRefresh(c, tts) 3635 } 3636 3637 _, dropped, err := snapstate.AutoAliasesDelta(s.state, []string{"some-snap", "other-snap"}) 3638 c.Assert(err, IsNil) 3639 3640 j := 0 3641 expectedUpdatesSet := make(map[string]bool) 3642 var expectedPruned map[string]map[string]bool 3643 var pruneTs *state.TaskSet 3644 if len(scenario.prune) != 0 { 3645 pruneTs = tts[0] 3646 j++ 3647 taskAliases := make(map[string]map[string]bool) 3648 for _, aliasTask := range pruneTs.Tasks() { 3649 c.Check(aliasTask.Kind(), Equals, "prune-auto-aliases") 3650 var aliases []string 3651 err := aliasTask.Get("aliases", &aliases) 3652 c.Assert(err, IsNil) 3653 snapsup, err := snapstate.TaskSnapSetup(aliasTask) 3654 c.Assert(err, IsNil) 3655 taskAliases[snapsup.InstanceName()] = expectedSet(aliases) 3656 } 3657 expectedPruned = make(map[string]map[string]bool) 3658 for _, instanceName := range scenario.prune { 3659 expectedPruned[instanceName] = expectedSet(dropped[instanceName]) 3660 if instanceName == "other-snap" && !scenario.new && !scenario.update { 3661 expectedUpdatesSet["other-snap"] = true 3662 } 3663 } 3664 c.Check(taskAliases, DeepEquals, expectedPruned) 3665 } 3666 if scenario.update { 3667 updateTs := tts[j] 3668 j++ 3669 expectedUpdatesSet["some-snap"] = true 3670 first := updateTs.Tasks()[0] 3671 c.Check(first.Kind(), Equals, "prerequisites") 3672 wait := false 3673 if expectedPruned["other-snap"]["aliasA"] { 3674 wait = true 3675 } else if expectedPruned["some-snap"] != nil { 3676 wait = true 3677 } 3678 if wait { 3679 c.Check(first.WaitTasks(), DeepEquals, pruneTs.Tasks()) 3680 } else { 3681 c.Check(first.WaitTasks(), HasLen, 0) 3682 } 3683 } 3684 if scenario.new { 3685 newTs := tts[j] 3686 j++ 3687 expectedUpdatesSet["other-snap"] = true 3688 tasks := newTs.Tasks() 3689 c.Check(tasks, HasLen, 1) 3690 aliasTask := tasks[0] 3691 c.Check(aliasTask.Kind(), Equals, "refresh-aliases") 3692 3693 wait := false 3694 if expectedPruned["some-snap"]["aliasB"] { 3695 wait = true 3696 } else if expectedPruned["other-snap"] != nil { 3697 wait = true 3698 } 3699 if wait { 3700 c.Check(aliasTask.WaitTasks(), DeepEquals, pruneTs.Tasks()) 3701 } else { 3702 c.Check(aliasTask.WaitTasks(), HasLen, 0) 3703 } 3704 } 3705 l := len(tts) 3706 if scenario.update { 3707 l-- 3708 } 3709 c.Assert(j, Equals, l, Commentf("%#v", scenario)) 3710 3711 // check reported updated names 3712 c.Check(len(updates) > 0, Equals, true) 3713 sort.Strings(updates) 3714 expectedUpdates := make([]string, 0, len(expectedUpdatesSet)) 3715 for x := range expectedUpdatesSet { 3716 expectedUpdates = append(expectedUpdates, x) 3717 } 3718 sort.Strings(expectedUpdates) 3719 c.Check(updates, DeepEquals, expectedUpdates) 3720 } 3721 } 3722 3723 func (s *snapmgrTestSuite) TestUpdateOneAutoAliasesScenarios(c *C) { 3724 s.state.Lock() 3725 defer s.state.Unlock() 3726 3727 snapstate.Set(s.state, "other-snap", &snapstate.SnapState{ 3728 Active: true, 3729 Sequence: []*snap.SideInfo{ 3730 {RealName: "other-snap", SnapID: "other-snap-id", Revision: snap.R(2)}, 3731 }, 3732 Current: snap.R(2), 3733 SnapType: "app", 3734 }) 3735 3736 snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) { 3737 switch info.InstanceName() { 3738 case "some-snap": 3739 return map[string]string{"aliasA": "cmdA"}, nil 3740 case "other-snap": 3741 return map[string]string{"aliasB": "cmdB"}, nil 3742 } 3743 return nil, nil 3744 } 3745 3746 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3747 Active: true, 3748 Sequence: []*snap.SideInfo{ 3749 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 3750 }, 3751 Current: snap.R(4), 3752 SnapType: "app", 3753 }) 3754 3755 expectedSet := func(aliases []string) map[string]bool { 3756 res := make(map[string]bool, len(aliases)) 3757 for _, alias := range aliases { 3758 res[alias] = true 3759 } 3760 return res 3761 } 3762 3763 for _, scenario := range orthogonalAutoAliasesScenarios { 3764 if len(scenario.names) != 1 { 3765 continue 3766 } 3767 3768 for _, instanceName := range []string{"some-snap", "other-snap"} { 3769 var snapst snapstate.SnapState 3770 err := snapstate.Get(s.state, instanceName, &snapst) 3771 c.Assert(err, IsNil) 3772 snapst.Aliases = nil 3773 snapst.AutoAliasesDisabled = false 3774 if autoAliases := scenario.aliasesBefore[instanceName]; autoAliases != nil { 3775 targets := make(map[string]*snapstate.AliasTarget) 3776 for _, alias := range autoAliases { 3777 targets[alias] = &snapstate.AliasTarget{Auto: "cmd" + alias[len(alias)-1:]} 3778 } 3779 3780 snapst.Aliases = targets 3781 } 3782 snapstate.Set(s.state, instanceName, &snapst) 3783 } 3784 3785 ts, err := snapstate.Update(s.state, scenario.names[0], nil, s.user.ID, snapstate.Flags{}) 3786 c.Assert(err, IsNil) 3787 _, dropped, err := snapstate.AutoAliasesDelta(s.state, []string{"some-snap", "other-snap"}) 3788 c.Assert(err, IsNil) 3789 3790 j := 0 3791 3792 tasks := ts.Tasks() 3793 // make sure the last task from Update is the rerefresh 3794 if scenario.update { 3795 reRefresh := tasks[len(tasks)-1] 3796 c.Check(reRefresh.Kind(), Equals, "check-rerefresh") 3797 // nothing should wait on it 3798 c.Check(reRefresh.NumHaltTasks(), Equals, 0) 3799 tasks = tasks[:len(tasks)-1] // and now forget about it 3800 } 3801 3802 var expectedPruned map[string]map[string]bool 3803 var pruneTasks []*state.Task 3804 if len(scenario.prune) != 0 { 3805 nprune := len(scenario.prune) 3806 pruneTasks = tasks[:nprune] 3807 j += nprune 3808 taskAliases := make(map[string]map[string]bool) 3809 for _, aliasTask := range pruneTasks { 3810 c.Check(aliasTask.Kind(), Equals, "prune-auto-aliases") 3811 var aliases []string 3812 err := aliasTask.Get("aliases", &aliases) 3813 c.Assert(err, IsNil) 3814 snapsup, err := snapstate.TaskSnapSetup(aliasTask) 3815 c.Assert(err, IsNil) 3816 taskAliases[snapsup.InstanceName()] = expectedSet(aliases) 3817 } 3818 expectedPruned = make(map[string]map[string]bool) 3819 for _, instanceName := range scenario.prune { 3820 expectedPruned[instanceName] = expectedSet(dropped[instanceName]) 3821 } 3822 c.Check(taskAliases, DeepEquals, expectedPruned) 3823 } 3824 if scenario.update { 3825 first := tasks[j] 3826 j += 19 3827 c.Check(first.Kind(), Equals, "prerequisites") 3828 wait := false 3829 if expectedPruned["other-snap"]["aliasA"] { 3830 wait = true 3831 } else if expectedPruned["some-snap"] != nil { 3832 wait = true 3833 } 3834 if wait { 3835 c.Check(first.WaitTasks(), DeepEquals, pruneTasks) 3836 } else { 3837 c.Check(first.WaitTasks(), HasLen, 0) 3838 } 3839 } 3840 if scenario.new { 3841 aliasTask := tasks[j] 3842 j++ 3843 c.Check(aliasTask.Kind(), Equals, "refresh-aliases") 3844 wait := false 3845 if expectedPruned["some-snap"]["aliasB"] { 3846 wait = true 3847 } else if expectedPruned["other-snap"] != nil { 3848 wait = true 3849 } 3850 if wait { 3851 c.Check(aliasTask.WaitTasks(), DeepEquals, pruneTasks) 3852 } else { 3853 c.Check(aliasTask.WaitTasks(), HasLen, 0) 3854 } 3855 } 3856 c.Assert(len(tasks), Equals, j, Commentf("%#v", scenario)) 3857 3858 // conflict checks are triggered 3859 chg := s.state.NewChange("update", "...") 3860 chg.AddAll(ts) 3861 err = snapstate.CheckChangeConflict(s.state, scenario.names[0], nil) 3862 c.Check(err, ErrorMatches, `.* has "update" change in progress`) 3863 chg.SetStatus(state.DoneStatus) 3864 } 3865 } 3866 3867 func (s *snapmgrTestSuite) TestUpdateLocalSnapFails(c *C) { 3868 si := snap.SideInfo{ 3869 RealName: "some-snap", 3870 Revision: snap.R(7), 3871 } 3872 3873 s.state.Lock() 3874 defer s.state.Unlock() 3875 3876 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3877 Active: true, 3878 Sequence: []*snap.SideInfo{&si}, 3879 Current: si.Revision, 3880 }) 3881 3882 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 3883 c.Assert(err, Equals, store.ErrLocalSnap) 3884 } 3885 3886 func (s *snapmgrTestSuite) TestUpdateDisabledUnsupported(c *C) { 3887 si := snap.SideInfo{ 3888 RealName: "some-snap", 3889 SnapID: "some-snap-id", 3890 Revision: snap.R(7), 3891 } 3892 3893 s.state.Lock() 3894 defer s.state.Unlock() 3895 3896 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3897 Active: false, 3898 Sequence: []*snap.SideInfo{&si}, 3899 Current: si.Revision, 3900 }) 3901 3902 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 3903 c.Assert(err, ErrorMatches, `refreshing disabled snap "some-snap" not supported`) 3904 } 3905 3906 func (s *snapmgrTestSuite) TestUpdateKernelTrackChecksSwitchingTracks(c *C) { 3907 si := snap.SideInfo{ 3908 RealName: "kernel", 3909 SnapID: "kernel-id", 3910 Revision: snap.R(7), 3911 } 3912 3913 s.state.Lock() 3914 defer s.state.Unlock() 3915 3916 r := snapstatetest.MockDeviceModel(ModelWithKernelTrack("18")) 3917 defer r() 3918 snapstate.Set(s.state, "kernel", &snapstate.SnapState{ 3919 Active: true, 3920 Sequence: []*snap.SideInfo{&si}, 3921 Current: si.Revision, 3922 TrackingChannel: "18/stable", 3923 }) 3924 3925 // switching tracks is not ok 3926 _, err := snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "new-channel"}, s.user.ID, snapstate.Flags{}) 3927 c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "new-channel"`) 3928 3929 // no change to the channel is ok 3930 _, err = snapstate.Update(s.state, "kernel", nil, s.user.ID, snapstate.Flags{}) 3931 c.Assert(err, IsNil) 3932 3933 // switching risk level is ok 3934 _, err = snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "18/beta"}, s.user.ID, snapstate.Flags{}) 3935 c.Assert(err, IsNil) 3936 3937 // switching just risk within the pinned track is ok 3938 _, err = snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "beta"}, s.user.ID, snapstate.Flags{}) 3939 c.Assert(err, IsNil) 3940 } 3941 3942 func (s *snapmgrTestSuite) TestUpdateGadgetTrackChecksSwitchingTracks(c *C) { 3943 si := snap.SideInfo{ 3944 RealName: "brand-gadget", 3945 SnapID: "brand-gadget-id", 3946 Revision: snap.R(7), 3947 } 3948 3949 s.state.Lock() 3950 defer s.state.Unlock() 3951 3952 r := snapstatetest.MockDeviceModel(ModelWithGadgetTrack("18")) 3953 defer r() 3954 snapstate.Set(s.state, "brand-gadget", &snapstate.SnapState{ 3955 Active: true, 3956 Sequence: []*snap.SideInfo{&si}, 3957 Current: si.Revision, 3958 TrackingChannel: "18/stable", 3959 }) 3960 3961 // switching tracks is not ok 3962 _, err := snapstate.Update(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "new-channel"}, s.user.ID, snapstate.Flags{}) 3963 c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "new-channel"`) 3964 3965 // no change to the channel is ok 3966 _, err = snapstate.Update(s.state, "brand-gadget", nil, s.user.ID, snapstate.Flags{}) 3967 c.Assert(err, IsNil) 3968 3969 // switching risk level is ok 3970 _, err = snapstate.Update(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "18/beta"}, s.user.ID, snapstate.Flags{}) 3971 c.Assert(err, IsNil) 3972 3973 // switching just risk within the pinned track is ok 3974 _, err = snapstate.Update(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "beta"}, s.user.ID, snapstate.Flags{}) 3975 c.Assert(err, IsNil) 3976 3977 } 3978 3979 func (s *snapmgrTestSuite) TestUpdateWithDeviceContext(c *C) { 3980 s.state.Lock() 3981 defer s.state.Unlock() 3982 3983 // unset the global store, it will need to come via the device context 3984 snapstate.ReplaceStore(s.state, nil) 3985 3986 deviceCtx := &snapstatetest.TrivialDeviceContext{ 3987 DeviceModel: DefaultModel(), 3988 CtxStore: s.fakeStore, 3989 } 3990 3991 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 3992 Active: true, 3993 TrackingChannel: "latest/edge", 3994 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 3995 Current: snap.R(7), 3996 SnapType: "app", 3997 }) 3998 3999 validateCalled := false 4000 happyValidateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx1 snapstate.DeviceContext) ([]*snap.Info, error) { 4001 c.Check(deviceCtx1, Equals, deviceCtx) 4002 validateCalled = true 4003 return refreshes, nil 4004 } 4005 // hook it up 4006 snapstate.ValidateRefreshes = happyValidateRefreshes 4007 4008 ts, err := snapstate.UpdateWithDeviceContext(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}, deviceCtx, "") 4009 c.Assert(err, IsNil) 4010 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 0, ts, s.state) 4011 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 4012 4013 c.Check(validateCalled, Equals, true) 4014 } 4015 4016 func (s *snapmgrTestSuite) TestUpdateWithDeviceContextToRevision(c *C) { 4017 s.state.Lock() 4018 defer s.state.Unlock() 4019 4020 // unset the global store, it will need to come via the device context 4021 snapstate.ReplaceStore(s.state, nil) 4022 4023 deviceCtx := &snapstatetest.TrivialDeviceContext{ 4024 DeviceModel: DefaultModel(), 4025 CtxStore: s.fakeStore, 4026 } 4027 4028 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4029 Active: true, 4030 Sequence: []*snap.SideInfo{ 4031 {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, 4032 }, 4033 Current: snap.R(5), 4034 SnapType: "app", 4035 UserID: 1, 4036 }) 4037 4038 opts := &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(11)} 4039 ts, err := snapstate.UpdateWithDeviceContext(s.state, "some-snap", opts, 0, snapstate.Flags{}, deviceCtx, "") 4040 c.Assert(err, IsNil) 4041 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 0, ts, s.state) 4042 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 4043 } 4044 4045 func (s *snapmgrTestSuite) TestUpdateTasksCoreSetsIgnoreOnConfigure(c *C) { 4046 s.state.Lock() 4047 defer s.state.Unlock() 4048 4049 snapstate.Set(s.state, "core", &snapstate.SnapState{ 4050 Active: true, 4051 TrackingChannel: "latest/edge", 4052 Sequence: []*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(7)}}, 4053 Current: snap.R(7), 4054 SnapType: "os", 4055 }) 4056 4057 oldConfigure := snapstate.Configure 4058 defer func() { snapstate.Configure = oldConfigure }() 4059 4060 var configureFlags int 4061 snapstate.Configure = func(st *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet { 4062 configureFlags = flags 4063 return state.NewTaskSet() 4064 } 4065 4066 _, err := snapstate.Update(s.state, "core", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 4067 c.Assert(err, IsNil) 4068 4069 // ensure the core snap sets the "ignore-hook-error" flag 4070 c.Check(configureFlags&snapstate.IgnoreHookError, Equals, 1) 4071 } 4072 4073 func (s *snapmgrTestSuite) TestUpdateDevModeConfinementFiltering(c *C) { 4074 restore := maybeMockClassicSupport(c) 4075 defer restore() 4076 4077 s.state.Lock() 4078 defer s.state.Unlock() 4079 4080 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4081 Active: true, 4082 TrackingChannel: "channel-for-devmode/stable", 4083 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4084 Current: snap.R(7), 4085 SnapType: "app", 4086 }) 4087 4088 // updated snap is devmode, refresh without --devmode, do nothing 4089 // TODO: better error message here 4090 _, err := snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{}) 4091 c.Assert(err, ErrorMatches, `.* requires devmode or confinement override`) 4092 4093 // updated snap is devmode, refresh with --devmode 4094 _, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{DevMode: true}) 4095 c.Assert(err, IsNil) 4096 } 4097 4098 func (s *snapmgrTestSuite) TestUpdateClassicConfinementFiltering(c *C) { 4099 restore := maybeMockClassicSupport(c) 4100 defer restore() 4101 4102 s.state.Lock() 4103 defer s.state.Unlock() 4104 4105 snapstate.Set(s.state, "some-snap-now-classic", &snapstate.SnapState{ 4106 Active: true, 4107 Sequence: []*snap.SideInfo{{RealName: "some-snap-now-classic", SnapID: "some-snap-now-classic-id", Revision: snap.R(7)}}, 4108 Current: snap.R(7), 4109 SnapType: "app", 4110 }) 4111 4112 // updated snap is classic, refresh without --classic, do nothing 4113 // TODO: better error message here 4114 _, err := snapstate.Update(s.state, "some-snap-now-classic", nil, s.user.ID, snapstate.Flags{}) 4115 c.Assert(err, ErrorMatches, `.* requires classic confinement`) 4116 4117 // updated snap is classic, refresh with --classic 4118 ts, err := snapstate.Update(s.state, "some-snap-now-classic", nil, s.user.ID, snapstate.Flags{Classic: true}) 4119 c.Assert(err, IsNil) 4120 4121 chg := s.state.NewChange("refresh", "refresh snap") 4122 chg.AddAll(ts) 4123 4124 s.state.Unlock() 4125 defer s.se.Stop() 4126 s.settle(c) 4127 s.state.Lock() 4128 4129 c.Assert(chg.Err(), IsNil) 4130 c.Assert(chg.IsReady(), Equals, true) 4131 4132 // verify snap is in classic 4133 var snapst snapstate.SnapState 4134 err = snapstate.Get(s.state, "some-snap-now-classic", &snapst) 4135 c.Assert(err, IsNil) 4136 c.Check(snapst.Classic, Equals, true) 4137 } 4138 4139 func (s *snapmgrTestSuite) TestUpdateClassicFromClassic(c *C) { 4140 restore := maybeMockClassicSupport(c) 4141 defer restore() 4142 4143 s.state.Lock() 4144 defer s.state.Unlock() 4145 4146 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4147 Active: true, 4148 TrackingChannel: "channel-for-classic/stable", 4149 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4150 Current: snap.R(7), 4151 SnapType: "app", 4152 Flags: snapstate.Flags{Classic: true}, 4153 }) 4154 4155 // snap installed with --classic, update needs classic, refresh with --classic works 4156 ts, err := snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{Classic: true}) 4157 c.Assert(err, IsNil) 4158 c.Assert(ts.Tasks(), Not(HasLen), 0) 4159 snapsup, err := snapstate.TaskSnapSetup(ts.Tasks()[0]) 4160 c.Assert(err, IsNil) 4161 c.Check(snapsup.Flags.Classic, Equals, true) 4162 4163 // devmode overrides the snapsetup classic flag 4164 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{DevMode: true}) 4165 c.Assert(err, IsNil) 4166 c.Assert(ts.Tasks(), Not(HasLen), 0) 4167 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4168 c.Assert(err, IsNil) 4169 c.Check(snapsup.Flags.Classic, Equals, false) 4170 4171 // jailmode overrides it too (you need to provide both) 4172 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{JailMode: true}) 4173 c.Assert(err, IsNil) 4174 c.Assert(ts.Tasks(), Not(HasLen), 0) 4175 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4176 c.Assert(err, IsNil) 4177 c.Check(snapsup.Flags.Classic, Equals, false) 4178 4179 // jailmode and classic together gets you both 4180 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{JailMode: true, Classic: true}) 4181 c.Assert(err, IsNil) 4182 c.Assert(ts.Tasks(), Not(HasLen), 0) 4183 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4184 c.Assert(err, IsNil) 4185 c.Check(snapsup.Flags.Classic, Equals, true) 4186 4187 // snap installed with --classic, update needs classic, refresh without --classic works 4188 ts, err = snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{}) 4189 c.Assert(err, IsNil) 4190 c.Assert(ts.Tasks(), Not(HasLen), 0) 4191 snapsup, err = snapstate.TaskSnapSetup(ts.Tasks()[0]) 4192 c.Assert(err, IsNil) 4193 c.Check(snapsup.Flags.Classic, Equals, true) 4194 4195 chg := s.state.NewChange("refresh", "refresh snap") 4196 chg.AddAll(ts) 4197 4198 s.state.Unlock() 4199 defer s.se.Stop() 4200 s.settle(c) 4201 s.state.Lock() 4202 4203 // verify snap is in classic 4204 var snapst snapstate.SnapState 4205 err = snapstate.Get(s.state, "some-snap", &snapst) 4206 c.Assert(err, IsNil) 4207 c.Check(snapst.Classic, Equals, true) 4208 } 4209 4210 func (s *snapmgrTestSuite) TestUpdateStrictFromClassic(c *C) { 4211 restore := maybeMockClassicSupport(c) 4212 defer restore() 4213 4214 s.state.Lock() 4215 defer s.state.Unlock() 4216 4217 snapstate.Set(s.state, "some-snap-was-classic", &snapstate.SnapState{ 4218 Active: true, 4219 TrackingChannel: "channel/stable", 4220 Sequence: []*snap.SideInfo{{RealName: "some-snap-was-classic", SnapID: "some-snap-was-classic-id", Revision: snap.R(7)}}, 4221 Current: snap.R(7), 4222 SnapType: "app", 4223 Flags: snapstate.Flags{Classic: true}, 4224 }) 4225 4226 // snap installed with --classic, update does not need classic, refresh works without --classic 4227 _, err := snapstate.Update(s.state, "some-snap-was-classic", nil, s.user.ID, snapstate.Flags{}) 4228 c.Assert(err, IsNil) 4229 4230 // snap installed with --classic, update does not need classic, refresh works with --classic 4231 _, err = snapstate.Update(s.state, "some-snap-was-classic", nil, s.user.ID, snapstate.Flags{Classic: true}) 4232 c.Assert(err, IsNil) 4233 } 4234 4235 func (s *snapmgrTestSuite) TestUpdateChannelFallback(c *C) { 4236 s.state.Lock() 4237 defer s.state.Unlock() 4238 4239 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4240 Active: true, 4241 TrackingChannel: "latest/edge", 4242 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4243 Current: snap.R(7), 4244 SnapType: "app", 4245 }) 4246 4247 ts, err := snapstate.Update(s.state, "some-snap", nil, s.user.ID, snapstate.Flags{}) 4248 c.Assert(err, IsNil) 4249 4250 var snapsup snapstate.SnapSetup 4251 err = ts.Tasks()[0].Get("snap-setup", &snapsup) 4252 c.Assert(err, IsNil) 4253 4254 c.Check(snapsup.Channel, Equals, "latest/edge") 4255 } 4256 4257 func (s *snapmgrTestSuite) TestUpdateTooEarly(c *C) { 4258 s.state.Lock() 4259 defer s.state.Unlock() 4260 4261 s.state.Set("seeded", nil) 4262 4263 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4264 Active: true, 4265 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4266 Current: snap.R(7), 4267 SnapType: "app", 4268 }) 4269 4270 _, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 4271 c.Check(err, FitsTypeOf, &snapstate.ChangeConflictError{}) 4272 c.Assert(err, ErrorMatches, `too early for operation, device not yet seeded or device model not acknowledged`) 4273 } 4274 4275 func (s *snapmgrTestSuite) TestUpdateConflict(c *C) { 4276 s.state.Lock() 4277 defer s.state.Unlock() 4278 4279 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4280 Active: true, 4281 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4282 Current: snap.R(7), 4283 SnapType: "app", 4284 }) 4285 4286 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 4287 c.Assert(err, IsNil) 4288 // need a change to make the tasks visible 4289 s.state.NewChange("refresh", "...").AddAll(ts) 4290 4291 _, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 4292 c.Assert(err, ErrorMatches, `snap "some-snap" has "refresh" change in progress`) 4293 } 4294 4295 func (s *snapmgrTestSuite) TestUpdateCreatesGCTasks(c *C) { 4296 restore := release.MockOnClassic(false) 4297 defer restore() 4298 4299 s.testUpdateCreatesGCTasks(c, 2) 4300 } 4301 4302 func (s *snapmgrTestSuite) TestUpdateCreatesGCTasksOnClassic(c *C) { 4303 restore := release.MockOnClassic(true) 4304 defer restore() 4305 4306 s.testUpdateCreatesGCTasks(c, 3) 4307 } 4308 4309 func (s *snapmgrTestSuite) testUpdateCreatesGCTasks(c *C, expectedDiscards int) { 4310 s.state.Lock() 4311 defer s.state.Unlock() 4312 4313 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4314 Active: true, 4315 Sequence: []*snap.SideInfo{ 4316 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4317 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4318 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4319 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4320 }, 4321 Current: snap.R(4), 4322 SnapType: "app", 4323 }) 4324 4325 ts, err := snapstate.Update(s.state, "some-snap", nil, 0, snapstate.Flags{}) 4326 c.Assert(err, IsNil) 4327 4328 // ensure edges information is still there 4329 te, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge) 4330 c.Assert(te, NotNil) 4331 c.Assert(err, IsNil) 4332 4333 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, expectedDiscards, ts, s.state) 4334 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 4335 } 4336 4337 func (s *snapmgrTestSuite) TestUpdateCreatesDiscardAfterCurrentTasks(c *C) { 4338 s.state.Lock() 4339 defer s.state.Unlock() 4340 4341 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4342 Active: true, 4343 Sequence: []*snap.SideInfo{ 4344 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4345 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4346 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4347 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4348 }, 4349 Current: snap.R(1), 4350 SnapType: "app", 4351 }) 4352 4353 ts, err := snapstate.Update(s.state, "some-snap", nil, 0, snapstate.Flags{}) 4354 c.Assert(err, IsNil) 4355 4356 verifyUpdateTasks(c, unlinkBefore|cleanupAfter|doesReRefresh, 3, ts, s.state) 4357 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) 4358 } 4359 4360 func (s *snapmgrTestSuite) TestUpdateManyTooEarly(c *C) { 4361 s.state.Lock() 4362 defer s.state.Unlock() 4363 4364 s.state.Set("seeded", nil) 4365 4366 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4367 Active: true, 4368 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4369 Current: snap.R(7), 4370 SnapType: "app", 4371 }) 4372 4373 _, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4374 c.Check(err, FitsTypeOf, &snapstate.ChangeConflictError{}) 4375 c.Assert(err, ErrorMatches, `too early for operation, device not yet seeded or device model not acknowledged`) 4376 } 4377 4378 func (s *snapmgrTestSuite) TestUpdateMany(c *C) { 4379 s.state.Lock() 4380 defer s.state.Unlock() 4381 4382 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4383 Active: true, 4384 Sequence: []*snap.SideInfo{ 4385 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4386 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4387 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4388 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4389 }, 4390 Current: snap.R(1), 4391 SnapType: "app", 4392 }) 4393 4394 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4395 c.Assert(err, IsNil) 4396 c.Assert(tts, HasLen, 2) 4397 verifyLastTasksetIsReRefresh(c, tts) 4398 c.Check(updates, DeepEquals, []string{"some-snap"}) 4399 4400 ts := tts[0] 4401 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 3, ts, s.state) 4402 4403 // check that the tasks are in non-default lane 4404 for _, t := range ts.Tasks() { 4405 c.Assert(t.Lanes(), DeepEquals, []int{1}) 4406 } 4407 c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())+1) // 1==rerefresh 4408 4409 // ensure edges information is still there 4410 te, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge) 4411 c.Assert(te, NotNil) 4412 c.Assert(err, IsNil) 4413 4414 checkIsAutoRefresh(c, ts.Tasks(), false) 4415 } 4416 4417 func (s *snapmgrTestSuite) TestUpdateManyFailureDoesntUndoSnapdRefresh(c *C) { 4418 s.state.Lock() 4419 defer s.state.Unlock() 4420 4421 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 4422 defer r() 4423 4424 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4425 Active: true, 4426 Sequence: []*snap.SideInfo{ 4427 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4428 }, 4429 Current: snap.R(1), 4430 SnapType: "app", 4431 TrackingChannel: "channel-for-base/stable", 4432 }) 4433 4434 snapstate.Set(s.state, "core18", &snapstate.SnapState{ 4435 Active: true, 4436 Sequence: []*snap.SideInfo{ 4437 {RealName: "core18", SnapID: "core18-snap-id", Revision: snap.R(1)}, 4438 }, 4439 Current: snap.R(1), 4440 SnapType: "base", 4441 }) 4442 4443 snapstate.Set(s.state, "some-base", &snapstate.SnapState{ 4444 Active: true, 4445 Sequence: []*snap.SideInfo{ 4446 {RealName: "some-base", SnapID: "some-base-id", Revision: snap.R(1)}, 4447 }, 4448 Current: snap.R(1), 4449 SnapType: "base", 4450 }) 4451 4452 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 4453 Active: true, 4454 Sequence: []*snap.SideInfo{ 4455 {RealName: "snapd", SnapID: "snapd-snap-id", Revision: snap.R(1)}, 4456 }, 4457 Current: snap.R(1), 4458 SnapType: "app", 4459 }) 4460 4461 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap", "some-base", "snapd"}, 0, nil) 4462 c.Assert(err, IsNil) 4463 c.Assert(tts, HasLen, 4) 4464 c.Assert(updates, HasLen, 3) 4465 4466 chg := s.state.NewChange("refresh", "...") 4467 for _, ts := range tts { 4468 chg.AddAll(ts) 4469 } 4470 4471 // refresh of some-snap fails on link-snap 4472 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/some-snap/11") 4473 4474 s.state.Unlock() 4475 defer s.se.Stop() 4476 s.settle(c) 4477 s.state.Lock() 4478 4479 c.Check(chg.Err(), ErrorMatches, ".*cannot perform the following tasks:\n- Make snap \"some-snap\" \\(11\\) available to the system.*") 4480 c.Check(chg.IsReady(), Equals, true) 4481 4482 var snapst snapstate.SnapState 4483 4484 // failed snap remains at the old revision, snapd and some-base are refreshed. 4485 c.Assert(snapstate.Get(s.state, "some-snap", &snapst), IsNil) 4486 c.Check(snapst.Current, Equals, snap.Revision{N: 1}) 4487 4488 c.Assert(snapstate.Get(s.state, "snapd", &snapst), IsNil) 4489 c.Check(snapst.Current, Equals, snap.Revision{N: 11}) 4490 4491 c.Assert(snapstate.Get(s.state, "some-base", &snapst), IsNil) 4492 c.Check(snapst.Current, Equals, snap.Revision{N: 11}) 4493 4494 var undoneDownloads, doneDownloads int 4495 for _, ts := range tts { 4496 for _, t := range ts.Tasks() { 4497 if t.Kind() == "download-snap" { 4498 sup, err := snapstate.TaskSnapSetup(t) 4499 c.Assert(err, IsNil) 4500 switch sup.SnapName() { 4501 case "some-snap": 4502 undoneDownloads++ 4503 c.Check(t.Status(), Equals, state.UndoneStatus) 4504 case "snapd", "some-base": 4505 doneDownloads++ 4506 c.Check(t.Status(), Equals, state.DoneStatus) 4507 default: 4508 c.Errorf("unexpected snap %s", sup.SnapName()) 4509 } 4510 } 4511 } 4512 } 4513 c.Assert(undoneDownloads, Equals, 1) 4514 c.Assert(doneDownloads, Equals, 2) 4515 } 4516 4517 func (s *snapmgrTestSuite) TestUpdateManyDevModeConfinementFiltering(c *C) { 4518 s.state.Lock() 4519 defer s.state.Unlock() 4520 4521 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4522 Active: true, 4523 TrackingChannel: "channel-for-devmode/stable", 4524 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4525 Current: snap.R(7), 4526 SnapType: "app", 4527 }) 4528 4529 // updated snap is devmode, updatemany doesn't update it 4530 _, tts, _ := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 4531 // FIXME: UpdateMany will not error out in this case (daemon catches this case, with a weird error) 4532 c.Assert(tts, HasLen, 0) 4533 } 4534 4535 func (s *snapmgrTestSuite) TestUpdateManyClassicConfinementFiltering(c *C) { 4536 restore := maybeMockClassicSupport(c) 4537 defer restore() 4538 4539 s.state.Lock() 4540 defer s.state.Unlock() 4541 4542 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4543 Active: true, 4544 TrackingChannel: "channel-for-classic/stable", 4545 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4546 Current: snap.R(7), 4547 SnapType: "app", 4548 }) 4549 4550 // if a snap installed without --classic gets a classic update it isn't installed 4551 _, tts, _ := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 4552 // FIXME: UpdateMany will not error out in this case (daemon catches this case, with a weird error) 4553 c.Assert(tts, HasLen, 0) 4554 } 4555 4556 func (s *snapmgrTestSuite) TestUpdateManyClassic(c *C) { 4557 restore := maybeMockClassicSupport(c) 4558 defer restore() 4559 4560 s.state.Lock() 4561 defer s.state.Unlock() 4562 4563 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4564 Active: true, 4565 TrackingChannel: "channel-for-classic/stable", 4566 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4567 Current: snap.R(7), 4568 SnapType: "app", 4569 Flags: snapstate.Flags{Classic: true}, 4570 }) 4571 4572 // snap installed with classic: refresh gets classic 4573 _, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, nil) 4574 c.Assert(err, IsNil) 4575 c.Assert(tts, HasLen, 2) 4576 verifyLastTasksetIsReRefresh(c, tts) 4577 } 4578 4579 func (s *snapmgrTestSuite) TestUpdateManyClassicToStrict(c *C) { 4580 restore := maybeMockClassicSupport(c) 4581 defer restore() 4582 4583 s.state.Lock() 4584 defer s.state.Unlock() 4585 4586 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4587 Active: true, 4588 TrackingChannel: "stable", 4589 Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(7)}}, 4590 Current: snap.R(7), 4591 SnapType: "app", 4592 Flags: snapstate.Flags{Classic: true}, 4593 }) 4594 4595 // snap installed with classic: refresh gets classic 4596 _, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, s.user.ID, &snapstate.Flags{Classic: true}) 4597 c.Assert(err, IsNil) 4598 c.Assert(tts, HasLen, 2) 4599 // ensure we clear the classic flag 4600 snapsup, err := snapstate.TaskSnapSetup(tts[0].Tasks()[0]) 4601 c.Assert(err, IsNil) 4602 c.Assert(snapsup.Flags.Classic, Equals, false) 4603 4604 verifyLastTasksetIsReRefresh(c, tts) 4605 } 4606 4607 func (s *snapmgrTestSuite) TestUpdateManyDevMode(c *C) { 4608 s.state.Lock() 4609 defer s.state.Unlock() 4610 4611 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4612 Active: true, 4613 Flags: snapstate.Flags{DevMode: true}, 4614 Sequence: []*snap.SideInfo{ 4615 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4616 }, 4617 Current: snap.R(1), 4618 SnapType: "app", 4619 }) 4620 4621 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, 0, nil) 4622 c.Assert(err, IsNil) 4623 c.Check(updates, HasLen, 1) 4624 } 4625 4626 func (s *snapmgrTestSuite) TestUpdateAllDevMode(c *C) { 4627 s.state.Lock() 4628 defer s.state.Unlock() 4629 4630 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4631 Active: true, 4632 Flags: snapstate.Flags{DevMode: true}, 4633 Sequence: []*snap.SideInfo{ 4634 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4635 }, 4636 Current: snap.R(1), 4637 SnapType: "app", 4638 }) 4639 4640 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4641 c.Assert(err, IsNil) 4642 c.Check(updates, HasLen, 0) 4643 } 4644 4645 func (s *snapmgrTestSuite) TestUpdateManyWaitForBasesUC16(c *C) { 4646 s.state.Lock() 4647 defer s.state.Unlock() 4648 4649 snapstate.Set(s.state, "core", &snapstate.SnapState{ 4650 Active: true, 4651 Sequence: []*snap.SideInfo{ 4652 {RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1)}, 4653 }, 4654 Current: snap.R(1), 4655 SnapType: "os", 4656 }) 4657 4658 snapstate.Set(s.state, "some-base", &snapstate.SnapState{ 4659 Active: true, 4660 Sequence: []*snap.SideInfo{ 4661 {RealName: "some-base", SnapID: "some-base-id", Revision: snap.R(1)}, 4662 }, 4663 Current: snap.R(1), 4664 SnapType: "base", 4665 }) 4666 4667 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4668 Active: true, 4669 Sequence: []*snap.SideInfo{ 4670 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4671 }, 4672 Current: snap.R(1), 4673 SnapType: "app", 4674 TrackingChannel: "channel-for-base/stable", 4675 }) 4676 4677 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap", "core", "some-base"}, 0, nil) 4678 c.Assert(err, IsNil) 4679 c.Assert(tts, HasLen, 4) 4680 verifyLastTasksetIsReRefresh(c, tts) 4681 c.Check(updates, HasLen, 3) 4682 4683 // to make TaskSnapSetup work 4684 chg := s.state.NewChange("refresh", "...") 4685 for _, ts := range tts { 4686 chg.AddAll(ts) 4687 } 4688 4689 prereqTotal := len(tts[0].Tasks()) + len(tts[1].Tasks()) 4690 prereqs := map[string]bool{} 4691 for i, task := range tts[2].Tasks() { 4692 waitTasks := task.WaitTasks() 4693 if i == 0 { 4694 c.Check(len(waitTasks), Equals, prereqTotal) 4695 } else if task.Kind() == "link-snap" { 4696 c.Check(len(waitTasks), Equals, prereqTotal+1) 4697 for _, pre := range waitTasks { 4698 if pre.Kind() == "link-snap" { 4699 snapsup, err := snapstate.TaskSnapSetup(pre) 4700 c.Assert(err, IsNil) 4701 prereqs[snapsup.InstanceName()] = true 4702 } 4703 } 4704 } 4705 } 4706 4707 c.Check(prereqs, DeepEquals, map[string]bool{ 4708 "core": true, 4709 "some-base": true, 4710 }) 4711 } 4712 4713 func (s *snapmgrTestSuite) TestUpdateManyWaitForBasesUC18(c *C) { 4714 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 4715 defer r() 4716 4717 s.state.Lock() 4718 defer s.state.Unlock() 4719 4720 snapstate.Set(s.state, "core18", &snapstate.SnapState{ 4721 Active: true, 4722 Sequence: []*snap.SideInfo{ 4723 {RealName: "core18", SnapID: "core18-snap-id", Revision: snap.R(1)}, 4724 }, 4725 Current: snap.R(1), 4726 SnapType: "base", 4727 }) 4728 4729 snapstate.Set(s.state, "some-base", &snapstate.SnapState{ 4730 Active: true, 4731 Sequence: []*snap.SideInfo{ 4732 {RealName: "some-base", SnapID: "some-base-id", Revision: snap.R(1)}, 4733 }, 4734 Current: snap.R(1), 4735 SnapType: "base", 4736 }) 4737 4738 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 4739 Active: true, 4740 Sequence: []*snap.SideInfo{ 4741 {RealName: "snapd", SnapID: "snapd-snap-id", Revision: snap.R(1)}, 4742 }, 4743 Current: snap.R(1), 4744 SnapType: "app", 4745 }) 4746 4747 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4748 Active: true, 4749 Sequence: []*snap.SideInfo{ 4750 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4751 }, 4752 Current: snap.R(1), 4753 SnapType: "app", 4754 TrackingChannel: "channel-for-base/stable", 4755 }) 4756 4757 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap", "core18", "some-base", "snapd"}, 0, nil) 4758 c.Assert(err, IsNil) 4759 c.Assert(tts, HasLen, 5) 4760 verifyLastTasksetIsReRefresh(c, tts) 4761 c.Check(updates, HasLen, 4) 4762 4763 // to make TaskSnapSetup work 4764 chg := s.state.NewChange("refresh", "...") 4765 for _, ts := range tts { 4766 chg.AddAll(ts) 4767 } 4768 4769 // Note that some-app only waits for snapd+some-base. The core18 4770 // base is not special to this snap and not waited for 4771 prereqTotal := len(tts[0].Tasks()) + len(tts[1].Tasks()) 4772 prereqs := map[string]bool{} 4773 for i, task := range tts[3].Tasks() { 4774 waitTasks := task.WaitTasks() 4775 if i == 0 { 4776 c.Check(len(waitTasks), Equals, prereqTotal) 4777 } else if task.Kind() == "link-snap" { 4778 c.Check(len(waitTasks), Equals, prereqTotal+1) 4779 for _, pre := range waitTasks { 4780 if pre.Kind() == "link-snap" { 4781 snapsup, err := snapstate.TaskSnapSetup(pre) 4782 c.Assert(err, IsNil) 4783 prereqs[snapsup.InstanceName()] = true 4784 } 4785 } 4786 } 4787 } 4788 4789 // Note that "core18" is not part of the prereqs for some-app 4790 // as it does not use this base. 4791 c.Check(prereqs, DeepEquals, map[string]bool{ 4792 "some-base": true, 4793 "snapd": true, 4794 }) 4795 } 4796 4797 func (s *snapmgrTestSuite) TestUpdateManyValidateRefreshes(c *C) { 4798 s.state.Lock() 4799 defer s.state.Unlock() 4800 4801 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4802 Active: true, 4803 Sequence: []*snap.SideInfo{ 4804 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4805 }, 4806 Current: snap.R(1), 4807 SnapType: "app", 4808 }) 4809 4810 validateCalled := false 4811 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 4812 validateCalled = true 4813 c.Check(refreshes, HasLen, 1) 4814 c.Check(refreshes[0].InstanceName(), Equals, "some-snap") 4815 c.Check(refreshes[0].SnapID, Equals, "some-snap-id") 4816 c.Check(refreshes[0].Revision, Equals, snap.R(11)) 4817 c.Check(ignoreValidation, HasLen, 0) 4818 return refreshes, nil 4819 } 4820 // hook it up 4821 snapstate.ValidateRefreshes = validateRefreshes 4822 4823 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4824 c.Assert(err, IsNil) 4825 c.Assert(tts, HasLen, 2) 4826 verifyLastTasksetIsReRefresh(c, tts) 4827 c.Check(updates, DeepEquals, []string{"some-snap"}) 4828 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 0, tts[0], s.state) 4829 4830 c.Check(validateCalled, Equals, true) 4831 } 4832 4833 func (s *snapmgrTestSuite) TestParallelInstanceUpdateMany(c *C) { 4834 restore := release.MockOnClassic(false) 4835 defer restore() 4836 4837 s.state.Lock() 4838 defer s.state.Unlock() 4839 4840 tr := config.NewTransaction(s.state) 4841 tr.Set("core", "experimental.parallel-instances", true) 4842 tr.Commit() 4843 4844 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4845 Active: true, 4846 Sequence: []*snap.SideInfo{ 4847 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4848 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4849 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4850 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 4851 }, 4852 Current: snap.R(1), 4853 SnapType: "app", 4854 }) 4855 snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{ 4856 Active: true, 4857 Sequence: []*snap.SideInfo{ 4858 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4859 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(2)}, 4860 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(3)}, 4861 }, 4862 Current: snap.R(3), 4863 SnapType: "app", 4864 InstanceKey: "instance", 4865 }) 4866 4867 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4868 c.Assert(err, IsNil) 4869 c.Assert(tts, HasLen, 3) 4870 verifyLastTasksetIsReRefresh(c, tts) 4871 // ensure stable ordering of updates list 4872 if updates[0] != "some-snap" { 4873 updates[1], updates[0] = updates[0], updates[1] 4874 } 4875 4876 c.Check(updates, DeepEquals, []string{"some-snap", "some-snap_instance"}) 4877 4878 var snapsup, snapsupInstance *snapstate.SnapSetup 4879 4880 // ensure stable ordering of task sets list 4881 snapsup, err = snapstate.TaskSnapSetup(tts[0].Tasks()[0]) 4882 c.Assert(err, IsNil) 4883 if snapsup.InstanceName() != "some-snap" { 4884 tts[0], tts[1] = tts[1], tts[0] 4885 snapsup, err = snapstate.TaskSnapSetup(tts[0].Tasks()[0]) 4886 c.Assert(err, IsNil) 4887 } 4888 snapsupInstance, err = snapstate.TaskSnapSetup(tts[1].Tasks()[0]) 4889 c.Assert(err, IsNil) 4890 4891 c.Assert(snapsup.InstanceName(), Equals, "some-snap") 4892 c.Assert(snapsupInstance.InstanceName(), Equals, "some-snap_instance") 4893 4894 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 3, tts[0], s.state) 4895 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 1, tts[1], s.state) 4896 } 4897 4898 func (s *snapmgrTestSuite) TestParallelInstanceUpdateManyValidateRefreshes(c *C) { 4899 s.state.Lock() 4900 defer s.state.Unlock() 4901 4902 tr := config.NewTransaction(s.state) 4903 tr.Set("core", "experimental.parallel-instances", true) 4904 tr.Commit() 4905 4906 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4907 Active: true, 4908 Sequence: []*snap.SideInfo{ 4909 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4910 }, 4911 Current: snap.R(1), 4912 SnapType: "app", 4913 }) 4914 snapstate.Set(s.state, "some-snap_instance", &snapstate.SnapState{ 4915 Active: true, 4916 Sequence: []*snap.SideInfo{ 4917 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4918 }, 4919 Current: snap.R(1), 4920 SnapType: "app", 4921 InstanceKey: "instance", 4922 }) 4923 4924 validateCalled := false 4925 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 4926 validateCalled = true 4927 c.Check(refreshes, HasLen, 2) 4928 instanceIdx := 0 4929 someIdx := 1 4930 if refreshes[0].InstanceName() != "some-snap_instance" { 4931 instanceIdx = 1 4932 someIdx = 0 4933 } 4934 c.Check(refreshes[someIdx].InstanceName(), Equals, "some-snap") 4935 c.Check(refreshes[instanceIdx].InstanceName(), Equals, "some-snap_instance") 4936 c.Check(refreshes[0].SnapID, Equals, "some-snap-id") 4937 c.Check(refreshes[0].Revision, Equals, snap.R(11)) 4938 c.Check(refreshes[1].SnapID, Equals, "some-snap-id") 4939 c.Check(refreshes[1].Revision, Equals, snap.R(11)) 4940 c.Check(ignoreValidation, HasLen, 0) 4941 return refreshes, nil 4942 } 4943 // hook it up 4944 snapstate.ValidateRefreshes = validateRefreshes 4945 4946 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4947 c.Assert(err, IsNil) 4948 c.Assert(tts, HasLen, 3) 4949 verifyLastTasksetIsReRefresh(c, tts) 4950 sort.Strings(updates) 4951 c.Check(updates, DeepEquals, []string{"some-snap", "some-snap_instance"}) 4952 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 0, tts[0], s.state) 4953 verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 0, tts[1], s.state) 4954 4955 c.Check(validateCalled, Equals, true) 4956 } 4957 4958 func (s *snapmgrTestSuite) TestUpdateManyValidateRefreshesUnhappy(c *C) { 4959 s.state.Lock() 4960 defer s.state.Unlock() 4961 4962 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 4963 Active: true, 4964 Sequence: []*snap.SideInfo{ 4965 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 4966 }, 4967 Current: snap.R(1), 4968 }) 4969 4970 validateErr := errors.New("refresh control error") 4971 validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx snapstate.DeviceContext) ([]*snap.Info, error) { 4972 c.Check(refreshes, HasLen, 1) 4973 c.Check(refreshes[0].SnapID, Equals, "some-snap-id") 4974 c.Check(refreshes[0].Revision, Equals, snap.R(11)) 4975 c.Check(ignoreValidation, HasLen, 0) 4976 return nil, validateErr 4977 } 4978 // hook it up 4979 snapstate.ValidateRefreshes = validateRefreshes 4980 4981 // refresh all => no error 4982 updates, tts, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 4983 c.Assert(err, IsNil) 4984 c.Check(tts, HasLen, 0) 4985 c.Check(updates, HasLen, 0) 4986 4987 // refresh some-snap => report error 4988 updates, tts, err = snapstate.UpdateMany(context.Background(), s.state, []string{"some-snap"}, 0, nil) 4989 c.Assert(err, Equals, validateErr) 4990 c.Check(tts, HasLen, 0) 4991 c.Check(updates, HasLen, 0) 4992 4993 } 4994 4995 func (s *snapmgrTestSuite) testUpdateManyDiskSpaceCheck(c *C, featureFlag, failDiskCheck, failInstallSize bool) error { 4996 var diskCheckCalled, installSizeCalled bool 4997 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 4998 diskCheckCalled = true 4999 c.Check(path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 5000 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 5001 if failDiskCheck { 5002 return &osutil.NotEnoughDiskSpaceError{} 5003 } 5004 return nil 5005 }) 5006 defer restore() 5007 5008 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 5009 installSizeCalled = true 5010 if failInstallSize { 5011 return 0, fmt.Errorf("boom") 5012 } 5013 c.Assert(snaps, HasLen, 2) 5014 c.Check(snaps[0].InstanceName(), Equals, "snapd") 5015 c.Check(snaps[1].InstanceName(), Equals, "some-snap") 5016 return 123, nil 5017 }) 5018 defer restoreInstallSize() 5019 5020 s.state.Lock() 5021 defer s.state.Unlock() 5022 5023 tr := config.NewTransaction(s.state) 5024 tr.Set("core", "experimental.check-disk-space-refresh", featureFlag) 5025 tr.Commit() 5026 5027 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5028 Active: true, 5029 Sequence: []*snap.SideInfo{ 5030 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 5031 }, 5032 Current: snap.R(1), 5033 SnapType: "app", 5034 }) 5035 5036 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 5037 Active: true, 5038 Sequence: []*snap.SideInfo{ 5039 {RealName: "snapd", SnapID: "snapd-snap-id", Revision: snap.R(1)}, 5040 }, 5041 Current: snap.R(1), 5042 SnapType: "app", 5043 }) 5044 5045 updates, _, err := snapstate.UpdateMany(context.Background(), s.state, nil, 0, nil) 5046 if featureFlag { 5047 c.Check(installSizeCalled, Equals, true) 5048 if failInstallSize { 5049 c.Check(diskCheckCalled, Equals, false) 5050 } else { 5051 c.Check(diskCheckCalled, Equals, true) 5052 if failDiskCheck { 5053 c.Check(updates, HasLen, 0) 5054 } else { 5055 c.Check(updates, HasLen, 2) 5056 } 5057 } 5058 } else { 5059 c.Check(installSizeCalled, Equals, false) 5060 c.Check(diskCheckCalled, Equals, false) 5061 } 5062 5063 return err 5064 } 5065 5066 func (s *snapmgrTestSuite) TestUpdateManyDiskSpaceCheckError(c *C) { 5067 featureFlag := true 5068 failDiskCheck := true 5069 failInstallSize := false 5070 err := s.testUpdateManyDiskSpaceCheck(c, featureFlag, failDiskCheck, failInstallSize) 5071 diskSpaceErr := err.(*snapstate.InsufficientSpaceError) 5072 c.Assert(diskSpaceErr, ErrorMatches, `insufficient space in .* to perform "refresh" change for the following snaps: snapd, some-snap`) 5073 c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 5074 c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"snapd", "some-snap"}) 5075 } 5076 5077 func (s *snapmgrTestSuite) TestUpdateManyDiskSpaceSkippedIfFeatureDisabled(c *C) { 5078 featureFlag := false 5079 failDiskCheck := true 5080 failInstallSize := false 5081 err := s.testUpdateManyDiskSpaceCheck(c, featureFlag, failDiskCheck, failInstallSize) 5082 c.Assert(err, IsNil) 5083 } 5084 5085 func (s *snapmgrTestSuite) TestUpdateManyDiskSpaceFailInstallSize(c *C) { 5086 featureFlag := true 5087 failDiskCheck := false 5088 failInstallSize := true 5089 err := s.testUpdateManyDiskSpaceCheck(c, featureFlag, failDiskCheck, failInstallSize) 5090 c.Assert(err, ErrorMatches, "boom") 5091 } 5092 5093 func (s *snapmgrTestSuite) TestUnlinkCurrentSnapLastActiveDisabledServicesSet(c *C) { 5094 si := snap.SideInfo{ 5095 RealName: "services-snap", 5096 Revision: snap.R(-42), 5097 } 5098 snaptest.MockSnap(c, `name: services-snap`, &si) 5099 5100 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5101 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1", "svc2"} 5102 5103 // reset the services to what they were before after the test is done 5104 defer func() { 5105 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5106 }() 5107 5108 s.state.Lock() 5109 defer s.state.Unlock() 5110 5111 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5112 Active: true, 5113 Sequence: []*snap.SideInfo{&si}, 5114 Current: si.Revision, 5115 SnapType: "app", 5116 TrackingChannel: "stable", 5117 LastActiveDisabledServices: []string{}, 5118 }) 5119 5120 chg := s.state.NewChange("refresh", "refresh a snap") 5121 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 5122 5123 c.Assert(err, IsNil) 5124 // only add up to unlink-current-snap task 5125 for _, t := range ts.Tasks() { 5126 chg.AddTask(t) 5127 if t.Kind() == "unlink-current-snap" { 5128 // don't add any more from this point on 5129 break 5130 } 5131 } 5132 5133 s.state.Unlock() 5134 defer s.se.Stop() 5135 s.settle(c) 5136 s.state.Lock() 5137 5138 c.Assert(chg.Err(), IsNil) 5139 c.Assert(chg.IsReady(), Equals, true) 5140 5141 // get the snap state 5142 var snapst snapstate.SnapState 5143 err = snapstate.Get(s.state, "services-snap", &snapst) 5144 c.Assert(err, IsNil) 5145 5146 // make sure that the disabled services in this snap's state is what we 5147 // provided 5148 sort.Strings(snapst.LastActiveDisabledServices) 5149 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"svc1", "svc2"}) 5150 } 5151 5152 func (s *snapmgrTestSuite) TestUnlinkCurrentSnapMergedLastActiveDisabledServicesSet(c *C) { 5153 si := snap.SideInfo{ 5154 RealName: "services-snap", 5155 Revision: snap.R(-42), 5156 } 5157 snaptest.MockSnap(c, `name: services-snap`, &si) 5158 5159 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5160 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1", "svc2"} 5161 5162 // reset the services to what they were before after the test is done 5163 defer func() { 5164 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5165 }() 5166 5167 s.state.Lock() 5168 defer s.state.Unlock() 5169 5170 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5171 Active: true, 5172 Sequence: []*snap.SideInfo{&si}, 5173 Current: si.Revision, 5174 SnapType: "app", 5175 TrackingChannel: "stable", 5176 LastActiveDisabledServices: []string{"missing-svc3"}, 5177 }) 5178 5179 chg := s.state.NewChange("refresh", "refresh a snap") 5180 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 5181 5182 c.Assert(err, IsNil) 5183 // only add up to unlink-current-snap task 5184 for _, t := range ts.Tasks() { 5185 chg.AddTask(t) 5186 if t.Kind() == "unlink-current-snap" { 5187 // don't add any more from this point on 5188 break 5189 } 5190 } 5191 5192 s.state.Unlock() 5193 defer s.se.Stop() 5194 s.settle(c) 5195 s.state.Lock() 5196 5197 c.Assert(chg.Err(), IsNil) 5198 c.Assert(chg.IsReady(), Equals, true) 5199 5200 // get the snap state 5201 var snapst snapstate.SnapState 5202 err = snapstate.Get(s.state, "services-snap", &snapst) 5203 c.Assert(err, IsNil) 5204 5205 // make sure that the disabled services in this snap's state is what we 5206 // provided 5207 sort.Strings(snapst.LastActiveDisabledServices) 5208 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"missing-svc3", "svc1", "svc2"}) 5209 } 5210 5211 func (s *snapmgrTestSuite) TestUnlinkCurrentSnapPassthroughLastActiveDisabledServicesSet(c *C) { 5212 si := snap.SideInfo{ 5213 RealName: "services-snap", 5214 Revision: snap.R(-42), 5215 } 5216 snaptest.MockSnap(c, `name: services-snap`, &si) 5217 5218 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5219 s.fakeBackend.servicesCurrentlyDisabled = []string{} 5220 5221 // reset the services to what they were before after the test is done 5222 defer func() { 5223 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5224 }() 5225 5226 s.state.Lock() 5227 defer s.state.Unlock() 5228 5229 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5230 Active: true, 5231 Sequence: []*snap.SideInfo{&si}, 5232 Current: si.Revision, 5233 SnapType: "app", 5234 TrackingChannel: "stable", 5235 LastActiveDisabledServices: []string{"missing-svc3"}, 5236 }) 5237 5238 chg := s.state.NewChange("refresh", "refresh a snap") 5239 ts, err := snapstate.Update(s.state, "services-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{Amend: true}) 5240 5241 c.Assert(err, IsNil) 5242 // only add up to unlink-current-snap task 5243 for _, t := range ts.Tasks() { 5244 chg.AddTask(t) 5245 if t.Kind() == "unlink-current-snap" { 5246 // don't add any more from this point on 5247 break 5248 } 5249 } 5250 5251 s.state.Unlock() 5252 defer s.se.Stop() 5253 s.settle(c) 5254 s.state.Lock() 5255 5256 c.Assert(chg.Err(), IsNil) 5257 c.Assert(chg.IsReady(), Equals, true) 5258 5259 // get the snap state 5260 var snapst snapstate.SnapState 5261 err = snapstate.Get(s.state, "services-snap", &snapst) 5262 c.Assert(err, IsNil) 5263 5264 // make sure that the disabled services in this snap's state is what we 5265 // provided 5266 sort.Strings(snapst.LastActiveDisabledServices) 5267 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"missing-svc3"}) 5268 } 5269 5270 func (s *snapmgrTestSuite) TestStopSnapServicesSavesSnapSetupLastActiveDisabledServices(c *C) { 5271 s.state.Lock() 5272 defer s.state.Unlock() 5273 5274 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5275 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1", "svc2"} 5276 5277 // reset the services to what they were before after the test is done 5278 defer func() { 5279 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5280 }() 5281 5282 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5283 Sequence: []*snap.SideInfo{ 5284 {RealName: "services-snap", Revision: snap.R(11)}, 5285 }, 5286 Current: snap.R(11), 5287 Active: true, 5288 }) 5289 5290 snapsup := &snapstate.SnapSetup{ 5291 SideInfo: &snap.SideInfo{ 5292 RealName: "services-snap", 5293 Revision: snap.R(11), 5294 SnapID: "services-snap-id", 5295 }, 5296 } 5297 5298 chg := s.state.NewChange("stop-services", "stop the services") 5299 t1 := s.state.NewTask("prerequisites", "...") 5300 t1.Set("snap-setup", snapsup) 5301 t2 := s.state.NewTask("stop-snap-services", "...") 5302 t2.Set("stop-reason", snap.StopReasonDisable) 5303 t2.Set("snap-setup-task", t1.ID()) 5304 t2.WaitFor(t1) 5305 chg.AddTask(t1) 5306 chg.AddTask(t2) 5307 5308 s.state.Unlock() 5309 defer s.se.Stop() 5310 s.settle(c) 5311 s.state.Lock() 5312 5313 c.Assert(chg.Err(), IsNil) 5314 c.Assert(chg.IsReady(), Equals, true) 5315 5316 // get the snap state 5317 var snapst snapstate.SnapState 5318 c.Assert(snapstate.Get(s.state, "services-snap", &snapst), IsNil) 5319 5320 // make sure that the disabled services in this snap's state is what we 5321 // provided 5322 sort.Strings(snapst.LastActiveDisabledServices) 5323 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"svc1", "svc2"}) 5324 } 5325 5326 func (s *snapmgrTestSuite) TestStopSnapServicesFirstSavesSnapSetupLastActiveDisabledServices(c *C) { 5327 s.state.Lock() 5328 defer s.state.Unlock() 5329 5330 prevCurrentlyDisabled := s.fakeBackend.servicesCurrentlyDisabled 5331 s.fakeBackend.servicesCurrentlyDisabled = []string{"svc1"} 5332 5333 // reset the services to what they were before after the test is done 5334 defer func() { 5335 s.fakeBackend.servicesCurrentlyDisabled = prevCurrentlyDisabled 5336 }() 5337 5338 snapstate.Set(s.state, "services-snap", &snapstate.SnapState{ 5339 Sequence: []*snap.SideInfo{ 5340 {RealName: "services-snap", Revision: snap.R(11)}, 5341 }, 5342 Current: snap.R(11), 5343 Active: true, 5344 // leave this line to keep gofmt 1.10 happy 5345 LastActiveDisabledServices: []string{"svc2"}, 5346 }) 5347 5348 snapsup := &snapstate.SnapSetup{ 5349 SideInfo: &snap.SideInfo{ 5350 RealName: "services-snap", 5351 Revision: snap.R(11), 5352 SnapID: "services-snap-id", 5353 }, 5354 } 5355 5356 chg := s.state.NewChange("stop-services", "stop the services") 5357 t := s.state.NewTask("stop-snap-services", "...") 5358 t.Set("stop-reason", snap.StopReasonDisable) 5359 t.Set("snap-setup", snapsup) 5360 chg.AddTask(t) 5361 5362 s.state.Unlock() 5363 defer s.se.Stop() 5364 s.settle(c) 5365 s.state.Lock() 5366 5367 c.Assert(chg.Err(), IsNil) 5368 c.Assert(chg.IsReady(), Equals, true) 5369 5370 // get the snap state 5371 var snapst snapstate.SnapState 5372 c.Assert(snapstate.Get(s.state, "services-snap", &snapst), IsNil) 5373 5374 // make sure that the disabled services in this snap's state is what we 5375 // provided 5376 sort.Strings(snapst.LastActiveDisabledServices) 5377 c.Assert(snapst.LastActiveDisabledServices, DeepEquals, []string{"svc1", "svc2"}) 5378 } 5379 5380 func (s *snapmgrTestSuite) TestRefreshDoesntRestoreRevisionConfig(c *C) { 5381 restore := release.MockOnClassic(false) 5382 defer restore() 5383 5384 s.state.Lock() 5385 defer s.state.Unlock() 5386 5387 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5388 Active: true, 5389 Sequence: []*snap.SideInfo{ 5390 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 5391 }, 5392 Current: snap.R(1), 5393 SnapType: "app", 5394 }) 5395 5396 // set global configuration (affecting current snap) 5397 tr := config.NewTransaction(s.state) 5398 tr.Set("some-snap", "foo", "100") 5399 tr.Commit() 5400 5401 // set per-revision config for the upcoming rev. 2, we don't expect it restored though 5402 // since only revert restores revision configs. 5403 s.state.Set("revision-config", map[string]interface{}{ 5404 "some-snap": map[string]interface{}{ 5405 "2": map[string]interface{}{"foo": "200"}, 5406 }, 5407 }) 5408 5409 // simulate a refresh to rev. 2 5410 chg := s.state.NewChange("update", "update some-snap") 5411 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(2)}, s.user.ID, snapstate.Flags{}) 5412 c.Assert(err, IsNil) 5413 chg.AddAll(ts) 5414 5415 s.state.Unlock() 5416 defer s.se.Stop() 5417 s.settle(c) 5418 5419 s.state.Lock() 5420 // config of rev. 1 has been stored in per-revision map 5421 var cfgs map[string]interface{} 5422 c.Assert(s.state.Get("revision-config", &cfgs), IsNil) 5423 c.Assert(cfgs["some-snap"], DeepEquals, map[string]interface{}{ 5424 "1": map[string]interface{}{"foo": "100"}, 5425 "2": map[string]interface{}{"foo": "200"}, 5426 }) 5427 5428 // config of rev. 2 hasn't been restored by refresh, old value returned 5429 tr = config.NewTransaction(s.state) 5430 var res string 5431 c.Assert(tr.Get("some-snap", "foo", &res), IsNil) 5432 c.Assert(res, Equals, "100") 5433 } 5434 5435 func (s *snapmgrTestSuite) TestRefreshFailureCausesErrorReport(c *C) { 5436 var errSnap, errMsg, errSig string 5437 var errExtra map[string]string 5438 var n int 5439 restore := snapstate.MockErrtrackerReport(func(aSnap, aErrMsg, aDupSig string, extra map[string]string) (string, error) { 5440 errSnap = aSnap 5441 errMsg = aErrMsg 5442 errSig = aDupSig 5443 errExtra = extra 5444 n += 1 5445 return "oopsid", nil 5446 }) 5447 defer restore() 5448 5449 si := snap.SideInfo{ 5450 RealName: "some-snap", 5451 SnapID: "some-snap-id", 5452 Revision: snap.R(7), 5453 } 5454 5455 s.state.Lock() 5456 defer s.state.Unlock() 5457 5458 s.state.Set("ubuntu-core-transition-retry", 7) 5459 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5460 Active: true, 5461 Sequence: []*snap.SideInfo{&si}, 5462 Current: si.Revision, 5463 SnapType: "app", 5464 }) 5465 5466 chg := s.state.NewChange("install", "install a snap") 5467 ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 5468 c.Assert(err, IsNil) 5469 chg.AddAll(ts) 5470 5471 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "some-snap/11") 5472 5473 s.state.Unlock() 5474 defer s.se.Stop() 5475 s.settle(c) 5476 s.state.Lock() 5477 5478 // verify we generated a failure report 5479 c.Check(n, Equals, 1) 5480 c.Check(errSnap, Equals, "some-snap") 5481 c.Check(errExtra, DeepEquals, map[string]string{ 5482 "UbuntuCoreTransitionCount": "7", 5483 "Channel": "some-channel", 5484 "Revision": "11", 5485 }) 5486 c.Check(errMsg, Matches, `(?sm)change "install": "install a snap" 5487 prerequisites: Undo 5488 snap-setup: "some-snap" \(11\) "some-channel" 5489 download-snap: Undoing 5490 validate-snap: Done 5491 .* 5492 link-snap: Error 5493 INFO unlink 5494 ERROR fail 5495 auto-connect: Hold 5496 set-auto-aliases: Hold 5497 setup-aliases: Hold 5498 run-hook: Hold 5499 start-snap-services: Hold 5500 cleanup: Hold 5501 run-hook: Hold`) 5502 c.Check(errSig, Matches, `(?sm)snap-install: 5503 prerequisites: Undo 5504 snap-setup: "some-snap" 5505 download-snap: Undoing 5506 validate-snap: Done 5507 .* 5508 link-snap: Error 5509 INFO unlink 5510 ERROR fail 5511 auto-connect: Hold 5512 set-auto-aliases: Hold 5513 setup-aliases: Hold 5514 run-hook: Hold 5515 start-snap-services: Hold 5516 cleanup: Hold 5517 run-hook: Hold`) 5518 5519 // run again with empty "ubuntu-core-transition-retry" 5520 s.state.Set("ubuntu-core-transition-retry", 0) 5521 chg = s.state.NewChange("install", "install a snap") 5522 ts, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{}) 5523 c.Assert(err, IsNil) 5524 chg.AddAll(ts) 5525 s.state.Unlock() 5526 defer s.se.Stop() 5527 s.settle(c) 5528 s.state.Lock() 5529 // verify that we excluded this field from the bugreport 5530 c.Check(n, Equals, 2) 5531 c.Check(errExtra, DeepEquals, map[string]string{ 5532 "Channel": "some-channel", 5533 "Revision": "11", 5534 }) 5535 } 5536 5537 func (s *snapmgrTestSuite) TestNoReRefreshInUpdate(c *C) { 5538 s.state.Lock() 5539 defer s.state.Unlock() 5540 5541 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5542 Active: true, 5543 Sequence: []*snap.SideInfo{ 5544 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}, 5545 }, 5546 Current: snap.R(1), 5547 SnapType: "app", 5548 }) 5549 5550 ts, err := snapstate.Update(s.state, "some-snap", nil, 0, snapstate.Flags{NoReRefresh: true}) 5551 c.Assert(err, IsNil) 5552 5553 // ensure we have no re-refresh task 5554 for _, t := range ts.Tasks() { 5555 c.Assert(t.Kind(), Not(Equals), "check-rerefresh") 5556 } 5557 5558 snapsup, err := snapstate.TaskSnapSetup(ts.Tasks()[0]) 5559 c.Assert(err, IsNil) 5560 // NoReRefresh is consumed and consulted when creating the taskset 5561 // but is not copied into SnapSetup 5562 c.Check(snapsup.Flags.NoReRefresh, Equals, false) 5563 } 5564 5565 func (s *snapmgrTestSuite) TestEmptyUpdateWithChannelChangeAndAutoAlias(c *C) { 5566 // this reproduces the cause behind lp:1860324, 5567 // namely an empty refresh with a channel change on a snap 5568 // with changed aliases 5569 5570 s.state.Lock() 5571 defer s.state.Unlock() 5572 5573 n := 0 5574 snapstate.AutoAliases = func(st *state.State, info *snap.Info) (map[string]string, error) { 5575 if info.InstanceName() == "alias-snap" { 5576 if n > 0 { 5577 return map[string]string{ 5578 "alias1": "cmd1", 5579 "alias2": "cmd2", 5580 }, nil 5581 } 5582 n++ 5583 } 5584 return nil, nil 5585 } 5586 5587 snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{ 5588 TrackingChannel: "latest/stable", 5589 Sequence: []*snap.SideInfo{ 5590 {RealName: "alias-snap", Revision: snap.R(11), SnapID: "alias-snap-id"}, 5591 }, 5592 Current: snap.R(11), 5593 Active: true, 5594 }) 5595 5596 s.state.Set("aliases", map[string]map[string]string{ 5597 "alias-snap": { 5598 "alias1": "auto", 5599 }, 5600 }) 5601 5602 s.state.Unlock() 5603 err := s.snapmgr.Ensure() 5604 s.state.Lock() 5605 c.Assert(err, IsNil) 5606 5607 ts, err := snapstate.Update(s.state, "alias-snap", &snapstate.RevisionOptions{Channel: "latest/candidate"}, s.user.ID, snapstate.Flags{}) 5608 c.Assert(err, IsNil) 5609 5610 chg := s.state.NewChange("refresh", "refresh snap") 5611 chg.AddAll(ts) 5612 5613 s.state.Unlock() 5614 defer s.se.Stop() 5615 s.settle(c) 5616 s.state.Lock() 5617 5618 c.Assert(chg.Err(), IsNil) 5619 c.Assert(chg.IsReady(), Equals, true) 5620 } 5621 5622 func (s *snapmgrTestSuite) testUpdateDiskSpaceCheck(c *C, featureFlag, failInstallSize, failDiskCheck bool) error { 5623 restore := snapstate.MockOsutilCheckFreeSpace(func(path string, sz uint64) error { 5624 c.Check(sz, Equals, snapstate.SafetyMarginDiskSpace(123)) 5625 if failDiskCheck { 5626 return &osutil.NotEnoughDiskSpaceError{} 5627 } 5628 return nil 5629 }) 5630 defer restore() 5631 5632 var installSizeCalled bool 5633 5634 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 5635 installSizeCalled = true 5636 if failInstallSize { 5637 return 0, fmt.Errorf("boom") 5638 } 5639 c.Assert(snaps, HasLen, 1) 5640 c.Check(snaps[0].InstanceName(), Equals, "some-snap") 5641 return 123, nil 5642 }) 5643 defer restoreInstallSize() 5644 5645 s.state.Lock() 5646 defer s.state.Unlock() 5647 5648 tr := config.NewTransaction(s.state) 5649 tr.Set("core", "experimental.check-disk-space-refresh", featureFlag) 5650 tr.Commit() 5651 5652 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 5653 Active: true, 5654 Sequence: []*snap.SideInfo{ 5655 {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(4)}, 5656 }, 5657 Current: snap.R(4), 5658 SnapType: "app", 5659 }) 5660 5661 opts := &snapstate.RevisionOptions{Channel: "some-channel"} 5662 _, err := snapstate.Update(s.state, "some-snap", opts, s.user.ID, snapstate.Flags{}) 5663 5664 if featureFlag { 5665 c.Check(installSizeCalled, Equals, true) 5666 } else { 5667 c.Check(installSizeCalled, Equals, false) 5668 } 5669 5670 return err 5671 } 5672 5673 func (s *snapmgrTestSuite) TestUpdateDiskSpaceError(c *C) { 5674 featureFlag := true 5675 failInstallSize := false 5676 failDiskCheck := true 5677 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5678 diskSpaceErr := err.(*snapstate.InsufficientSpaceError) 5679 c.Assert(diskSpaceErr, ErrorMatches, `insufficient space in .* to perform "refresh" change for the following snaps: some-snap`) 5680 c.Check(diskSpaceErr.Path, Equals, filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd")) 5681 c.Check(diskSpaceErr.Snaps, DeepEquals, []string{"some-snap"}) 5682 } 5683 5684 func (s *snapmgrTestSuite) TestUpdateDiskCheckSkippedIfDisabled(c *C) { 5685 featureFlag := false 5686 failInstallSize := false 5687 failDiskCheck := true 5688 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5689 c.Check(err, IsNil) 5690 } 5691 5692 func (s *snapmgrTestSuite) TestUpdateDiskCheckInstallSizeError(c *C) { 5693 featureFlag := true 5694 failInstallSize := true 5695 failDiskCheck := false 5696 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5697 c.Check(err, ErrorMatches, "boom") 5698 } 5699 5700 func (s *snapmgrTestSuite) TestUpdateDiskCheckHappy(c *C) { 5701 featureFlag := true 5702 failInstallSize := false 5703 failDiskCheck := false 5704 err := s.testUpdateDiskSpaceCheck(c, featureFlag, failInstallSize, failDiskCheck) 5705 c.Check(err, IsNil) 5706 }