github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/devicestate/remodel_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "time" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/asserts" 33 "github.com/snapcore/snapd/asserts/assertstest" 34 "github.com/snapcore/snapd/boot" 35 "github.com/snapcore/snapd/bootloader" 36 "github.com/snapcore/snapd/bootloader/bootloadertest" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/overlord" 39 "github.com/snapcore/snapd/overlord/assertstate" 40 "github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 41 "github.com/snapcore/snapd/overlord/auth" 42 "github.com/snapcore/snapd/overlord/devicestate" 43 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 44 "github.com/snapcore/snapd/overlord/hookstate" 45 "github.com/snapcore/snapd/overlord/snapstate" 46 "github.com/snapcore/snapd/overlord/state" 47 "github.com/snapcore/snapd/overlord/storecontext" 48 "github.com/snapcore/snapd/snap/snaptest" 49 "github.com/snapcore/snapd/store/storetest" 50 "github.com/snapcore/snapd/testutil" 51 ) 52 53 type remodelLogicBaseSuite struct { 54 testutil.BaseTest 55 56 state *state.State 57 mgr *devicestate.DeviceManager 58 59 storeSigning *assertstest.StoreStack 60 brands *assertstest.SigningAccounts 61 62 capturedDevBE storecontext.DeviceBackend 63 dummyStore snapstate.StoreService 64 } 65 66 func (s *remodelLogicBaseSuite) SetUpTest(c *C) { 67 s.BaseTest.SetUpTest(c) 68 69 dirs.SetRootDir(c.MkDir()) 70 s.AddCleanup(func() { dirs.SetRootDir("/") }) 71 72 o := overlord.Mock() 73 s.state = o.State() 74 75 s.storeSigning = assertstest.NewStoreStack("canonical", nil) 76 s.brands = assertstest.NewSigningAccounts(s.storeSigning) 77 s.brands.Register("my-brand", brandPrivKey, nil) 78 79 db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 80 Backstore: asserts.NewMemoryBackstore(), 81 Trusted: s.storeSigning.Trusted, 82 }) 83 c.Assert(err, IsNil) 84 85 func() { 86 s.state.Lock() 87 defer s.state.Unlock() 88 assertstate.ReplaceDB(s.state, db) 89 90 assertstatetest.AddMany(s.state, s.storeSigning.StoreAccountKey("")) 91 assertstatetest.AddMany(s.state, s.brands.AccountsAndKeys("my-brand")...) 92 }() 93 94 s.dummyStore = new(storetest.Store) 95 96 newStore := func(devBE storecontext.DeviceBackend) snapstate.StoreService { 97 s.capturedDevBE = devBE 98 return s.dummyStore 99 } 100 101 hookMgr, err := hookstate.Manager(s.state, o.TaskRunner()) 102 c.Assert(err, IsNil) 103 s.mgr, err = devicestate.Manager(s.state, hookMgr, o.TaskRunner(), newStore) 104 c.Assert(err, IsNil) 105 } 106 107 type remodelLogicSuite struct { 108 remodelLogicBaseSuite 109 } 110 111 var _ = Suite(&remodelLogicSuite{}) 112 113 var modelDefaults = map[string]interface{}{ 114 "architecture": "amd64", 115 "kernel": "my-brand-kernel", 116 "gadget": "my-brand-gadget", 117 "store": "my-brand-store", 118 "required-snaps": []interface{}{"required1"}, 119 } 120 121 func fakeRemodelingModel(extra map[string]interface{}) *asserts.Model { 122 primary := map[string]interface{}{ 123 "type": "model", 124 "authority-id": "my-brand", 125 "series": "16", 126 "brand-id": "my-brand", 127 "model": "my-model", 128 } 129 return assertstest.FakeAssertion(primary, modelDefaults, extra).(*asserts.Model) 130 } 131 132 func (s *remodelLogicSuite) TestClassifyRemodel(c *C) { 133 oldModel := fakeRemodelingModel(nil) 134 135 cases := []struct { 136 newHeaders map[string]interface{} 137 kind devicestate.RemodelKind 138 }{ 139 {map[string]interface{}{}, devicestate.UpdateRemodel}, 140 {map[string]interface{}{ 141 "required-snaps": []interface{}{"required1", "required2"}, 142 "revision": "1", 143 }, devicestate.UpdateRemodel}, 144 {map[string]interface{}{ 145 "store": "my-other-store", 146 "revision": "1", 147 }, devicestate.StoreSwitchRemodel}, 148 {map[string]interface{}{ 149 "model": "my-other-model", 150 "store": "my-other-store", 151 }, devicestate.ReregRemodel}, 152 {map[string]interface{}{ 153 "authority-id": "other-brand", 154 "brand-id": "other-brand", 155 "model": "other-model", 156 }, devicestate.ReregRemodel}, 157 {map[string]interface{}{ 158 "authority-id": "other-brand", 159 "brand-id": "other-brand", 160 "model": "other-model", 161 "required-snaps": []interface{}{"other-required1"}, 162 }, devicestate.ReregRemodel}, 163 {map[string]interface{}{ 164 "authority-id": "other-brand", 165 "brand-id": "other-brand", 166 "model": "other-model", 167 "store": "my-other-store", 168 }, devicestate.ReregRemodel}, 169 } 170 171 for _, t := range cases { 172 newModel := fakeRemodelingModel(t.newHeaders) 173 c.Check(devicestate.ClassifyRemodel(oldModel, newModel), Equals, t.kind) 174 } 175 } 176 177 func (s *remodelLogicSuite) TestUpdateRemodelContext(c *C) { 178 oldModel := fakeRemodelingModel(nil) 179 newModel := fakeRemodelingModel(map[string]interface{}{ 180 "required-snaps": []interface{}{"required1", "required2"}, 181 "revision": "1", 182 }) 183 184 s.state.Lock() 185 defer s.state.Unlock() 186 187 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 188 c.Assert(err, IsNil) 189 190 c.Check(remodCtx.ForRemodeling(), Equals, true) 191 c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel) 192 groundCtx := remodCtx.GroundContext() 193 c.Check(groundCtx.ForRemodeling(), Equals, false) 194 c.Check(groundCtx.Model().Revision(), Equals, 0) 195 c.Check(groundCtx.Store, PanicMatches, `retrieved ground context is not intended to drive store operations`) 196 197 chg := s.state.NewChange("remodel", "...") 198 199 remodCtx.Init(chg) 200 201 var encNewModel string 202 c.Assert(chg.Get("new-model", &encNewModel), IsNil) 203 204 c.Check(encNewModel, Equals, string(asserts.Encode(newModel))) 205 206 c.Check(remodCtx.Model(), DeepEquals, newModel) 207 // an update remodel does not need a new/dedicated store 208 c.Check(remodCtx.Store(), IsNil) 209 } 210 211 func (s *remodelLogicSuite) TestNewStoreRemodelContextInit(c *C) { 212 oldModel := fakeRemodelingModel(nil) 213 newModel := fakeRemodelingModel(map[string]interface{}{ 214 "store": "my-other-store", 215 "revision": "1", 216 }) 217 218 s.state.Lock() 219 defer s.state.Unlock() 220 221 // we have a device state 222 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 223 Brand: "my-brand", 224 Model: "my-model", 225 Serial: "serialserialserial", 226 SessionMacaroon: "prev-session", 227 }) 228 229 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 230 c.Assert(err, IsNil) 231 232 c.Check(remodCtx.ForRemodeling(), Equals, true) 233 c.Check(remodCtx.Kind(), Equals, devicestate.StoreSwitchRemodel) 234 groundCtx := remodCtx.GroundContext() 235 c.Check(groundCtx.ForRemodeling(), Equals, false) 236 c.Check(groundCtx.Model().Revision(), Equals, 0) 237 238 chg := s.state.NewChange("remodel", "...") 239 240 remodCtx.Init(chg) 241 242 var encNewModel string 243 c.Assert(chg.Get("new-model", &encNewModel), IsNil) 244 245 c.Check(encNewModel, Equals, string(asserts.Encode(newModel))) 246 247 var device *auth.DeviceState 248 c.Assert(chg.Get("device", &device), IsNil) 249 // session macaroon was reset 250 c.Check(device, DeepEquals, &auth.DeviceState{ 251 Brand: "my-brand", 252 Model: "my-model", 253 Serial: "serialserialserial", 254 }) 255 256 c.Check(remodCtx.Model(), DeepEquals, newModel) 257 } 258 259 func (s *remodelLogicSuite) TestRemodelDeviceBackendNoChangeYet(c *C) { 260 oldModel := fakeRemodelingModel(nil) 261 newModel := fakeRemodelingModel(map[string]interface{}{ 262 "store": "my-other-store", 263 "revision": "1", 264 }) 265 266 s.state.Lock() 267 defer s.state.Unlock() 268 269 // we have a device state 270 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 271 Brand: "my-brand", 272 Model: "my-model", 273 Serial: "serialserialserial", 274 }) 275 276 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 277 c.Assert(err, IsNil) 278 279 devBE := s.capturedDevBE 280 c.Check(devBE, NotNil) 281 282 device, err := devBE.Device() 283 c.Assert(err, IsNil) 284 c.Check(device, DeepEquals, &auth.DeviceState{ 285 Brand: "my-brand", 286 Model: "my-model", 287 Serial: "serialserialserial", 288 }) 289 290 mod, err := devBE.Model() 291 c.Assert(err, IsNil) 292 c.Check(mod, DeepEquals, newModel) 293 294 // set device state for the context 295 device1 := &auth.DeviceState{ 296 Brand: "my-brand", 297 Model: "my-model", 298 Serial: "serialserialserial", 299 SessionMacaroon: "session", 300 } 301 302 err = devBE.SetDevice(device1) 303 c.Assert(err, IsNil) 304 305 device, err = devBE.Device() 306 c.Assert(err, IsNil) 307 c.Check(device, DeepEquals, device1) 308 309 // have a change 310 chg := s.state.NewChange("remodel", "...") 311 312 remodCtx.Init(chg) 313 314 // check device state is preserved across association with a Change 315 device, err = devBE.Device() 316 c.Assert(err, IsNil) 317 c.Check(device, DeepEquals, device1) 318 } 319 320 func (s *remodelLogicSuite) TestRemodelDeviceBackend(c *C) { 321 oldModel := fakeRemodelingModel(nil) 322 newModel := fakeRemodelingModel(map[string]interface{}{ 323 "store": "my-other-store", 324 "revision": "1", 325 }) 326 327 s.state.Lock() 328 defer s.state.Unlock() 329 330 // we have a device state 331 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 332 Brand: "my-brand", 333 Model: "my-model", 334 Serial: "serialserialserial", 335 }) 336 337 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 338 c.Assert(err, IsNil) 339 340 devBE := devicestate.RemodelDeviceBackend(remodCtx) 341 342 chg := s.state.NewChange("remodel", "...") 343 344 remodCtx.Init(chg) 345 346 device, err := devBE.Device() 347 c.Assert(err, IsNil) 348 c.Check(device, DeepEquals, &auth.DeviceState{ 349 Brand: "my-brand", 350 Model: "my-model", 351 Serial: "serialserialserial", 352 }) 353 354 mod, err := devBE.Model() 355 c.Assert(err, IsNil) 356 c.Check(mod, DeepEquals, newModel) 357 358 // set a device state for the context 359 device1 := &auth.DeviceState{ 360 Brand: "my-brand", 361 Model: "my-model", 362 Serial: "serialserialserial", 363 SessionMacaroon: "session", 364 } 365 366 err = devBE.SetDevice(device1) 367 c.Assert(err, IsNil) 368 369 // it's stored on change now 370 var device2 *auth.DeviceState 371 c.Assert(chg.Get("device", &device2), IsNil) 372 c.Check(device2, DeepEquals, device1) 373 374 device, err = devBE.Device() 375 c.Assert(err, IsNil) 376 c.Check(device, DeepEquals, device1) 377 } 378 379 func (s *remodelLogicSuite) TestRemodelDeviceBackendIsolation(c *C) { 380 oldModel := fakeRemodelingModel(nil) 381 newModel := fakeRemodelingModel(map[string]interface{}{ 382 "store": "my-other-store", 383 "revision": "1", 384 }) 385 386 s.state.Lock() 387 defer s.state.Unlock() 388 389 // we have a device state 390 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 391 Brand: "my-brand", 392 Model: "my-model", 393 Serial: "serialserialserial", 394 }) 395 396 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 397 c.Assert(err, IsNil) 398 399 chg := s.state.NewChange("remodel", "...") 400 401 remodCtx.Init(chg) 402 403 devBE := devicestate.RemodelDeviceBackend(remodCtx) 404 405 err = devBE.SetDevice(&auth.DeviceState{ 406 Brand: "my-brand", 407 Model: "my-model", 408 Serial: "serialserialserial", 409 SessionMacaroon: "remodel-session", 410 }) 411 c.Assert(err, IsNil) 412 413 // the global device state is as before 414 expectedGlobalDevice := &auth.DeviceState{ 415 Brand: "my-brand", 416 Model: "my-model", 417 Serial: "serialserialserial", 418 } 419 420 device, err := s.mgr.StoreContextBackend().Device() 421 c.Assert(err, IsNil) 422 c.Check(device, DeepEquals, expectedGlobalDevice) 423 } 424 func (s *remodelLogicSuite) TestNewStoreRemodelContextStore(c *C) { 425 oldModel := fakeRemodelingModel(nil) 426 newModel := fakeRemodelingModel(map[string]interface{}{ 427 "store": "my-other-store", 428 "revision": "1", 429 }) 430 431 s.state.Lock() 432 defer s.state.Unlock() 433 434 // we have a device state 435 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 436 Brand: "my-brand", 437 Model: "my-model", 438 Serial: "serialserialserial", 439 SessionMacaroon: "prev-session", 440 }) 441 442 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 443 c.Assert(err, IsNil) 444 445 c.Check(s.capturedDevBE, NotNil) 446 447 // new store remodel context device state built ignoring the 448 // previous session 449 device1, err := s.capturedDevBE.Device() 450 c.Assert(err, IsNil) 451 c.Check(device1, DeepEquals, &auth.DeviceState{ 452 Brand: "my-brand", 453 Model: "my-model", 454 Serial: "serialserialserial", 455 }) 456 457 sto := remodCtx.Store() 458 c.Check(sto, Equals, s.dummyStore) 459 460 // store is kept and not rebuilt 461 s.dummyStore = nil 462 463 sto1 := remodCtx.Store() 464 c.Check(sto1, Equals, sto) 465 } 466 467 func (s *remodelLogicSuite) TestNewStoreRemodelContextFinish(c *C) { 468 oldModel := fakeRemodelingModel(nil) 469 newModel := fakeRemodelingModel(map[string]interface{}{ 470 "store": "my-other-store", 471 "revision": "1", 472 }) 473 474 s.state.Lock() 475 defer s.state.Unlock() 476 477 // we have a device state 478 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 479 Brand: "my-brand", 480 Model: "my-model", 481 Serial: "serialserialserial", 482 SessionMacaroon: "orig-session", 483 }) 484 485 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 486 c.Assert(err, IsNil) 487 488 chg := s.state.NewChange("remodel", "...") 489 490 remodCtx.Init(chg) 491 492 devBE := devicestate.RemodelDeviceBackend(remodCtx) 493 494 err = devBE.SetDevice(&auth.DeviceState{ 495 Brand: "my-brand", 496 Model: "my-model", 497 Serial: "serialserialserial", 498 SessionMacaroon: "new-session", 499 }) 500 c.Assert(err, IsNil) 501 502 err = remodCtx.Finish() 503 c.Assert(err, IsNil) 504 505 // the global device now matches the state reached in the remodel 506 expectedGlobalDevice := &auth.DeviceState{ 507 Brand: "my-brand", 508 Model: "my-model", 509 Serial: "serialserialserial", 510 SessionMacaroon: "new-session", 511 } 512 513 device, err := s.mgr.StoreContextBackend().Device() 514 c.Assert(err, IsNil) 515 c.Check(device, DeepEquals, expectedGlobalDevice) 516 } 517 518 func (s *remodelLogicSuite) TestNewStoreRemodelContextFinishVsGlobalUpdateDeviceAuth(c *C) { 519 oldModel := fakeRemodelingModel(nil) 520 newModel := fakeRemodelingModel(map[string]interface{}{ 521 "store": "my-other-store", 522 "revision": "1", 523 }) 524 525 s.state.Lock() 526 defer s.state.Unlock() 527 528 // we have a device state 529 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 530 Brand: "my-brand", 531 Model: "my-model", 532 Serial: "serialserialserial", 533 SessionMacaroon: "old-session", 534 }) 535 536 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 537 c.Assert(err, IsNil) 538 539 chg := s.state.NewChange("remodel", "...") 540 541 remodCtx.Init(chg) 542 543 devBE := devicestate.RemodelDeviceBackend(remodCtx) 544 545 err = devBE.SetDevice(&auth.DeviceState{ 546 Brand: "my-brand", 547 Model: "my-model", 548 Serial: "serialserialserial", 549 SessionMacaroon: "remodel-session", 550 }) 551 c.Assert(err, IsNil) 552 553 // global store device and auth context 554 scb := s.mgr.StoreContextBackend() 555 dac := storecontext.New(s.state, scb) 556 // this is the unlikely start of the global store trying to 557 // refresh the session 558 s.state.Unlock() 559 globalDevice, err := dac.Device() 560 s.state.Lock() 561 c.Assert(err, IsNil) 562 c.Check(globalDevice.SessionMacaroon, Equals, "old-session") 563 564 err = remodCtx.Finish() 565 c.Assert(err, IsNil) 566 567 s.state.Unlock() 568 device1, err := dac.UpdateDeviceAuth(globalDevice, "fresh-session") 569 s.state.Lock() 570 c.Assert(err, IsNil) 571 572 // the global device now matches the state reached in the remodel 573 expectedGlobalDevice := &auth.DeviceState{ 574 Brand: "my-brand", 575 Model: "my-model", 576 Serial: "serialserialserial", 577 SessionMacaroon: "remodel-session", 578 } 579 580 s.state.Unlock() 581 device, err := dac.Device() 582 s.state.Lock() 583 c.Assert(err, IsNil) 584 c.Check(device, DeepEquals, expectedGlobalDevice) 585 586 // also this was already the case 587 c.Check(device1, DeepEquals, expectedGlobalDevice) 588 } 589 590 func (s *remodelLogicSuite) TestRemodelDeviceBackendKeptSerial(c *C) { 591 oldModel := fakeRemodelingModel(nil) 592 newModel := fakeRemodelingModel(map[string]interface{}{ 593 "store": "my-other-store", 594 "revision": "1", 595 }) 596 597 s.state.Lock() 598 defer s.state.Unlock() 599 600 // we have a device state and serial 601 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 602 Brand: "my-brand", 603 Model: "my-model", 604 Serial: "serialserialserial1", 605 }) 606 607 makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "my-model", "serialserialserial1") 608 609 serial, err := s.mgr.Serial() 610 c.Assert(err, IsNil) 611 c.Check(serial.Serial(), Equals, "serialserialserial1") 612 613 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 614 c.Assert(err, IsNil) 615 616 devBE := devicestate.RemodelDeviceBackend(remodCtx) 617 618 serial0, err := devBE.Serial() 619 c.Assert(err, IsNil) 620 c.Check(serial0.Serial(), Equals, "serialserialserial1") 621 622 chg := s.state.NewChange("remodel", "...") 623 624 remodCtx.Init(chg) 625 626 serial0, err = devBE.Serial() 627 c.Assert(err, IsNil) 628 c.Check(serial0.Serial(), Equals, "serialserialserial1") 629 } 630 631 func (s *remodelLogicSuite) TestRemodelContextSystemModeDefaultRun(c *C) { 632 oldModel := s.brands.Model("my-brand", "my-model", modelDefaults) 633 newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{"revision": "2"}) 634 635 s.state.Lock() 636 defer s.state.Unlock() 637 638 assertstatetest.AddMany(s.state, oldModel) 639 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 640 Brand: "my-brand", 641 Model: "my-model", 642 Serial: "serialserialserial", 643 }) 644 645 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 646 c.Assert(err, IsNil) 647 c.Check(remodCtx.SystemMode(), Equals, "run") 648 } 649 650 func (s *remodelLogicSuite) TestRemodelContextSystemModeWorks(c *C) { 651 oldModel := s.brands.Model("my-brand", "my-model", modelDefaults) 652 newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{"revision": "2"}) 653 654 s.state.Lock() 655 defer s.state.Unlock() 656 657 assertstatetest.AddMany(s.state, oldModel) 658 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 659 Brand: "my-brand", 660 Model: "my-model", 661 Serial: "serialserialserial", 662 }) 663 devicestate.SetSystemMode(s.mgr, "install") 664 665 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 666 c.Assert(err, IsNil) 667 c.Check(remodCtx.SystemMode(), Equals, "install") 668 } 669 670 func (s *remodelLogicSuite) TestRemodelContextForTaskAndCaching(c *C) { 671 oldModel := s.brands.Model("my-brand", "my-model", modelDefaults) 672 newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{ 673 "store": "my-other-store", 674 "revision": "1", 675 }) 676 677 s.state.Lock() 678 defer s.state.Unlock() 679 680 // we have a device state 681 assertstatetest.AddMany(s.state, oldModel) 682 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 683 Brand: "my-brand", 684 Model: "my-model", 685 Serial: "serialserialserial", 686 }) 687 688 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 689 c.Assert(err, IsNil) 690 691 c.Check(remodCtx.ForRemodeling(), Equals, true) 692 693 chg := s.state.NewChange("remodel", "...") 694 695 remodCtx.Init(chg) 696 697 t := s.state.NewTask("remodel-task-1", "...") 698 chg.AddTask(t) 699 700 // caching, internally this use remodelCtxFromTask 701 remodCtx1, err := devicestate.DeviceCtx(s.state, t, nil) 702 c.Assert(err, IsNil) 703 c.Check(remodCtx1, Equals, remodCtx) 704 705 // if the context goes away (e.g. because of restart) we 706 // compute a new one 707 devicestate.CleanupRemodelCtx(chg) 708 709 remodCtx2, err := devicestate.DeviceCtx(s.state, t, nil) 710 c.Assert(err, IsNil) 711 c.Check(remodCtx2 != remodCtx, Equals, true) 712 c.Check(remodCtx2.Model(), DeepEquals, newModel) 713 } 714 715 func (s *remodelLogicSuite) TestRemodelContextForTaskNo(c *C) { 716 s.state.Lock() 717 defer s.state.Unlock() 718 719 // internally these use remodelCtxFromTask 720 721 // task is nil 722 remodCtx1, err := devicestate.DeviceCtx(s.state, nil, nil) 723 c.Check(err, Equals, state.ErrNoState) 724 c.Check(remodCtx1, IsNil) 725 726 // no change 727 t := s.state.NewTask("random-task", "...") 728 _, err = devicestate.DeviceCtx(s.state, t, nil) 729 c.Check(err, Equals, state.ErrNoState) 730 731 // not a remodel change 732 chg := s.state.NewChange("not-remodel", "...") 733 chg.AddTask(t) 734 _, err = devicestate.DeviceCtx(s.state, t, nil) 735 c.Check(err, Equals, state.ErrNoState) 736 } 737 738 func (s *remodelLogicSuite) setupForRereg(c *C) (oldModel, newModel *asserts.Model) { 739 oldModel = s.brands.Model("my-brand", "my-model", modelDefaults) 740 newModel = s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{ 741 "authority-id": "other-brand", 742 "brand-id": "other-brand", 743 "model": "other-model", 744 "store": "other-store", 745 }) 746 747 s.state.Lock() 748 defer s.state.Unlock() 749 750 encDevKey, err := asserts.EncodePublicKey(devKey.PublicKey()) 751 c.Assert(err, IsNil) 752 serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{ 753 "authority-id": "my-brand", 754 "brand-id": "my-brand", 755 "model": "my-model", 756 "serial": "orig-serial", 757 "device-key": string(encDevKey), 758 "device-key-sha3-384": devKey.PublicKey().ID(), 759 "timestamp": time.Now().Format(time.RFC3339), 760 }, nil, "") 761 c.Assert(err, IsNil) 762 763 assertstatetest.AddMany(s.state, oldModel, serial) 764 765 return oldModel, newModel 766 } 767 768 func (s *remodelLogicSuite) TestReregRemodelContextInit(c *C) { 769 oldModel, newModel := s.setupForRereg(c) 770 771 s.state.Lock() 772 defer s.state.Unlock() 773 774 // we have a device state 775 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 776 Brand: "my-brand", 777 Model: "my-model", 778 Serial: "orig-serial", 779 KeyID: "device-key-id", 780 SessionMacaroon: "prev-session", 781 }) 782 783 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 784 c.Assert(err, IsNil) 785 786 c.Check(remodCtx.ForRemodeling(), Equals, true) 787 c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel) 788 groundCtx := remodCtx.GroundContext() 789 c.Check(groundCtx.ForRemodeling(), Equals, false) 790 c.Check(groundCtx.Model().BrandID(), Equals, "my-brand") 791 792 chg := s.state.NewChange("remodel", "...") 793 794 remodCtx.Init(chg) 795 796 var encNewModel string 797 c.Assert(chg.Get("new-model", &encNewModel), IsNil) 798 799 c.Check(encNewModel, Equals, string(asserts.Encode(newModel))) 800 801 var device *auth.DeviceState 802 c.Assert(chg.Get("device", &device), IsNil) 803 // fresh device state before registration but with device-key 804 c.Check(device, DeepEquals, &auth.DeviceState{ 805 Brand: "other-brand", 806 Model: "other-model", 807 KeyID: "device-key-id", 808 }) 809 810 c.Check(remodCtx.Model(), DeepEquals, newModel) 811 // caching 812 t := s.state.NewTask("remodel-task-1", "...") 813 chg.AddTask(t) 814 815 remodCtx1, err := devicestate.DeviceCtx(s.state, t, nil) 816 c.Assert(err, IsNil) 817 c.Check(remodCtx1, Equals, remodCtx) 818 } 819 820 func (s *remodelLogicSuite) TestReregRemodelContextAsRegistrationContext(c *C) { 821 oldModel, newModel := s.setupForRereg(c) 822 823 s.state.Lock() 824 defer s.state.Unlock() 825 826 // we have a device state 827 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 828 Brand: "my-brand", 829 Model: "my-model", 830 Serial: "orig-serial", 831 KeyID: "device-key-id", 832 SessionMacaroon: "prev-session", 833 }) 834 835 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 836 c.Assert(err, IsNil) 837 838 c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel) 839 840 chg := s.state.NewChange("remodel", "...") 841 842 remodCtx.Init(chg) 843 844 regCtx := remodCtx.(devicestate.RegistrationContext) 845 846 c.Check(regCtx.ForRemodeling(), Equals, true) 847 device1, err := regCtx.Device() 848 c.Assert(err, IsNil) 849 // fresh device state before registration but with device-key 850 c.Check(device1, DeepEquals, &auth.DeviceState{ 851 Brand: "other-brand", 852 Model: "other-model", 853 KeyID: "device-key-id", 854 }) 855 c.Check(regCtx.GadgetForSerialRequestConfig(), Equals, "my-brand-gadget") 856 c.Check(regCtx.SerialRequestExtraHeaders(), DeepEquals, map[string]interface{}{ 857 "original-brand-id": "my-brand", 858 "original-model": "my-model", 859 "original-serial": "orig-serial", 860 }) 861 862 serial, err := s.mgr.Serial() 863 c.Assert(err, IsNil) 864 c.Check(regCtx.SerialRequestAncillaryAssertions(), DeepEquals, []asserts.Assertion{newModel, serial}) 865 } 866 867 func (s *remodelLogicSuite) TestReregRemodelContextNewSerial(c *C) { 868 // re-registration case 869 oldModel := s.brands.Model("my-brand", "my-model", modelDefaults) 870 newModel := fakeRemodelingModel(map[string]interface{}{ 871 "model": "other-model", 872 }) 873 874 s.state.Lock() 875 defer s.state.Unlock() 876 877 assertstatetest.AddMany(s.state, oldModel) 878 879 // we have a device state and serial 880 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 881 Brand: "my-brand", 882 Model: "my-model", 883 Serial: "serialserialserial1", 884 }) 885 886 makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "my-model", "serialserialserial1") 887 888 serial, err := s.mgr.Serial() 889 c.Assert(err, IsNil) 890 c.Check(serial.Serial(), Equals, "serialserialserial1") 891 892 remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel) 893 c.Assert(err, IsNil) 894 895 devBE := devicestate.RemodelDeviceBackend(remodCtx) 896 897 // no new serial yet 898 _, err = devBE.Serial() 899 c.Assert(err, Equals, state.ErrNoState) 900 901 chg := s.state.NewChange("remodel", "...") 902 903 remodCtx.Init(chg) 904 905 // sanity check 906 device1, err := devBE.Device() 907 c.Assert(err, IsNil) 908 c.Check(device1, DeepEquals, &auth.DeviceState{ 909 Brand: "my-brand", 910 Model: "other-model", 911 }) 912 913 // still no new serial 914 _, err = devBE.Serial() 915 c.Assert(err, Equals, state.ErrNoState) 916 917 newSerial := makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "other-model", "serialserialserial2") 918 919 // same 920 _, err = devBE.Serial() 921 c.Check(err, Equals, state.ErrNoState) 922 923 // finish registration 924 regCtx := remodCtx.(devicestate.RegistrationContext) 925 err = regCtx.FinishRegistration(newSerial) 926 c.Assert(err, IsNil) 927 928 serial, err = devBE.Serial() 929 c.Check(err, IsNil) 930 c.Check(serial.Model(), Equals, "other-model") 931 c.Check(serial.Serial(), Equals, "serialserialserial2") 932 933 // not exposed yet 934 serial, err = s.mgr.Serial() 935 c.Assert(err, IsNil) 936 c.Check(serial.Model(), Equals, "my-model") 937 c.Check(serial.Serial(), Equals, "serialserialserial1") 938 939 // finish 940 err = remodCtx.Finish() 941 c.Assert(err, IsNil) 942 943 serial, err = s.mgr.Serial() 944 c.Assert(err, IsNil) 945 c.Check(serial.Model(), Equals, "other-model") 946 c.Check(serial.Serial(), Equals, "serialserialserial2") 947 } 948 949 type uc20RemodelLogicSuite struct { 950 remodelLogicBaseSuite 951 952 oldModel *asserts.Model 953 bootloader *bootloadertest.MockRecoveryAwareTrustedAssetsBootloader 954 oldSeededTs time.Time 955 } 956 957 var _ = Suite(&uc20RemodelLogicSuite{}) 958 959 func writeDeviceModelToUbuntuBoot(c *C, model *asserts.Model) { 960 var buf bytes.Buffer 961 c.Assert(asserts.NewEncoder(&buf).Encode(model), IsNil) 962 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755), IsNil) 963 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), 964 buf.Bytes(), 0755), 965 IsNil) 966 } 967 968 func (s *uc20RemodelLogicSuite) SetUpTest(c *C) { 969 s.remodelLogicBaseSuite.SetUpTest(c) 970 971 s.oldModel = s.brands.Model("my-brand", "my-model", uc20ModelDefaults) 972 writeDeviceModelToUbuntuBoot(c, s.oldModel) 973 s.bootloader = bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets() 974 bootloader.Force(s.bootloader) 975 s.AddCleanup(func() { bootloader.Force(nil) }) 976 977 m := boot.Modeenv{ 978 Mode: "run", 979 980 CurrentRecoverySystems: []string{"0000"}, 981 GoodRecoverySystems: []string{"0000"}, 982 983 Model: s.oldModel.Model(), 984 Grade: string(s.oldModel.Grade()), 985 BrandID: s.oldModel.BrandID(), 986 ModelSignKeyID: s.oldModel.SignKeyID(), 987 } 988 err := m.WriteTo("") 989 c.Assert(err, IsNil) 990 991 restore := boot.MockResealKeyToModeenv(func(_ string, m *boot.Modeenv, expectReseal bool) error { 992 return fmt.Errorf("not expected to be called") 993 }) 994 s.AddCleanup(restore) 995 996 s.state.Lock() 997 defer s.state.Unlock() 998 sys := devicestate.SeededSystem{ 999 System: "0000", 1000 1001 Model: "my-model", 1002 BrandID: "my-brand", 1003 Revision: 0, 1004 Timestamp: s.oldModel.Timestamp(), 1005 1006 SeedTime: time.Now(), 1007 } 1008 err = devicestate.RecordSeededSystem(s.mgr, s.state, &sys) 1009 c.Assert(err, IsNil) 1010 s.oldSeededTs = sys.SeedTime 1011 } 1012 1013 var uc20ModelDefaults = map[string]interface{}{ 1014 "architecture": "amd64", 1015 "base": "core20", 1016 "grade": "dangerous", 1017 "snaps": []interface{}{ 1018 map[string]interface{}{ 1019 "name": "pc-kernel", 1020 "id": snaptest.AssertedSnapID("pc-kernel"), 1021 "type": "kernel", 1022 "default-channel": "20", 1023 }, 1024 map[string]interface{}{ 1025 "name": "pc", 1026 "id": snaptest.AssertedSnapID("pc"), 1027 "type": "gadget", 1028 "default-channel": "20", 1029 }}, 1030 } 1031 1032 func (s *uc20RemodelLogicSuite) TestReregRemodelContextUC20(c *C) { 1033 newModel := s.brands.Model("my-brand", "other-model", uc20ModelDefaults) 1034 1035 m, err := boot.ReadModeenv("") 1036 c.Assert(err, IsNil) 1037 // the system has already been promoted 1038 m.CurrentRecoverySystems = append(m.CurrentRecoverySystems, "1234") 1039 m.GoodRecoverySystems = append(m.GoodRecoverySystems, "1234") 1040 c.Assert(m.Write(), IsNil) 1041 1042 s.state.Lock() 1043 defer s.state.Unlock() 1044 1045 assertstatetest.AddMany(s.state, s.oldModel) 1046 1047 // we have a device state and serial 1048 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1049 Brand: "my-brand", 1050 Model: "my-model", 1051 Serial: "serialserialserial1", 1052 }) 1053 1054 makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "my-model", "serialserialserial1") 1055 1056 serial, err := s.mgr.Serial() 1057 c.Assert(err, IsNil) 1058 c.Check(serial.Serial(), Equals, "serialserialserial1") 1059 1060 remodCtx, err := devicestate.RemodelCtx(s.state, s.oldModel, newModel) 1061 c.Assert(err, IsNil) 1062 c.Check(remodCtx.ForRemodeling(), Equals, true) 1063 c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel) 1064 1065 devBE := devicestate.RemodelDeviceBackend(remodCtx) 1066 1067 chg := s.state.NewChange("remodel", "...") 1068 1069 remodCtx.Init(chg) 1070 1071 // sanity check 1072 device1, err := devBE.Device() 1073 c.Assert(err, IsNil) 1074 c.Check(device1, DeepEquals, &auth.DeviceState{ 1075 Brand: "my-brand", 1076 Model: "other-model", 1077 }) 1078 1079 newSerial := makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "other-model", "serialserialserial2") 1080 1081 // finish registration 1082 regCtx := remodCtx.(devicestate.RegistrationContext) 1083 err = regCtx.FinishRegistration(newSerial) 1084 c.Assert(err, IsNil) 1085 1086 resealKeysCalls := 0 1087 restore := boot.MockResealKeyToModeenv(func(_ string, m *boot.Modeenv, expectReseal bool) error { 1088 resealKeysCalls++ 1089 c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"0000", "1234"}) 1090 c.Check(m.GoodRecoverySystems, DeepEquals, []string{"0000", "1234"}) 1091 switch resealKeysCalls { 1092 case 1: 1093 // intermediate step, new and old models 1094 c.Check(m.ModelForSealing().Model(), Equals, "my-model") 1095 c.Check(m.TryModelForSealing().Model(), Equals, "other-model") 1096 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, "model: my-model\n") 1097 case 2: 1098 // new model 1099 c.Check(m.ModelForSealing().Model(), Equals, "other-model") 1100 c.Check(m.TryModelForSealing().Model(), Equals, "") 1101 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, "model: other-model\n") 1102 default: 1103 c.Fatalf("unexpected call #%v to reseal key to modeenv", resealKeysCalls) 1104 } 1105 // this is running as part of post finish step, so the state has 1106 // already been updated 1107 serial, err = s.mgr.Serial() 1108 c.Assert(err, IsNil) 1109 c.Check(serial.Model(), Equals, "other-model") 1110 c.Check(serial.Serial(), Equals, "serialserialserial2") 1111 return nil 1112 }) 1113 s.AddCleanup(restore) 1114 1115 // finish fails because we haven't set the seed system label yet 1116 err = remodCtx.Finish() 1117 c.Assert(err, ErrorMatches, "internal error: recovery system label is unset during remodel finish") 1118 c.Check(resealKeysCalls, Equals, 0) 1119 1120 // set the label internally 1121 devicestate.RemodelSetRecoverySystemLabel(remodCtx, "1234") 1122 err = remodCtx.Finish() 1123 c.Assert(err, IsNil) 1124 c.Check(resealKeysCalls, Equals, 2) 1125 1126 var seededSystemsFromState []map[string]interface{} 1127 err = s.state.Get("seeded-systems", &seededSystemsFromState) 1128 c.Assert(err, IsNil) 1129 c.Assert(seededSystemsFromState, HasLen, 2) 1130 c.Assert(seededSystemsFromState[1], DeepEquals, map[string]interface{}{ 1131 "system": "0000", 1132 "model": "my-model", 1133 "brand-id": "my-brand", 1134 "revision": float64(0), 1135 "timestamp": s.oldModel.Timestamp().Format(time.RFC3339Nano), 1136 "seed-time": s.oldSeededTs.Format(time.RFC3339Nano), 1137 }) 1138 // new system is prepended, since timestamps are involved clear ones that weren't mocked 1139 c.Assert(seededSystemsFromState[0]["seed-time"], FitsTypeOf, "") 1140 newSeedTs, err := time.Parse(time.RFC3339Nano, seededSystemsFromState[0]["seed-time"].(string)) 1141 c.Assert(err, IsNil) 1142 seededSystemsFromState[0]["seed-time"] = "" 1143 c.Assert(seededSystemsFromState[0], DeepEquals, map[string]interface{}{ 1144 "system": "1234", 1145 "model": "other-model", 1146 "brand-id": "my-brand", 1147 "revision": float64(0), 1148 "timestamp": newModel.Timestamp().Format(time.RFC3339Nano), 1149 "seed-time": "", 1150 }) 1151 c.Assert(newSeedTs.After(s.oldSeededTs), Equals, true) 1152 } 1153 1154 func (s *uc20RemodelLogicSuite) TestUpdateRemodelContext(c *C) { 1155 modelDefaults := make(map[string]interface{}, len(uc20ModelDefaults)) 1156 for k, v := range uc20ModelDefaults { 1157 modelDefaults[k] = v 1158 } 1159 // simple model update with bumped revision 1160 modelDefaults["revision"] = "2" 1161 newModel := s.brands.Model("my-brand", "my-model", modelDefaults) 1162 1163 s.state.Lock() 1164 defer s.state.Unlock() 1165 1166 m, err := boot.ReadModeenv("") 1167 c.Assert(err, IsNil) 1168 // the system has already been promoted 1169 m.CurrentRecoverySystems = append(m.CurrentRecoverySystems, "1234") 1170 m.GoodRecoverySystems = append(m.GoodRecoverySystems, "1234") 1171 c.Assert(m.Write(), IsNil) 1172 1173 remodCtx, err := devicestate.RemodelCtx(s.state, s.oldModel, newModel) 1174 c.Assert(err, IsNil) 1175 c.Check(remodCtx.ForRemodeling(), Equals, true) 1176 c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel) 1177 groundCtx := remodCtx.GroundContext() 1178 c.Check(groundCtx.ForRemodeling(), Equals, false) 1179 c.Check(groundCtx.Model().Revision(), Equals, 0) 1180 c.Check(groundCtx.Store, PanicMatches, `retrieved ground context is not intended to drive store operations`) 1181 1182 chg := s.state.NewChange("remodel", "...") 1183 1184 remodCtx.Init(chg) 1185 1186 var encNewModel string 1187 c.Assert(chg.Get("new-model", &encNewModel), IsNil) 1188 1189 resealKeysCalls := 0 1190 restore := boot.MockResealKeyToModeenv(func(_ string, m *boot.Modeenv, expectReseal bool) error { 1191 resealKeysCalls++ 1192 c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"0000", "1234"}) 1193 c.Check(m.GoodRecoverySystems, DeepEquals, []string{"0000", "1234"}) 1194 switch resealKeysCalls { 1195 case 1: 1196 // intermediate step, new and old models 1197 c.Check(m.ModelForSealing().Model(), Equals, "my-model") 1198 c.Check(m.TryModelForSealing().Model(), Equals, "my-model") 1199 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, "model: my-model\n") 1200 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), Not(testutil.FileContains), "revision:") 1201 case 2: 1202 // new model 1203 c.Check(m.ModelForSealing().Model(), Equals, "my-model") 1204 c.Check(m.TryModelForSealing().Model(), Equals, "") 1205 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, "model: my-model\n") 1206 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, "revision: 2\n") 1207 default: 1208 c.Fatalf("unexpected call #%v to reseal key to modeenv", resealKeysCalls) 1209 } 1210 return nil 1211 }) 1212 s.AddCleanup(restore) 1213 1214 // finish fails because we haven't set the seed system label yet 1215 err = remodCtx.Finish() 1216 c.Assert(err, ErrorMatches, "internal error: recovery system label is unset during remodel finish") 1217 c.Check(resealKeysCalls, Equals, 0) 1218 1219 // set the label internally 1220 devicestate.RemodelSetRecoverySystemLabel(remodCtx, "1234") 1221 err = remodCtx.Finish() 1222 c.Assert(err, IsNil) 1223 c.Check(resealKeysCalls, Equals, 2) 1224 1225 var seededSystemsFromState []map[string]interface{} 1226 err = s.state.Get("seeded-systems", &seededSystemsFromState) 1227 c.Assert(err, IsNil) 1228 c.Assert(seededSystemsFromState, HasLen, 2) 1229 c.Assert(seededSystemsFromState[1], DeepEquals, map[string]interface{}{ 1230 "system": "0000", 1231 "model": "my-model", 1232 "brand-id": "my-brand", 1233 "revision": float64(0), 1234 "timestamp": s.oldModel.Timestamp().Format(time.RFC3339Nano), 1235 "seed-time": s.oldSeededTs.Format(time.RFC3339Nano), 1236 }) 1237 // new system is prepended, since timestamps are involved clear ones that weren't mocked 1238 c.Assert(seededSystemsFromState[0]["seed-time"], FitsTypeOf, "") 1239 newSeedTs, err := time.Parse(time.RFC3339Nano, seededSystemsFromState[0]["seed-time"].(string)) 1240 c.Assert(err, IsNil) 1241 seededSystemsFromState[0]["seed-time"] = "" 1242 c.Assert(seededSystemsFromState[0], DeepEquals, map[string]interface{}{ 1243 "system": "1234", 1244 "model": "my-model", 1245 "brand-id": "my-brand", 1246 "revision": float64(2), 1247 "timestamp": newModel.Timestamp().Format(time.RFC3339Nano), 1248 "seed-time": "", 1249 }) 1250 c.Assert(newSeedTs.After(s.oldSeededTs), Equals, true) 1251 } 1252 1253 func (s *uc20RemodelLogicSuite) TestSimpleRemodelErr(c *C) { 1254 modelDefaults := make(map[string]interface{}, len(uc20ModelDefaults)) 1255 for k, v := range uc20ModelDefaults { 1256 modelDefaults[k] = v 1257 } 1258 // simple model update with bumped revision 1259 modelDefaults["revision"] = "2" 1260 newModel := s.brands.Model("my-brand", "my-model", modelDefaults) 1261 1262 s.state.Lock() 1263 defer s.state.Unlock() 1264 1265 m, err := boot.ReadModeenv("") 1266 c.Assert(err, IsNil) 1267 // the system has already been promoted 1268 m.CurrentRecoverySystems = append(m.CurrentRecoverySystems, "1234") 1269 m.GoodRecoverySystems = append(m.GoodRecoverySystems, "1234") 1270 c.Assert(m.Write(), IsNil) 1271 1272 remodCtx, err := devicestate.RemodelCtx(s.state, s.oldModel, newModel) 1273 c.Assert(err, IsNil) 1274 c.Check(remodCtx.ForRemodeling(), Equals, true) 1275 c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel) 1276 1277 chg := s.state.NewChange("remodel", "...") 1278 remodCtx.Init(chg) 1279 1280 var encNewModel string 1281 c.Assert(chg.Get("new-model", &encNewModel), IsNil) 1282 1283 resealKeysCalls := 0 1284 restore := boot.MockResealKeyToModeenv(func(_ string, m *boot.Modeenv, expectReseal bool) error { 1285 resealKeysCalls++ 1286 c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"0000", "1234"}) 1287 c.Check(m.GoodRecoverySystems, DeepEquals, []string{"0000", "1234"}) 1288 switch resealKeysCalls { 1289 case 1: 1290 // intermediate step, new and old models 1291 c.Check(m.ModelForSealing().Model(), Equals, "my-model") 1292 c.Check(m.TryModelForSealing().Model(), Equals, "my-model") 1293 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, "model: my-model\n") 1294 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), Not(testutil.FileContains), "revision:") 1295 return fmt.Errorf("mock reseal failure") 1296 default: 1297 c.Fatalf("unexpected call #%v to reseal key to modeenv", resealKeysCalls) 1298 } 1299 return nil 1300 }) 1301 s.AddCleanup(restore) 1302 1303 // set the label internally 1304 devicestate.RemodelSetRecoverySystemLabel(remodCtx, "1234") 1305 err = remodCtx.Finish() 1306 c.Assert(err, ErrorMatches, "cannot switch device: mock reseal failure") 1307 c.Check(resealKeysCalls, Equals, 1) 1308 1309 // the error occurred before seeded systems was updated 1310 var seededSystemsFromState []map[string]interface{} 1311 err = s.state.Get("seeded-systems", &seededSystemsFromState) 1312 c.Assert(err, IsNil) 1313 c.Assert(seededSystemsFromState, DeepEquals, []map[string]interface{}{{ 1314 "system": "0000", 1315 "model": "my-model", 1316 "brand-id": "my-brand", 1317 "revision": float64(0), 1318 "timestamp": s.oldModel.Timestamp().Format(time.RFC3339Nano), 1319 "seed-time": s.oldSeededTs.Format(time.RFC3339Nano), 1320 }}) 1321 }