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  }