gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/snapstate/handlers_prereq_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017-2018 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 "fmt" 24 "os" 25 "time" 26 27 . "gopkg.in/check.v1" 28 "gopkg.in/tomb.v2" 29 30 "github.com/snapcore/snapd/overlord/snapstate" 31 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 32 "github.com/snapcore/snapd/overlord/state" 33 "github.com/snapcore/snapd/release" 34 "github.com/snapcore/snapd/snap" 35 "github.com/snapcore/snapd/store" 36 ) 37 38 type prereqSuite struct { 39 baseHandlerSuite 40 41 fakeStore *fakeStore 42 } 43 44 var _ = Suite(&prereqSuite{}) 45 46 func (s *prereqSuite) SetUpTest(c *C) { 47 s.setup(c, nil) 48 49 s.fakeStore = &fakeStore{ 50 state: s.state, 51 fakeBackend: s.fakeBackend, 52 } 53 s.state.Lock() 54 defer s.state.Unlock() 55 snapstate.ReplaceStore(s.state, s.fakeStore) 56 57 s.state.Set("seeded", true) 58 s.state.Set("refresh-privacy-key", "privacy-key") 59 s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel())) 60 61 restoreCheckFreeSpace := snapstate.MockOsutilCheckFreeSpace(func(string, uint64) error { return nil }) 62 s.AddCleanup(restoreCheckFreeSpace) 63 64 restoreInstallSize := snapstate.MockInstallSize(func(st *state.State, snaps []snapstate.MinimalInstallInfo, userID int) (uint64, error) { 65 return 0, nil 66 }) 67 s.AddCleanup(restoreInstallSize) 68 } 69 70 func (s *prereqSuite) TestDoPrereqNothingToDo(c *C) { 71 s.state.Lock() 72 73 si1 := &snap.SideInfo{ 74 RealName: "core", 75 Revision: snap.R(1), 76 } 77 snapstate.Set(s.state, "core", &snapstate.SnapState{ 78 Sequence: []*snap.SideInfo{si1}, 79 Current: si1.Revision, 80 }) 81 82 t := s.state.NewTask("prerequisites", "test") 83 t.Set("snap-setup", &snapstate.SnapSetup{ 84 SideInfo: &snap.SideInfo{ 85 RealName: "foo", 86 Revision: snap.R(33), 87 }, 88 }) 89 s.state.NewChange("dummy", "...").AddTask(t) 90 s.state.Unlock() 91 92 s.se.Ensure() 93 s.se.Wait() 94 95 s.state.Lock() 96 defer s.state.Unlock() 97 c.Assert(s.fakeBackend.ops, HasLen, 0) 98 c.Check(t.Status(), Equals, state.DoneStatus) 99 } 100 101 func (s *prereqSuite) TestDoPrereqWithBaseNone(c *C) { 102 s.state.Lock() 103 104 t := s.state.NewTask("prerequisites", "test") 105 t.Set("snap-setup", &snapstate.SnapSetup{ 106 SideInfo: &snap.SideInfo{ 107 RealName: "foo", 108 Revision: snap.R(33), 109 }, 110 Base: "none", 111 Prereq: []string{"prereq1"}, 112 }) 113 chg := s.state.NewChange("dummy", "...") 114 chg.AddTask(t) 115 s.state.Unlock() 116 117 s.se.Ensure() 118 s.se.Wait() 119 120 s.state.Lock() 121 defer s.state.Unlock() 122 c.Check(t.Status(), Equals, state.DoneStatus) 123 124 // check that the do-prereq task added all needed prereqs 125 expectedLinkedSnaps := []string{"prereq1", "snapd"} 126 linkedSnaps := make([]string, 0, len(expectedLinkedSnaps)) 127 for _, t := range chg.Tasks() { 128 if t.Kind() == "link-snap" { 129 snapsup, err := snapstate.TaskSnapSetup(t) 130 c.Assert(err, IsNil) 131 linkedSnaps = append(linkedSnaps, snapsup.InstanceName()) 132 } 133 } 134 c.Check(linkedSnaps, DeepEquals, expectedLinkedSnaps) 135 } 136 137 func (s *prereqSuite) TestDoPrereqTalksToStoreAndQueues(c *C) { 138 s.state.Lock() 139 140 snapstate.Set(s.state, "core", &snapstate.SnapState{ 141 Active: true, 142 Sequence: []*snap.SideInfo{ 143 {RealName: "core", Revision: snap.R(1)}, 144 }, 145 Current: snap.R(1), 146 SnapType: "os", 147 }) 148 149 t := s.state.NewTask("prerequisites", "test") 150 t.Set("snap-setup", &snapstate.SnapSetup{ 151 SideInfo: &snap.SideInfo{ 152 RealName: "foo", 153 Revision: snap.R(33), 154 }, 155 Channel: "beta", 156 Base: "some-base", 157 Prereq: []string{"prereq1", "prereq2"}, 158 }) 159 chg := s.state.NewChange("dummy", "...") 160 chg.AddTask(t) 161 s.state.Unlock() 162 163 s.se.Ensure() 164 s.se.Wait() 165 166 s.state.Lock() 167 defer s.state.Unlock() 168 c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{ 169 { 170 op: "storesvc-snap-action", 171 }, 172 { 173 op: "storesvc-snap-action:action", 174 action: store.SnapAction{ 175 Action: "install", 176 InstanceName: "prereq1", 177 Channel: "stable", 178 }, 179 revno: snap.R(11), 180 }, 181 { 182 op: "storesvc-snap-action", 183 }, 184 { 185 op: "storesvc-snap-action:action", 186 action: store.SnapAction{ 187 Action: "install", 188 InstanceName: "prereq2", 189 Channel: "stable", 190 }, 191 revno: snap.R(11), 192 }, 193 { 194 op: "storesvc-snap-action", 195 }, 196 { 197 op: "storesvc-snap-action:action", 198 action: store.SnapAction{ 199 Action: "install", 200 InstanceName: "some-base", 201 Channel: "stable", 202 }, 203 revno: snap.R(11), 204 }, 205 }) 206 c.Check(t.Status(), Equals, state.DoneStatus) 207 208 // check that the do-prereq task added all needed prereqs 209 expectedLinkedSnaps := []string{"prereq1", "prereq2", "some-base"} 210 linkedSnaps := make([]string, 0, len(expectedLinkedSnaps)) 211 for _, t := range chg.Tasks() { 212 if t.Kind() == "link-snap" { 213 snapsup, err := snapstate.TaskSnapSetup(t) 214 c.Assert(err, IsNil) 215 linkedSnaps = append(linkedSnaps, snapsup.InstanceName()) 216 } 217 } 218 c.Check(linkedSnaps, DeepEquals, expectedLinkedSnaps) 219 } 220 221 func (s *prereqSuite) TestDoPrereqRetryWhenBaseInFlight(c *C) { 222 restore := snapstate.MockPrerequisitesRetryTimeout(1 * time.Millisecond) 223 defer restore() 224 225 var prereqTask *state.Task 226 227 calls := 0 228 s.runner.AddHandler("link-snap", 229 func(task *state.Task, _ *tomb.Tomb) error { 230 st := task.State() 231 st.Lock() 232 defer st.Unlock() 233 234 calls += 1 235 if calls == 1 { 236 // retry again later, this forces taskrunner 237 // to pick prequisites task. 238 return &state.Retry{After: 1 * time.Millisecond} 239 } 240 241 // setup everything as if the snap is installed 242 243 snapsup, _ := snapstate.TaskSnapSetup(task) 244 var snapst snapstate.SnapState 245 snapstate.Get(st, snapsup.InstanceName(), &snapst) 246 snapst.Current = snapsup.Revision() 247 snapst.Sequence = append(snapst.Sequence, snapsup.SideInfo) 248 snapstate.Set(st, snapsup.InstanceName(), &snapst) 249 250 // check that prerequisites task is not done yet, it must wait for core. 251 // This check guarantees that prerequisites task found link-snap snap 252 // task in flight, and returned a retry error, resulting in DoingStatus. 253 c.Check(prereqTask.Status(), Equals, state.DoingStatus) 254 255 return nil 256 }, nil) 257 s.state.Lock() 258 tCore := s.state.NewTask("link-snap", "Pretend core gets installed") 259 tCore.Set("snap-setup", &snapstate.SnapSetup{ 260 SideInfo: &snap.SideInfo{ 261 RealName: "core", 262 Revision: snap.R(11), 263 }, 264 }) 265 266 // pretend foo gets installed and needs core (which is in progress) 267 prereqTask = s.state.NewTask("prerequisites", "foo") 268 prereqTask.Set("snap-setup", &snapstate.SnapSetup{ 269 SideInfo: &snap.SideInfo{ 270 RealName: "foo", 271 }, 272 }) 273 274 chg := s.state.NewChange("dummy", "...") 275 chg.AddTask(prereqTask) 276 chg.AddTask(tCore) 277 278 // NOTE: tasks are iterated on in undefined order, we have fixed the 279 // link-snap handler to return a 'fake' retry what results 280 // 'prerequisites' task handler observing the state of the world we 281 // want, even if 'link-snap' ran first 282 283 // wait, we will hit prereq-retry-timeout eventually 284 // (this can take a while on very slow machines) 285 for i := 0; i < 500; i++ { 286 time.Sleep(1 * time.Millisecond) 287 s.state.Unlock() 288 s.se.Ensure() 289 s.se.Wait() 290 s.state.Lock() 291 if prereqTask.Status() == state.DoneStatus { 292 break 293 } 294 } 295 296 // sanity check, exactly two calls to link-snap due to retry error on 1st call 297 c.Check(calls, Equals, 2) 298 299 c.Check(tCore.Status(), Equals, state.DoneStatus) 300 c.Check(prereqTask.Status(), Equals, state.DoneStatus) 301 302 // sanity 303 c.Check(chg.Status(), Equals, state.DoneStatus) 304 } 305 306 func (s *prereqSuite) TestDoPrereqChannelEnvvars(c *C) { 307 os.Setenv("SNAPD_BASES_CHANNEL", "edge") 308 defer os.Unsetenv("SNAPD_BASES_CHANNEL") 309 os.Setenv("SNAPD_PREREQS_CHANNEL", "candidate") 310 defer os.Unsetenv("SNAPD_PREREQS_CHANNEL") 311 s.state.Lock() 312 313 snapstate.Set(s.state, "core", &snapstate.SnapState{ 314 Active: true, 315 Sequence: []*snap.SideInfo{ 316 {RealName: "core", Revision: snap.R(1)}, 317 }, 318 Current: snap.R(1), 319 SnapType: "os", 320 }) 321 322 t := s.state.NewTask("prerequisites", "test") 323 t.Set("snap-setup", &snapstate.SnapSetup{ 324 SideInfo: &snap.SideInfo{ 325 RealName: "foo", 326 Revision: snap.R(33), 327 }, 328 Channel: "beta", 329 Base: "some-base", 330 Prereq: []string{"prereq1", "prereq2"}, 331 }) 332 chg := s.state.NewChange("dummy", "...") 333 chg.AddTask(t) 334 s.state.Unlock() 335 336 s.se.Ensure() 337 s.se.Wait() 338 339 s.state.Lock() 340 defer s.state.Unlock() 341 c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{ 342 { 343 op: "storesvc-snap-action", 344 }, 345 { 346 op: "storesvc-snap-action:action", 347 action: store.SnapAction{ 348 Action: "install", 349 InstanceName: "prereq1", 350 Channel: "candidate", 351 }, 352 revno: snap.R(11), 353 }, 354 { 355 op: "storesvc-snap-action", 356 }, 357 { 358 op: "storesvc-snap-action:action", 359 action: store.SnapAction{ 360 Action: "install", 361 InstanceName: "prereq2", 362 Channel: "candidate", 363 }, 364 revno: snap.R(11), 365 }, 366 { 367 op: "storesvc-snap-action", 368 }, 369 { 370 op: "storesvc-snap-action:action", 371 action: store.SnapAction{ 372 Action: "install", 373 InstanceName: "some-base", 374 Channel: "edge", 375 }, 376 revno: snap.R(11), 377 }, 378 }) 379 c.Check(t.Status(), Equals, state.DoneStatus) 380 } 381 382 func (s *prereqSuite) TestDoPrereqNothingToDoForBase(c *C) { 383 for _, typ := range []snap.Type{ 384 snap.TypeOS, 385 snap.TypeGadget, 386 snap.TypeKernel, 387 snap.TypeBase, 388 } { 389 390 s.state.Lock() 391 t := s.state.NewTask("prerequisites", "test") 392 t.Set("snap-setup", &snapstate.SnapSetup{ 393 SideInfo: &snap.SideInfo{ 394 RealName: fmt.Sprintf("foo-%s", typ), 395 Revision: snap.R(1), 396 }, 397 Type: typ, 398 }) 399 s.state.NewChange("dummy", "...").AddTask(t) 400 s.state.Unlock() 401 402 s.se.Ensure() 403 s.se.Wait() 404 405 s.state.Lock() 406 c.Assert(s.fakeBackend.ops, HasLen, 0) 407 c.Check(t.Status(), Equals, state.DoneStatus) 408 s.state.Unlock() 409 } 410 } 411 412 func (s *prereqSuite) TestDoPrereqNothingToDoForSnapdSnap(c *C) { 413 s.state.Lock() 414 t := s.state.NewTask("prerequisites", "test") 415 t.Set("snap-setup", &snapstate.SnapSetup{ 416 // type is normally set from snap info at install time 417 Type: snap.TypeSnapd, 418 SideInfo: &snap.SideInfo{ 419 RealName: "snapd", 420 Revision: snap.R(1), 421 }, 422 }) 423 s.state.NewChange("dummy", "...").AddTask(t) 424 s.state.Unlock() 425 426 s.se.Ensure() 427 s.se.Wait() 428 429 s.state.Lock() 430 c.Assert(s.fakeBackend.ops, HasLen, 0) 431 c.Check(t.Status(), Equals, state.DoneStatus) 432 s.state.Unlock() 433 } 434 435 func (s *prereqSuite) TestDoPrereqCore16wCoreNothingToDo(c *C) { 436 s.state.Lock() 437 438 si1 := &snap.SideInfo{ 439 RealName: "core", 440 Revision: snap.R(1), 441 } 442 snapstate.Set(s.state, "core", &snapstate.SnapState{ 443 Sequence: []*snap.SideInfo{si1}, 444 Current: si1.Revision, 445 }) 446 447 t := s.state.NewTask("prerequisites", "test") 448 t.Set("snap-setup", &snapstate.SnapSetup{ 449 SideInfo: &snap.SideInfo{ 450 RealName: "foo", 451 Revision: snap.R(33), 452 }, 453 Base: "core16", 454 }) 455 s.state.NewChange("dummy", "...").AddTask(t) 456 s.state.Unlock() 457 458 s.se.Ensure() 459 s.se.Wait() 460 461 s.state.Lock() 462 defer s.state.Unlock() 463 c.Assert(s.fakeBackend.ops, HasLen, 0) 464 c.Check(t.Status(), Equals, state.DoneStatus) 465 } 466 467 func (s *prereqSuite) testDoPrereqNoCorePullsInSnaps(c *C, base string) { 468 restore := release.MockOnClassic(true) 469 defer restore() 470 471 s.state.Lock() 472 473 t := s.state.NewTask("prerequisites", "test") 474 t.Set("snap-setup", &snapstate.SnapSetup{ 475 SideInfo: &snap.SideInfo{ 476 RealName: "foo", 477 Revision: snap.R(33), 478 }, 479 Base: base, 480 }) 481 s.state.NewChange("dummy", "...").AddTask(t) 482 s.state.Unlock() 483 484 s.se.Ensure() 485 s.se.Wait() 486 487 s.state.Lock() 488 defer s.state.Unlock() 489 c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{ 490 { 491 op: "storesvc-snap-action", 492 }, 493 { 494 op: "storesvc-snap-action:action", 495 action: store.SnapAction{ 496 Action: "install", 497 InstanceName: base, 498 Channel: "stable", 499 }, 500 revno: snap.R(11), 501 }, 502 { 503 op: "storesvc-snap-action", 504 }, 505 { 506 op: "storesvc-snap-action:action", 507 action: store.SnapAction{ 508 Action: "install", 509 InstanceName: "snapd", 510 Channel: "stable", 511 }, 512 revno: snap.R(11), 513 }, 514 }) 515 516 c.Check(t.Change().Err(), IsNil) 517 c.Check(t.Status(), Equals, state.DoneStatus) 518 } 519 520 func (s *prereqSuite) TestDoPrereqCore16noCore(c *C) { 521 s.testDoPrereqNoCorePullsInSnaps(c, "core16") 522 } 523 524 func (s *prereqSuite) TestDoPrereqCore18NoCorePullsInSnapd(c *C) { 525 s.testDoPrereqNoCorePullsInSnaps(c, "core18") 526 } 527 528 func (s *prereqSuite) TestDoPrereqOtherBaseNoCorePullsInSnapd(c *C) { 529 s.testDoPrereqNoCorePullsInSnaps(c, "some-base") 530 } 531 532 func (s *prereqSuite) TestDoPrereqBaseIsNotBase(c *C) { 533 s.state.Lock() 534 535 t := s.state.NewTask("prerequisites", "test") 536 t.Set("snap-setup", &snapstate.SnapSetup{ 537 SideInfo: &snap.SideInfo{ 538 RealName: "foo", 539 Revision: snap.R(33), 540 }, 541 Channel: "beta", 542 Base: "app-snap", 543 Prereq: []string{"prereq1"}, 544 }) 545 chg := s.state.NewChange("dummy", "...") 546 chg.AddTask(t) 547 s.state.Unlock() 548 549 s.se.Ensure() 550 s.se.Wait() 551 552 s.state.Lock() 553 defer s.state.Unlock() 554 c.Check(chg.Status(), Equals, state.ErrorStatus) 555 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*- test \(cannot install snap base "app-snap": unexpected snap type "app", instead of 'base'\)`) 556 } 557 558 func (s *prereqSuite) TestDoPrereqBaseNoRevision(c *C) { 559 os.Setenv("SNAPD_BASES_CHANNEL", "channel-no-revision") 560 defer os.Unsetenv("SNAPD_BASES_CHANNEL") 561 562 s.state.Lock() 563 564 t := s.state.NewTask("prerequisites", "test") 565 t.Set("snap-setup", &snapstate.SnapSetup{ 566 SideInfo: &snap.SideInfo{ 567 RealName: "foo", 568 Revision: snap.R(33), 569 }, 570 Channel: "beta", 571 Base: "some-base", 572 Prereq: []string{"prereq1"}, 573 }) 574 chg := s.state.NewChange("dummy", "...") 575 chg.AddTask(t) 576 s.state.Unlock() 577 578 s.se.Ensure() 579 s.se.Wait() 580 581 s.state.Lock() 582 defer s.state.Unlock() 583 c.Check(chg.Status(), Equals, state.ErrorStatus) 584 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*- test \(cannot install snap base "some-base": no snap revision available as specified\)`) 585 } 586 587 func (s *prereqSuite) TestDoPrereqNoRevision(c *C) { 588 os.Setenv("SNAPD_PREREQS_CHANNEL", "channel-no-revision") 589 defer os.Unsetenv("SNAPD_PREREQS_CHANNEL") 590 591 s.state.Lock() 592 593 t := s.state.NewTask("prerequisites", "test") 594 t.Set("snap-setup", &snapstate.SnapSetup{ 595 SideInfo: &snap.SideInfo{ 596 RealName: "foo", 597 Revision: snap.R(33), 598 }, 599 Channel: "beta", 600 Prereq: []string{"prereq1"}, 601 }) 602 chg := s.state.NewChange("dummy", "...") 603 chg.AddTask(t) 604 s.state.Unlock() 605 606 s.se.Ensure() 607 s.se.Wait() 608 609 s.state.Lock() 610 defer s.state.Unlock() 611 c.Check(chg.Status(), Equals, state.ErrorStatus) 612 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*- test \(cannot install prerequisite "prereq1": no snap revision available as specified\)`) 613 } 614 615 func (s *prereqSuite) TestDoPrereqSnapdNoRevision(c *C) { 616 os.Setenv("SNAPD_SNAPD_CHANNEL", "channel-no-revision") 617 defer os.Unsetenv("SNAPD_SNAPD_CHANNEL") 618 619 s.state.Lock() 620 621 t := s.state.NewTask("prerequisites", "test") 622 t.Set("snap-setup", &snapstate.SnapSetup{ 623 SideInfo: &snap.SideInfo{ 624 RealName: "foo", 625 Revision: snap.R(33), 626 }, 627 Base: "core18", 628 Channel: "beta", 629 }) 630 chg := s.state.NewChange("dummy", "...") 631 chg.AddTask(t) 632 s.state.Unlock() 633 634 s.se.Ensure() 635 s.se.Wait() 636 637 s.state.Lock() 638 defer s.state.Unlock() 639 c.Check(chg.Status(), Equals, state.ErrorStatus) 640 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*- test \(cannot install system snap "snapd": no snap revision available as specified\)`) 641 }