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