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