github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/devicestate/handlers_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-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 devicestate_test 21 22 import ( 23 "context" 24 "fmt" 25 "path/filepath" 26 "time" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/asserts" 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/interfaces" 33 "github.com/snapcore/snapd/overlord/assertstate" 34 "github.com/snapcore/snapd/overlord/auth" 35 "github.com/snapcore/snapd/overlord/devicestate" 36 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 37 "github.com/snapcore/snapd/overlord/snapstate" 38 "github.com/snapcore/snapd/overlord/state" 39 "github.com/snapcore/snapd/overlord/storecontext" 40 "github.com/snapcore/snapd/snap" 41 "github.com/snapcore/snapd/snap/snaptest" 42 "github.com/snapcore/snapd/snapdenv" 43 "github.com/snapcore/snapd/testutil" 44 "github.com/snapcore/snapd/timings" 45 ) 46 47 // TODO: should we move this into a new handlers suite? 48 func (s *deviceMgrSuite) TestSetModelHandlerNewRevision(c *C) { 49 s.state.Lock() 50 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 51 Brand: "canonical", 52 Model: "pc-model", 53 }) 54 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 55 "architecture": "amd64", 56 "kernel": "pc-kernel", 57 "gadget": "pc", 58 "revision": "1", 59 "required-snaps": []interface{}{"foo", "bar"}, 60 }) 61 // foo and bar 62 fooSI := &snap.SideInfo{ 63 RealName: "foo", 64 Revision: snap.R(1), 65 } 66 barSI := &snap.SideInfo{ 67 RealName: "foo", 68 Revision: snap.R(1), 69 } 70 pcKernelSI := &snap.SideInfo{ 71 RealName: "pc-kernel", 72 Revision: snap.R(1), 73 } 74 snapstate.Set(s.state, "foo", &snapstate.SnapState{ 75 SnapType: "app", 76 Active: true, 77 Sequence: []*snap.SideInfo{fooSI}, 78 Current: fooSI.Revision, 79 Flags: snapstate.Flags{Required: true}, 80 }) 81 snapstate.Set(s.state, "bar", &snapstate.SnapState{ 82 SnapType: "app", 83 Active: true, 84 Sequence: []*snap.SideInfo{barSI}, 85 Current: barSI.Revision, 86 Flags: snapstate.Flags{Required: true}, 87 }) 88 snapstate.Set(s.state, "pc-kernel", &snapstate.SnapState{ 89 SnapType: "kernel", 90 Active: true, 91 Sequence: []*snap.SideInfo{pcKernelSI}, 92 Current: pcKernelSI.Revision, 93 Flags: snapstate.Flags{Required: true}, 94 }) 95 s.state.Unlock() 96 97 newModel := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 98 "architecture": "amd64", 99 "kernel": "other-kernel", 100 "gadget": "pc", 101 "revision": "2", 102 "required-snaps": []interface{}{"foo"}, 103 }) 104 105 s.state.Lock() 106 t := s.state.NewTask("set-model", "set-model test") 107 chg := s.state.NewChange("dummy", "...") 108 chg.Set("new-model", string(asserts.Encode(newModel))) 109 chg.AddTask(t) 110 111 s.state.Unlock() 112 113 s.se.Ensure() 114 s.se.Wait() 115 116 s.state.Lock() 117 defer s.state.Unlock() 118 m, err := s.mgr.Model() 119 c.Assert(err, IsNil) 120 c.Assert(m, DeepEquals, newModel) 121 122 c.Assert(chg.Err(), IsNil) 123 124 // check required 125 var fooState snapstate.SnapState 126 var barState snapstate.SnapState 127 err = snapstate.Get(s.state, "foo", &fooState) 128 c.Assert(err, IsNil) 129 err = snapstate.Get(s.state, "bar", &barState) 130 c.Assert(err, IsNil) 131 c.Check(fooState.Flags.Required, Equals, true) 132 c.Check(barState.Flags.Required, Equals, false) 133 // the kernel is no longer required 134 var kernelState snapstate.SnapState 135 err = snapstate.Get(s.state, "pc-kernel", &kernelState) 136 c.Assert(err, IsNil) 137 c.Check(kernelState.Flags.Required, Equals, false) 138 } 139 140 func (s *deviceMgrSuite) TestSetModelHandlerSameRevisionNoError(c *C) { 141 model := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 142 "architecture": "amd64", 143 "kernel": "pc-kernel", 144 "gadget": "pc", 145 "revision": "1", 146 }) 147 148 s.state.Lock() 149 150 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 151 Brand: "canonical", 152 Model: "pc-model", 153 }) 154 err := assertstate.Add(s.state, model) 155 c.Assert(err, IsNil) 156 157 t := s.state.NewTask("set-model", "set-model test") 158 chg := s.state.NewChange("dummy", "...") 159 chg.Set("new-model", string(asserts.Encode(model))) 160 chg.AddTask(t) 161 162 s.state.Unlock() 163 164 s.se.Ensure() 165 s.se.Wait() 166 167 s.state.Lock() 168 defer s.state.Unlock() 169 c.Assert(chg.Err(), IsNil) 170 } 171 172 func (s *deviceMgrSuite) TestSetModelHandlerStoreSwitch(c *C) { 173 s.state.Lock() 174 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 175 Brand: "canonical", 176 Model: "pc-model", 177 }) 178 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 179 "architecture": "amd64", 180 "kernel": "pc-kernel", 181 "gadget": "pc", 182 "revision": "1", 183 }) 184 s.state.Unlock() 185 186 newModel := s.brands.Model("canonical", "pc-model", map[string]interface{}{ 187 "architecture": "amd64", 188 "kernel": "pc-kernel", 189 "gadget": "pc", 190 "store": "switched-store", 191 "revision": "2", 192 }) 193 194 s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService { 195 mod, err := devBE.Model() 196 c.Check(err, IsNil) 197 if err == nil { 198 c.Check(mod, DeepEquals, newModel) 199 } 200 return &freshSessionStore{} 201 } 202 203 s.state.Lock() 204 t := s.state.NewTask("set-model", "set-model test") 205 chg := s.state.NewChange("dummy", "...") 206 chg.Set("new-model", string(asserts.Encode(newModel))) 207 chg.Set("device", auth.DeviceState{ 208 Brand: "canonical", 209 Model: "pc-model", 210 SessionMacaroon: "switched-store-session", 211 }) 212 chg.AddTask(t) 213 214 s.state.Unlock() 215 216 s.se.Ensure() 217 s.se.Wait() 218 219 s.state.Lock() 220 defer s.state.Unlock() 221 c.Assert(chg.Err(), IsNil) 222 223 m, err := s.mgr.Model() 224 c.Assert(err, IsNil) 225 c.Assert(m, DeepEquals, newModel) 226 227 device, err := devicestatetest.Device(s.state) 228 c.Assert(err, IsNil) 229 c.Check(device, DeepEquals, &auth.DeviceState{ 230 Brand: "canonical", 231 Model: "pc-model", 232 SessionMacaroon: "switched-store-session", 233 }) 234 235 // cleanup 236 _, ok := devicestate.CachedRemodelCtx(chg) 237 c.Check(ok, Equals, true) 238 239 s.state.Unlock() 240 241 s.se.Ensure() 242 s.se.Wait() 243 244 s.state.Lock() 245 246 _, ok = devicestate.CachedRemodelCtx(chg) 247 c.Check(ok, Equals, false) 248 } 249 250 func (s *deviceMgrSuite) TestSetModelHandlerRereg(c *C) { 251 s.state.Lock() 252 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 253 Brand: "canonical", 254 Model: "pc-model", 255 Serial: "orig-serial", 256 }) 257 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 258 "architecture": "amd64", 259 "kernel": "pc-kernel", 260 "gadget": "pc", 261 }) 262 s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial") 263 s.state.Unlock() 264 265 newModel := s.brands.Model("canonical", "rereg-model", map[string]interface{}{ 266 "architecture": "amd64", 267 "kernel": "pc-kernel", 268 "gadget": "pc", 269 }) 270 271 s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService { 272 mod, err := devBE.Model() 273 c.Check(err, IsNil) 274 if err == nil { 275 c.Check(mod, DeepEquals, newModel) 276 } 277 return &freshSessionStore{} 278 } 279 280 s.state.Lock() 281 t := s.state.NewTask("set-model", "set-model test") 282 chg := s.state.NewChange("dummy", "...") 283 chg.Set("new-model", string(asserts.Encode(newModel))) 284 chg.Set("device", auth.DeviceState{ 285 Brand: "canonical", 286 Model: "rereg-model", 287 Serial: "orig-serial", 288 SessionMacaroon: "switched-store-session", 289 }) 290 chg.AddTask(t) 291 292 s.state.Unlock() 293 294 s.se.Ensure() 295 s.se.Wait() 296 297 s.state.Lock() 298 defer s.state.Unlock() 299 c.Assert(chg.Err(), IsNil) 300 301 m, err := s.mgr.Model() 302 c.Assert(err, IsNil) 303 c.Assert(m, DeepEquals, newModel) 304 305 device, err := devicestatetest.Device(s.state) 306 c.Assert(err, IsNil) 307 c.Check(device, DeepEquals, &auth.DeviceState{ 308 Brand: "canonical", 309 Model: "rereg-model", 310 Serial: "orig-serial", 311 SessionMacaroon: "switched-store-session", 312 }) 313 } 314 315 func (s *deviceMgrSuite) TestDoPrepareRemodeling(c *C) { 316 s.state.Lock() 317 defer s.state.Unlock() 318 s.state.Set("seeded", true) 319 s.state.Set("refresh-privacy-key", "some-privacy-key") 320 321 var testStore snapstate.StoreService 322 323 restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { 324 c.Check(flags.Required, Equals, true) 325 c.Check(deviceCtx, NotNil) 326 c.Check(deviceCtx.ForRemodeling(), Equals, true) 327 328 tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name)) 329 tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name)) 330 tValidate.WaitFor(tDownload) 331 tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name)) 332 tInstall.WaitFor(tValidate) 333 ts := state.NewTaskSet(tDownload, tValidate, tInstall) 334 ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge) 335 return ts, nil 336 }) 337 defer restore() 338 339 // set a model assertion 340 s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{ 341 "architecture": "amd64", 342 "kernel": "pc-kernel", 343 "gadget": "pc", 344 "base": "core18", 345 }) 346 s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial") 347 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 348 Brand: "canonical", 349 Model: "pc-model", 350 Serial: "orig-serial", 351 SessionMacaroon: "old-session", 352 }) 353 354 new := s.brands.Model("canonical", "rereg-model", map[string]interface{}{ 355 "architecture": "amd64", 356 "kernel": "pc-kernel", 357 "gadget": "pc", 358 "base": "core18", 359 "required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"}, 360 }) 361 362 freshStore := &freshSessionStore{} 363 testStore = freshStore 364 365 s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService { 366 mod, err := devBE.Model() 367 c.Check(err, IsNil) 368 if err == nil { 369 c.Check(mod, DeepEquals, new) 370 } 371 return testStore 372 } 373 374 cur, err := s.mgr.Model() 375 c.Assert(err, IsNil) 376 377 remodCtx, err := devicestate.RemodelCtx(s.state, cur, new) 378 c.Assert(err, IsNil) 379 380 c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel) 381 382 chg := s.state.NewChange("remodel", "...") 383 remodCtx.Init(chg) 384 t := s.state.NewTask("prepare-remodeling", "...") 385 chg.AddTask(t) 386 387 // set new serial 388 s.makeSerialAssertionInState(c, "canonical", "rereg-model", "orig-serial") 389 chg.Set("device", auth.DeviceState{ 390 Brand: "canonical", 391 Model: "rereg-model", 392 Serial: "orig-serial", 393 SessionMacaroon: "switched-store-session", 394 }) 395 396 s.state.Unlock() 397 398 s.se.Ensure() 399 s.se.Wait() 400 401 s.state.Lock() 402 c.Assert(chg.Err(), IsNil) 403 404 c.Check(freshStore.ensureDeviceSession, Equals, 1) 405 406 // check that the expected tasks were injected 407 tl := chg.Tasks() 408 // 1 prepare-remodeling 409 // 2 snaps * 3 tasks (from the mock install above) + 410 // 1 "set-model" task at the end 411 c.Assert(tl, HasLen, 1+2*3+1) 412 413 // sanity 414 c.Check(tl[1].Kind(), Equals, "fake-download") 415 c.Check(tl[1+2*3].Kind(), Equals, "set-model") 416 417 // cleanup 418 // fake completion 419 for _, t := range tl[1:] { 420 t.SetStatus(state.DoneStatus) 421 } 422 _, ok := devicestate.CachedRemodelCtx(chg) 423 c.Check(ok, Equals, true) 424 425 s.state.Unlock() 426 427 s.se.Ensure() 428 s.se.Wait() 429 430 s.state.Lock() 431 432 _, ok = devicestate.CachedRemodelCtx(chg) 433 c.Check(ok, Equals, false) 434 } 435 436 type preseedBaseSuite struct { 437 deviceMgrBaseSuite 438 439 cmdUmount *testutil.MockCmd 440 cmdSystemctl *testutil.MockCmd 441 } 442 443 func (s *preseedBaseSuite) SetUpTest(c *C, preseed bool) { 444 r := snapdenv.MockPreseeding(preseed) 445 446 // preseed mode helper needs to be mocked before setting up 447 // deviceMgrBaseSuite due to device Manager init. 448 s.deviceMgrBaseSuite.SetUpTest(c) 449 450 // can use cleanup only after having called base SetUpTest 451 s.AddCleanup(r) 452 453 s.AddCleanup(interfaces.MockSystemKey(`{"build-id":"abcde"}`)) 454 c.Assert(interfaces.WriteSystemKey(), IsNil) 455 456 s.cmdUmount = testutil.MockCommand(c, "umount", "") 457 s.cmdSystemctl = testutil.MockCommand(c, "systemctl", "") 458 s.AddCleanup(func() { 459 s.cmdUmount.Restore() 460 s.cmdSystemctl.Restore() 461 }) 462 463 st := s.state 464 st.Lock() 465 defer st.Unlock() 466 467 si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(3), SnapID: "test-snap-id"} 468 snaptest.MockSnap(c, `name: test-snap 469 version: 1.0 470 apps: 471 srv: 472 command: bin/service 473 daemon: simple 474 `, si) 475 476 snapstate.Set(st, "test-snap", &snapstate.SnapState{ 477 Active: true, 478 Sequence: []*snap.SideInfo{si}, 479 Current: si.Revision, 480 SnapType: "app", 481 }) 482 } 483 484 // TODO: rename preesed mode to just preseeding as much as possible, 485 // preseed mode souns like a UC20 system mode but is just a snapd mode 486 // but preseed snapd mode is a mouthful 487 type preseedModeSuite struct { 488 preseedBaseSuite 489 } 490 491 var _ = Suite(&preseedModeSuite{}) 492 493 func (s *preseedModeSuite) SetUpTest(c *C) { 494 s.preseedBaseSuite.SetUpTest(c, true) 495 } 496 497 func (s *preseedModeSuite) TearDownTest(c *C) { 498 s.preseedBaseSuite.TearDownTest(c) 499 } 500 501 func (s *preseedModeSuite) TestDoMarkPreseeded(c *C) { 502 now := time.Now() 503 restore := devicestate.MockTimeNow(func() time.Time { 504 return now 505 }) 506 defer restore() 507 508 st := s.state 509 st.Lock() 510 defer st.Unlock() 511 512 chg := st.NewChange("firstboot seeding", "...") 513 t := st.NewTask("mark-preseeded", "...") 514 chg.AddTask(t) 515 516 st.Unlock() 517 s.se.Ensure() 518 s.se.Wait() 519 st.Lock() 520 521 // mark-preseeded task is left in Doing, meaning it will be re-executed 522 // after restart in normal (not preseeding) mode. 523 c.Check(t.Status(), Equals, state.DoingStatus) 524 525 var preseeded bool 526 c.Check(t.Get("preseeded", &preseeded), IsNil) 527 c.Check(preseeded, Equals, true) 528 529 c.Assert(st.Get("preseeded", &preseeded), IsNil) 530 c.Check(preseeded, Equals, true) 531 532 var systemKey map[string]interface{} 533 c.Assert(st.Get("seed-restart-system-key", &systemKey), Equals, state.ErrNoState) 534 c.Assert(st.Get("preseed-system-key", &systemKey), IsNil) 535 c.Check(systemKey["build-id"], Equals, "abcde") 536 537 var preseededTime time.Time 538 c.Assert(st.Get("preseed-time", &preseededTime), IsNil) 539 c.Check(preseededTime.Equal(now), Equals, true) 540 541 // core snap was "manually" unmounted 542 c.Check(s.cmdUmount.Calls(), DeepEquals, [][]string{ 543 {"umount", "-d", "-l", filepath.Join(dirs.SnapMountDir, "test-snap/3")}, 544 }) 545 546 // and snapd stop was requested 547 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.StopDaemon}) 548 549 s.cmdUmount.ForgetCalls() 550 551 // re-trying mark-preseeded task has no effect 552 st.Unlock() 553 s.se.Ensure() 554 s.se.Wait() 555 st.Lock() 556 557 c.Check(s.cmdUmount.Calls(), HasLen, 0) 558 c.Check(t.Status(), Equals, state.DoingStatus) 559 } 560 561 func (s *preseedModeSuite) TestEnsureSeededPreseedFlag(c *C) { 562 now := time.Now() 563 restoreTimeNow := devicestate.MockTimeNow(func() time.Time { 564 return now 565 }) 566 defer restoreTimeNow() 567 568 called := false 569 restore := devicestate.MockPopulateStateFromSeed(func(st *state.State, opts *devicestate.PopulateStateFromSeedOptions, tm timings.Measurer) ([]*state.TaskSet, error) { 570 called = true 571 c.Check(opts.Preseed, Equals, true) 572 return nil, nil 573 }) 574 defer restore() 575 576 err := devicestate.EnsureSeeded(s.mgr) 577 c.Assert(err, IsNil) 578 c.Check(called, Equals, true) 579 580 s.state.Lock() 581 defer s.state.Unlock() 582 583 var preseedStartTime time.Time 584 c.Assert(s.state.Get("preseed-start-time", &preseedStartTime), IsNil) 585 c.Check(preseedStartTime.Equal(now), Equals, true) 586 } 587 588 type preseedDoneSuite struct { 589 preseedBaseSuite 590 } 591 592 var _ = Suite(&preseedDoneSuite{}) 593 594 func (s *preseedDoneSuite) SetUpTest(c *C) { 595 s.preseedBaseSuite.SetUpTest(c, false) 596 } 597 598 func (s *preseedDoneSuite) TearDownTest(c *C) { 599 s.preseedBaseSuite.TearDownTest(c) 600 } 601 602 func (s *preseedDoneSuite) TestDoMarkPreseededAfterFirstboot(c *C) { 603 st := s.state 604 st.Lock() 605 defer st.Unlock() 606 607 chg := st.NewChange("firstboot seeding", "...") 608 t := st.NewTask("mark-preseeded", "...") 609 chg.AddTask(t) 610 t.SetStatus(state.DoingStatus) 611 612 st.Unlock() 613 s.se.Ensure() 614 s.se.Wait() 615 st.Lock() 616 617 // no umount calls expected, just transitioned to Done status. 618 c.Check(chg.Status(), Equals, state.DoneStatus) 619 c.Check(s.cmdUmount.Calls(), HasLen, 0) 620 c.Check(s.restartRequests, HasLen, 0) 621 622 var systemKey map[string]interface{} 623 // in real world preseed-system-key would be present at this point because 624 // mark-preseeded would be run twice (before & after preseeding); this is 625 // not the case in this test. 626 c.Assert(st.Get("preseed-system-key", &systemKey), Equals, state.ErrNoState) 627 c.Assert(st.Get("seed-restart-system-key", &systemKey), IsNil) 628 c.Check(systemKey["build-id"], Equals, "abcde") 629 630 var seedRestartTime time.Time 631 c.Assert(st.Get("seed-restart-time", &seedRestartTime), IsNil) 632 c.Check(seedRestartTime.Equal(devicestate.StartTime()), Equals, true) 633 }