github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	groundCtx := remodCtx.GroundContext()
   178  	c.Check(groundCtx.ForRemodeling(), Equals, false)
   179  	c.Check(groundCtx.Model().Revision(), Equals, 0)
   180  	c.Check(groundCtx.Store, PanicMatches, `retrieved ground context is not intended to drive store operations`)
   181  
   182  	chg := s.state.NewChange("remodel", "...")
   183  
   184  	remodCtx.Init(chg)
   185  
   186  	var encNewModel string
   187  	c.Assert(chg.Get("new-model", &encNewModel), IsNil)
   188  
   189  	c.Check(encNewModel, Equals, string(asserts.Encode(newModel)))
   190  
   191  	c.Check(remodCtx.Model(), DeepEquals, newModel)
   192  	// an update remodel does not need a new/dedicated store
   193  	c.Check(remodCtx.Store(), IsNil)
   194  }
   195  
   196  func (s *remodelLogicSuite) TestNewStoreRemodelContextInit(c *C) {
   197  	oldModel := fakeRemodelingModel(nil)
   198  	newModel := fakeRemodelingModel(map[string]interface{}{
   199  		"store":    "my-other-store",
   200  		"revision": "1",
   201  	})
   202  
   203  	s.state.Lock()
   204  	defer s.state.Unlock()
   205  
   206  	// we have a device state
   207  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   208  		Brand:           "my-brand",
   209  		Model:           "my-model",
   210  		Serial:          "serialserialserial",
   211  		SessionMacaroon: "prev-session",
   212  	})
   213  
   214  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   215  	c.Assert(err, IsNil)
   216  
   217  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   218  	c.Check(remodCtx.Kind(), Equals, devicestate.StoreSwitchRemodel)
   219  	groundCtx := remodCtx.GroundContext()
   220  	c.Check(groundCtx.ForRemodeling(), Equals, false)
   221  	c.Check(groundCtx.Model().Revision(), Equals, 0)
   222  
   223  	chg := s.state.NewChange("remodel", "...")
   224  
   225  	remodCtx.Init(chg)
   226  
   227  	var encNewModel string
   228  	c.Assert(chg.Get("new-model", &encNewModel), IsNil)
   229  
   230  	c.Check(encNewModel, Equals, string(asserts.Encode(newModel)))
   231  
   232  	var device *auth.DeviceState
   233  	c.Assert(chg.Get("device", &device), IsNil)
   234  	// session macaroon was reset
   235  	c.Check(device, DeepEquals, &auth.DeviceState{
   236  		Brand:  "my-brand",
   237  		Model:  "my-model",
   238  		Serial: "serialserialserial",
   239  	})
   240  
   241  	c.Check(remodCtx.Model(), DeepEquals, newModel)
   242  }
   243  
   244  func (s *remodelLogicSuite) TestRemodelDeviceBackendNoChangeYet(c *C) {
   245  	oldModel := fakeRemodelingModel(nil)
   246  	newModel := fakeRemodelingModel(map[string]interface{}{
   247  		"store":    "my-other-store",
   248  		"revision": "1",
   249  	})
   250  
   251  	s.state.Lock()
   252  	defer s.state.Unlock()
   253  
   254  	// we have a device state
   255  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   256  		Brand:  "my-brand",
   257  		Model:  "my-model",
   258  		Serial: "serialserialserial",
   259  	})
   260  
   261  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   262  	c.Assert(err, IsNil)
   263  
   264  	devBE := s.capturedDevBE
   265  	c.Check(devBE, NotNil)
   266  
   267  	device, err := devBE.Device()
   268  	c.Assert(err, IsNil)
   269  	c.Check(device, DeepEquals, &auth.DeviceState{
   270  		Brand:  "my-brand",
   271  		Model:  "my-model",
   272  		Serial: "serialserialserial",
   273  	})
   274  
   275  	mod, err := devBE.Model()
   276  	c.Assert(err, IsNil)
   277  	c.Check(mod, DeepEquals, newModel)
   278  
   279  	// set device state for the context
   280  	device1 := &auth.DeviceState{
   281  		Brand:           "my-brand",
   282  		Model:           "my-model",
   283  		Serial:          "serialserialserial",
   284  		SessionMacaroon: "session",
   285  	}
   286  
   287  	err = devBE.SetDevice(device1)
   288  	c.Assert(err, IsNil)
   289  
   290  	device, err = devBE.Device()
   291  	c.Assert(err, IsNil)
   292  	c.Check(device, DeepEquals, device1)
   293  
   294  	// have a change
   295  	chg := s.state.NewChange("remodel", "...")
   296  
   297  	remodCtx.Init(chg)
   298  
   299  	// check device state is preserved across association with a Change
   300  	device, err = devBE.Device()
   301  	c.Assert(err, IsNil)
   302  	c.Check(device, DeepEquals, device1)
   303  }
   304  
   305  func (s *remodelLogicSuite) TestRemodelDeviceBackend(c *C) {
   306  	oldModel := fakeRemodelingModel(nil)
   307  	newModel := fakeRemodelingModel(map[string]interface{}{
   308  		"store":    "my-other-store",
   309  		"revision": "1",
   310  	})
   311  
   312  	s.state.Lock()
   313  	defer s.state.Unlock()
   314  
   315  	// we have a device state
   316  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   317  		Brand:  "my-brand",
   318  		Model:  "my-model",
   319  		Serial: "serialserialserial",
   320  	})
   321  
   322  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   323  	c.Assert(err, IsNil)
   324  
   325  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   326  
   327  	chg := s.state.NewChange("remodel", "...")
   328  
   329  	remodCtx.Init(chg)
   330  
   331  	device, err := devBE.Device()
   332  	c.Assert(err, IsNil)
   333  	c.Check(device, DeepEquals, &auth.DeviceState{
   334  		Brand:  "my-brand",
   335  		Model:  "my-model",
   336  		Serial: "serialserialserial",
   337  	})
   338  
   339  	mod, err := devBE.Model()
   340  	c.Assert(err, IsNil)
   341  	c.Check(mod, DeepEquals, newModel)
   342  
   343  	// set a device state for the context
   344  	device1 := &auth.DeviceState{
   345  		Brand:           "my-brand",
   346  		Model:           "my-model",
   347  		Serial:          "serialserialserial",
   348  		SessionMacaroon: "session",
   349  	}
   350  
   351  	err = devBE.SetDevice(device1)
   352  	c.Assert(err, IsNil)
   353  
   354  	// it's stored on change now
   355  	var device2 *auth.DeviceState
   356  	c.Assert(chg.Get("device", &device2), IsNil)
   357  	c.Check(device2, DeepEquals, device1)
   358  
   359  	device, err = devBE.Device()
   360  	c.Assert(err, IsNil)
   361  	c.Check(device, DeepEquals, device1)
   362  }
   363  
   364  func (s *remodelLogicSuite) TestRemodelDeviceBackendIsolation(c *C) {
   365  	oldModel := fakeRemodelingModel(nil)
   366  	newModel := fakeRemodelingModel(map[string]interface{}{
   367  		"store":    "my-other-store",
   368  		"revision": "1",
   369  	})
   370  
   371  	s.state.Lock()
   372  	defer s.state.Unlock()
   373  
   374  	// we have a device state
   375  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   376  		Brand:  "my-brand",
   377  		Model:  "my-model",
   378  		Serial: "serialserialserial",
   379  	})
   380  
   381  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   382  	c.Assert(err, IsNil)
   383  
   384  	chg := s.state.NewChange("remodel", "...")
   385  
   386  	remodCtx.Init(chg)
   387  
   388  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   389  
   390  	err = devBE.SetDevice(&auth.DeviceState{
   391  		Brand:           "my-brand",
   392  		Model:           "my-model",
   393  		Serial:          "serialserialserial",
   394  		SessionMacaroon: "remodel-session",
   395  	})
   396  	c.Assert(err, IsNil)
   397  
   398  	// the global device state is as before
   399  	expectedGlobalDevice := &auth.DeviceState{
   400  		Brand:  "my-brand",
   401  		Model:  "my-model",
   402  		Serial: "serialserialserial",
   403  	}
   404  
   405  	device, err := s.mgr.StoreContextBackend().Device()
   406  	c.Assert(err, IsNil)
   407  	c.Check(device, DeepEquals, expectedGlobalDevice)
   408  }
   409  func (s *remodelLogicSuite) TestNewStoreRemodelContextStore(c *C) {
   410  	oldModel := fakeRemodelingModel(nil)
   411  	newModel := fakeRemodelingModel(map[string]interface{}{
   412  		"store":    "my-other-store",
   413  		"revision": "1",
   414  	})
   415  
   416  	s.state.Lock()
   417  	defer s.state.Unlock()
   418  
   419  	// we have a device state
   420  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   421  		Brand:           "my-brand",
   422  		Model:           "my-model",
   423  		Serial:          "serialserialserial",
   424  		SessionMacaroon: "prev-session",
   425  	})
   426  
   427  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   428  	c.Assert(err, IsNil)
   429  
   430  	c.Check(s.capturedDevBE, NotNil)
   431  
   432  	// new store remodel context device state built ignoring the
   433  	// previous session
   434  	device1, err := s.capturedDevBE.Device()
   435  	c.Assert(err, IsNil)
   436  	c.Check(device1, DeepEquals, &auth.DeviceState{
   437  		Brand:  "my-brand",
   438  		Model:  "my-model",
   439  		Serial: "serialserialserial",
   440  	})
   441  
   442  	sto := remodCtx.Store()
   443  	c.Check(sto, Equals, s.dummyStore)
   444  
   445  	// store is kept and not rebuilt
   446  	s.dummyStore = nil
   447  
   448  	sto1 := remodCtx.Store()
   449  	c.Check(sto1, Equals, sto)
   450  }
   451  
   452  func (s *remodelLogicSuite) TestNewStoreRemodelContextFinish(c *C) {
   453  	oldModel := fakeRemodelingModel(nil)
   454  	newModel := fakeRemodelingModel(map[string]interface{}{
   455  		"store":    "my-other-store",
   456  		"revision": "1",
   457  	})
   458  
   459  	s.state.Lock()
   460  	defer s.state.Unlock()
   461  
   462  	// we have a device state
   463  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   464  		Brand:           "my-brand",
   465  		Model:           "my-model",
   466  		Serial:          "serialserialserial",
   467  		SessionMacaroon: "orig-session",
   468  	})
   469  
   470  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   471  	c.Assert(err, IsNil)
   472  
   473  	chg := s.state.NewChange("remodel", "...")
   474  
   475  	remodCtx.Init(chg)
   476  
   477  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   478  
   479  	err = devBE.SetDevice(&auth.DeviceState{
   480  		Brand:           "my-brand",
   481  		Model:           "my-model",
   482  		Serial:          "serialserialserial",
   483  		SessionMacaroon: "new-session",
   484  	})
   485  	c.Assert(err, IsNil)
   486  
   487  	err = remodCtx.Finish()
   488  	c.Assert(err, IsNil)
   489  
   490  	// the global device now matches the state reached in the remodel
   491  	expectedGlobalDevice := &auth.DeviceState{
   492  		Brand:           "my-brand",
   493  		Model:           "my-model",
   494  		Serial:          "serialserialserial",
   495  		SessionMacaroon: "new-session",
   496  	}
   497  
   498  	device, err := s.mgr.StoreContextBackend().Device()
   499  	c.Assert(err, IsNil)
   500  	c.Check(device, DeepEquals, expectedGlobalDevice)
   501  }
   502  
   503  func (s *remodelLogicSuite) TestNewStoreRemodelContextFinishVsGlobalUpdateDeviceAuth(c *C) {
   504  	oldModel := fakeRemodelingModel(nil)
   505  	newModel := fakeRemodelingModel(map[string]interface{}{
   506  		"store":    "my-other-store",
   507  		"revision": "1",
   508  	})
   509  
   510  	s.state.Lock()
   511  	defer s.state.Unlock()
   512  
   513  	// we have a device state
   514  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   515  		Brand:           "my-brand",
   516  		Model:           "my-model",
   517  		Serial:          "serialserialserial",
   518  		SessionMacaroon: "old-session",
   519  	})
   520  
   521  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   522  	c.Assert(err, IsNil)
   523  
   524  	chg := s.state.NewChange("remodel", "...")
   525  
   526  	remodCtx.Init(chg)
   527  
   528  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   529  
   530  	err = devBE.SetDevice(&auth.DeviceState{
   531  		Brand:           "my-brand",
   532  		Model:           "my-model",
   533  		Serial:          "serialserialserial",
   534  		SessionMacaroon: "remodel-session",
   535  	})
   536  	c.Assert(err, IsNil)
   537  
   538  	// global store device and auth context
   539  	scb := s.mgr.StoreContextBackend()
   540  	dac := storecontext.New(s.state, scb)
   541  	// this is the unlikely start of the global store trying to
   542  	// refresh the session
   543  	s.state.Unlock()
   544  	globalDevice, err := dac.Device()
   545  	s.state.Lock()
   546  	c.Assert(err, IsNil)
   547  	c.Check(globalDevice.SessionMacaroon, Equals, "old-session")
   548  
   549  	err = remodCtx.Finish()
   550  	c.Assert(err, IsNil)
   551  
   552  	s.state.Unlock()
   553  	device1, err := dac.UpdateDeviceAuth(globalDevice, "fresh-session")
   554  	s.state.Lock()
   555  	c.Assert(err, IsNil)
   556  
   557  	// the global device now matches the state reached in the remodel
   558  	expectedGlobalDevice := &auth.DeviceState{
   559  		Brand:           "my-brand",
   560  		Model:           "my-model",
   561  		Serial:          "serialserialserial",
   562  		SessionMacaroon: "remodel-session",
   563  	}
   564  
   565  	s.state.Unlock()
   566  	device, err := dac.Device()
   567  	s.state.Lock()
   568  	c.Assert(err, IsNil)
   569  	c.Check(device, DeepEquals, expectedGlobalDevice)
   570  
   571  	// also this was already the case
   572  	c.Check(device1, DeepEquals, expectedGlobalDevice)
   573  }
   574  
   575  func (s *remodelLogicSuite) TestRemodelDeviceBackendKeptSerial(c *C) {
   576  	oldModel := fakeRemodelingModel(nil)
   577  	newModel := fakeRemodelingModel(map[string]interface{}{
   578  		"store":    "my-other-store",
   579  		"revision": "1",
   580  	})
   581  
   582  	s.state.Lock()
   583  	defer s.state.Unlock()
   584  
   585  	// we have a device state and serial
   586  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   587  		Brand:  "my-brand",
   588  		Model:  "my-model",
   589  		Serial: "serialserialserial1",
   590  	})
   591  
   592  	makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "my-model", "serialserialserial1")
   593  
   594  	serial, err := s.mgr.Serial()
   595  	c.Assert(err, IsNil)
   596  	c.Check(serial.Serial(), Equals, "serialserialserial1")
   597  
   598  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   599  	c.Assert(err, IsNil)
   600  
   601  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   602  
   603  	serial0, err := devBE.Serial()
   604  	c.Assert(err, IsNil)
   605  	c.Check(serial0.Serial(), Equals, "serialserialserial1")
   606  
   607  	chg := s.state.NewChange("remodel", "...")
   608  
   609  	remodCtx.Init(chg)
   610  
   611  	serial0, err = devBE.Serial()
   612  	c.Assert(err, IsNil)
   613  	c.Check(serial0.Serial(), Equals, "serialserialserial1")
   614  }
   615  
   616  func (s *remodelLogicSuite) TestRemodelContextSystemModeDefaultRun(c *C) {
   617  	oldModel := s.brands.Model("my-brand", "my-model", modelDefaults)
   618  	newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{"revision": "2"})
   619  
   620  	s.state.Lock()
   621  	defer s.state.Unlock()
   622  
   623  	assertstatetest.AddMany(s.state, oldModel)
   624  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   625  		Brand:  "my-brand",
   626  		Model:  "my-model",
   627  		Serial: "serialserialserial",
   628  	})
   629  
   630  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   631  	c.Assert(err, IsNil)
   632  	c.Check(remodCtx.SystemMode(), Equals, "run")
   633  }
   634  
   635  func (s *remodelLogicSuite) TestRemodelContextSystemModeWorks(c *C) {
   636  	oldModel := s.brands.Model("my-brand", "my-model", modelDefaults)
   637  	newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{"revision": "2"})
   638  
   639  	s.state.Lock()
   640  	defer s.state.Unlock()
   641  
   642  	assertstatetest.AddMany(s.state, oldModel)
   643  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   644  		Brand:  "my-brand",
   645  		Model:  "my-model",
   646  		Serial: "serialserialserial",
   647  	})
   648  	devicestate.SetSystemMode(s.mgr, "install")
   649  
   650  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   651  	c.Assert(err, IsNil)
   652  	c.Check(remodCtx.SystemMode(), Equals, "install")
   653  }
   654  
   655  func (s *remodelLogicSuite) TestRemodelContextForTaskAndCaching(c *C) {
   656  	oldModel := s.brands.Model("my-brand", "my-model", modelDefaults)
   657  	newModel := s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{
   658  		"store":    "my-other-store",
   659  		"revision": "1",
   660  	})
   661  
   662  	s.state.Lock()
   663  	defer s.state.Unlock()
   664  
   665  	// we have a device state
   666  	assertstatetest.AddMany(s.state, oldModel)
   667  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   668  		Brand:  "my-brand",
   669  		Model:  "my-model",
   670  		Serial: "serialserialserial",
   671  	})
   672  
   673  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   674  	c.Assert(err, IsNil)
   675  
   676  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   677  
   678  	chg := s.state.NewChange("remodel", "...")
   679  
   680  	remodCtx.Init(chg)
   681  
   682  	t := s.state.NewTask("remodel-task-1", "...")
   683  	chg.AddTask(t)
   684  
   685  	// caching, internally this use remodelCtxFromTask
   686  	remodCtx1, err := devicestate.DeviceCtx(s.state, t, nil)
   687  	c.Assert(err, IsNil)
   688  	c.Check(remodCtx1, Equals, remodCtx)
   689  
   690  	// if the context goes away (e.g. because of restart) we
   691  	// compute a new one
   692  	devicestate.CleanupRemodelCtx(chg)
   693  
   694  	remodCtx2, err := devicestate.DeviceCtx(s.state, t, nil)
   695  	c.Assert(err, IsNil)
   696  	c.Check(remodCtx2 != remodCtx, Equals, true)
   697  	c.Check(remodCtx2.Model(), DeepEquals, newModel)
   698  }
   699  
   700  func (s *remodelLogicSuite) TestRemodelContextForTaskNo(c *C) {
   701  	s.state.Lock()
   702  	defer s.state.Unlock()
   703  
   704  	// internally these use remodelCtxFromTask
   705  
   706  	// task is nil
   707  	remodCtx1, err := devicestate.DeviceCtx(s.state, nil, nil)
   708  	c.Check(err, Equals, state.ErrNoState)
   709  	c.Check(remodCtx1, IsNil)
   710  
   711  	// no change
   712  	t := s.state.NewTask("random-task", "...")
   713  	_, err = devicestate.DeviceCtx(s.state, t, nil)
   714  	c.Check(err, Equals, state.ErrNoState)
   715  
   716  	// not a remodel change
   717  	chg := s.state.NewChange("not-remodel", "...")
   718  	chg.AddTask(t)
   719  	_, err = devicestate.DeviceCtx(s.state, t, nil)
   720  	c.Check(err, Equals, state.ErrNoState)
   721  }
   722  
   723  func (s *remodelLogicSuite) setupForRereg(c *C) (oldModel, newModel *asserts.Model) {
   724  	oldModel = s.brands.Model("my-brand", "my-model", modelDefaults)
   725  	newModel = s.brands.Model("my-brand", "my-model", modelDefaults, map[string]interface{}{
   726  		"authority-id": "other-brand",
   727  		"brand-id":     "other-brand",
   728  		"model":        "other-model",
   729  		"store":        "other-store",
   730  	})
   731  
   732  	s.state.Lock()
   733  	defer s.state.Unlock()
   734  
   735  	encDevKey, err := asserts.EncodePublicKey(devKey.PublicKey())
   736  	c.Assert(err, IsNil)
   737  	serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
   738  		"authority-id":        "my-brand",
   739  		"brand-id":            "my-brand",
   740  		"model":               "my-model",
   741  		"serial":              "orig-serial",
   742  		"device-key":          string(encDevKey),
   743  		"device-key-sha3-384": devKey.PublicKey().ID(),
   744  		"timestamp":           time.Now().Format(time.RFC3339),
   745  	}, nil, "")
   746  	c.Assert(err, IsNil)
   747  
   748  	assertstatetest.AddMany(s.state, oldModel, serial)
   749  
   750  	return oldModel, newModel
   751  }
   752  
   753  func (s *remodelLogicSuite) TestReregRemodelContextInit(c *C) {
   754  	oldModel, newModel := s.setupForRereg(c)
   755  
   756  	s.state.Lock()
   757  	defer s.state.Unlock()
   758  
   759  	// we have a device state
   760  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   761  		Brand:           "my-brand",
   762  		Model:           "my-model",
   763  		Serial:          "orig-serial",
   764  		KeyID:           "device-key-id",
   765  		SessionMacaroon: "prev-session",
   766  	})
   767  
   768  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   769  	c.Assert(err, IsNil)
   770  
   771  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   772  	c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel)
   773  	groundCtx := remodCtx.GroundContext()
   774  	c.Check(groundCtx.ForRemodeling(), Equals, false)
   775  	c.Check(groundCtx.Model().BrandID(), Equals, "my-brand")
   776  
   777  	chg := s.state.NewChange("remodel", "...")
   778  
   779  	remodCtx.Init(chg)
   780  
   781  	var encNewModel string
   782  	c.Assert(chg.Get("new-model", &encNewModel), IsNil)
   783  
   784  	c.Check(encNewModel, Equals, string(asserts.Encode(newModel)))
   785  
   786  	var device *auth.DeviceState
   787  	c.Assert(chg.Get("device", &device), IsNil)
   788  	// fresh device state before registration but with device-key
   789  	c.Check(device, DeepEquals, &auth.DeviceState{
   790  		Brand: "other-brand",
   791  		Model: "other-model",
   792  		KeyID: "device-key-id",
   793  	})
   794  
   795  	c.Check(remodCtx.Model(), DeepEquals, newModel)
   796  
   797  	// caching
   798  	t := s.state.NewTask("remodel-task-1", "...")
   799  	chg.AddTask(t)
   800  
   801  	remodCtx1, err := devicestate.DeviceCtx(s.state, t, nil)
   802  	c.Assert(err, IsNil)
   803  	c.Check(remodCtx1, Equals, remodCtx)
   804  }
   805  
   806  func (s *remodelLogicSuite) TestReregRemodelContextAsRegistrationContext(c *C) {
   807  	oldModel, newModel := s.setupForRereg(c)
   808  
   809  	s.state.Lock()
   810  	defer s.state.Unlock()
   811  
   812  	// we have a device state
   813  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   814  		Brand:           "my-brand",
   815  		Model:           "my-model",
   816  		Serial:          "orig-serial",
   817  		KeyID:           "device-key-id",
   818  		SessionMacaroon: "prev-session",
   819  	})
   820  
   821  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   822  	c.Assert(err, IsNil)
   823  
   824  	c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel)
   825  
   826  	chg := s.state.NewChange("remodel", "...")
   827  
   828  	remodCtx.Init(chg)
   829  
   830  	regCtx := remodCtx.(devicestate.RegistrationContext)
   831  
   832  	c.Check(regCtx.ForRemodeling(), Equals, true)
   833  	device1, err := regCtx.Device()
   834  	c.Assert(err, IsNil)
   835  	// fresh device state before registration but with device-key
   836  	c.Check(device1, DeepEquals, &auth.DeviceState{
   837  		Brand: "other-brand",
   838  		Model: "other-model",
   839  		KeyID: "device-key-id",
   840  	})
   841  	c.Check(regCtx.GadgetForSerialRequestConfig(), Equals, "my-brand-gadget")
   842  	c.Check(regCtx.SerialRequestExtraHeaders(), DeepEquals, map[string]interface{}{
   843  		"original-brand-id": "my-brand",
   844  		"original-model":    "my-model",
   845  		"original-serial":   "orig-serial",
   846  	})
   847  
   848  	serial, err := s.mgr.Serial()
   849  	c.Assert(err, IsNil)
   850  	c.Check(regCtx.SerialRequestAncillaryAssertions(), DeepEquals, []asserts.Assertion{newModel, serial})
   851  }
   852  
   853  func (s *remodelLogicSuite) TestReregRemodelContextNewSerial(c *C) {
   854  	// re-registration case
   855  	oldModel := s.brands.Model("my-brand", "my-model", modelDefaults)
   856  	newModel := fakeRemodelingModel(map[string]interface{}{
   857  		"model": "other-model",
   858  	})
   859  
   860  	s.state.Lock()
   861  	defer s.state.Unlock()
   862  
   863  	assertstatetest.AddMany(s.state, oldModel)
   864  
   865  	// we have a device state and serial
   866  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   867  		Brand:  "my-brand",
   868  		Model:  "my-model",
   869  		Serial: "serialserialserial1",
   870  	})
   871  
   872  	makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "my-model", "serialserialserial1")
   873  
   874  	serial, err := s.mgr.Serial()
   875  	c.Assert(err, IsNil)
   876  	c.Check(serial.Serial(), Equals, "serialserialserial1")
   877  
   878  	remodCtx, err := devicestate.RemodelCtx(s.state, oldModel, newModel)
   879  	c.Assert(err, IsNil)
   880  
   881  	devBE := devicestate.RemodelDeviceBackend(remodCtx)
   882  
   883  	// no new serial yet
   884  	_, err = devBE.Serial()
   885  	c.Assert(err, Equals, state.ErrNoState)
   886  
   887  	chg := s.state.NewChange("remodel", "...")
   888  
   889  	remodCtx.Init(chg)
   890  
   891  	// sanity check
   892  	device1, err := devBE.Device()
   893  	c.Assert(err, IsNil)
   894  	c.Check(device1, DeepEquals, &auth.DeviceState{
   895  		Brand: "my-brand",
   896  		Model: "other-model",
   897  	})
   898  
   899  	// still no new serial
   900  	_, err = devBE.Serial()
   901  	c.Assert(err, Equals, state.ErrNoState)
   902  
   903  	newSerial := makeSerialAssertionInState(c, s.brands, s.state, "my-brand", "other-model", "serialserialserial2")
   904  
   905  	// same
   906  	_, err = devBE.Serial()
   907  	c.Check(err, Equals, state.ErrNoState)
   908  
   909  	// finish registration
   910  	regCtx := remodCtx.(devicestate.RegistrationContext)
   911  	err = regCtx.FinishRegistration(newSerial)
   912  	c.Assert(err, IsNil)
   913  
   914  	serial, err = devBE.Serial()
   915  	c.Check(err, IsNil)
   916  	c.Check(serial.Model(), Equals, "other-model")
   917  	c.Check(serial.Serial(), Equals, "serialserialserial2")
   918  
   919  	// not exposed yet
   920  	serial, err = s.mgr.Serial()
   921  	c.Assert(err, IsNil)
   922  	c.Check(serial.Model(), Equals, "my-model")
   923  	c.Check(serial.Serial(), Equals, "serialserialserial1")
   924  
   925  	// finish
   926  	err = remodCtx.Finish()
   927  	c.Assert(err, IsNil)
   928  
   929  	serial, err = s.mgr.Serial()
   930  	c.Assert(err, IsNil)
   931  	c.Check(serial.Model(), Equals, "other-model")
   932  	c.Check(serial.Serial(), Equals, "serialserialserial2")
   933  }