github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  	"time"
    24  
    25  	. "gopkg.in/check.v1"
    26  
    27  	"github.com/snapcore/snapd/asserts"
    28  	"github.com/snapcore/snapd/asserts/assertstest"
    29  	"github.com/snapcore/snapd/dirs"
    30  	"github.com/snapcore/snapd/overlord"
    31  	"github.com/snapcore/snapd/overlord/assertstate"
    32  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    33  	"github.com/snapcore/snapd/overlord/auth"
    34  	"github.com/snapcore/snapd/overlord/devicestate"
    35  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    36  	"github.com/snapcore/snapd/overlord/hookstate"
    37  	"github.com/snapcore/snapd/overlord/snapstate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  	"github.com/snapcore/snapd/overlord/storecontext"
    40  	"github.com/snapcore/snapd/store/storetest"
    41  )
    42  
    43  type remodelLogicSuite struct {
    44  	state *state.State
    45  	mgr   *devicestate.DeviceManager
    46  
    47  	storeSigning *assertstest.StoreStack
    48  	brands       *assertstest.SigningAccounts
    49  
    50  	capturedDevBE storecontext.DeviceBackend
    51  	dummyStore    snapstate.StoreService
    52  }
    53  
    54  var _ = Suite(&remodelLogicSuite{})
    55  
    56  func (s *remodelLogicSuite) SetUpTest(c *C) {
    57  	dirs.SetRootDir(c.MkDir())
    58  
    59  	o := overlord.Mock()
    60  	s.state = o.State()
    61  
    62  	s.storeSigning = assertstest.NewStoreStack("canonical", nil)
    63  	s.brands = assertstest.NewSigningAccounts(s.storeSigning)
    64  	s.brands.Register("my-brand", brandPrivKey, nil)
    65  
    66  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    67  		Backstore: asserts.NewMemoryBackstore(),
    68  		Trusted:   s.storeSigning.Trusted,
    69  	})
    70  	c.Assert(err, IsNil)
    71  
    72  	func() {
    73  		s.state.Lock()
    74  		defer s.state.Unlock()
    75  		assertstate.ReplaceDB(s.state, db)
    76  
    77  		assertstatetest.AddMany(s.state, s.storeSigning.StoreAccountKey(""))
    78  		assertstatetest.AddMany(s.state, s.brands.AccountsAndKeys("my-brand")...)
    79  	}()
    80  
    81  	s.dummyStore = new(storetest.Store)
    82  
    83  	newStore := func(devBE storecontext.DeviceBackend) snapstate.StoreService {
    84  		s.capturedDevBE = devBE
    85  		return s.dummyStore
    86  	}
    87  
    88  	hookMgr, err := hookstate.Manager(s.state, o.TaskRunner())
    89  	c.Assert(err, IsNil)
    90  	s.mgr, err = devicestate.Manager(s.state, hookMgr, o.TaskRunner(), newStore)
    91  	c.Assert(err, IsNil)
    92  }
    93  
    94  func (s *remodelLogicSuite) TearDownTest(c *C) {
    95  	dirs.SetRootDir("")
    96  }
    97  
    98  var modelDefaults = map[string]interface{}{
    99  	"architecture":   "amd64",
   100  	"kernel":         "my-brand-kernel",
   101  	"gadget":         "my-brand-gadget",
   102  	"store":          "my-brand-store",
   103  	"required-snaps": []interface{}{"required1"},
   104  }
   105  
   106  func fakeRemodelingModel(extra map[string]interface{}) *asserts.Model {
   107  	primary := map[string]interface{}{
   108  		"type":         "model",
   109  		"authority-id": "my-brand",
   110  		"series":       "16",
   111  		"brand-id":     "my-brand",
   112  		"model":        "my-model",
   113  	}
   114  	return assertstest.FakeAssertion(primary, modelDefaults, extra).(*asserts.Model)
   115  }
   116  
   117  func (s *remodelLogicSuite) TestClassifyRemodel(c *C) {
   118  	oldModel := fakeRemodelingModel(nil)
   119  
   120  	cases := []struct {
   121  		newHeaders map[string]interface{}
   122  		kind       devicestate.RemodelKind
   123  	}{
   124  		{map[string]interface{}{}, devicestate.UpdateRemodel},
   125  		{map[string]interface{}{
   126  			"required-snaps": []interface{}{"required1", "required2"},
   127  			"revision":       "1",
   128  		}, devicestate.UpdateRemodel},
   129  		{map[string]interface{}{
   130  			"store":    "my-other-store",
   131  			"revision": "1",
   132  		}, devicestate.StoreSwitchRemodel},
   133  		{map[string]interface{}{
   134  			"model": "my-other-model",
   135  			"store": "my-other-store",
   136  		}, devicestate.ReregRemodel},
   137  		{map[string]interface{}{
   138  			"authority-id": "other-brand",
   139  			"brand-id":     "other-brand",
   140  			"model":        "other-model",
   141  		}, devicestate.ReregRemodel},
   142  		{map[string]interface{}{
   143  			"authority-id":   "other-brand",
   144  			"brand-id":       "other-brand",
   145  			"model":          "other-model",
   146  			"required-snaps": []interface{}{"other-required1"},
   147  		}, devicestate.ReregRemodel},
   148  		{map[string]interface{}{
   149  			"authority-id": "other-brand",
   150  			"brand-id":     "other-brand",
   151  			"model":        "other-model",
   152  			"store":        "my-other-store",
   153  		}, devicestate.ReregRemodel},
   154  	}
   155  
   156  	for _, t := range cases {
   157  		newModel := fakeRemodelingModel(t.newHeaders)
   158  		c.Check(devicestate.ClassifyRemodel(oldModel, newModel), Equals, t.kind)
   159  	}
   160  }
   161  
   162  func (s *remodelLogicSuite) TestUpdateRemodelContext(c *C) {
   163  	oldModel := fakeRemodelingModel(nil)
   164  	newModel := fakeRemodelingModel(map[string]interface{}{
   165  		"required-snaps": []interface{}{"required1", "required2"},
   166  		"revision":       "1",
   167  	})
   168  
   169  	s.state.Lock()
   170  	defer s.state.Unlock()
   171  
   172  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   173  	c.Assert(err, IsNil)
   174  
   175  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   176  	c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel)
   177  
   178  	chg := s.state.NewChange("remodel", "...")
   179  
   180  	remodCtx.Init(chg)
   181  
   182  	var encNewModel string
   183  	c.Assert(chg.Get("new-model", &encNewModel), IsNil)
   184  
   185  	c.Check(encNewModel, Equals, string(asserts.Encode(newModel)))
   186  
   187  	c.Check(remodCtx.Model(), DeepEquals, newModel)
   188  	// an update remodel does not need a new/dedicated store
   189  	c.Check(remodCtx.Store(), IsNil)
   190  }
   191  
   192  func (s *remodelLogicSuite) TestNewStoreRemodelContextInit(c *C) {
   193  	oldModel := fakeRemodelingModel(nil)
   194  	newModel := fakeRemodelingModel(map[string]interface{}{
   195  		"store":    "my-other-store",
   196  		"revision": "1",
   197  	})
   198  
   199  	s.state.Lock()
   200  	defer s.state.Unlock()
   201  
   202  	// we have a device state
   203  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   204  		Brand:           "my-brand",
   205  		Model:           "my-model",
   206  		Serial:          "serialserialserial",
   207  		SessionMacaroon: "prev-session",
   208  	})
   209  
   210  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   211  	c.Assert(err, IsNil)
   212  
   213  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   214  	c.Check(remodCtx.Kind(), Equals, devicestate.StoreSwitchRemodel)
   215  
   216  	chg := s.state.NewChange("remodel", "...")
   217  
   218  	remodCtx.Init(chg)
   219  
   220  	var encNewModel string
   221  	c.Assert(chg.Get("new-model", &encNewModel), IsNil)
   222  
   223  	c.Check(encNewModel, Equals, string(asserts.Encode(newModel)))
   224  
   225  	var device *auth.DeviceState
   226  	c.Assert(chg.Get("device", &device), IsNil)
   227  	// session macaroon was reset
   228  	c.Check(device, DeepEquals, &auth.DeviceState{
   229  		Brand:  "my-brand",
   230  		Model:  "my-model",
   231  		Serial: "serialserialserial",
   232  	})
   233  
   234  	c.Check(remodCtx.Model(), DeepEquals, newModel)
   235  }
   236  
   237  func (s *remodelLogicSuite) TestRemodelDeviceBackendNoChangeYet(c *C) {
   238  	oldModel := fakeRemodelingModel(nil)
   239  	newModel := fakeRemodelingModel(map[string]interface{}{
   240  		"store":    "my-other-store",
   241  		"revision": "1",
   242  	})
   243  
   244  	s.state.Lock()
   245  	defer s.state.Unlock()
   246  
   247  	// we have a device state
   248  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   249  		Brand:  "my-brand",
   250  		Model:  "my-model",
   251  		Serial: "serialserialserial",
   252  	})
   253  
   254  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   255  	c.Assert(err, IsNil)
   256  
   257  	devBE := s.capturedDevBE
   258  	c.Check(devBE, NotNil)
   259  
   260  	device, err := devBE.Device()
   261  	c.Assert(err, IsNil)
   262  	c.Check(device, DeepEquals, &auth.DeviceState{
   263  		Brand:  "my-brand",
   264  		Model:  "my-model",
   265  		Serial: "serialserialserial",
   266  	})
   267  
   268  	mod, err := devBE.Model()
   269  	c.Assert(err, IsNil)
   270  	c.Check(mod, DeepEquals, newModel)
   271  
   272  	// set device state for the context
   273  	device1 := &auth.DeviceState{
   274  		Brand:           "my-brand",
   275  		Model:           "my-model",
   276  		Serial:          "serialserialserial",
   277  		SessionMacaroon: "session",
   278  	}
   279  
   280  	err = devBE.SetDevice(device1)
   281  	c.Assert(err, IsNil)
   282  
   283  	device, err = devBE.Device()
   284  	c.Assert(err, IsNil)
   285  	c.Check(device, DeepEquals, device1)
   286  
   287  	// have a change
   288  	chg := s.state.NewChange("remodel", "...")
   289  
   290  	remodCtx.Init(chg)
   291  
   292  	// check device state is preserved across association with a Change
   293  	device, err = devBE.Device()
   294  	c.Assert(err, IsNil)
   295  	c.Check(device, DeepEquals, device1)
   296  }
   297  
   298  func (s *remodelLogicSuite) TestRemodelDeviceBackend(c *C) {
   299  	oldModel := fakeRemodelingModel(nil)
   300  	newModel := fakeRemodelingModel(map[string]interface{}{
   301  		"store":    "my-other-store",
   302  		"revision": "1",
   303  	})
   304  
   305  	s.state.Lock()
   306  	defer s.state.Unlock()
   307  
   308  	// we have a device state
   309  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   310  		Brand:  "my-brand",
   311  		Model:  "my-model",
   312  		Serial: "serialserialserial",
   313  	})
   314  
   315  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   316  	c.Assert(err, IsNil)
   317  
   318  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   319  
   320  	chg := s.state.NewChange("remodel", "...")
   321  
   322  	remodCtx.Init(chg)
   323  
   324  	device, err := devBE.Device()
   325  	c.Assert(err, IsNil)
   326  	c.Check(device, DeepEquals, &auth.DeviceState{
   327  		Brand:  "my-brand",
   328  		Model:  "my-model",
   329  		Serial: "serialserialserial",
   330  	})
   331  
   332  	mod, err := devBE.Model()
   333  	c.Assert(err, IsNil)
   334  	c.Check(mod, DeepEquals, newModel)
   335  
   336  	// set a device state for the context
   337  	device1 := &auth.DeviceState{
   338  		Brand:           "my-brand",
   339  		Model:           "my-model",
   340  		Serial:          "serialserialserial",
   341  		SessionMacaroon: "session",
   342  	}
   343  
   344  	err = devBE.SetDevice(device1)
   345  	c.Assert(err, IsNil)
   346  
   347  	// it's stored on change now
   348  	var device2 *auth.DeviceState
   349  	c.Assert(chg.Get("device", &device2), IsNil)
   350  	c.Check(device2, DeepEquals, device1)
   351  
   352  	device, err = devBE.Device()
   353  	c.Assert(err, IsNil)
   354  	c.Check(device, DeepEquals, device1)
   355  }
   356  
   357  func (s *remodelLogicSuite) TestRemodelDeviceBackendIsolation(c *C) {
   358  	oldModel := fakeRemodelingModel(nil)
   359  	newModel := fakeRemodelingModel(map[string]interface{}{
   360  		"store":    "my-other-store",
   361  		"revision": "1",
   362  	})
   363  
   364  	s.state.Lock()
   365  	defer s.state.Unlock()
   366  
   367  	// we have a device state
   368  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   369  		Brand:  "my-brand",
   370  		Model:  "my-model",
   371  		Serial: "serialserialserial",
   372  	})
   373  
   374  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   375  	c.Assert(err, IsNil)
   376  
   377  	chg := s.state.NewChange("remodel", "...")
   378  
   379  	remodCtx.Init(chg)
   380  
   381  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   382  
   383  	err = devBE.SetDevice(&auth.DeviceState{
   384  		Brand:           "my-brand",
   385  		Model:           "my-model",
   386  		Serial:          "serialserialserial",
   387  		SessionMacaroon: "remodel-session",
   388  	})
   389  	c.Assert(err, IsNil)
   390  
   391  	// the global device state is as before
   392  	expectedGlobalDevice := &auth.DeviceState{
   393  		Brand:  "my-brand",
   394  		Model:  "my-model",
   395  		Serial: "serialserialserial",
   396  	}
   397  
   398  	device, err := s.mgr.StoreContextBackend().Device()
   399  	c.Assert(err, IsNil)
   400  	c.Check(device, DeepEquals, expectedGlobalDevice)
   401  }
   402  func (s *remodelLogicSuite) TestNewStoreRemodelContextStore(c *C) {
   403  	oldModel := fakeRemodelingModel(nil)
   404  	newModel := fakeRemodelingModel(map[string]interface{}{
   405  		"store":    "my-other-store",
   406  		"revision": "1",
   407  	})
   408  
   409  	s.state.Lock()
   410  	defer s.state.Unlock()
   411  
   412  	// we have a device state
   413  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   414  		Brand:           "my-brand",
   415  		Model:           "my-model",
   416  		Serial:          "serialserialserial",
   417  		SessionMacaroon: "prev-session",
   418  	})
   419  
   420  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   421  	c.Assert(err, IsNil)
   422  
   423  	c.Check(s.capturedDevBE, NotNil)
   424  
   425  	// new store remodel context device state built ignoring the
   426  	// previous session
   427  	device1, err := s.capturedDevBE.Device()
   428  	c.Assert(err, IsNil)
   429  	c.Check(device1, DeepEquals, &auth.DeviceState{
   430  		Brand:  "my-brand",
   431  		Model:  "my-model",
   432  		Serial: "serialserialserial",
   433  	})
   434  
   435  	sto := remodCtx.Store()
   436  	c.Check(sto, Equals, s.dummyStore)
   437  
   438  	// store is kept and not rebuilt
   439  	s.dummyStore = nil
   440  
   441  	sto1 := remodCtx.Store()
   442  	c.Check(sto1, Equals, sto)
   443  }
   444  
   445  func (s *remodelLogicSuite) TestNewStoreRemodelContextFinish(c *C) {
   446  	oldModel := fakeRemodelingModel(nil)
   447  	newModel := fakeRemodelingModel(map[string]interface{}{
   448  		"store":    "my-other-store",
   449  		"revision": "1",
   450  	})
   451  
   452  	s.state.Lock()
   453  	defer s.state.Unlock()
   454  
   455  	// we have a device state
   456  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   457  		Brand:           "my-brand",
   458  		Model:           "my-model",
   459  		Serial:          "serialserialserial",
   460  		SessionMacaroon: "orig-session",
   461  	})
   462  
   463  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   464  	c.Assert(err, IsNil)
   465  
   466  	chg := s.state.NewChange("remodel", "...")
   467  
   468  	remodCtx.Init(chg)
   469  
   470  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   471  
   472  	err = devBE.SetDevice(&auth.DeviceState{
   473  		Brand:           "my-brand",
   474  		Model:           "my-model",
   475  		Serial:          "serialserialserial",
   476  		SessionMacaroon: "new-session",
   477  	})
   478  	c.Assert(err, IsNil)
   479  
   480  	err = remodCtx.Finish()
   481  	c.Assert(err, IsNil)
   482  
   483  	// the global device now matches the state reached in the remodel
   484  	expectedGlobalDevice := &auth.DeviceState{
   485  		Brand:           "my-brand",
   486  		Model:           "my-model",
   487  		Serial:          "serialserialserial",
   488  		SessionMacaroon: "new-session",
   489  	}
   490  
   491  	device, err := s.mgr.StoreContextBackend().Device()
   492  	c.Assert(err, IsNil)
   493  	c.Check(device, DeepEquals, expectedGlobalDevice)
   494  }
   495  
   496  func (s *remodelLogicSuite) TestNewStoreRemodelContextFinishVsGlobalUpdateDeviceAuth(c *C) {
   497  	oldModel := fakeRemodelingModel(nil)
   498  	newModel := fakeRemodelingModel(map[string]interface{}{
   499  		"store":    "my-other-store",
   500  		"revision": "1",
   501  	})
   502  
   503  	s.state.Lock()
   504  	defer s.state.Unlock()
   505  
   506  	// we have a device state
   507  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   508  		Brand:           "my-brand",
   509  		Model:           "my-model",
   510  		Serial:          "serialserialserial",
   511  		SessionMacaroon: "old-session",
   512  	})
   513  
   514  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   515  	c.Assert(err, IsNil)
   516  
   517  	chg := s.state.NewChange("remodel", "...")
   518  
   519  	remodCtx.Init(chg)
   520  
   521  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   522  
   523  	err = devBE.SetDevice(&auth.DeviceState{
   524  		Brand:           "my-brand",
   525  		Model:           "my-model",
   526  		Serial:          "serialserialserial",
   527  		SessionMacaroon: "remodel-session",
   528  	})
   529  	c.Assert(err, IsNil)
   530  
   531  	// global store device and auth context
   532  	scb := s.mgr.StoreContextBackend()
   533  	dac := storecontext.New(s.state, scb)
   534  	// this is the unlikely start of the global store trying to
   535  	// refresh the session
   536  	s.state.Unlock()
   537  	globalDevice, err := dac.Device()
   538  	s.state.Lock()
   539  	c.Assert(err, IsNil)
   540  	c.Check(globalDevice.SessionMacaroon, Equals, "old-session")
   541  
   542  	err = remodCtx.Finish()
   543  	c.Assert(err, IsNil)
   544  
   545  	s.state.Unlock()
   546  	device1, err := dac.UpdateDeviceAuth(globalDevice, "fresh-session")
   547  	s.state.Lock()
   548  	c.Assert(err, IsNil)
   549  
   550  	// the global device now matches the state reached in the remodel
   551  	expectedGlobalDevice := &auth.DeviceState{
   552  		Brand:           "my-brand",
   553  		Model:           "my-model",
   554  		Serial:          "serialserialserial",
   555  		SessionMacaroon: "remodel-session",
   556  	}
   557  
   558  	s.state.Unlock()
   559  	device, err := dac.Device()
   560  	s.state.Lock()
   561  	c.Assert(err, IsNil)
   562  	c.Check(device, DeepEquals, expectedGlobalDevice)
   563  
   564  	// also this was already the case
   565  	c.Check(device1, DeepEquals, expectedGlobalDevice)
   566  }
   567  
   568  func (s *remodelLogicSuite) TestRemodelDeviceBackendKeptSerial(c *C) {
   569  	oldModel := fakeRemodelingModel(nil)
   570  	newModel := fakeRemodelingModel(map[string]interface{}{
   571  		"store":    "my-other-store",
   572  		"revision": "1",
   573  	})
   574  
   575  	s.state.Lock()
   576  	defer s.state.Unlock()
   577  
   578  	// we have a device state and serial
   579  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   580  		Brand:  "my-brand",
   581  		Model:  "my-model",
   582  		Serial: "serialserialserial1",
   583  	})
   584  
   585  	makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "my-model", "serialserialserial1")
   586  
   587  	serial, err := s.mgr.Serial()
   588  	c.Assert(err, IsNil)
   589  	c.Check(serial.Serial(), Equals, "serialserialserial1")
   590  
   591  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   592  	c.Assert(err, IsNil)
   593  
   594  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   595  
   596  	serial0, err := devBE.Serial()
   597  	c.Assert(err, IsNil)
   598  	c.Check(serial0.Serial(), Equals, "serialserialserial1")
   599  
   600  	chg := s.state.NewChange("remodel", "...")
   601  
   602  	remodCtx.Init(chg)
   603  
   604  	serial0, err = devBE.Serial()
   605  	c.Assert(err, IsNil)
   606  	c.Check(serial0.Serial(), Equals, "serialserialserial1")
   607  }
   608  
   609  func (s *remodelLogicSuite) TestRemodelContextForTaskAndCaching(c *C) {
   610  	oldModel := s.brands.Model("my-brand", "my-model", modelDefaults)
   611  	newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{
   612  		"store":    "my-other-store",
   613  		"revision": "1",
   614  	})
   615  
   616  	s.state.Lock()
   617  	defer s.state.Unlock()
   618  
   619  	// we have a device state
   620  	assertstatetest.AddMany(s.state, oldModel)
   621  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   622  		Brand:  "my-brand",
   623  		Model:  "my-model",
   624  		Serial: "serialserialserial",
   625  	})
   626  
   627  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   628  	c.Assert(err, IsNil)
   629  
   630  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   631  
   632  	chg := s.state.NewChange("remodel", "...")
   633  
   634  	remodCtx.Init(chg)
   635  
   636  	t := s.state.NewTask("remodel-task-1", "...")
   637  	chg.AddTask(t)
   638  
   639  	// caching, internally this use remodelCtxFromTask
   640  	remodCtx1, err := devicestate.DeviceCtx(s.state, t, nil)
   641  	c.Assert(err, IsNil)
   642  	c.Check(remodCtx1, Equals, remodCtx)
   643  
   644  	// if the context goes away (e.g. because of restart) we
   645  	// compute a new one
   646  	devicestate.CleanupRemodelCtx(chg)
   647  
   648  	remodCtx2, err := devicestate.DeviceCtx(s.state, t, nil)
   649  	c.Assert(err, IsNil)
   650  	c.Check(remodCtx2 != remodCtx, Equals, true)
   651  	c.Check(remodCtx2.Model(), DeepEquals, newModel)
   652  }
   653  
   654  func (s *remodelLogicSuite) TestRemodelContextForTaskNo(c *C) {
   655  	s.state.Lock()
   656  	defer s.state.Unlock()
   657  
   658  	// internally these use remodelCtxFromTask
   659  
   660  	// task is nil
   661  	remodCtx1, err := devicestate.DeviceCtx(s.state, nil, nil)
   662  	c.Check(err, Equals, state.ErrNoState)
   663  	c.Check(remodCtx1, IsNil)
   664  
   665  	// no change
   666  	t := s.state.NewTask("random-task", "...")
   667  	_, err = devicestate.DeviceCtx(s.state, t, nil)
   668  	c.Check(err, Equals, state.ErrNoState)
   669  
   670  	// not a remodel change
   671  	chg := s.state.NewChange("not-remodel", "...")
   672  	chg.AddTask(t)
   673  	_, err = devicestate.DeviceCtx(s.state, t, nil)
   674  	c.Check(err, Equals, state.ErrNoState)
   675  }
   676  
   677  func (s *remodelLogicSuite) setupForRereg(c *C) (oldModel, newModel *asserts.Model) {
   678  	oldModel = s.brands.Model("my-brand", "my-model", modelDefaults)
   679  	newModel = s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{
   680  		"authority-id": "other-brand",
   681  		"brand-id":     "other-brand",
   682  		"model":        "other-model",
   683  		"store":        "other-store",
   684  	})
   685  
   686  	s.state.Lock()
   687  	defer s.state.Unlock()
   688  
   689  	encDevKey, err := asserts.EncodePublicKey(devKey.PublicKey())
   690  	c.Assert(err, IsNil)
   691  	serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
   692  		"authority-id":        "my-brand",
   693  		"brand-id":            "my-brand",
   694  		"model":               "my-model",
   695  		"serial":              "orig-serial",
   696  		"device-key":          string(encDevKey),
   697  		"device-key-sha3-384": devKey.PublicKey().ID(),
   698  		"timestamp":           time.Now().Format(time.RFC3339),
   699  	}, nil, "")
   700  	c.Assert(err, IsNil)
   701  
   702  	assertstatetest.AddMany(s.state, oldModel, serial)
   703  
   704  	return oldModel, newModel
   705  }
   706  
   707  func (s *remodelLogicSuite) TestReregRemodelContextInit(c *C) {
   708  	oldModel, newModel := s.setupForRereg(c)
   709  
   710  	s.state.Lock()
   711  	defer s.state.Unlock()
   712  
   713  	// we have a device state
   714  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   715  		Brand:           "my-brand",
   716  		Model:           "my-model",
   717  		Serial:          "orig-serial",
   718  		KeyID:           "device-key-id",
   719  		SessionMacaroon: "prev-session",
   720  	})
   721  
   722  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   723  	c.Assert(err, IsNil)
   724  
   725  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   726  	c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel)
   727  
   728  	chg := s.state.NewChange("remodel", "...")
   729  
   730  	remodCtx.Init(chg)
   731  
   732  	var encNewModel string
   733  	c.Assert(chg.Get("new-model", &encNewModel), IsNil)
   734  
   735  	c.Check(encNewModel, Equals, string(asserts.Encode(newModel)))
   736  
   737  	var device *auth.DeviceState
   738  	c.Assert(chg.Get("device", &device), IsNil)
   739  	// fresh device state before registration but with device-key
   740  	c.Check(device, DeepEquals, &auth.DeviceState{
   741  		Brand: "other-brand",
   742  		Model: "other-model",
   743  		KeyID: "device-key-id",
   744  	})
   745  
   746  	c.Check(remodCtx.Model(), DeepEquals, newModel)
   747  
   748  	// caching
   749  	t := s.state.NewTask("remodel-task-1", "...")
   750  	chg.AddTask(t)
   751  
   752  	remodCtx1, err := devicestate.DeviceCtx(s.state, t, nil)
   753  	c.Assert(err, IsNil)
   754  	c.Check(remodCtx1, Equals, remodCtx)
   755  }
   756  
   757  func (s *remodelLogicSuite) TestReregRemodelContextAsRegistrationContext(c *C) {
   758  	oldModel, newModel := s.setupForRereg(c)
   759  
   760  	s.state.Lock()
   761  	defer s.state.Unlock()
   762  
   763  	// we have a device state
   764  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   765  		Brand:           "my-brand",
   766  		Model:           "my-model",
   767  		Serial:          "orig-serial",
   768  		KeyID:           "device-key-id",
   769  		SessionMacaroon: "prev-session",
   770  	})
   771  
   772  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   773  	c.Assert(err, IsNil)
   774  
   775  	c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel)
   776  
   777  	chg := s.state.NewChange("remodel", "...")
   778  
   779  	remodCtx.Init(chg)
   780  
   781  	regCtx := remodCtx.(devicestate.RegistrationContext)
   782  
   783  	c.Check(regCtx.ForRemodeling(), Equals, true)
   784  	device1, err := regCtx.Device()
   785  	c.Assert(err, IsNil)
   786  	// fresh device state before registration but with device-key
   787  	c.Check(device1, DeepEquals, &auth.DeviceState{
   788  		Brand: "other-brand",
   789  		Model: "other-model",
   790  		KeyID: "device-key-id",
   791  	})
   792  	c.Check(regCtx.GadgetForSerialRequestConfig(), Equals, "my-brand-gadget")
   793  	c.Check(regCtx.SerialRequestExtraHeaders(), DeepEquals, map[string]interface{}{
   794  		"original-brand-id": "my-brand",
   795  		"original-model":    "my-model",
   796  		"original-serial":   "orig-serial",
   797  	})
   798  
   799  	serial, err := s.mgr.Serial()
   800  	c.Assert(err, IsNil)
   801  	c.Check(regCtx.SerialRequestAncillaryAssertions(), DeepEquals, []asserts.Assertion{newModel, serial})
   802  }
   803  
   804  func (s *remodelLogicSuite) TestReregRemodelContextNewSerial(c *C) {
   805  	// re-registration case
   806  	oldModel := s.brands.Model("my-brand", "my-model", modelDefaults)
   807  	newModel := fakeRemodelingModel(map[string]interface{}{
   808  		"model": "other-model",
   809  	})
   810  
   811  	s.state.Lock()
   812  	defer s.state.Unlock()
   813  
   814  	assertstatetest.AddMany(s.state, oldModel)
   815  
   816  	// we have a device state and serial
   817  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   818  		Brand:  "my-brand",
   819  		Model:  "my-model",
   820  		Serial: "serialserialserial1",
   821  	})
   822  
   823  	makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "my-model", "serialserialserial1")
   824  
   825  	serial, err := s.mgr.Serial()
   826  	c.Assert(err, IsNil)
   827  	c.Check(serial.Serial(), Equals, "serialserialserial1")
   828  
   829  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   830  	c.Assert(err, IsNil)
   831  
   832  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   833  
   834  	// no new serial yet
   835  	_, err = devBE.Serial()
   836  	c.Assert(err, Equals, state.ErrNoState)
   837  
   838  	chg := s.state.NewChange("remodel", "...")
   839  
   840  	remodCtx.Init(chg)
   841  
   842  	// sanity check
   843  	device1, err := devBE.Device()
   844  	c.Assert(err, IsNil)
   845  	c.Check(device1, DeepEquals, &auth.DeviceState{
   846  		Brand: "my-brand",
   847  		Model: "other-model",
   848  	})
   849  
   850  	// still no new serial
   851  	_, err = devBE.Serial()
   852  	c.Assert(err, Equals, state.ErrNoState)
   853  
   854  	newSerial := makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "other-model", "serialserialserial2")
   855  
   856  	// same
   857  	_, err = devBE.Serial()
   858  	c.Check(err, Equals, state.ErrNoState)
   859  
   860  	// finish registration
   861  	regCtx := remodCtx.(devicestate.RegistrationContext)
   862  	err = regCtx.FinishRegistration(newSerial)
   863  	c.Assert(err, IsNil)
   864  
   865  	serial, err = devBE.Serial()
   866  	c.Check(err, IsNil)
   867  	c.Check(serial.Model(), Equals, "other-model")
   868  	c.Check(serial.Serial(), Equals, "serialserialserial2")
   869  
   870  	// not exposed yet
   871  	serial, err = s.mgr.Serial()
   872  	c.Assert(err, IsNil)
   873  	c.Check(serial.Model(), Equals, "my-model")
   874  	c.Check(serial.Serial(), Equals, "serialserialserial1")
   875  
   876  	// finish
   877  	err = remodCtx.Finish()
   878  	c.Assert(err, IsNil)
   879  
   880  	serial, err = s.mgr.Serial()
   881  	c.Assert(err, IsNil)
   882  	c.Check(serial.Model(), Equals, "other-model")
   883  	c.Check(serial.Serial(), Equals, "serialserialserial2")
   884  }