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