github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 62 func (s *prereqSuite) TestDoPrereqNothingToDo(c *C) { 63 s.state.Lock() 64 65 si1 := &snap.SideInfo{ 66 RealName: "core", 67 Revision: snap.R(1), 68 } 69 snapstate.Set(s.state, "core", &snapstate.SnapState{ 70 Sequence: []*snap.SideInfo{si1}, 71 Current: si1.Revision, 72 }) 73 74 t := s.state.NewTask("prerequisites", "test") 75 t.Set("snap-setup", &snapstate.SnapSetup{ 76 SideInfo: &snap.SideInfo{ 77 RealName: "foo", 78 Revision: snap.R(33), 79 }, 80 }) 81 s.state.NewChange("dummy", "...").AddTask(t) 82 s.state.Unlock() 83 84 s.se.Ensure() 85 s.se.Wait() 86 87 s.state.Lock() 88 defer s.state.Unlock() 89 c.Assert(s.fakeBackend.ops, HasLen, 0) 90 c.Check(t.Status(), Equals, state.DoneStatus) 91 } 92 93 func (s *prereqSuite) TestDoPrereqWithBaseNone(c *C) { 94 s.state.Lock() 95 96 t := s.state.NewTask("prerequisites", "test") 97 t.Set("snap-setup", &snapstate.SnapSetup{ 98 SideInfo: &snap.SideInfo{ 99 RealName: "foo", 100 Revision: snap.R(33), 101 }, 102 Base: "none", 103 Prereq: []string{"prereq1"}, 104 }) 105 chg := s.state.NewChange("dummy", "...") 106 chg.AddTask(t) 107 s.state.Unlock() 108 109 s.se.Ensure() 110 s.se.Wait() 111 112 s.state.Lock() 113 defer s.state.Unlock() 114 c.Check(t.Status(), Equals, state.DoneStatus) 115 116 // check that the do-prereq task added all needed prereqs 117 expectedLinkedSnaps := []string{"prereq1", "snapd"} 118 linkedSnaps := make([]string, 0, len(expectedLinkedSnaps)) 119 for _, t := range chg.Tasks() { 120 if t.Kind() == "link-snap" { 121 snapsup, err := snapstate.TaskSnapSetup(t) 122 c.Assert(err, IsNil) 123 linkedSnaps = append(linkedSnaps, snapsup.InstanceName()) 124 } 125 } 126 c.Check(linkedSnaps, DeepEquals, expectedLinkedSnaps) 127 } 128 129 func (s *prereqSuite) TestDoPrereqTalksToStoreAndQueues(c *C) { 130 s.state.Lock() 131 132 snapstate.Set(s.state, "core", &snapstate.SnapState{ 133 Active: true, 134 Sequence: []*snap.SideInfo{ 135 {RealName: "core", Revision: snap.R(1)}, 136 }, 137 Current: snap.R(1), 138 SnapType: "os", 139 }) 140 141 t := s.state.NewTask("prerequisites", "test") 142 t.Set("snap-setup", &snapstate.SnapSetup{ 143 SideInfo: &snap.SideInfo{ 144 RealName: "foo", 145 Revision: snap.R(33), 146 }, 147 Channel: "beta", 148 Base: "some-base", 149 Prereq: []string{"prereq1", "prereq2"}, 150 }) 151 chg := s.state.NewChange("dummy", "...") 152 chg.AddTask(t) 153 s.state.Unlock() 154 155 s.se.Ensure() 156 s.se.Wait() 157 158 s.state.Lock() 159 defer s.state.Unlock() 160 c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{ 161 { 162 op: "storesvc-snap-action", 163 }, 164 { 165 op: "storesvc-snap-action:action", 166 action: store.SnapAction{ 167 Action: "install", 168 InstanceName: "prereq1", 169 Channel: "stable", 170 }, 171 revno: snap.R(11), 172 }, 173 { 174 op: "storesvc-snap-action", 175 }, 176 { 177 op: "storesvc-snap-action:action", 178 action: store.SnapAction{ 179 Action: "install", 180 InstanceName: "prereq2", 181 Channel: "stable", 182 }, 183 revno: snap.R(11), 184 }, 185 { 186 op: "storesvc-snap-action", 187 }, 188 { 189 op: "storesvc-snap-action:action", 190 action: store.SnapAction{ 191 Action: "install", 192 InstanceName: "some-base", 193 Channel: "stable", 194 }, 195 revno: snap.R(11), 196 }, 197 }) 198 c.Check(t.Status(), Equals, state.DoneStatus) 199 200 // check that the do-prereq task added all needed prereqs 201 expectedLinkedSnaps := []string{"prereq1", "prereq2", "some-base"} 202 linkedSnaps := make([]string, 0, len(expectedLinkedSnaps)) 203 for _, t := range chg.Tasks() { 204 if t.Kind() == "link-snap" { 205 snapsup, err := snapstate.TaskSnapSetup(t) 206 c.Assert(err, IsNil) 207 linkedSnaps = append(linkedSnaps, snapsup.InstanceName()) 208 } 209 } 210 c.Check(linkedSnaps, DeepEquals, expectedLinkedSnaps) 211 } 212 213 func (s *prereqSuite) TestDoPrereqRetryWhenBaseInFlight(c *C) { 214 restore := snapstate.MockPrerequisitesRetryTimeout(5 * time.Millisecond) 215 defer restore() 216 217 calls := 0 218 s.runner.AddHandler("link-snap", 219 func(task *state.Task, _ *tomb.Tomb) error { 220 if calls == 0 { 221 // retry again later, this forces ordering of 222 // tasks, so that the prerequisites tasks ends 223 // up waiting for this one 224 calls += 1 225 return &state.Retry{After: 1 * time.Millisecond} 226 } 227 228 // setup everything as if the snap is installed 229 st := task.State() 230 st.Lock() 231 defer st.Unlock() 232 snapsup, _ := snapstate.TaskSnapSetup(task) 233 var snapst snapstate.SnapState 234 snapstate.Get(st, snapsup.InstanceName(), &snapst) 235 snapst.Current = snapsup.Revision() 236 snapst.Sequence = append(snapst.Sequence, snapsup.SideInfo) 237 snapstate.Set(st, snapsup.InstanceName(), &snapst) 238 return nil 239 }, 240 func(*state.Task, *tomb.Tomb) error { 241 return nil 242 }) 243 s.state.Lock() 244 tCore := s.state.NewTask("link-snap", "Pretend core gets installed") 245 tCore.Set("snap-setup", &snapstate.SnapSetup{ 246 SideInfo: &snap.SideInfo{ 247 RealName: "core", 248 Revision: snap.R(11), 249 }, 250 }) 251 252 // pretend foo gets installed and needs core (which is in progress) 253 t := s.state.NewTask("prerequisites", "foo") 254 t.Set("snap-setup", &snapstate.SnapSetup{ 255 SideInfo: &snap.SideInfo{ 256 RealName: "foo", 257 }, 258 }) 259 260 chg := s.state.NewChange("dummy", "...") 261 chg.AddTask(t) 262 chg.AddTask(tCore) 263 264 // NOTE: tasks are iterated on in undefined order, we have fixed the 265 // link-snap handler to return a 'fake' retry what results 266 // 'prerequisites' task handler observing the state of the world we 267 // want, even if 'link-snap' ran first 268 269 for i := 0; i < 10; i++ { 270 time.Sleep(1 * time.Millisecond) 271 s.state.Unlock() 272 s.se.Ensure() 273 s.se.Wait() 274 s.state.Lock() 275 if tCore.Status() == state.DoneStatus { 276 break 277 } 278 } 279 280 // check that t is not done yet, it must wait for core 281 c.Check(t.Status(), Equals, state.DoingStatus) 282 c.Check(tCore.Status(), Equals, state.DoneStatus) 283 284 // wait, we will hit prereq-retry-timeout eventually 285 // (this can take a while on very slow machines) 286 for i := 0; i < 50; i++ { 287 time.Sleep(10 * time.Millisecond) 288 s.state.Unlock() 289 s.se.Ensure() 290 s.se.Wait() 291 s.state.Lock() 292 if t.Status() == state.DoneStatus { 293 break 294 } 295 } 296 c.Check(t.Status(), Equals, state.DoneStatus) 297 } 298 299 func (s *prereqSuite) TestDoPrereqChannelEnvvars(c *C) { 300 os.Setenv("SNAPD_BASES_CHANNEL", "edge") 301 defer os.Unsetenv("SNAPD_BASES_CHANNEL") 302 os.Setenv("SNAPD_PREREQS_CHANNEL", "candidate") 303 defer os.Unsetenv("SNAPD_PREREQS_CHANNEL") 304 s.state.Lock() 305 306 snapstate.Set(s.state, "core", &snapstate.SnapState{ 307 Active: true, 308 Sequence: []*snap.SideInfo{ 309 {RealName: "core", Revision: snap.R(1)}, 310 }, 311 Current: snap.R(1), 312 SnapType: "os", 313 }) 314 315 t := s.state.NewTask("prerequisites", "test") 316 t.Set("snap-setup", &snapstate.SnapSetup{ 317 SideInfo: &snap.SideInfo{ 318 RealName: "foo", 319 Revision: snap.R(33), 320 }, 321 Channel: "beta", 322 Base: "some-base", 323 Prereq: []string{"prereq1", "prereq2"}, 324 }) 325 chg := s.state.NewChange("dummy", "...") 326 chg.AddTask(t) 327 s.state.Unlock() 328 329 s.se.Ensure() 330 s.se.Wait() 331 332 s.state.Lock() 333 defer s.state.Unlock() 334 c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{ 335 { 336 op: "storesvc-snap-action", 337 }, 338 { 339 op: "storesvc-snap-action:action", 340 action: store.SnapAction{ 341 Action: "install", 342 InstanceName: "prereq1", 343 Channel: "candidate", 344 }, 345 revno: snap.R(11), 346 }, 347 { 348 op: "storesvc-snap-action", 349 }, 350 { 351 op: "storesvc-snap-action:action", 352 action: store.SnapAction{ 353 Action: "install", 354 InstanceName: "prereq2", 355 Channel: "candidate", 356 }, 357 revno: snap.R(11), 358 }, 359 { 360 op: "storesvc-snap-action", 361 }, 362 { 363 op: "storesvc-snap-action:action", 364 action: store.SnapAction{ 365 Action: "install", 366 InstanceName: "some-base", 367 Channel: "edge", 368 }, 369 revno: snap.R(11), 370 }, 371 }) 372 c.Check(t.Status(), Equals, state.DoneStatus) 373 } 374 375 func (s *prereqSuite) TestDoPrereqNothingToDoForBase(c *C) { 376 for _, typ := range []snap.Type{ 377 snap.TypeOS, 378 snap.TypeGadget, 379 snap.TypeKernel, 380 snap.TypeBase, 381 } { 382 383 s.state.Lock() 384 t := s.state.NewTask("prerequisites", "test") 385 t.Set("snap-setup", &snapstate.SnapSetup{ 386 SideInfo: &snap.SideInfo{ 387 RealName: fmt.Sprintf("foo-%s", typ), 388 Revision: snap.R(1), 389 }, 390 Type: typ, 391 }) 392 s.state.NewChange("dummy", "...").AddTask(t) 393 s.state.Unlock() 394 395 s.se.Ensure() 396 s.se.Wait() 397 398 s.state.Lock() 399 c.Assert(s.fakeBackend.ops, HasLen, 0) 400 c.Check(t.Status(), Equals, state.DoneStatus) 401 s.state.Unlock() 402 } 403 } 404 405 func (s *prereqSuite) TestDoPrereqNothingToDoForSnapdSnap(c *C) { 406 s.state.Lock() 407 t := s.state.NewTask("prerequisites", "test") 408 t.Set("snap-setup", &snapstate.SnapSetup{ 409 // type is normally set from snap info at install time 410 Type: snap.TypeSnapd, 411 SideInfo: &snap.SideInfo{ 412 RealName: "snapd", 413 Revision: snap.R(1), 414 }, 415 }) 416 s.state.NewChange("dummy", "...").AddTask(t) 417 s.state.Unlock() 418 419 s.se.Ensure() 420 s.se.Wait() 421 422 s.state.Lock() 423 c.Assert(s.fakeBackend.ops, HasLen, 0) 424 c.Check(t.Status(), Equals, state.DoneStatus) 425 s.state.Unlock() 426 } 427 428 func (s *prereqSuite) TestDoPrereqCore16wCoreNothingToDo(c *C) { 429 s.state.Lock() 430 431 si1 := &snap.SideInfo{ 432 RealName: "core", 433 Revision: snap.R(1), 434 } 435 snapstate.Set(s.state, "core", &snapstate.SnapState{ 436 Sequence: []*snap.SideInfo{si1}, 437 Current: si1.Revision, 438 }) 439 440 t := s.state.NewTask("prerequisites", "test") 441 t.Set("snap-setup", &snapstate.SnapSetup{ 442 SideInfo: &snap.SideInfo{ 443 RealName: "foo", 444 Revision: snap.R(33), 445 }, 446 Base: "core16", 447 }) 448 s.state.NewChange("dummy", "...").AddTask(t) 449 s.state.Unlock() 450 451 s.se.Ensure() 452 s.se.Wait() 453 454 s.state.Lock() 455 defer s.state.Unlock() 456 c.Assert(s.fakeBackend.ops, HasLen, 0) 457 c.Check(t.Status(), Equals, state.DoneStatus) 458 } 459 460 func (s *prereqSuite) testDoPrereqNoCorePullsInSnaps(c *C, base string) { 461 restore := release.MockOnClassic(true) 462 defer restore() 463 464 s.state.Lock() 465 466 t := s.state.NewTask("prerequisites", "test") 467 t.Set("snap-setup", &snapstate.SnapSetup{ 468 SideInfo: &snap.SideInfo{ 469 RealName: "foo", 470 Revision: snap.R(33), 471 }, 472 Base: base, 473 }) 474 s.state.NewChange("dummy", "...").AddTask(t) 475 s.state.Unlock() 476 477 s.se.Ensure() 478 s.se.Wait() 479 480 s.state.Lock() 481 defer s.state.Unlock() 482 c.Assert(s.fakeBackend.ops, DeepEquals, fakeOps{ 483 { 484 op: "storesvc-snap-action", 485 }, 486 { 487 op: "storesvc-snap-action:action", 488 action: store.SnapAction{ 489 Action: "install", 490 InstanceName: base, 491 Channel: "stable", 492 }, 493 revno: snap.R(11), 494 }, 495 { 496 op: "storesvc-snap-action", 497 }, 498 { 499 op: "storesvc-snap-action:action", 500 action: store.SnapAction{ 501 Action: "install", 502 InstanceName: "snapd", 503 Channel: "stable", 504 }, 505 revno: snap.R(11), 506 }, 507 }) 508 509 c.Check(t.Change().Err(), IsNil) 510 c.Check(t.Status(), Equals, state.DoneStatus) 511 } 512 513 func (s *prereqSuite) TestDoPrereqCore16noCore(c *C) { 514 s.testDoPrereqNoCorePullsInSnaps(c, "core16") 515 } 516 517 func (s *prereqSuite) TestDoPrereqCore18NoCorePullsInSnapd(c *C) { 518 s.testDoPrereqNoCorePullsInSnaps(c, "core18") 519 } 520 521 func (s *prereqSuite) TestDoPrereqOtherBaseNoCorePullsInSnapd(c *C) { 522 s.testDoPrereqNoCorePullsInSnaps(c, "some-base") 523 } 524 525 func (s *prereqSuite) TestDoPrereqBaseIsNotBase(c *C) { 526 s.state.Lock() 527 528 t := s.state.NewTask("prerequisites", "test") 529 t.Set("snap-setup", &snapstate.SnapSetup{ 530 SideInfo: &snap.SideInfo{ 531 RealName: "foo", 532 Revision: snap.R(33), 533 }, 534 Channel: "beta", 535 Base: "some-epoch-snap", 536 Prereq: []string{"prereq1"}, 537 }) 538 chg := s.state.NewChange("dummy", "...") 539 chg.AddTask(t) 540 s.state.Unlock() 541 542 s.se.Ensure() 543 s.se.Wait() 544 545 s.state.Lock() 546 defer s.state.Unlock() 547 c.Check(chg.Status(), Equals, state.ErrorStatus) 548 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*- test \(declared snap base "some-epoch-snap" has unexpected type "app", instead of 'base'\)`) 549 }