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