github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/handlers_link_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 "encoding/json" 24 "fmt" 25 "io/ioutil" 26 "path/filepath" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/boot" 33 "github.com/snapcore/snapd/boot/boottest" 34 "github.com/snapcore/snapd/bootloader" 35 "github.com/snapcore/snapd/bootloader/bootloadertest" 36 "github.com/snapcore/snapd/dirs" 37 "github.com/snapcore/snapd/overlord/auth" 38 "github.com/snapcore/snapd/overlord/configstate/config" 39 "github.com/snapcore/snapd/overlord/servicestate" 40 "github.com/snapcore/snapd/overlord/snapstate" 41 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 42 "github.com/snapcore/snapd/overlord/state" 43 "github.com/snapcore/snapd/release" 44 "github.com/snapcore/snapd/snap" 45 "github.com/snapcore/snapd/snap/snaptest" 46 "github.com/snapcore/snapd/testutil" 47 ) 48 49 type linkSnapSuite struct { 50 baseHandlerSuite 51 52 stateBackend *witnessRestartReqStateBackend 53 } 54 55 var _ = Suite(&linkSnapSuite{}) 56 57 type witnessRestartReqStateBackend struct { 58 restartRequested []state.RestartType 59 } 60 61 func (b *witnessRestartReqStateBackend) Checkpoint([]byte) error { 62 return nil 63 } 64 65 func (b *witnessRestartReqStateBackend) RequestRestart(t state.RestartType) { 66 b.restartRequested = append(b.restartRequested, t) 67 } 68 69 func (b *witnessRestartReqStateBackend) EnsureBefore(time.Duration) {} 70 71 func (s *linkSnapSuite) SetUpTest(c *C) { 72 s.stateBackend = &witnessRestartReqStateBackend{} 73 74 s.setup(c, s.stateBackend) 75 76 s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel())) 77 78 oldSnapServiceOptions := snapstate.SnapServiceOptions 79 snapstate.SnapServiceOptions = servicestate.SnapServiceOptions 80 s.AddCleanup(func() { 81 snapstate.SnapServiceOptions = oldSnapServiceOptions 82 }) 83 } 84 85 func checkHasCookieForSnap(c *C, st *state.State, instanceName string) { 86 var contexts map[string]interface{} 87 err := st.Get("snap-cookies", &contexts) 88 c.Assert(err, IsNil) 89 c.Check(contexts, HasLen, 1) 90 91 for _, snap := range contexts { 92 if instanceName == snap { 93 return 94 } 95 } 96 panic(fmt.Sprintf("Cookie missing for snap %q", instanceName)) 97 } 98 99 func (s *linkSnapSuite) TestDoLinkSnapSuccess(c *C) { 100 // we start without the auxiliary store info 101 c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent) 102 103 lp := &testLinkParticipant{} 104 restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp}) 105 defer restore() 106 107 s.state.Lock() 108 t := s.state.NewTask("link-snap", "test") 109 t.Set("snap-setup", &snapstate.SnapSetup{ 110 SideInfo: &snap.SideInfo{ 111 RealName: "foo", 112 Revision: snap.R(33), 113 SnapID: "foo-id", 114 }, 115 Channel: "beta", 116 UserID: 2, 117 }) 118 s.state.NewChange("dummy", "...").AddTask(t) 119 120 s.state.Unlock() 121 122 s.se.Ensure() 123 s.se.Wait() 124 125 s.state.Lock() 126 defer s.state.Unlock() 127 var snapst snapstate.SnapState 128 err := snapstate.Get(s.state, "foo", &snapst) 129 c.Assert(err, IsNil) 130 131 checkHasCookieForSnap(c, s.state, "foo") 132 133 typ, err := snapst.Type() 134 c.Check(err, IsNil) 135 c.Check(typ, Equals, snap.TypeApp) 136 137 c.Check(snapst.Active, Equals, true) 138 c.Check(snapst.Sequence, HasLen, 1) 139 c.Check(snapst.Current, Equals, snap.R(33)) 140 c.Check(snapst.TrackingChannel, Equals, "latest/beta") 141 c.Check(snapst.UserID, Equals, 2) 142 c.Check(snapst.CohortKey, Equals, "") 143 c.Check(t.Status(), Equals, state.DoneStatus) 144 c.Check(s.stateBackend.restartRequested, HasLen, 0) 145 146 // we end with the auxiliary store info 147 c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent) 148 149 // link snap participant was invoked 150 c.Check(lp.instanceNames, DeepEquals, []string{"foo"}) 151 } 152 153 func (s *linkSnapSuite) TestDoLinkSnapSuccessWithCohort(c *C) { 154 // we start without the auxiliary store info 155 c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent) 156 157 s.state.Lock() 158 t := s.state.NewTask("link-snap", "test") 159 t.Set("snap-setup", &snapstate.SnapSetup{ 160 SideInfo: &snap.SideInfo{ 161 RealName: "foo", 162 Revision: snap.R(33), 163 SnapID: "foo-id", 164 }, 165 Channel: "beta", 166 UserID: 2, 167 CohortKey: "wobbling", 168 }) 169 s.state.NewChange("dummy", "...").AddTask(t) 170 171 s.state.Unlock() 172 173 s.se.Ensure() 174 s.se.Wait() 175 176 s.state.Lock() 177 defer s.state.Unlock() 178 var snapst snapstate.SnapState 179 err := snapstate.Get(s.state, "foo", &snapst) 180 c.Assert(err, IsNil) 181 182 checkHasCookieForSnap(c, s.state, "foo") 183 184 typ, err := snapst.Type() 185 c.Check(err, IsNil) 186 c.Check(typ, Equals, snap.TypeApp) 187 188 c.Check(snapst.Active, Equals, true) 189 c.Check(snapst.Sequence, HasLen, 1) 190 c.Check(snapst.Current, Equals, snap.R(33)) 191 c.Check(snapst.TrackingChannel, Equals, "latest/beta") 192 c.Check(snapst.UserID, Equals, 2) 193 c.Check(snapst.CohortKey, Equals, "wobbling") 194 c.Check(t.Status(), Equals, state.DoneStatus) 195 c.Check(s.stateBackend.restartRequested, HasLen, 0) 196 197 // we end with the auxiliary store info 198 c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent) 199 } 200 201 func (s *linkSnapSuite) TestDoLinkSnapSuccessNoUserID(c *C) { 202 s.state.Lock() 203 t := s.state.NewTask("link-snap", "test") 204 t.Set("snap-setup", &snapstate.SnapSetup{ 205 SideInfo: &snap.SideInfo{ 206 RealName: "foo", 207 Revision: snap.R(33), 208 }, 209 Channel: "beta", 210 }) 211 s.state.NewChange("dummy", "...").AddTask(t) 212 213 s.state.Unlock() 214 s.se.Ensure() 215 s.se.Wait() 216 s.state.Lock() 217 defer s.state.Unlock() 218 219 // check that snapst.UserID does not get set 220 var snapst snapstate.SnapState 221 err := snapstate.Get(s.state, "foo", &snapst) 222 c.Assert(err, IsNil) 223 c.Check(snapst.UserID, Equals, 0) 224 225 var snaps map[string]*json.RawMessage 226 err = s.state.Get("snaps", &snaps) 227 c.Assert(err, IsNil) 228 raw := []byte(*snaps["foo"]) 229 c.Check(string(raw), Not(testutil.Contains), "user-id") 230 } 231 232 func (s *linkSnapSuite) TestDoLinkSnapSuccessUserIDAlreadySet(c *C) { 233 s.state.Lock() 234 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 235 Sequence: []*snap.SideInfo{ 236 {RealName: "foo", Revision: snap.R(1)}, 237 }, 238 Current: snap.R(1), 239 UserID: 1, 240 }) 241 // the user 242 user, err := auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"}) 243 c.Assert(err, IsNil) 244 c.Assert(user.ID, Equals, 1) 245 246 t := s.state.NewTask("link-snap", "test") 247 t.Set("snap-setup", &snapstate.SnapSetup{ 248 SideInfo: &snap.SideInfo{ 249 RealName: "foo", 250 Revision: snap.R(33), 251 }, 252 Channel: "beta", 253 UserID: 2, 254 }) 255 s.state.NewChange("dummy", "...").AddTask(t) 256 257 s.state.Unlock() 258 s.se.Ensure() 259 s.se.Wait() 260 s.state.Lock() 261 defer s.state.Unlock() 262 263 // check that snapst.UserID was not "transferred" 264 var snapst snapstate.SnapState 265 err = snapstate.Get(s.state, "foo", &snapst) 266 c.Assert(err, IsNil) 267 c.Check(snapst.UserID, Equals, 1) 268 } 269 270 func (s *linkSnapSuite) TestDoLinkSnapSuccessUserLoggedOut(c *C) { 271 s.state.Lock() 272 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 273 Sequence: []*snap.SideInfo{ 274 {RealName: "foo", Revision: snap.R(1)}, 275 }, 276 Current: snap.R(1), 277 UserID: 1, 278 }) 279 280 t := s.state.NewTask("link-snap", "test") 281 t.Set("snap-setup", &snapstate.SnapSetup{ 282 SideInfo: &snap.SideInfo{ 283 RealName: "foo", 284 Revision: snap.R(33), 285 }, 286 Channel: "beta", 287 UserID: 2, 288 }) 289 s.state.NewChange("dummy", "...").AddTask(t) 290 291 s.state.Unlock() 292 s.se.Ensure() 293 s.se.Wait() 294 s.state.Lock() 295 defer s.state.Unlock() 296 297 // check that snapst.UserID was transferred 298 // given that user 1 doesn't exist anymore 299 var snapst snapstate.SnapState 300 err := snapstate.Get(s.state, "foo", &snapst) 301 c.Assert(err, IsNil) 302 c.Check(snapst.UserID, Equals, 2) 303 } 304 305 func (s *linkSnapSuite) TestDoLinkSnapSeqFile(c *C) { 306 s.state.Lock() 307 // pretend we have an installed snap 308 si11 := &snap.SideInfo{ 309 RealName: "foo", 310 Revision: snap.R(11), 311 } 312 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 313 Sequence: []*snap.SideInfo{si11}, 314 Current: si11.Revision, 315 }) 316 // add a new one 317 t := s.state.NewTask("link-snap", "test") 318 t.Set("snap-setup", &snapstate.SnapSetup{ 319 SideInfo: &snap.SideInfo{ 320 RealName: "foo", 321 Revision: snap.R(33), 322 }, 323 Channel: "beta", 324 }) 325 s.state.NewChange("dummy", "...").AddTask(t) 326 s.state.Unlock() 327 328 s.se.Ensure() 329 s.se.Wait() 330 331 s.state.Lock() 332 defer s.state.Unlock() 333 var snapst snapstate.SnapState 334 err := snapstate.Get(s.state, "foo", &snapst) 335 c.Assert(err, IsNil) 336 337 // and check that the sequence file got updated 338 seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json")) 339 c.Assert(err, IsNil) 340 c.Check(string(seqContent), Equals, `{"sequence":[{"name":"foo","snap-id":"","revision":"11"},{"name":"foo","snap-id":"","revision":"33"}],"current":"33"}`) 341 } 342 343 func (s *linkSnapSuite) TestDoUndoLinkSnap(c *C) { 344 s.state.Lock() 345 defer s.state.Unlock() 346 347 linkChangeCount := 0 348 lp := &testLinkParticipant{ 349 linkageChanged: func(st *state.State, instanceName string) error { 350 var snapst snapstate.SnapState 351 err := snapstate.Get(st, instanceName, &snapst) 352 linkChangeCount++ 353 switch linkChangeCount { 354 case 1: 355 // Initially the snap gets linked. 356 c.Check(err, IsNil) 357 c.Check(snapst.Active, Equals, true) 358 case 2: 359 // Then link-snap is undone and the snap gets unlinked. 360 c.Check(err, Equals, state.ErrNoState) 361 } 362 return nil 363 }, 364 } 365 restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp}) 366 defer restore() 367 368 // a hook might have set some config 369 cfg := json.RawMessage(`{"c":true}`) 370 err := config.SetSnapConfig(s.state, "foo", &cfg) 371 c.Assert(err, IsNil) 372 373 si := &snap.SideInfo{ 374 RealName: "foo", 375 Revision: snap.R(33), 376 } 377 t := s.state.NewTask("link-snap", "test") 378 t.Set("snap-setup", &snapstate.SnapSetup{ 379 SideInfo: si, 380 Channel: "beta", 381 }) 382 chg := s.state.NewChange("dummy", "...") 383 chg.AddTask(t) 384 385 terr := s.state.NewTask("error-trigger", "provoking total undo") 386 terr.WaitFor(t) 387 chg.AddTask(terr) 388 389 s.state.Unlock() 390 391 for i := 0; i < 6; i++ { 392 s.se.Ensure() 393 s.se.Wait() 394 } 395 396 s.state.Lock() 397 var snapst snapstate.SnapState 398 err = snapstate.Get(s.state, "foo", &snapst) 399 c.Assert(err, Equals, state.ErrNoState) 400 c.Check(t.Status(), Equals, state.UndoneStatus) 401 402 // and check that the sequence file got updated 403 seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json")) 404 c.Assert(err, IsNil) 405 c.Check(string(seqContent), Equals, `{"sequence":[],"current":"unset"}`) 406 407 // nothing in config 408 var config map[string]*json.RawMessage 409 err = s.state.Get("config", &config) 410 c.Assert(err, IsNil) 411 c.Check(config, HasLen, 1) 412 _, ok := config["core"] 413 c.Check(ok, Equals, true) 414 415 // link snap participant was invoked, once for do, once for undo. 416 c.Check(lp.instanceNames, DeepEquals, []string{"foo", "foo"}) 417 } 418 419 func (s *linkSnapSuite) TestDoUnlinkCurrentSnapWithIgnoreRunning(c *C) { 420 s.state.Lock() 421 defer s.state.Unlock() 422 423 // With refresh-app-awareness enabled 424 tr := config.NewTransaction(s.state) 425 tr.Set("core", "experimental.refresh-app-awareness", true) 426 tr.Commit() 427 428 // With a snap "pkg" at revision 42 429 si := &snap.SideInfo{RealName: "pkg", Revision: snap.R(42)} 430 snapstate.Set(s.state, "pkg", &snapstate.SnapState{ 431 Sequence: []*snap.SideInfo{si}, 432 Current: si.Revision, 433 Active: true, 434 }) 435 436 // With an app belonging to the snap that is apparently running. 437 snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) { 438 c.Assert(name, Equals, "pkg") 439 info := &snap.Info{SuggestedName: name, SideInfo: *si, SnapType: snap.TypeApp} 440 info.Apps = map[string]*snap.AppInfo{ 441 "app": {Snap: info, Name: "app"}, 442 } 443 return info, nil 444 }) 445 restore := snapstate.MockPidsOfSnap(func(instanceName string) (map[string][]int, error) { 446 c.Assert(instanceName, Equals, "pkg") 447 return map[string][]int{"snap.pkg.app": {1234}}, nil 448 }) 449 defer restore() 450 451 // We can unlink the current revision of that snap, by setting IgnoreRunning flag. 452 task := s.state.NewTask("unlink-current-snap", "") 453 task.Set("snap-setup", &snapstate.SnapSetup{ 454 SideInfo: si, 455 Flags: snapstate.Flags{IgnoreRunning: true}, 456 }) 457 chg := s.state.NewChange("dummy", "...") 458 chg.AddTask(task) 459 460 // Run the task we created 461 s.state.Unlock() 462 s.se.Ensure() 463 s.se.Wait() 464 s.state.Lock() 465 466 // And observe the results. 467 var snapst snapstate.SnapState 468 err := snapstate.Get(s.state, "pkg", &snapst) 469 c.Assert(err, IsNil) 470 c.Check(snapst.Active, Equals, false) 471 c.Check(snapst.Sequence, HasLen, 1) 472 c.Check(snapst.Current, Equals, snap.R(42)) 473 c.Check(task.Status(), Equals, state.DoneStatus) 474 expected := fakeOps{{ 475 op: "unlink-snap", 476 path: filepath.Join(dirs.SnapMountDir, "pkg/42"), 477 }} 478 c.Check(s.fakeBackend.ops, DeepEquals, expected) 479 } 480 481 func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapWithVitalityScore(c *C) { 482 s.state.Lock() 483 defer s.state.Unlock() 484 // foo has a vitality-hint 485 cfg := json.RawMessage(`{"resilience":{"vitality-hint":"bar,foo,baz"}}`) 486 err := config.SetSnapConfig(s.state, "core", &cfg) 487 c.Assert(err, IsNil) 488 489 si1 := &snap.SideInfo{ 490 RealName: "foo", 491 Revision: snap.R(11), 492 } 493 si2 := &snap.SideInfo{ 494 RealName: "foo", 495 Revision: snap.R(33), 496 } 497 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 498 Sequence: []*snap.SideInfo{si1}, 499 Current: si1.Revision, 500 Active: true, 501 }) 502 t := s.state.NewTask("unlink-current-snap", "test") 503 t.Set("snap-setup", &snapstate.SnapSetup{ 504 SideInfo: si2, 505 }) 506 chg := s.state.NewChange("dummy", "...") 507 chg.AddTask(t) 508 509 terr := s.state.NewTask("error-trigger", "provoking total undo") 510 terr.WaitFor(t) 511 chg.AddTask(terr) 512 513 s.state.Unlock() 514 515 for i := 0; i < 3; i++ { 516 s.se.Ensure() 517 s.se.Wait() 518 } 519 520 s.state.Lock() 521 var snapst snapstate.SnapState 522 err = snapstate.Get(s.state, "foo", &snapst) 523 c.Assert(err, IsNil) 524 c.Check(snapst.Active, Equals, true) 525 c.Check(snapst.Sequence, HasLen, 1) 526 c.Check(snapst.Current, Equals, snap.R(11)) 527 c.Check(t.Status(), Equals, state.UndoneStatus) 528 529 expected := fakeOps{ 530 { 531 op: "unlink-snap", 532 path: filepath.Join(dirs.SnapMountDir, "foo/11"), 533 }, 534 { 535 op: "link-snap", 536 path: filepath.Join(dirs.SnapMountDir, "foo/11"), 537 vitalityRank: 2, 538 }, 539 } 540 c.Check(s.fakeBackend.ops, DeepEquals, expected) 541 } 542 543 func (s *linkSnapSuite) TestDoUnlinkCurrentSnapSnapdNop(c *C) { 544 s.state.Lock() 545 defer s.state.Unlock() 546 547 si := &snap.SideInfo{ 548 RealName: "snapd", 549 SnapID: "snapd-snap-id", 550 Revision: snap.R(22), 551 } 552 siOld := *si 553 siOld.Revision = snap.R(20) 554 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 555 Sequence: []*snap.SideInfo{&siOld}, 556 Current: siOld.Revision, 557 Active: true, 558 SnapType: "snapd", 559 }) 560 561 task := s.state.NewTask("unlink-current-snap", "") 562 task.Set("snap-setup", &snapstate.SnapSetup{ 563 SideInfo: si, 564 Channel: "beta", 565 }) 566 chg := s.state.NewChange("dummy", "...") 567 chg.AddTask(task) 568 569 // Run the task we created 570 s.state.Unlock() 571 s.se.Ensure() 572 s.se.Wait() 573 s.state.Lock() 574 575 // And observe the results. 576 var snapst snapstate.SnapState 577 err := snapstate.Get(s.state, "snapd", &snapst) 578 c.Assert(err, IsNil) 579 c.Check(snapst.Active, Equals, false) 580 c.Check(snapst.Sequence, HasLen, 1) 581 c.Check(snapst.Current, Equals, snap.R(20)) 582 c.Check(task.Status(), Equals, state.DoneStatus) 583 // backend was not called to unlink the snap 584 c.Check(s.fakeBackend.ops, HasLen, 0) 585 } 586 587 func (s *linkSnapSuite) TestDoUnlinkSnapdUnlinks(c *C) { 588 s.state.Lock() 589 defer s.state.Unlock() 590 591 si := &snap.SideInfo{ 592 RealName: "snapd", 593 Revision: snap.R(20), 594 } 595 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 596 Sequence: []*snap.SideInfo{si}, 597 Current: si.Revision, 598 Active: true, 599 }) 600 601 task := s.state.NewTask("unlink-snap", "") 602 task.Set("snap-setup", &snapstate.SnapSetup{ 603 SideInfo: si, 604 Channel: "beta", 605 }) 606 chg := s.state.NewChange("dummy", "...") 607 chg.AddTask(task) 608 609 // Run the task we created 610 s.state.Unlock() 611 s.se.Ensure() 612 s.se.Wait() 613 s.state.Lock() 614 615 // And observe the results. 616 var snapst snapstate.SnapState 617 err := snapstate.Get(s.state, "snapd", &snapst) 618 c.Assert(err, IsNil) 619 c.Check(snapst.Active, Equals, false) 620 c.Check(snapst.Sequence, HasLen, 1) 621 c.Check(snapst.Current, Equals, snap.R(20)) 622 c.Check(task.Status(), Equals, state.DoneStatus) 623 // backend was called to unlink the snap 624 expected := fakeOps{{ 625 op: "unlink-snap", 626 path: filepath.Join(dirs.SnapMountDir, "snapd/20"), 627 }} 628 c.Check(s.fakeBackend.ops, DeepEquals, expected) 629 } 630 631 func (s *linkSnapSuite) TestDoLinkSnapWithVitalityScore(c *C) { 632 s.state.Lock() 633 defer s.state.Unlock() 634 // a hook might have set some config 635 cfg := json.RawMessage(`{"resilience":{"vitality-hint":"bar,foo,baz"}}`) 636 err := config.SetSnapConfig(s.state, "core", &cfg) 637 c.Assert(err, IsNil) 638 639 si := &snap.SideInfo{ 640 RealName: "foo", 641 Revision: snap.R(33), 642 } 643 t := s.state.NewTask("link-snap", "test") 644 t.Set("snap-setup", &snapstate.SnapSetup{ 645 SideInfo: si, 646 }) 647 chg := s.state.NewChange("dummy", "...") 648 chg.AddTask(t) 649 650 s.state.Unlock() 651 652 for i := 0; i < 6; i++ { 653 s.se.Ensure() 654 s.se.Wait() 655 } 656 657 s.state.Lock() 658 expected := fakeOps{ 659 { 660 op: "candidate", 661 sinfo: *si, 662 }, 663 { 664 op: "link-snap", 665 path: filepath.Join(dirs.SnapMountDir, "foo/33"), 666 vitalityRank: 2, 667 }, 668 } 669 c.Check(s.fakeBackend.ops, DeepEquals, expected) 670 } 671 672 func (s *linkSnapSuite) TestDoLinkSnapTryToCleanupOnError(c *C) { 673 s.state.Lock() 674 defer s.state.Unlock() 675 676 lp := &testLinkParticipant{} 677 restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp}) 678 defer restore() 679 680 si := &snap.SideInfo{ 681 RealName: "foo", 682 Revision: snap.R(35), 683 } 684 t := s.state.NewTask("link-snap", "test") 685 t.Set("snap-setup", &snapstate.SnapSetup{ 686 SideInfo: si, 687 Channel: "beta", 688 }) 689 690 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "foo/35") 691 s.state.NewChange("dummy", "...").AddTask(t) 692 s.state.Unlock() 693 694 s.se.Ensure() 695 s.se.Wait() 696 697 s.state.Lock() 698 699 // state as expected 700 var snapst snapstate.SnapState 701 err := snapstate.Get(s.state, "foo", &snapst) 702 c.Assert(err, Equals, state.ErrNoState) 703 704 // tried to cleanup 705 expected := fakeOps{ 706 { 707 op: "candidate", 708 sinfo: *si, 709 }, 710 { 711 op: "link-snap.failed", 712 path: filepath.Join(dirs.SnapMountDir, "foo/35"), 713 }, 714 { 715 op: "unlink-snap", 716 path: filepath.Join(dirs.SnapMountDir, "foo/35"), 717 718 unlinkFirstInstallUndo: true, 719 }, 720 } 721 722 // start with an easier-to-read error if this fails: 723 c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 724 c.Check(s.fakeBackend.ops, DeepEquals, expected) 725 726 // link snap participant was invoked 727 c.Check(lp.instanceNames, DeepEquals, []string{"foo"}) 728 } 729 730 func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreRestarts(c *C) { 731 restore := release.MockOnClassic(true) 732 defer restore() 733 734 s.state.Lock() 735 si := &snap.SideInfo{ 736 RealName: "core", 737 Revision: snap.R(33), 738 } 739 t := s.state.NewTask("link-snap", "test") 740 t.Set("snap-setup", &snapstate.SnapSetup{ 741 SideInfo: si, 742 }) 743 s.state.NewChange("dummy", "...").AddTask(t) 744 745 s.state.Unlock() 746 747 s.se.Ensure() 748 s.se.Wait() 749 750 s.state.Lock() 751 defer s.state.Unlock() 752 753 var snapst snapstate.SnapState 754 err := snapstate.Get(s.state, "core", &snapst) 755 c.Assert(err, IsNil) 756 757 typ, err := snapst.Type() 758 c.Check(err, IsNil) 759 c.Check(typ, Equals, snap.TypeOS) 760 761 c.Check(t.Status(), Equals, state.DoneStatus) 762 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon}) 763 c.Check(t.Log(), HasLen, 1) 764 c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart\.`) 765 } 766 767 func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnCoreWithBase(c *C) { 768 restore := release.MockOnClassic(false) 769 defer restore() 770 771 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 772 defer r() 773 774 s.state.Lock() 775 si := &snap.SideInfo{ 776 RealName: "snapd", 777 SnapID: "snapd-snap-id", 778 Revision: snap.R(22), 779 } 780 t := s.state.NewTask("link-snap", "test") 781 t.Set("snap-setup", &snapstate.SnapSetup{ 782 SideInfo: si, 783 Type: snap.TypeSnapd, 784 }) 785 s.state.NewChange("dummy", "...").AddTask(t) 786 787 s.state.Unlock() 788 789 s.se.Ensure() 790 s.se.Wait() 791 792 s.state.Lock() 793 defer s.state.Unlock() 794 795 var snapst snapstate.SnapState 796 err := snapstate.Get(s.state, "snapd", &snapst) 797 c.Assert(err, IsNil) 798 799 typ, err := snapst.Type() 800 c.Check(err, IsNil) 801 c.Check(typ, Equals, snap.TypeSnapd) 802 803 c.Check(t.Status(), Equals, state.DoneStatus) 804 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon}) 805 c.Check(t.Log(), HasLen, 1) 806 c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`) 807 } 808 809 func (s *linkSnapSuite) TestDoLinkSnapSuccessRebootForCoreBase(c *C) { 810 restore := release.MockOnClassic(false) 811 defer restore() 812 813 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 814 defer r() 815 816 s.fakeBackend.linkSnapMaybeReboot = true 817 818 s.state.Lock() 819 defer s.state.Unlock() 820 821 // we need to init the boot-id 822 err := s.state.VerifyReboot("some-boot-id") 823 c.Assert(err, IsNil) 824 825 si := &snap.SideInfo{ 826 RealName: "core18", 827 SnapID: "core18-id", 828 Revision: snap.R(22), 829 } 830 t := s.state.NewTask("link-snap", "test") 831 t.Set("snap-setup", &snapstate.SnapSetup{ 832 SideInfo: si, 833 }) 834 s.state.NewChange("dummy", "...").AddTask(t) 835 836 s.state.Unlock() 837 s.se.Ensure() 838 s.se.Wait() 839 s.state.Lock() 840 841 c.Check(t.Status(), Equals, state.DoneStatus) 842 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartSystem}) 843 c.Assert(t.Log(), HasLen, 1) 844 c.Check(t.Log()[0], Matches, `.*INFO Requested system restart.*`) 845 } 846 847 func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnClassic(c *C) { 848 restore := release.MockOnClassic(true) 849 defer restore() 850 851 s.state.Lock() 852 si := &snap.SideInfo{ 853 RealName: "snapd", 854 SnapID: "snapd-snap-id", 855 Revision: snap.R(22), 856 } 857 t := s.state.NewTask("link-snap", "test") 858 t.Set("snap-setup", &snapstate.SnapSetup{ 859 SideInfo: si, 860 Type: snap.TypeSnapd, 861 }) 862 s.state.NewChange("dummy", "...").AddTask(t) 863 864 s.state.Unlock() 865 866 s.se.Ensure() 867 s.se.Wait() 868 869 s.state.Lock() 870 defer s.state.Unlock() 871 872 var snapst snapstate.SnapState 873 err := snapstate.Get(s.state, "snapd", &snapst) 874 c.Assert(err, IsNil) 875 876 typ, err := snapst.Type() 877 c.Check(err, IsNil) 878 c.Check(typ, Equals, snap.TypeSnapd) 879 880 c.Check(t.Status(), Equals, state.DoneStatus) 881 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon}) 882 c.Check(t.Log(), HasLen, 1) 883 } 884 885 func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreAndSnapdNoCoreRestart(c *C) { 886 restore := release.MockOnClassic(true) 887 defer restore() 888 889 s.state.Lock() 890 siSnapd := &snap.SideInfo{ 891 RealName: "snapd", 892 Revision: snap.R(64), 893 } 894 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 895 Sequence: []*snap.SideInfo{siSnapd}, 896 Current: siSnapd.Revision, 897 Active: true, 898 SnapType: "snapd", 899 }) 900 901 si := &snap.SideInfo{ 902 RealName: "core", 903 Revision: snap.R(33), 904 } 905 t := s.state.NewTask("link-snap", "test") 906 t.Set("snap-setup", &snapstate.SnapSetup{ 907 SideInfo: si, 908 }) 909 s.state.NewChange("dummy", "...").AddTask(t) 910 911 s.state.Unlock() 912 913 s.se.Ensure() 914 s.se.Wait() 915 916 s.state.Lock() 917 defer s.state.Unlock() 918 919 var snapst snapstate.SnapState 920 err := snapstate.Get(s.state, "core", &snapst) 921 c.Assert(err, IsNil) 922 923 typ, err := snapst.Type() 924 c.Check(err, IsNil) 925 c.Check(typ, Equals, snap.TypeOS) 926 927 c.Check(t.Status(), Equals, state.DoneStatus) 928 c.Check(s.stateBackend.restartRequested, IsNil) 929 c.Check(t.Log(), HasLen, 0) 930 } 931 932 func (s *linkSnapSuite) TestDoLinkSnapdSnapCleanupOnErrorFirstInstall(c *C) { 933 s.state.Lock() 934 defer s.state.Unlock() 935 936 lp := &testLinkParticipant{} 937 restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp}) 938 defer restore() 939 940 si := &snap.SideInfo{ 941 RealName: "snapd", 942 SnapID: "snapd-snap-id", 943 Revision: snap.R(22), 944 } 945 t := s.state.NewTask("link-snap", "test") 946 t.Set("snap-setup", &snapstate.SnapSetup{ 947 SideInfo: si, 948 Channel: "beta", 949 }) 950 951 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "snapd/22") 952 s.state.NewChange("dummy", "...").AddTask(t) 953 s.state.Unlock() 954 955 s.se.Ensure() 956 s.se.Wait() 957 958 s.state.Lock() 959 960 // state as expected 961 var snapst snapstate.SnapState 962 err := snapstate.Get(s.state, "foo", &snapst) 963 c.Assert(err, Equals, state.ErrNoState) 964 965 // tried to cleanup 966 expected := fakeOps{ 967 { 968 op: "candidate", 969 sinfo: *si, 970 }, 971 { 972 op: "link-snap.failed", 973 path: filepath.Join(dirs.SnapMountDir, "snapd/22"), 974 }, 975 { 976 op: "unlink-snap", 977 path: filepath.Join(dirs.SnapMountDir, "snapd/22"), 978 979 unlinkFirstInstallUndo: true, 980 }, 981 } 982 983 // start with an easier-to-read error if this fails: 984 c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 985 c.Check(s.fakeBackend.ops, DeepEquals, expected) 986 987 // link snap participant was invoked 988 c.Check(lp.instanceNames, DeepEquals, []string{"snapd"}) 989 } 990 991 func (s *linkSnapSuite) TestDoLinkSnapdSnapCleanupOnErrorNthInstall(c *C) { 992 s.state.Lock() 993 defer s.state.Unlock() 994 995 lp := &testLinkParticipant{} 996 restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp}) 997 defer restore() 998 999 si := &snap.SideInfo{ 1000 RealName: "snapd", 1001 SnapID: "snapd-snap-id", 1002 Revision: snap.R(22), 1003 } 1004 siOld := *si 1005 siOld.Revision = snap.R(20) 1006 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 1007 Sequence: []*snap.SideInfo{&siOld}, 1008 Current: siOld.Revision, 1009 Active: true, 1010 SnapType: "snapd", 1011 }) 1012 t := s.state.NewTask("link-snap", "test") 1013 t.Set("snap-setup", &snapstate.SnapSetup{ 1014 SideInfo: si, 1015 Channel: "beta", 1016 }) 1017 1018 s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "snapd/22") 1019 s.state.NewChange("dummy", "...").AddTask(t) 1020 s.state.Unlock() 1021 1022 s.se.Ensure() 1023 s.se.Wait() 1024 1025 s.state.Lock() 1026 1027 // state as expected 1028 var snapst snapstate.SnapState 1029 err := snapstate.Get(s.state, "foo", &snapst) 1030 c.Assert(err, Equals, state.ErrNoState) 1031 1032 // tried to cleanup 1033 expected := fakeOps{ 1034 { 1035 op: "candidate", 1036 sinfo: *si, 1037 }, 1038 { 1039 op: "link-snap.failed", 1040 path: filepath.Join(dirs.SnapMountDir, "snapd/22"), 1041 }, 1042 { 1043 // we link the old revision 1044 op: "link-snap", 1045 path: filepath.Join(dirs.SnapMountDir, "snapd/20"), 1046 1047 unlinkFirstInstallUndo: false, 1048 }, 1049 } 1050 1051 // start with an easier-to-read error if this fails: 1052 c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 1053 c.Check(s.fakeBackend.ops, DeepEquals, expected) 1054 1055 // link snap participant was invoked 1056 c.Check(lp.instanceNames, DeepEquals, []string{"snapd"}) 1057 } 1058 1059 func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceDidNotHaveCandidate(c *C) { 1060 s.state.Lock() 1061 defer s.state.Unlock() 1062 si1 := &snap.SideInfo{ 1063 RealName: "foo", 1064 Revision: snap.R(1), 1065 } 1066 si2 := &snap.SideInfo{ 1067 RealName: "foo", 1068 Revision: snap.R(2), 1069 } 1070 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 1071 Sequence: []*snap.SideInfo{si1}, 1072 Current: si1.Revision, 1073 }) 1074 t := s.state.NewTask("link-snap", "test") 1075 t.Set("snap-setup", &snapstate.SnapSetup{ 1076 SideInfo: si2, 1077 Channel: "beta", 1078 }) 1079 chg := s.state.NewChange("dummy", "...") 1080 chg.AddTask(t) 1081 1082 terr := s.state.NewTask("error-trigger", "provoking total undo") 1083 terr.WaitFor(t) 1084 chg.AddTask(terr) 1085 1086 s.state.Unlock() 1087 1088 for i := 0; i < 6; i++ { 1089 s.se.Ensure() 1090 s.se.Wait() 1091 } 1092 1093 s.state.Lock() 1094 var snapst snapstate.SnapState 1095 err := snapstate.Get(s.state, "foo", &snapst) 1096 c.Assert(err, IsNil) 1097 c.Check(snapst.Active, Equals, false) 1098 c.Check(snapst.Sequence, HasLen, 1) 1099 c.Check(snapst.Current, Equals, snap.R(1)) 1100 c.Check(t.Status(), Equals, state.UndoneStatus) 1101 } 1102 1103 func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceHadCandidate(c *C) { 1104 s.state.Lock() 1105 defer s.state.Unlock() 1106 si1 := &snap.SideInfo{ 1107 RealName: "foo", 1108 Revision: snap.R(1), 1109 } 1110 si2 := &snap.SideInfo{ 1111 RealName: "foo", 1112 Revision: snap.R(2), 1113 } 1114 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 1115 Sequence: []*snap.SideInfo{si1, si2}, 1116 Current: si2.Revision, 1117 }) 1118 t := s.state.NewTask("link-snap", "test") 1119 t.Set("snap-setup", &snapstate.SnapSetup{ 1120 SideInfo: si1, 1121 Channel: "beta", 1122 }) 1123 chg := s.state.NewChange("dummy", "...") 1124 chg.AddTask(t) 1125 1126 terr := s.state.NewTask("error-trigger", "provoking total undo") 1127 terr.WaitFor(t) 1128 chg.AddTask(terr) 1129 1130 s.state.Unlock() 1131 1132 for i := 0; i < 6; i++ { 1133 s.se.Ensure() 1134 s.se.Wait() 1135 } 1136 1137 s.state.Lock() 1138 var snapst snapstate.SnapState 1139 err := snapstate.Get(s.state, "foo", &snapst) 1140 c.Assert(err, IsNil) 1141 c.Check(snapst.Active, Equals, false) 1142 c.Check(snapst.Sequence, HasLen, 2) 1143 c.Check(snapst.Current, Equals, snap.R(2)) 1144 c.Check(t.Status(), Equals, state.UndoneStatus) 1145 } 1146 1147 func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapCore(c *C) { 1148 restore := release.MockOnClassic(true) 1149 defer restore() 1150 1151 linkChangeCount := 0 1152 lp := &testLinkParticipant{ 1153 linkageChanged: func(st *state.State, instanceName string) error { 1154 var snapst snapstate.SnapState 1155 err := snapstate.Get(st, instanceName, &snapst) 1156 linkChangeCount++ 1157 switch linkChangeCount { 1158 case 1: 1159 // Initially the snap gets unlinked. 1160 c.Check(err, IsNil) 1161 c.Check(snapst.Active, Equals, false) 1162 case 2: 1163 // Then the undo handler re-links it. 1164 c.Check(err, IsNil) 1165 c.Check(snapst.Active, Equals, true) 1166 } 1167 return nil 1168 }, 1169 } 1170 restore = snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp}) 1171 defer restore() 1172 1173 s.state.Lock() 1174 defer s.state.Unlock() 1175 si1 := &snap.SideInfo{ 1176 RealName: "core", 1177 Revision: snap.R(1), 1178 } 1179 si2 := &snap.SideInfo{ 1180 RealName: "core", 1181 Revision: snap.R(2), 1182 } 1183 snapstate.Set(s.state, "core", &snapstate.SnapState{ 1184 Sequence: []*snap.SideInfo{si1}, 1185 Current: si1.Revision, 1186 Active: true, 1187 SnapType: "os", 1188 }) 1189 t := s.state.NewTask("unlink-current-snap", "test") 1190 t.Set("snap-setup", &snapstate.SnapSetup{ 1191 SideInfo: si2, 1192 }) 1193 chg := s.state.NewChange("dummy", "...") 1194 chg.AddTask(t) 1195 1196 terr := s.state.NewTask("error-trigger", "provoking total undo") 1197 terr.WaitFor(t) 1198 chg.AddTask(terr) 1199 1200 s.state.Unlock() 1201 1202 for i := 0; i < 3; i++ { 1203 s.se.Ensure() 1204 s.se.Wait() 1205 } 1206 1207 s.state.Lock() 1208 var snapst snapstate.SnapState 1209 err := snapstate.Get(s.state, "core", &snapst) 1210 c.Assert(err, IsNil) 1211 c.Check(snapst.Active, Equals, true) 1212 c.Check(snapst.Sequence, HasLen, 1) 1213 c.Check(snapst.Current, Equals, snap.R(1)) 1214 c.Check(t.Status(), Equals, state.UndoneStatus) 1215 1216 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon}) 1217 c.Check(lp.instanceNames, DeepEquals, []string{"core", "core"}) 1218 } 1219 1220 func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapCoreBase(c *C) { 1221 restore := release.MockOnClassic(false) 1222 defer restore() 1223 1224 r := snapstatetest.MockDeviceModel(ModelWithBase("core18")) 1225 defer r() 1226 1227 s.fakeBackend.linkSnapMaybeReboot = true 1228 1229 s.state.Lock() 1230 defer s.state.Unlock() 1231 // we need to init the boot-id 1232 err := s.state.VerifyReboot("some-boot-id") 1233 c.Assert(err, IsNil) 1234 1235 si1 := &snap.SideInfo{ 1236 RealName: "core18", 1237 Revision: snap.R(1), 1238 } 1239 si2 := &snap.SideInfo{ 1240 RealName: "core18", 1241 Revision: snap.R(2), 1242 } 1243 snapstate.Set(s.state, "core18", &snapstate.SnapState{ 1244 Sequence: []*snap.SideInfo{si1}, 1245 Current: si1.Revision, 1246 Active: true, 1247 SnapType: "base", 1248 }) 1249 t := s.state.NewTask("unlink-current-snap", "test") 1250 t.Set("snap-setup", &snapstate.SnapSetup{ 1251 SideInfo: si2, 1252 }) 1253 chg := s.state.NewChange("dummy", "...") 1254 chg.AddTask(t) 1255 1256 terr := s.state.NewTask("error-trigger", "provoking total undo") 1257 terr.WaitFor(t) 1258 chg.AddTask(terr) 1259 1260 s.state.Unlock() 1261 for i := 0; i < 3; i++ { 1262 s.se.Ensure() 1263 s.se.Wait() 1264 } 1265 s.state.Lock() 1266 1267 var snapst snapstate.SnapState 1268 err = snapstate.Get(s.state, "core18", &snapst) 1269 c.Assert(err, IsNil) 1270 c.Check(snapst.Active, Equals, true) 1271 c.Check(snapst.Sequence, HasLen, 1) 1272 c.Check(snapst.Current, Equals, snap.R(1)) 1273 c.Check(t.Status(), Equals, state.UndoneStatus) 1274 1275 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartSystem}) 1276 } 1277 1278 func (s *linkSnapSuite) TestDoUndoLinkSnapCoreClassic(c *C) { 1279 restore := release.MockOnClassic(true) 1280 defer restore() 1281 1282 s.state.Lock() 1283 defer s.state.Unlock() 1284 1285 // no previous core snap and an error on link, in this 1286 // case we need to restart on classic back into the distro 1287 // package version 1288 si1 := &snap.SideInfo{ 1289 RealName: "core", 1290 Revision: snap.R(1), 1291 } 1292 t := s.state.NewTask("link-snap", "test") 1293 t.Set("snap-setup", &snapstate.SnapSetup{ 1294 SideInfo: si1, 1295 }) 1296 chg := s.state.NewChange("dummy", "...") 1297 chg.AddTask(t) 1298 1299 terr := s.state.NewTask("error-trigger", "provoking total undo") 1300 terr.WaitFor(t) 1301 chg.AddTask(terr) 1302 1303 s.state.Unlock() 1304 1305 for i := 0; i < 3; i++ { 1306 s.se.Ensure() 1307 s.se.Wait() 1308 } 1309 1310 s.state.Lock() 1311 var snapst snapstate.SnapState 1312 err := snapstate.Get(s.state, "core", &snapst) 1313 c.Assert(err, Equals, state.ErrNoState) 1314 c.Check(t.Status(), Equals, state.UndoneStatus) 1315 1316 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon}) 1317 1318 } 1319 1320 func (s *linkSnapSuite) TestLinkSnapInjectsAutoConnectIfMissing(c *C) { 1321 si1 := &snap.SideInfo{ 1322 RealName: "snap1", 1323 Revision: snap.R(1), 1324 } 1325 sup1 := &snapstate.SnapSetup{SideInfo: si1} 1326 si2 := &snap.SideInfo{ 1327 RealName: "snap2", 1328 Revision: snap.R(1), 1329 } 1330 sup2 := &snapstate.SnapSetup{SideInfo: si2} 1331 1332 s.state.Lock() 1333 defer s.state.Unlock() 1334 1335 task0 := s.state.NewTask("setup-profiles", "") 1336 task1 := s.state.NewTask("link-snap", "") 1337 task1.WaitFor(task0) 1338 task0.Set("snap-setup", sup1) 1339 task1.Set("snap-setup", sup1) 1340 1341 task2 := s.state.NewTask("setup-profiles", "") 1342 task3 := s.state.NewTask("link-snap", "") 1343 task2.WaitFor(task1) 1344 task3.WaitFor(task2) 1345 task2.Set("snap-setup", sup2) 1346 task3.Set("snap-setup", sup2) 1347 1348 chg := s.state.NewChange("test", "") 1349 chg.AddTask(task0) 1350 chg.AddTask(task1) 1351 chg.AddTask(task2) 1352 chg.AddTask(task3) 1353 1354 s.state.Unlock() 1355 1356 for i := 0; i < 10; i++ { 1357 s.se.Ensure() 1358 s.se.Wait() 1359 } 1360 1361 s.state.Lock() 1362 1363 // ensure all our tasks ran 1364 c.Assert(chg.Err(), IsNil) 1365 c.Assert(chg.Tasks(), HasLen, 6) 1366 1367 // sanity checks 1368 t := chg.Tasks()[1] 1369 c.Assert(t.Kind(), Equals, "link-snap") 1370 t = chg.Tasks()[3] 1371 c.Assert(t.Kind(), Equals, "link-snap") 1372 1373 // check that auto-connect tasks were added and have snap-setup 1374 var autoconnectSup snapstate.SnapSetup 1375 t = chg.Tasks()[4] 1376 c.Assert(t.Kind(), Equals, "auto-connect") 1377 c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil) 1378 c.Assert(autoconnectSup.InstanceName(), Equals, "snap1") 1379 1380 t = chg.Tasks()[5] 1381 c.Assert(t.Kind(), Equals, "auto-connect") 1382 c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil) 1383 c.Assert(autoconnectSup.InstanceName(), Equals, "snap2") 1384 } 1385 1386 func (s *linkSnapSuite) TestDoLinkSnapFailureCleansUpAux(c *C) { 1387 // this is very chummy with the order of LinkSnap 1388 c.Assert(ioutil.WriteFile(dirs.SnapSeqDir, nil, 0644), IsNil) 1389 1390 // we start without the auxiliary store info 1391 c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent) 1392 1393 s.state.Lock() 1394 t := s.state.NewTask("link-snap", "test") 1395 t.Set("snap-setup", &snapstate.SnapSetup{ 1396 SideInfo: &snap.SideInfo{ 1397 RealName: "foo", 1398 Revision: snap.R(33), 1399 SnapID: "foo-id", 1400 }, 1401 Channel: "beta", 1402 UserID: 2, 1403 }) 1404 s.state.NewChange("dummy", "...").AddTask(t) 1405 1406 s.state.Unlock() 1407 1408 s.se.Ensure() 1409 s.se.Wait() 1410 1411 s.state.Lock() 1412 defer s.state.Unlock() 1413 1414 c.Check(t.Status(), Equals, state.ErrorStatus) 1415 c.Check(s.stateBackend.restartRequested, HasLen, 0) 1416 1417 // we end without the auxiliary store info 1418 c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent) 1419 } 1420 1421 func (s *linkSnapSuite) TestLinkSnapResetsRefreshInhibitedTime(c *C) { 1422 // When a snap is linked the refresh-inhibited-time is reset to zero 1423 // to indicate a successful refresh. The old value is stored in task 1424 // state for task undo logic. 1425 s.state.Lock() 1426 defer s.state.Unlock() 1427 1428 instant := time.Now() 1429 1430 si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)} 1431 sup := &snapstate.SnapSetup{SideInfo: si} 1432 snapstate.Set(s.state, "snap", &snapstate.SnapState{ 1433 Sequence: []*snap.SideInfo{si}, 1434 Current: si.Revision, 1435 RefreshInhibitedTime: &instant, 1436 }) 1437 1438 task := s.state.NewTask("link-snap", "") 1439 task.Set("snap-setup", sup) 1440 chg := s.state.NewChange("test", "") 1441 chg.AddTask(task) 1442 1443 s.state.Unlock() 1444 1445 for i := 0; i < 10; i++ { 1446 s.se.Ensure() 1447 s.se.Wait() 1448 } 1449 1450 s.state.Lock() 1451 1452 c.Assert(chg.Err(), IsNil) 1453 c.Assert(chg.Tasks(), HasLen, 1) 1454 1455 var snapst snapstate.SnapState 1456 err := snapstate.Get(s.state, "snap", &snapst) 1457 c.Assert(err, IsNil) 1458 c.Check(snapst.RefreshInhibitedTime, IsNil) 1459 1460 var oldTime time.Time 1461 c.Assert(task.Get("old-refresh-inhibited-time", &oldTime), IsNil) 1462 c.Check(oldTime.Equal(instant), Equals, true) 1463 } 1464 1465 func (s *linkSnapSuite) TestDoUndoLinkSnapRestoresRefreshInhibitedTime(c *C) { 1466 s.state.Lock() 1467 defer s.state.Unlock() 1468 1469 instant := time.Now() 1470 1471 si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)} 1472 sup := &snapstate.SnapSetup{SideInfo: si} 1473 snapstate.Set(s.state, "snap", &snapstate.SnapState{ 1474 Sequence: []*snap.SideInfo{si}, 1475 Current: si.Revision, 1476 RefreshInhibitedTime: &instant, 1477 }) 1478 1479 task := s.state.NewTask("link-snap", "") 1480 task.Set("snap-setup", sup) 1481 chg := s.state.NewChange("test", "") 1482 chg.AddTask(task) 1483 1484 terr := s.state.NewTask("error-trigger", "provoking total undo") 1485 terr.WaitFor(task) 1486 chg.AddTask(terr) 1487 1488 s.state.Unlock() 1489 1490 for i := 0; i < 6; i++ { 1491 s.se.Ensure() 1492 s.se.Wait() 1493 } 1494 1495 s.state.Lock() 1496 1497 c.Assert(chg.Err(), NotNil) 1498 c.Assert(chg.Tasks(), HasLen, 2) 1499 c.Check(task.Status(), Equals, state.UndoneStatus) 1500 1501 var snapst snapstate.SnapState 1502 err := snapstate.Get(s.state, "snap", &snapst) 1503 c.Assert(err, IsNil) 1504 c.Check(snapst.RefreshInhibitedTime.Equal(instant), Equals, true) 1505 } 1506 1507 func (s *linkSnapSuite) TestUndoLinkSnapdFirstInstall(c *C) { 1508 restore := release.MockOnClassic(false) 1509 defer restore() 1510 1511 s.state.Lock() 1512 si := &snap.SideInfo{ 1513 RealName: "snapd", 1514 SnapID: "snapd-snap-id", 1515 Revision: snap.R(22), 1516 } 1517 chg := s.state.NewChange("dummy", "...") 1518 t := s.state.NewTask("link-snap", "test") 1519 t.Set("snap-setup", &snapstate.SnapSetup{ 1520 SideInfo: si, 1521 Type: snap.TypeSnapd, 1522 }) 1523 chg.AddTask(t) 1524 terr := s.state.NewTask("error-trigger", "provoking total undo") 1525 terr.WaitFor(t) 1526 chg.AddTask(terr) 1527 1528 s.state.Unlock() 1529 1530 for i := 0; i < 6; i++ { 1531 s.se.Ensure() 1532 s.se.Wait() 1533 } 1534 1535 s.state.Lock() 1536 defer s.state.Unlock() 1537 1538 var snapst snapstate.SnapState 1539 err := snapstate.Get(s.state, "snapd", &snapst) 1540 c.Assert(err, Equals, state.ErrNoState) 1541 c.Check(t.Status(), Equals, state.UndoneStatus) 1542 1543 expected := fakeOps{ 1544 { 1545 op: "candidate", 1546 sinfo: *si, 1547 }, 1548 { 1549 op: "link-snap", 1550 path: filepath.Join(dirs.SnapMountDir, "snapd/22"), 1551 }, 1552 { 1553 op: "discard-namespace", 1554 name: "snapd", 1555 }, 1556 { 1557 op: "unlink-snap", 1558 path: filepath.Join(dirs.SnapMountDir, "snapd/22"), 1559 1560 unlinkFirstInstallUndo: true, 1561 }, 1562 } 1563 1564 // start with an easier-to-read error if this fails: 1565 c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 1566 c.Check(s.fakeBackend.ops, DeepEquals, expected) 1567 1568 // 2 restarts, one from link snap, another one from undo 1569 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon}) 1570 c.Check(t.Log(), HasLen, 3) 1571 c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`) 1572 c.Check(t.Log()[2], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`) 1573 1574 } 1575 1576 func (s *linkSnapSuite) TestUndoLinkSnapdNthInstall(c *C) { 1577 restore := release.MockOnClassic(false) 1578 defer restore() 1579 1580 s.state.Lock() 1581 si := &snap.SideInfo{ 1582 RealName: "snapd", 1583 SnapID: "snapd-snap-id", 1584 Revision: snap.R(22), 1585 } 1586 siOld := *si 1587 siOld.Revision = snap.R(20) 1588 snapstate.Set(s.state, "snapd", &snapstate.SnapState{ 1589 Sequence: []*snap.SideInfo{&siOld}, 1590 Current: siOld.Revision, 1591 Active: true, 1592 SnapType: "snapd", 1593 }) 1594 chg := s.state.NewChange("dummy", "...") 1595 t := s.state.NewTask("link-snap", "test") 1596 t.Set("snap-setup", &snapstate.SnapSetup{ 1597 SideInfo: si, 1598 Type: snap.TypeSnapd, 1599 }) 1600 chg.AddTask(t) 1601 terr := s.state.NewTask("error-trigger", "provoking total undo") 1602 terr.WaitFor(t) 1603 chg.AddTask(terr) 1604 1605 s.state.Unlock() 1606 1607 for i := 0; i < 6; i++ { 1608 s.se.Ensure() 1609 s.se.Wait() 1610 } 1611 1612 s.state.Lock() 1613 defer s.state.Unlock() 1614 1615 var snapst snapstate.SnapState 1616 err := snapstate.Get(s.state, "snapd", &snapst) 1617 c.Assert(err, IsNil) 1618 snapst.Current = siOld.Revision 1619 c.Check(t.Status(), Equals, state.UndoneStatus) 1620 1621 expected := fakeOps{ 1622 { 1623 op: "candidate", 1624 sinfo: *si, 1625 }, 1626 { 1627 op: "link-snap", 1628 path: filepath.Join(dirs.SnapMountDir, "snapd/22"), 1629 }, 1630 { 1631 op: "link-snap", 1632 path: filepath.Join(dirs.SnapMountDir, "snapd/20"), 1633 }, 1634 } 1635 1636 // start with an easier-to-read error if this fails: 1637 c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 1638 c.Check(s.fakeBackend.ops, DeepEquals, expected) 1639 1640 // 1 restart from link snap, the other restart happens 1641 // in undoUnlinkCurrentSnap (not tested here) 1642 c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon}) 1643 c.Assert(t.Log(), HasLen, 1) 1644 c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`) 1645 } 1646 1647 func (s *linkSnapSuite) TestDoUnlinkSnapRefreshAwarenessHardCheckOn(c *C) { 1648 s.state.Lock() 1649 defer s.state.Unlock() 1650 1651 tr := config.NewTransaction(s.state) 1652 tr.Set("core", "experimental.refresh-app-awareness", true) 1653 tr.Commit() 1654 1655 chg := s.testDoUnlinkSnapRefreshAwareness(c) 1656 1657 c.Check(chg.Err(), ErrorMatches, `(?ms).*^- some-change-descr \(snap "some-snap" has running apps \(some-app\)\).*`) 1658 } 1659 1660 func (s *linkSnapSuite) TestDoUnlinkSnapRefreshHardCheckOff(c *C) { 1661 s.state.Lock() 1662 defer s.state.Unlock() 1663 1664 tr := config.NewTransaction(s.state) 1665 tr.Set("core", "experimental.refresh-app-awareness", false) 1666 tr.Commit() 1667 1668 chg := s.testDoUnlinkSnapRefreshAwareness(c) 1669 1670 c.Check(chg.Err(), IsNil) 1671 } 1672 1673 func (s *linkSnapSuite) testDoUnlinkSnapRefreshAwareness(c *C) *state.Change { 1674 restore := release.MockOnClassic(true) 1675 defer restore() 1676 1677 dirs.SetRootDir(c.MkDir()) 1678 defer dirs.SetRootDir("/") 1679 1680 snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) { 1681 info := &snap.Info{SuggestedName: name, SideInfo: *si, SnapType: snap.TypeApp} 1682 info.Apps = map[string]*snap.AppInfo{ 1683 "some-app": {Snap: info, Name: "some-app"}, 1684 } 1685 return info, nil 1686 }) 1687 // mock that "some-snap" has an app and that this app has pids running 1688 restore = snapstate.MockPidsOfSnap(func(instanceName string) (map[string][]int, error) { 1689 c.Assert(instanceName, Equals, "some-snap") 1690 return map[string][]int{ 1691 "snap.some-snap.some-app": {1234}, 1692 }, nil 1693 }) 1694 defer restore() 1695 1696 si1 := &snap.SideInfo{ 1697 RealName: "some-snap", 1698 Revision: snap.R(1), 1699 } 1700 snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ 1701 Sequence: []*snap.SideInfo{si1}, 1702 Current: si1.Revision, 1703 Active: true, 1704 }) 1705 t := s.state.NewTask("unlink-current-snap", "some-change-descr") 1706 t.Set("snap-setup", &snapstate.SnapSetup{ 1707 SideInfo: si1, 1708 }) 1709 chg := s.state.NewChange("dummy", "...") 1710 chg.AddTask(t) 1711 1712 s.state.Unlock() 1713 defer s.state.Lock() 1714 1715 for i := 0; i < 3; i++ { 1716 s.se.Ensure() 1717 s.se.Wait() 1718 } 1719 1720 return chg 1721 } 1722 1723 func (s *linkSnapSuite) setMockKernelRemodelCtx(c *C, oldKernel, newKernel string) { 1724 newModel := MakeModel(map[string]interface{}{"kernel": newKernel}) 1725 oldModel := MakeModel(map[string]interface{}{"kernel": oldKernel}) 1726 mockRemodelCtx := &snapstatetest.TrivialDeviceContext{ 1727 DeviceModel: newModel, 1728 OldDeviceModel: oldModel, 1729 Remodeling: true, 1730 } 1731 restore := snapstatetest.MockDeviceContext(mockRemodelCtx) 1732 s.AddCleanup(restore) 1733 } 1734 1735 func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesUnrelatedAppDoesNothing(c *C) { 1736 s.state.Lock() 1737 defer s.state.Unlock() 1738 1739 s.setMockKernelRemodelCtx(c, "kernel", "new-kernel") 1740 t := s.state.NewTask("link-snap", "...") 1741 t.Set("snap-setup", &snapstate.SnapSetup{ 1742 SideInfo: &snap.SideInfo{ 1743 RealName: "some-app", 1744 Revision: snap.R(1), 1745 }, 1746 }) 1747 1748 err := s.snapmgr.MaybeUndoRemodelBootChanges(t) 1749 c.Assert(err, IsNil) 1750 c.Check(s.stateBackend.restartRequested, HasLen, 0) 1751 } 1752 1753 func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesSameKernel(c *C) { 1754 s.state.Lock() 1755 defer s.state.Unlock() 1756 1757 s.setMockKernelRemodelCtx(c, "kernel", "kernel") 1758 t := s.state.NewTask("link-snap", "...") 1759 t.Set("snap-setup", &snapstate.SnapSetup{ 1760 SideInfo: &snap.SideInfo{ 1761 RealName: "kernel", 1762 Revision: snap.R(1), 1763 }, 1764 Type: "kernel", 1765 }) 1766 1767 err := s.snapmgr.MaybeUndoRemodelBootChanges(t) 1768 c.Assert(err, IsNil) 1769 c.Check(s.stateBackend.restartRequested, HasLen, 0) 1770 } 1771 1772 func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesNeedsUndo(c *C) { 1773 s.state.Lock() 1774 defer s.state.Unlock() 1775 1776 // undoing remodel bootenv changes is only relevant on !classic 1777 restore := release.MockOnClassic(false) 1778 defer restore() 1779 1780 // using "snaptest.MockSnap()" is more convenient here so we avoid 1781 // the (default) mocking of snapReadInfo() 1782 restore = snapstate.MockSnapReadInfo(snap.ReadInfo) 1783 defer restore() 1784 1785 // we need to init the boot-id 1786 err := s.state.VerifyReboot("some-boot-id") 1787 c.Assert(err, IsNil) 1788 1789 // we pretend we do a remodel from kernel -> new-kernel 1790 s.setMockKernelRemodelCtx(c, "kernel", "new-kernel") 1791 1792 // and we pretend that we booted into the "new-kernel" already 1793 // and now that needs to be undone 1794 bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 1795 bootloader.Force(bloader) 1796 bloader.SetBootKernel("new-kernel_1.snap") 1797 1798 // both kernel and new-kernel are instaleld at this point 1799 si := &snap.SideInfo{RealName: "kernel", Revision: snap.R(1)} 1800 snapstate.Set(s.state, "kernel", &snapstate.SnapState{ 1801 Sequence: []*snap.SideInfo{si}, 1802 SnapType: "kernel", 1803 Current: snap.R(1), 1804 }) 1805 snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 1.0", si) 1806 si2 := &snap.SideInfo{RealName: "new-kernel", Revision: snap.R(1)} 1807 snapstate.Set(s.state, "new-kernel", &snapstate.SnapState{ 1808 Sequence: []*snap.SideInfo{si2}, 1809 SnapType: "kernel", 1810 Current: snap.R(1), 1811 }) 1812 snaptest.MockSnap(c, "name: new-kernel\ntype: kernel\nversion: 1.0", si) 1813 1814 t := s.state.NewTask("link-snap", "...") 1815 t.Set("snap-setup", &snapstate.SnapSetup{ 1816 SideInfo: &snap.SideInfo{ 1817 RealName: "new-kernel", 1818 Revision: snap.R(1), 1819 SnapID: "new-kernel-id", 1820 }, 1821 Type: "kernel", 1822 }) 1823 1824 // now we simulate that the new kernel is getting undone 1825 err = s.snapmgr.MaybeUndoRemodelBootChanges(t) 1826 c.Assert(err, IsNil) 1827 1828 // that will schedule a boot into the previous kernel 1829 c.Assert(bloader.BootVars, DeepEquals, map[string]string{ 1830 "snap_mode": boot.TryStatus, 1831 "snap_kernel": "new-kernel_1.snap", 1832 "snap_try_kernel": "kernel_1.snap", 1833 }) 1834 c.Check(s.stateBackend.restartRequested, HasLen, 1) 1835 c.Check(s.stateBackend.restartRequested[0], Equals, state.RestartSystem) 1836 } 1837 1838 func (s *linkSnapSuite) testDoLinkSnapWithToolingDependency(c *C, classicOrBase string) { 1839 var model *asserts.Model 1840 var needsTooling bool 1841 switch classicOrBase { 1842 case "classic-system": 1843 model = ClassicModel() 1844 case "": 1845 model = DefaultModel() 1846 default: 1847 // the tooling mount is needed on UC18+ 1848 needsTooling = true 1849 model = ModelWithBase(classicOrBase) 1850 } 1851 r := snapstatetest.MockDeviceModel(model) 1852 defer r() 1853 1854 s.state.Lock() 1855 si := &snap.SideInfo{ 1856 RealName: "services-snap", 1857 SnapID: "services-snap-id", 1858 Revision: snap.R(11), 1859 } 1860 t := s.state.NewTask("link-snap", "test") 1861 t.Set("snap-setup", &snapstate.SnapSetup{ 1862 SideInfo: si, 1863 Type: snap.TypeApp, 1864 }) 1865 s.state.NewChange("dummy", "...").AddTask(t) 1866 1867 s.state.Unlock() 1868 1869 s.se.Ensure() 1870 s.se.Wait() 1871 1872 s.state.Lock() 1873 defer s.state.Unlock() 1874 1875 expected := fakeOps{ 1876 { 1877 op: "candidate", 1878 sinfo: *si, 1879 }, 1880 { 1881 op: "link-snap", 1882 path: filepath.Join(dirs.SnapMountDir, "services-snap/11"), 1883 requireSnapdTooling: needsTooling, 1884 }, 1885 } 1886 1887 // start with an easier-to-read error if this fails: 1888 c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops()) 1889 c.Check(s.fakeBackend.ops, DeepEquals, expected) 1890 } 1891 1892 func (s *linkSnapSuite) TestDoLinkSnapWithToolingClassic(c *C) { 1893 s.testDoLinkSnapWithToolingDependency(c, "classic-system") 1894 } 1895 1896 func (s *linkSnapSuite) TestDoLinkSnapWithToolingCore(c *C) { 1897 s.testDoLinkSnapWithToolingDependency(c, "") 1898 } 1899 1900 func (s *linkSnapSuite) TestDoLinkSnapWithToolingCore18(c *C) { 1901 s.testDoLinkSnapWithToolingDependency(c, "core18") 1902 } 1903 1904 func (s *linkSnapSuite) TestDoLinkSnapWithToolingCore20(c *C) { 1905 s.testDoLinkSnapWithToolingDependency(c, "core20") 1906 }