github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/devicestate/devicestate_remodel_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package devicestate_test
    21  
    22  import (
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"path/filepath"
    28  	"reflect"
    29  
    30  	. "gopkg.in/check.v1"
    31  	"gopkg.in/tomb.v2"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/asserts/assertstest"
    35  	"github.com/snapcore/snapd/gadget"
    36  	"github.com/snapcore/snapd/gadget/quantity"
    37  	"github.com/snapcore/snapd/overlord/assertstate"
    38  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    39  	"github.com/snapcore/snapd/overlord/auth"
    40  	"github.com/snapcore/snapd/overlord/devicestate"
    41  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    42  	"github.com/snapcore/snapd/overlord/snapstate"
    43  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    44  	"github.com/snapcore/snapd/overlord/state"
    45  	"github.com/snapcore/snapd/overlord/storecontext"
    46  	"github.com/snapcore/snapd/release"
    47  	"github.com/snapcore/snapd/snap"
    48  	"github.com/snapcore/snapd/snap/snapfile"
    49  	"github.com/snapcore/snapd/snap/snaptest"
    50  	"github.com/snapcore/snapd/store/storetest"
    51  )
    52  
    53  type deviceMgrRemodelSuite struct {
    54  	deviceMgrBaseSuite
    55  }
    56  
    57  var _ = Suite(&deviceMgrRemodelSuite{})
    58  
    59  func (s *deviceMgrRemodelSuite) TestRemodelUnhappyNotSeeded(c *C) {
    60  	s.state.Lock()
    61  	defer s.state.Unlock()
    62  	s.state.Set("seeded", false)
    63  
    64  	newModel := s.brands.Model("canonical", "pc", map[string]interface{}{
    65  		"architecture": "amd64",
    66  		"kernel":       "pc-kernel",
    67  		"gadget":       "pc",
    68  	})
    69  	_, err := devicestate.Remodel(s.state, newModel)
    70  	c.Assert(err, ErrorMatches, "cannot remodel until fully seeded")
    71  }
    72  
    73  var mockCore20ModelHeaders = map[string]interface{}{
    74  	"brand":        "canonical",
    75  	"model":        "pc-model-20",
    76  	"architecture": "amd64",
    77  	"grade":        "dangerous",
    78  	"base":         "core20",
    79  	"snaps":        mockCore20ModelSnaps,
    80  }
    81  
    82  var mockCore20ModelSnaps = []interface{}{
    83  	map[string]interface{}{
    84  		"name":            "pc-kernel",
    85  		"id":              "pckernelidididididididididididid",
    86  		"type":            "kernel",
    87  		"default-channel": "20",
    88  	},
    89  	map[string]interface{}{
    90  		"name":            "pc",
    91  		"id":              "pcididididididididididididididid",
    92  		"type":            "gadget",
    93  		"default-channel": "20",
    94  	},
    95  }
    96  
    97  // copy current model unless new model test data is different
    98  // and delete nil keys in new model
    99  func mergeMockModelHeaders(cur, new map[string]interface{}) {
   100  	for k, v := range cur {
   101  		if v, ok := new[k]; ok {
   102  			if v == nil {
   103  				delete(new, k)
   104  			}
   105  			continue
   106  		}
   107  		new[k] = v
   108  	}
   109  }
   110  
   111  func (s *deviceMgrRemodelSuite) TestRemodelUnhappy(c *C) {
   112  	s.state.Lock()
   113  	defer s.state.Unlock()
   114  	s.state.Set("seeded", true)
   115  
   116  	// set a model assertion
   117  	cur := map[string]interface{}{
   118  		"brand":        "canonical",
   119  		"model":        "pc-model",
   120  		"architecture": "amd64",
   121  		"kernel":       "pc-kernel",
   122  		"gadget":       "pc",
   123  	}
   124  	s.makeModelAssertionInState(c, cur["brand"].(string), cur["model"].(string), map[string]interface{}{
   125  		"architecture": cur["architecture"],
   126  		"kernel":       cur["kernel"],
   127  		"gadget":       cur["gadget"],
   128  	})
   129  	s.makeSerialAssertionInState(c, cur["brand"].(string), cur["model"].(string), "orig-serial")
   130  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   131  		Brand:  cur["brand"].(string),
   132  		Model:  cur["model"].(string),
   133  		Serial: "orig-serial",
   134  	})
   135  
   136  	// ensure all error cases are checked
   137  	for _, t := range []struct {
   138  		new    map[string]interface{}
   139  		errStr string
   140  	}{
   141  		{map[string]interface{}{"architecture": "pdp-7"}, "cannot remodel to different architectures yet"},
   142  		{map[string]interface{}{"base": "core18"}, "cannot remodel from core to bases yet"},
   143  		{map[string]interface{}{"base": "core20", "kernel": nil, "gadget": nil, "snaps": mockCore20ModelSnaps}, "cannot remodel to Ubuntu Core 20 models yet"},
   144  	} {
   145  		mergeMockModelHeaders(cur, t.new)
   146  		new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new)
   147  		chg, err := devicestate.Remodel(s.state, new)
   148  		c.Check(chg, IsNil)
   149  		c.Check(err, ErrorMatches, t.errStr)
   150  	}
   151  }
   152  
   153  func (s *deviceMgrRemodelSuite) TestRemodelNoUC20RemodelYet(c *C) {
   154  	s.state.Lock()
   155  	defer s.state.Unlock()
   156  	s.state.Set("seeded", true)
   157  
   158  	// set a model assertion
   159  	cur := mockCore20ModelHeaders
   160  	s.makeModelAssertionInState(c, cur["brand"].(string), cur["model"].(string), map[string]interface{}{
   161  		"architecture": cur["architecture"],
   162  		"base":         cur["base"],
   163  		"grade":        cur["grade"],
   164  		"snaps":        cur["snaps"],
   165  	})
   166  	s.makeSerialAssertionInState(c, cur["brand"].(string), cur["model"].(string), "orig-serial")
   167  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   168  		Brand:  cur["brand"].(string),
   169  		Model:  cur["model"].(string),
   170  		Serial: "orig-serial",
   171  	})
   172  
   173  	// ensure all error cases are checked
   174  	for _, t := range []struct {
   175  		new    map[string]interface{}
   176  		errStr string
   177  	}{
   178  		// uc20 model
   179  		{map[string]interface{}{"grade": "signed"}, "cannot remodel Ubuntu Core 20 models yet"},
   180  		{map[string]interface{}{"base": "core22"}, "cannot remodel Ubuntu Core 20 models yet"},
   181  		// non-uc20 model
   182  		{map[string]interface{}{"snaps": nil, "grade": nil, "base": "core", "gadget": "pc", "kernel": "pc-kernel"}, "cannot remodel Ubuntu Core 20 models yet"},
   183  	} {
   184  		mergeMockModelHeaders(cur, t.new)
   185  		new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new)
   186  		chg, err := devicestate.Remodel(s.state, new)
   187  		c.Check(chg, IsNil)
   188  		c.Check(err, ErrorMatches, t.errStr)
   189  	}
   190  }
   191  
   192  func (s *deviceMgrRemodelSuite) TestRemodelRequiresSerial(c *C) {
   193  	s.state.Lock()
   194  	defer s.state.Unlock()
   195  	s.state.Set("seeded", true)
   196  
   197  	// set a model assertion
   198  	cur := map[string]interface{}{
   199  		"brand":        "canonical",
   200  		"model":        "pc-model",
   201  		"architecture": "amd64",
   202  		"kernel":       "pc-kernel",
   203  		"gadget":       "pc",
   204  	}
   205  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   206  		"architecture": "amd64",
   207  		"kernel":       "pc-kernel",
   208  		"gadget":       "pc",
   209  	})
   210  	// no serial assertion, no serial in state
   211  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   212  		Brand: "canonical",
   213  		Model: "pc-model",
   214  	})
   215  
   216  	newModelHdrs := map[string]interface{}{
   217  		"revision": "2",
   218  	}
   219  	mergeMockModelHeaders(cur, newModelHdrs)
   220  	new := s.brands.Model("canonical", "pc-model", newModelHdrs)
   221  	chg, err := devicestate.Remodel(s.state, new)
   222  	c.Check(chg, IsNil)
   223  	c.Check(err, ErrorMatches, "cannot remodel without a serial")
   224  }
   225  
   226  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchGadgetTrack(c *C) {
   227  	s.testRemodelTasksSwitchTrack(c, "pc", map[string]interface{}{
   228  		"gadget": "pc=18",
   229  	})
   230  }
   231  
   232  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchKernelTrack(c *C) {
   233  	s.testRemodelTasksSwitchTrack(c, "pc-kernel", map[string]interface{}{
   234  		"kernel": "pc-kernel=18",
   235  	})
   236  }
   237  
   238  func (s *deviceMgrRemodelSuite) testRemodelTasksSwitchTrack(c *C, whatRefreshes string, newModelOverrides map[string]interface{}) {
   239  	s.state.Lock()
   240  	defer s.state.Unlock()
   241  	s.state.Set("seeded", true)
   242  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   243  
   244  	var testDeviceCtx snapstate.DeviceContext
   245  
   246  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   247  		c.Check(flags.Required, Equals, true)
   248  		c.Check(deviceCtx, Equals, testDeviceCtx)
   249  		c.Check(fromChange, Equals, "99")
   250  
   251  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   252  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   253  		tValidate.WaitFor(tDownload)
   254  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   255  		tInstall.WaitFor(tValidate)
   256  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   257  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   258  		return ts, nil
   259  	})
   260  	defer restore()
   261  
   262  	restore = devicestate.MockSnapstateUpdateWithDeviceContext(func(st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   263  		c.Check(flags.Required, Equals, false)
   264  		c.Check(flags.NoReRefresh, Equals, true)
   265  		c.Check(deviceCtx, Equals, testDeviceCtx)
   266  		c.Check(fromChange, Equals, "99")
   267  		c.Check(name, Equals, whatRefreshes)
   268  		c.Check(opts.Channel, Equals, "18")
   269  
   270  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s to track %s", name, opts.Channel))
   271  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   272  		tValidate.WaitFor(tDownload)
   273  		tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
   274  		tUpdate.WaitFor(tValidate)
   275  		ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
   276  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   277  		return ts, nil
   278  	})
   279  	defer restore()
   280  
   281  	// set a model assertion
   282  	current := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   283  		"architecture": "amd64",
   284  		"kernel":       "pc-kernel",
   285  		"gadget":       "pc",
   286  		"base":         "core18",
   287  	})
   288  	err := assertstate.Add(s.state, current)
   289  	c.Assert(err, IsNil)
   290  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   291  		Brand: "canonical",
   292  		Model: "pc-model",
   293  	})
   294  
   295  	headers := map[string]interface{}{
   296  		"architecture":   "amd64",
   297  		"kernel":         "pc-kernel",
   298  		"gadget":         "pc",
   299  		"base":           "core18",
   300  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   301  		"revision":       "1",
   302  	}
   303  	for k, v := range newModelOverrides {
   304  		headers[k] = v
   305  	}
   306  	new := s.brands.Model("canonical", "pc-model", headers)
   307  
   308  	testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true}
   309  
   310  	tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99")
   311  	c.Assert(err, IsNil)
   312  	// 2 snaps, plus one track switch plus the remodel task, the
   313  	// wait chain is tested in TestRemodel*
   314  	c.Assert(tss, HasLen, 4)
   315  }
   316  
   317  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchGadget(c *C) {
   318  	s.testRemodelSwitchTasks(c, "other-gadget", "18", map[string]interface{}{
   319  		"gadget": "other-gadget=18",
   320  	})
   321  }
   322  
   323  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchKernel(c *C) {
   324  	s.testRemodelSwitchTasks(c, "other-kernel", "18", map[string]interface{}{
   325  		"kernel": "other-kernel=18",
   326  	})
   327  }
   328  
   329  func (s *deviceMgrRemodelSuite) testRemodelSwitchTasks(c *C, whatsNew, whatNewTrack string, newModelOverrides map[string]interface{}) {
   330  	c.Check(newModelOverrides, HasLen, 1, Commentf("test expects a single model property to change"))
   331  	s.state.Lock()
   332  	defer s.state.Unlock()
   333  	s.state.Set("seeded", true)
   334  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   335  
   336  	var testDeviceCtx snapstate.DeviceContext
   337  
   338  	var snapstateInstallWithDeviceContextCalled int
   339  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   340  		snapstateInstallWithDeviceContextCalled++
   341  		c.Check(name, Equals, whatsNew)
   342  		if whatNewTrack != "" {
   343  			c.Check(opts.Channel, Equals, whatNewTrack)
   344  		}
   345  
   346  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   347  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   348  		tValidate.WaitFor(tDownload)
   349  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   350  		tInstall.WaitFor(tValidate)
   351  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   352  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   353  		return ts, nil
   354  	})
   355  	defer restore()
   356  
   357  	// set a model assertion
   358  	current := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   359  		"architecture": "amd64",
   360  		"kernel":       "pc-kernel",
   361  		"gadget":       "pc",
   362  		"base":         "core18",
   363  	})
   364  	err := assertstate.Add(s.state, current)
   365  	c.Assert(err, IsNil)
   366  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   367  		Brand: "canonical",
   368  		Model: "pc-model",
   369  	})
   370  
   371  	headers := map[string]interface{}{
   372  		"architecture": "amd64",
   373  		"kernel":       "pc-kernel",
   374  		"gadget":       "pc",
   375  		"base":         "core18",
   376  		"revision":     "1",
   377  	}
   378  	for k, v := range newModelOverrides {
   379  		headers[k] = v
   380  	}
   381  	new := s.brands.Model("canonical", "pc-model", headers)
   382  
   383  	testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true}
   384  
   385  	tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99")
   386  	c.Assert(err, IsNil)
   387  	// 1 of switch-kernel/base/gadget plus the remodel task
   388  	c.Assert(tss, HasLen, 2)
   389  	// API was hit
   390  	c.Assert(snapstateInstallWithDeviceContextCalled, Equals, 1)
   391  }
   392  
   393  func (s *deviceMgrRemodelSuite) TestRemodelRequiredSnaps(c *C) {
   394  	s.state.Lock()
   395  	defer s.state.Unlock()
   396  	s.state.Set("seeded", true)
   397  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   398  
   399  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   400  		c.Check(flags.Required, Equals, true)
   401  		c.Check(deviceCtx, NotNil)
   402  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   403  
   404  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   405  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   406  		tValidate.WaitFor(tDownload)
   407  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   408  		tInstall.WaitFor(tValidate)
   409  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   410  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   411  		return ts, nil
   412  	})
   413  	defer restore()
   414  
   415  	// set a model assertion
   416  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   417  		"architecture": "amd64",
   418  		"kernel":       "pc-kernel",
   419  		"gadget":       "pc",
   420  		"base":         "core18",
   421  	})
   422  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   423  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   424  		Brand:  "canonical",
   425  		Model:  "pc-model",
   426  		Serial: "1234",
   427  	})
   428  
   429  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   430  		"architecture":   "amd64",
   431  		"kernel":         "pc-kernel",
   432  		"gadget":         "pc",
   433  		"base":           "core18",
   434  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   435  		"revision":       "1",
   436  	})
   437  	chg, err := devicestate.Remodel(s.state, new)
   438  	c.Assert(err, IsNil)
   439  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   440  
   441  	tl := chg.Tasks()
   442  	// 2 snaps,
   443  	c.Assert(tl, HasLen, 2*3+1)
   444  
   445  	deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil)
   446  	c.Assert(err, IsNil)
   447  	// deviceCtx is actually a remodelContext here
   448  	remodCtx, ok := deviceCtx.(devicestate.RemodelContext)
   449  	c.Assert(ok, Equals, true)
   450  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   451  	c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel)
   452  	c.Check(remodCtx.Model(), DeepEquals, new)
   453  	c.Check(remodCtx.Store(), IsNil)
   454  
   455  	// check the tasks
   456  	tDownloadSnap1 := tl[0]
   457  	tValidateSnap1 := tl[1]
   458  	tInstallSnap1 := tl[2]
   459  	tDownloadSnap2 := tl[3]
   460  	tValidateSnap2 := tl[4]
   461  	tInstallSnap2 := tl[5]
   462  	tSetModel := tl[6]
   463  
   464  	// check the tasks
   465  	c.Assert(tDownloadSnap1.Kind(), Equals, "fake-download")
   466  	c.Assert(tDownloadSnap1.Summary(), Equals, "Download new-required-snap-1")
   467  	c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0)
   468  	c.Assert(tValidateSnap1.Kind(), Equals, "validate-snap")
   469  	c.Assert(tValidateSnap1.Summary(), Equals, "Validate new-required-snap-1")
   470  	c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0)
   471  	c.Assert(tDownloadSnap2.Kind(), Equals, "fake-download")
   472  	c.Assert(tDownloadSnap2.Summary(), Equals, "Download new-required-snap-2")
   473  	// check the ordering, download/validate everything first, then install
   474  
   475  	// snap2 downloads wait for the downloads of snap1
   476  	c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0)
   477  	c.Assert(tValidateSnap1.WaitTasks(), DeepEquals, []*state.Task{
   478  		tDownloadSnap1,
   479  	})
   480  	c.Assert(tDownloadSnap2.WaitTasks(), DeepEquals, []*state.Task{
   481  		tValidateSnap1,
   482  	})
   483  	c.Assert(tValidateSnap2.WaitTasks(), DeepEquals, []*state.Task{
   484  		tDownloadSnap2,
   485  	})
   486  	c.Assert(tInstallSnap1.WaitTasks(), DeepEquals, []*state.Task{
   487  		// wait for own check-snap
   488  		tValidateSnap1,
   489  		// and also the last check-snap of the download chain
   490  		tValidateSnap2,
   491  	})
   492  	c.Assert(tInstallSnap2.WaitTasks(), DeepEquals, []*state.Task{
   493  		// last snap of the download chain
   494  		tValidateSnap2,
   495  		// previous install chain
   496  		tInstallSnap1,
   497  	})
   498  
   499  	c.Assert(tSetModel.Kind(), Equals, "set-model")
   500  	c.Assert(tSetModel.Summary(), Equals, "Set new model assertion")
   501  	// setModel waits for everything in the change
   502  	c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{tDownloadSnap1, tValidateSnap1, tInstallSnap1, tDownloadSnap2, tValidateSnap2, tInstallSnap2})
   503  }
   504  
   505  func (s *deviceMgrRemodelSuite) TestRemodelSwitchKernelTrack(c *C) {
   506  	s.state.Lock()
   507  	defer s.state.Unlock()
   508  	s.state.Set("seeded", true)
   509  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   510  
   511  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   512  		c.Check(flags.Required, Equals, true)
   513  		c.Check(deviceCtx, NotNil)
   514  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   515  
   516  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   517  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   518  		tValidate.WaitFor(tDownload)
   519  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   520  		tInstall.WaitFor(tValidate)
   521  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   522  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   523  		return ts, nil
   524  	})
   525  	defer restore()
   526  
   527  	restore = devicestate.MockSnapstateUpdateWithDeviceContext(func(st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   528  		c.Check(flags.Required, Equals, false)
   529  		c.Check(flags.NoReRefresh, Equals, true)
   530  		c.Check(deviceCtx, NotNil)
   531  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   532  
   533  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s to track %s", name, opts.Channel))
   534  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   535  		tValidate.WaitFor(tDownload)
   536  		tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
   537  		tUpdate.WaitFor(tValidate)
   538  		ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
   539  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   540  		return ts, nil
   541  	})
   542  	defer restore()
   543  
   544  	// set a model assertion
   545  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   546  		"architecture": "amd64",
   547  		"kernel":       "pc-kernel",
   548  		"gadget":       "pc",
   549  		"base":         "core18",
   550  	})
   551  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   552  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   553  		Brand:  "canonical",
   554  		Model:  "pc-model",
   555  		Serial: "1234",
   556  	})
   557  
   558  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   559  		"architecture":   "amd64",
   560  		"kernel":         "pc-kernel=18",
   561  		"gadget":         "pc",
   562  		"base":           "core18",
   563  		"required-snaps": []interface{}{"new-required-snap-1"},
   564  		"revision":       "1",
   565  	})
   566  	chg, err := devicestate.Remodel(s.state, new)
   567  	c.Assert(err, IsNil)
   568  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   569  
   570  	tl := chg.Tasks()
   571  	c.Assert(tl, HasLen, 2*3+1)
   572  
   573  	tDownloadKernel := tl[0]
   574  	tValidateKernel := tl[1]
   575  	tUpdateKernel := tl[2]
   576  	tDownloadSnap1 := tl[3]
   577  	tValidateSnap1 := tl[4]
   578  	tInstallSnap1 := tl[5]
   579  	tSetModel := tl[6]
   580  
   581  	c.Assert(tDownloadKernel.Kind(), Equals, "fake-download")
   582  	c.Assert(tDownloadKernel.Summary(), Equals, "Download pc-kernel to track 18")
   583  	c.Assert(tValidateKernel.Kind(), Equals, "validate-snap")
   584  	c.Assert(tValidateKernel.Summary(), Equals, "Validate pc-kernel")
   585  	c.Assert(tUpdateKernel.Kind(), Equals, "fake-update")
   586  	c.Assert(tUpdateKernel.Summary(), Equals, "Update pc-kernel to track 18")
   587  	c.Assert(tDownloadSnap1.Kind(), Equals, "fake-download")
   588  	c.Assert(tDownloadSnap1.Summary(), Equals, "Download new-required-snap-1")
   589  	c.Assert(tValidateSnap1.Kind(), Equals, "validate-snap")
   590  	c.Assert(tValidateSnap1.Summary(), Equals, "Validate new-required-snap-1")
   591  	c.Assert(tInstallSnap1.Kind(), Equals, "fake-install")
   592  	c.Assert(tInstallSnap1.Summary(), Equals, "Install new-required-snap-1")
   593  
   594  	c.Assert(tSetModel.Kind(), Equals, "set-model")
   595  	c.Assert(tSetModel.Summary(), Equals, "Set new model assertion")
   596  
   597  	// check the ordering
   598  	c.Assert(tDownloadSnap1.WaitTasks(), DeepEquals, []*state.Task{
   599  		// previous download finished
   600  		tValidateKernel,
   601  	})
   602  	c.Assert(tInstallSnap1.WaitTasks(), DeepEquals, []*state.Task{
   603  		// last download in the chain finished
   604  		tValidateSnap1,
   605  		// and kernel got updated
   606  		tUpdateKernel,
   607  	})
   608  	c.Assert(tUpdateKernel.WaitTasks(), DeepEquals, []*state.Task{
   609  		// kernel is valid
   610  		tValidateKernel,
   611  		// and last download in the chain finished
   612  		tValidateSnap1,
   613  	})
   614  }
   615  
   616  func (s *deviceMgrRemodelSuite) TestRemodelLessRequiredSnaps(c *C) {
   617  	s.state.Lock()
   618  	defer s.state.Unlock()
   619  	s.state.Set("seeded", true)
   620  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   621  
   622  	// set a model assertion
   623  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   624  		"architecture":   "amd64",
   625  		"kernel":         "pc-kernel",
   626  		"gadget":         "pc",
   627  		"base":           "core18",
   628  		"required-snaps": []interface{}{"some-required-snap"},
   629  	})
   630  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   631  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   632  		Brand:  "canonical",
   633  		Model:  "pc-model",
   634  		Serial: "1234",
   635  	})
   636  
   637  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   638  		"architecture": "amd64",
   639  		"kernel":       "pc-kernel",
   640  		"gadget":       "pc",
   641  		"base":         "core18",
   642  		"revision":     "1",
   643  	})
   644  	chg, err := devicestate.Remodel(s.state, new)
   645  	c.Assert(err, IsNil)
   646  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   647  
   648  	tl := chg.Tasks()
   649  	c.Assert(tl, HasLen, 1)
   650  	tSetModel := tl[0]
   651  	c.Assert(tSetModel.Kind(), Equals, "set-model")
   652  	c.Assert(tSetModel.Summary(), Equals, "Set new model assertion")
   653  }
   654  
   655  type freshSessionStore struct {
   656  	storetest.Store
   657  
   658  	ensureDeviceSession int
   659  }
   660  
   661  func (sto *freshSessionStore) EnsureDeviceSession() (*auth.DeviceState, error) {
   662  	sto.ensureDeviceSession += 1
   663  	return nil, nil
   664  }
   665  
   666  func (s *deviceMgrRemodelSuite) TestRemodelStoreSwitch(c *C) {
   667  	s.state.Lock()
   668  	defer s.state.Unlock()
   669  	s.state.Set("seeded", true)
   670  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   671  
   672  	var testStore snapstate.StoreService
   673  
   674  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   675  		c.Check(flags.Required, Equals, true)
   676  		c.Check(deviceCtx, NotNil)
   677  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   678  
   679  		c.Check(deviceCtx.Store(), Equals, testStore)
   680  
   681  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   682  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   683  		tValidate.WaitFor(tDownload)
   684  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   685  		tInstall.WaitFor(tValidate)
   686  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   687  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   688  		return ts, nil
   689  	})
   690  	defer restore()
   691  
   692  	// set a model assertion
   693  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   694  		"architecture": "amd64",
   695  		"kernel":       "pc-kernel",
   696  		"gadget":       "pc",
   697  		"base":         "core18",
   698  	})
   699  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   700  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   701  		Brand:  "canonical",
   702  		Model:  "pc-model",
   703  		Serial: "1234",
   704  	})
   705  
   706  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   707  		"architecture":   "amd64",
   708  		"kernel":         "pc-kernel",
   709  		"gadget":         "pc",
   710  		"base":           "core18",
   711  		"store":          "switched-store",
   712  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   713  		"revision":       "1",
   714  	})
   715  
   716  	freshStore := &freshSessionStore{}
   717  	testStore = freshStore
   718  
   719  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   720  		mod, err := devBE.Model()
   721  		c.Check(err, IsNil)
   722  		if err == nil {
   723  			c.Check(mod, DeepEquals, new)
   724  		}
   725  		return testStore
   726  	}
   727  
   728  	chg, err := devicestate.Remodel(s.state, new)
   729  	c.Assert(err, IsNil)
   730  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   731  
   732  	c.Check(freshStore.ensureDeviceSession, Equals, 1)
   733  
   734  	tl := chg.Tasks()
   735  	// 2 snaps * 3 tasks (from the mock install above) +
   736  	// 1 "set-model" task at the end
   737  	c.Assert(tl, HasLen, 2*3+1)
   738  
   739  	deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil)
   740  	c.Assert(err, IsNil)
   741  	// deviceCtx is actually a remodelContext here
   742  	remodCtx, ok := deviceCtx.(devicestate.RemodelContext)
   743  	c.Assert(ok, Equals, true)
   744  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   745  	c.Check(remodCtx.Kind(), Equals, devicestate.StoreSwitchRemodel)
   746  	c.Check(remodCtx.Model(), DeepEquals, new)
   747  	c.Check(remodCtx.Store(), Equals, testStore)
   748  }
   749  
   750  func (s *deviceMgrRemodelSuite) TestRemodelRereg(c *C) {
   751  	s.state.Lock()
   752  	defer s.state.Unlock()
   753  	s.state.Set("seeded", true)
   754  
   755  	// set a model assertion
   756  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   757  		"architecture": "amd64",
   758  		"kernel":       "pc-kernel",
   759  		"gadget":       "pc",
   760  		"base":         "core18",
   761  	})
   762  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial")
   763  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   764  		Brand:           "canonical",
   765  		Model:           "pc-model",
   766  		Serial:          "orig-serial",
   767  		SessionMacaroon: "old-session",
   768  	})
   769  
   770  	new := s.brands.Model("canonical", "rereg-model", map[string]interface{}{
   771  		"architecture":   "amd64",
   772  		"kernel":         "pc-kernel",
   773  		"gadget":         "pc",
   774  		"base":           "core18",
   775  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   776  	})
   777  
   778  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   779  		mod, err := devBE.Model()
   780  		c.Check(err, IsNil)
   781  		if err == nil {
   782  			c.Check(mod, DeepEquals, new)
   783  		}
   784  		return nil
   785  	}
   786  
   787  	chg, err := devicestate.Remodel(s.state, new)
   788  	c.Assert(err, IsNil)
   789  
   790  	c.Assert(chg.Summary(), Equals, "Remodel device to canonical/rereg-model (0)")
   791  
   792  	tl := chg.Tasks()
   793  	c.Assert(tl, HasLen, 2)
   794  
   795  	// check the tasks
   796  	tRequestSerial := tl[0]
   797  	tPrepareRemodeling := tl[1]
   798  
   799  	// check the tasks
   800  	c.Assert(tRequestSerial.Kind(), Equals, "request-serial")
   801  	c.Assert(tRequestSerial.Summary(), Equals, "Request new device serial")
   802  	c.Assert(tRequestSerial.WaitTasks(), HasLen, 0)
   803  
   804  	c.Assert(tPrepareRemodeling.Kind(), Equals, "prepare-remodeling")
   805  	c.Assert(tPrepareRemodeling.Summary(), Equals, "Prepare remodeling")
   806  	c.Assert(tPrepareRemodeling.WaitTasks(), DeepEquals, []*state.Task{tRequestSerial})
   807  }
   808  
   809  func (s *deviceMgrRemodelSuite) TestRemodelClash(c *C) {
   810  	s.state.Lock()
   811  	defer s.state.Unlock()
   812  	s.state.Set("seeded", true)
   813  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   814  
   815  	var clashing *asserts.Model
   816  
   817  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   818  		// simulate things changing under our feet
   819  		assertstatetest.AddMany(st, clashing)
   820  		devicestatetest.SetDevice(s.state, &auth.DeviceState{
   821  			Brand: "canonical",
   822  			Model: clashing.Model(),
   823  		})
   824  
   825  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   826  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   827  		tValidate.WaitFor(tDownload)
   828  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   829  		tInstall.WaitFor(tValidate)
   830  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   831  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   832  		return ts, nil
   833  	})
   834  	defer restore()
   835  
   836  	// set a model assertion
   837  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   838  		"architecture": "amd64",
   839  		"kernel":       "pc-kernel",
   840  		"gadget":       "pc",
   841  		"base":         "core18",
   842  	})
   843  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   844  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   845  		Brand:  "canonical",
   846  		Model:  "pc-model",
   847  		Serial: "1234",
   848  	})
   849  
   850  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   851  		"architecture":   "amd64",
   852  		"kernel":         "pc-kernel",
   853  		"gadget":         "pc",
   854  		"base":           "core18",
   855  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   856  		"revision":       "1",
   857  	})
   858  	other := s.brands.Model("canonical", "pc-model-other", map[string]interface{}{
   859  		"architecture":   "amd64",
   860  		"kernel":         "pc-kernel",
   861  		"gadget":         "pc",
   862  		"base":           "core18",
   863  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   864  	})
   865  
   866  	clashing = other
   867  	_, err := devicestate.Remodel(s.state, new)
   868  	c.Check(err, DeepEquals, &snapstate.ChangeConflictError{
   869  		Message: "cannot start remodel, clashing with concurrent remodel to canonical/pc-model-other (0)",
   870  	})
   871  
   872  	// reset
   873  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   874  		Brand:  "canonical",
   875  		Model:  "pc-model",
   876  		Serial: "1234",
   877  	})
   878  	clashing = new
   879  	_, err = devicestate.Remodel(s.state, new)
   880  	c.Check(err, DeepEquals, &snapstate.ChangeConflictError{
   881  		Message: "cannot start remodel, clashing with concurrent remodel to canonical/pc-model (1)",
   882  	})
   883  }
   884  
   885  func (s *deviceMgrRemodelSuite) TestRemodelClashInProgress(c *C) {
   886  	s.state.Lock()
   887  	defer s.state.Unlock()
   888  	s.state.Set("seeded", true)
   889  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   890  
   891  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   892  		// simulate another started remodeling
   893  		st.NewChange("remodel", "other remodel")
   894  
   895  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   896  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   897  		tValidate.WaitFor(tDownload)
   898  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   899  		tInstall.WaitFor(tValidate)
   900  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   901  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   902  		return ts, nil
   903  	})
   904  	defer restore()
   905  
   906  	// set a model assertion
   907  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   908  		"architecture": "amd64",
   909  		"kernel":       "pc-kernel",
   910  		"gadget":       "pc",
   911  		"base":         "core18",
   912  	})
   913  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   914  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   915  		Brand:  "canonical",
   916  		Model:  "pc-model",
   917  		Serial: "1234",
   918  	})
   919  
   920  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   921  		"architecture":   "amd64",
   922  		"kernel":         "pc-kernel",
   923  		"gadget":         "pc",
   924  		"base":           "core18",
   925  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   926  		"revision":       "1",
   927  	})
   928  
   929  	_, err := devicestate.Remodel(s.state, new)
   930  	c.Check(err, DeepEquals, &snapstate.ChangeConflictError{
   931  		Message: "cannot start remodel, clashing with concurrent one",
   932  	})
   933  }
   934  
   935  func (s *deviceMgrRemodelSuite) TestReregRemodelClashAnyChange(c *C) {
   936  	s.state.Lock()
   937  	defer s.state.Unlock()
   938  	s.state.Set("seeded", true)
   939  
   940  	// set a model assertion
   941  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   942  		"architecture": "amd64",
   943  		"kernel":       "pc-kernel",
   944  		"gadget":       "pc",
   945  		"base":         "core18",
   946  	})
   947  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial")
   948  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   949  		Brand:           "canonical",
   950  		Model:           "pc-model",
   951  		Serial:          "orig-serial",
   952  		SessionMacaroon: "old-session",
   953  	})
   954  
   955  	new := s.brands.Model("canonical", "pc-model-2", map[string]interface{}{
   956  		"architecture":   "amd64",
   957  		"kernel":         "pc-kernel",
   958  		"gadget":         "pc",
   959  		"base":           "core18",
   960  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   961  		"revision":       "1",
   962  	})
   963  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   964  		// we never reach the place where this gets called
   965  		c.Fatalf("unexpected call")
   966  		return nil
   967  	}
   968  
   969  	// simulate any other change
   970  	chg := s.state.NewChange("chg", "other change")
   971  	chg.SetStatus(state.DoingStatus)
   972  
   973  	_, err := devicestate.Remodel(s.state, new)
   974  	c.Assert(err, NotNil)
   975  	c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{
   976  		ChangeKind: "chg",
   977  		Message:    `other changes in progress (conflicting change "chg"), change "remodel" not allowed until they are done`,
   978  	})
   979  }
   980  
   981  func (s *deviceMgrRemodelSuite) TestRemodeling(c *C) {
   982  	s.state.Lock()
   983  	defer s.state.Unlock()
   984  
   985  	// no changes
   986  	c.Check(devicestate.Remodeling(s.state), Equals, false)
   987  
   988  	// other change
   989  	s.state.NewChange("other", "...")
   990  	c.Check(devicestate.Remodeling(s.state), Equals, false)
   991  
   992  	// remodel change
   993  	chg := s.state.NewChange("remodel", "...")
   994  	c.Check(devicestate.Remodeling(s.state), Equals, true)
   995  
   996  	// done
   997  	chg.SetStatus(state.DoneStatus)
   998  	c.Check(devicestate.Remodeling(s.state), Equals, false)
   999  }
  1000  
  1001  func (s *deviceMgrRemodelSuite) TestDeviceCtxNoTask(c *C) {
  1002  	s.state.Lock()
  1003  	defer s.state.Unlock()
  1004  	// nothing in the state
  1005  
  1006  	_, err := devicestate.DeviceCtx(s.state, nil, nil)
  1007  	c.Check(err, Equals, state.ErrNoState)
  1008  
  1009  	// have a model assertion
  1010  	model := s.brands.Model("canonical", "pc", map[string]interface{}{
  1011  		"gadget":       "pc",
  1012  		"kernel":       "kernel",
  1013  		"architecture": "amd64",
  1014  	})
  1015  	assertstatetest.AddMany(s.state, model)
  1016  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1017  		Brand: "canonical",
  1018  		Model: "pc",
  1019  	})
  1020  
  1021  	deviceCtx, err := devicestate.DeviceCtx(s.state, nil, nil)
  1022  	c.Assert(err, IsNil)
  1023  	c.Assert(deviceCtx.Model().BrandID(), Equals, "canonical")
  1024  
  1025  	c.Check(deviceCtx.Classic(), Equals, false)
  1026  	c.Check(deviceCtx.Kernel(), Equals, "kernel")
  1027  	c.Check(deviceCtx.Base(), Equals, "")
  1028  	c.Check(deviceCtx.RunMode(), Equals, true)
  1029  	// not a uc20 model, so no modeenv
  1030  	c.Check(deviceCtx.HasModeenv(), Equals, false)
  1031  }
  1032  
  1033  func (s *deviceMgrRemodelSuite) TestDeviceCtxGroundContext(c *C) {
  1034  	s.state.Lock()
  1035  	defer s.state.Unlock()
  1036  
  1037  	// have a model assertion
  1038  	model := s.brands.Model("canonical", "pc", map[string]interface{}{
  1039  		"gadget":       "pc",
  1040  		"kernel":       "kernel",
  1041  		"architecture": "amd64",
  1042  	})
  1043  	assertstatetest.AddMany(s.state, model)
  1044  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1045  		Brand: "canonical",
  1046  		Model: "pc",
  1047  	})
  1048  
  1049  	deviceCtx, err := devicestate.DeviceCtx(s.state, nil, nil)
  1050  	c.Assert(err, IsNil)
  1051  	c.Assert(deviceCtx.Model().BrandID(), Equals, "canonical")
  1052  	groundCtx := deviceCtx.GroundContext()
  1053  	c.Check(groundCtx.ForRemodeling(), Equals, false)
  1054  	c.Check(groundCtx.Model().Model(), Equals, "pc")
  1055  	c.Check(groundCtx.Store, PanicMatches, `retrieved ground context is not intended to drive store operations`)
  1056  }
  1057  
  1058  func (s *deviceMgrRemodelSuite) TestDeviceCtxProvided(c *C) {
  1059  	s.state.Lock()
  1060  	defer s.state.Unlock()
  1061  
  1062  	model := assertstest.FakeAssertion(map[string]interface{}{
  1063  		"type":         "model",
  1064  		"authority-id": "canonical",
  1065  		"series":       "16",
  1066  		"brand-id":     "canonical",
  1067  		"model":        "pc",
  1068  		"gadget":       "pc",
  1069  		"kernel":       "kernel",
  1070  		"architecture": "amd64",
  1071  	}).(*asserts.Model)
  1072  
  1073  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model}
  1074  
  1075  	deviceCtx1, err := devicestate.DeviceCtx(s.state, nil, deviceCtx)
  1076  	c.Assert(err, IsNil)
  1077  	c.Assert(deviceCtx1, Equals, deviceCtx)
  1078  }
  1079  
  1080  func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatible(c *C) {
  1081  	s.state.Lock()
  1082  	defer s.state.Unlock()
  1083  
  1084  	currentSnapYaml := `
  1085  name: gadget
  1086  type: gadget
  1087  version: 123
  1088  `
  1089  	remodelSnapYaml := `
  1090  name: new-gadget
  1091  type: gadget
  1092  version: 123
  1093  `
  1094  	mockGadget := `
  1095  type: gadget
  1096  name: gadget
  1097  volumes:
  1098    volume:
  1099      schema: gpt
  1100      bootloader: grub
  1101  `
  1102  	siCurrent := &snap.SideInfo{Revision: snap.R(123), RealName: "gadget"}
  1103  	// so that we get a directory
  1104  	currInfo := snaptest.MockSnapWithFiles(c, currentSnapYaml, siCurrent, nil)
  1105  	info := snaptest.MockSnapWithFiles(c, remodelSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, nil)
  1106  	snapf, err := snapfile.Open(info.MountDir())
  1107  	c.Assert(err, IsNil)
  1108  
  1109  	s.setupBrands(c)
  1110  
  1111  	oldModel := fakeMyModel(map[string]interface{}{
  1112  		"architecture": "amd64",
  1113  		"gadget":       "gadget",
  1114  		"kernel":       "kernel",
  1115  	})
  1116  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: oldModel}
  1117  
  1118  	// model assertion in device context
  1119  	newModel := fakeMyModel(map[string]interface{}{
  1120  		"architecture": "amd64",
  1121  		"gadget":       "new-gadget",
  1122  		"kernel":       "kernel",
  1123  	})
  1124  	remodelCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: newModel, Remodeling: true, OldDeviceModel: oldModel}
  1125  
  1126  	restore := devicestate.MockGadgetIsCompatible(func(current, update *gadget.Info) error {
  1127  		c.Assert(current.Volumes, HasLen, 1)
  1128  		c.Assert(update.Volumes, HasLen, 1)
  1129  		return errors.New("fail")
  1130  	})
  1131  	defer restore()
  1132  
  1133  	// not on classic
  1134  	release.OnClassic = true
  1135  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1136  	c.Check(err, IsNil)
  1137  	release.OnClassic = false
  1138  
  1139  	// nothing if not remodeling
  1140  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, deviceCtx)
  1141  	c.Check(err, IsNil)
  1142  
  1143  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1144  	c.Check(err, ErrorMatches, "cannot read new gadget metadata: .*/new-gadget/1/meta/gadget.yaml: no such file or directory")
  1145  
  1146  	// drop gadget.yaml to the new gadget
  1147  	err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(mockGadget), 0644)
  1148  	c.Assert(err, IsNil)
  1149  
  1150  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1151  	c.Check(err, ErrorMatches, "cannot read current gadget metadata: .*/gadget/123/meta/gadget.yaml: no such file or directory")
  1152  
  1153  	// drop gadget.yaml to the current gadget
  1154  	err = ioutil.WriteFile(filepath.Join(currInfo.MountDir(), "meta/gadget.yaml"), []byte(mockGadget), 0644)
  1155  	c.Assert(err, IsNil)
  1156  
  1157  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1158  	c.Check(err, ErrorMatches, "cannot remodel to an incompatible gadget: fail")
  1159  
  1160  	restore = devicestate.MockGadgetIsCompatible(func(current, update *gadget.Info) error {
  1161  		c.Assert(current.Volumes, HasLen, 1)
  1162  		c.Assert(update.Volumes, HasLen, 1)
  1163  		return nil
  1164  	})
  1165  	defer restore()
  1166  
  1167  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1168  	c.Check(err, IsNil)
  1169  
  1170  	// when remodeling to completely new gadget snap, there is no current
  1171  	// snap passed to the check callback
  1172  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx)
  1173  	c.Check(err, ErrorMatches, "cannot identify the current gadget snap")
  1174  
  1175  	// mock data to obtain current gadget info
  1176  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1177  		Brand: "canonical",
  1178  		Model: "gadget",
  1179  	})
  1180  	s.makeModelAssertionInState(c, "canonical", "gadget", map[string]interface{}{
  1181  		"architecture": "amd64",
  1182  		"kernel":       "kernel",
  1183  		"gadget":       "gadget",
  1184  	})
  1185  
  1186  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx)
  1187  	c.Check(err, ErrorMatches, "cannot identify the current gadget snap")
  1188  
  1189  	snapstate.Set(s.state, "gadget", &snapstate.SnapState{
  1190  		SnapType: "gadget",
  1191  		Sequence: []*snap.SideInfo{siCurrent},
  1192  		Current:  siCurrent.Revision,
  1193  		Active:   true,
  1194  	})
  1195  
  1196  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx)
  1197  	c.Check(err, IsNil)
  1198  }
  1199  
  1200  var (
  1201  	compatibleTestMockOkGadget = `
  1202  type: gadget
  1203  name: gadget
  1204  volumes:
  1205    volume:
  1206      schema: gpt
  1207      bootloader: grub
  1208      structure:
  1209        - name: foo
  1210          size: 10M
  1211          type: 00000000-0000-0000-0000-0000deadbeef
  1212  `
  1213  )
  1214  
  1215  func (s *deviceMgrRemodelSuite) testCheckGadgetRemodelCompatibleWithYaml(c *C, currentGadgetYaml, newGadgetYaml string, expErr string) {
  1216  	s.state.Lock()
  1217  	defer s.state.Unlock()
  1218  
  1219  	currentSnapYaml := `
  1220  name: gadget
  1221  type: gadget
  1222  version: 123
  1223  `
  1224  	remodelSnapYaml := `
  1225  name: new-gadget
  1226  type: gadget
  1227  version: 123
  1228  `
  1229  
  1230  	currInfo := snaptest.MockSnapWithFiles(c, currentSnapYaml, &snap.SideInfo{Revision: snap.R(123)}, [][]string{
  1231  		{"meta/gadget.yaml", currentGadgetYaml},
  1232  	})
  1233  	// gadget we're remodeling to is identical
  1234  	info := snaptest.MockSnapWithFiles(c, remodelSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, [][]string{
  1235  		{"meta/gadget.yaml", newGadgetYaml},
  1236  	})
  1237  	snapf, err := snapfile.Open(info.MountDir())
  1238  	c.Assert(err, IsNil)
  1239  
  1240  	s.setupBrands(c)
  1241  	// model assertion in device context
  1242  	oldModel := fakeMyModel(map[string]interface{}{
  1243  		"architecture": "amd64",
  1244  		"gadget":       "new-gadget",
  1245  		"kernel":       "krnl",
  1246  	})
  1247  	model := fakeMyModel(map[string]interface{}{
  1248  		"architecture": "amd64",
  1249  		"gadget":       "new-gadget",
  1250  		"kernel":       "krnl",
  1251  	})
  1252  	remodelCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model, Remodeling: true, OldDeviceModel: oldModel}
  1253  
  1254  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1255  	if expErr == "" {
  1256  		c.Check(err, IsNil)
  1257  	} else {
  1258  		c.Check(err, ErrorMatches, expErr)
  1259  	}
  1260  
  1261  }
  1262  
  1263  func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatibleWithYamlHappy(c *C) {
  1264  	s.testCheckGadgetRemodelCompatibleWithYaml(c, compatibleTestMockOkGadget, compatibleTestMockOkGadget, "")
  1265  }
  1266  
  1267  func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatibleWithYamlBad(c *C) {
  1268  	mockBadGadgetYaml := `
  1269  type: gadget
  1270  name: gadget
  1271  volumes:
  1272    volume:
  1273      schema: gpt
  1274      bootloader: grub
  1275      structure:
  1276        - name: foo
  1277          size: 20M
  1278          type: 00000000-0000-0000-0000-0000deadbeef
  1279  `
  1280  
  1281  	errMatch := `cannot remodel to an incompatible gadget: incompatible layout change: incompatible structure #0 \("foo"\) change: cannot change structure size from 10485760 to 20971520`
  1282  	s.testCheckGadgetRemodelCompatibleWithYaml(c, compatibleTestMockOkGadget, mockBadGadgetYaml, errMatch)
  1283  }
  1284  
  1285  func (s *deviceMgrRemodelSuite) TestRemodelGadgetAssetsUpdate(c *C) {
  1286  	var currentGadgetYaml = `
  1287  volumes:
  1288    pc:
  1289      bootloader: grub
  1290      structure:
  1291         - name: foo
  1292           type: 00000000-0000-0000-0000-0000deadcafe
  1293           filesystem: ext4
  1294           size: 10M
  1295           content:
  1296              - source: foo-content
  1297                target: /
  1298         - name: bare-one
  1299           type: bare
  1300           size: 1M
  1301           content:
  1302              - image: bare.img
  1303  `
  1304  
  1305  	var remodelGadgetYaml = `
  1306  volumes:
  1307    pc:
  1308      bootloader: grub
  1309      structure:
  1310         - name: foo
  1311           type: 00000000-0000-0000-0000-0000deadcafe
  1312           filesystem: ext4
  1313           size: 10M
  1314           content:
  1315              - source: new-foo-content
  1316                target: /
  1317         - name: bare-one
  1318           type: bare
  1319           size: 1M
  1320           content:
  1321              - image: new-bare-content.img
  1322  `
  1323  
  1324  	s.state.Lock()
  1325  	s.state.Set("seeded", true)
  1326  	s.state.Set("refresh-privacy-key", "some-privacy-key")
  1327  
  1328  	nopHandler := func(task *state.Task, _ *tomb.Tomb) error {
  1329  		return nil
  1330  	}
  1331  	s.o.TaskRunner().AddHandler("fake-download", nopHandler, nil)
  1332  	s.o.TaskRunner().AddHandler("validate-snap", nopHandler, nil)
  1333  	s.o.TaskRunner().AddHandler("set-model", nopHandler, nil)
  1334  
  1335  	// set a model assertion we remodel from
  1336  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
  1337  		"architecture": "amd64",
  1338  		"kernel":       "pc-kernel",
  1339  		"gadget":       "pc",
  1340  		"base":         "core18",
  1341  	})
  1342  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial")
  1343  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1344  		Brand:  "canonical",
  1345  		Model:  "pc-model",
  1346  		Serial: "serial",
  1347  	})
  1348  
  1349  	// the target model
  1350  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
  1351  		"architecture": "amd64",
  1352  		"kernel":       "pc-kernel",
  1353  		"base":         "core18",
  1354  		"revision":     "1",
  1355  		// remodel to new gadget
  1356  		"gadget": "new-gadget",
  1357  	})
  1358  
  1359  	// current gadget
  1360  	siModelGadget := &snap.SideInfo{
  1361  		RealName: "pc",
  1362  		Revision: snap.R(33),
  1363  		SnapID:   "foo-id",
  1364  	}
  1365  	currentGadgetInfo := snaptest.MockSnapWithFiles(c, snapYaml, siModelGadget, [][]string{
  1366  		{"meta/gadget.yaml", currentGadgetYaml},
  1367  	})
  1368  	snapstate.Set(s.state, "pc", &snapstate.SnapState{
  1369  		SnapType: "gadget",
  1370  		Sequence: []*snap.SideInfo{siModelGadget},
  1371  		Current:  siModelGadget.Revision,
  1372  		Active:   true,
  1373  	})
  1374  
  1375  	// new gadget snap
  1376  	siNewModelGadget := &snap.SideInfo{
  1377  		RealName: "new-gadget",
  1378  		Revision: snap.R(34),
  1379  	}
  1380  	newGadgetInfo := snaptest.MockSnapWithFiles(c, snapYaml, siNewModelGadget, [][]string{
  1381  		{"meta/gadget.yaml", remodelGadgetYaml},
  1382  	})
  1383  
  1384  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
  1385  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
  1386  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
  1387  		tValidate.WaitFor(tDownload)
  1388  		tGadgetUpdate := s.state.NewTask("update-gadget-assets", fmt.Sprintf("Update gadget %s", name))
  1389  		tGadgetUpdate.Set("snap-setup", &snapstate.SnapSetup{
  1390  			SideInfo: siNewModelGadget,
  1391  			Type:     snap.TypeGadget,
  1392  		})
  1393  		tGadgetUpdate.WaitFor(tValidate)
  1394  		ts := state.NewTaskSet(tDownload, tValidate, tGadgetUpdate)
  1395  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
  1396  		return ts, nil
  1397  	})
  1398  	defer restore()
  1399  	restore = release.MockOnClassic(false)
  1400  	defer restore()
  1401  
  1402  	gadgetUpdateCalled := false
  1403  	restore = devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error {
  1404  		gadgetUpdateCalled = true
  1405  		c.Check(policy, NotNil)
  1406  		c.Check(reflect.ValueOf(policy).Pointer(), Equals, reflect.ValueOf(gadget.RemodelUpdatePolicy).Pointer())
  1407  		c.Check(current, DeepEquals, gadget.GadgetData{
  1408  			Info: &gadget.Info{
  1409  				Volumes: map[string]*gadget.Volume{
  1410  					"pc": {
  1411  						Bootloader: "grub",
  1412  						Schema:     "gpt",
  1413  						Structure: []gadget.VolumeStructure{{
  1414  							Name:       "foo",
  1415  							Type:       "00000000-0000-0000-0000-0000deadcafe",
  1416  							Size:       10 * quantity.SizeMiB,
  1417  							Filesystem: "ext4",
  1418  							Content: []gadget.VolumeContent{
  1419  								{UnresolvedSource: "foo-content", Target: "/"},
  1420  							},
  1421  						}, {
  1422  							Name: "bare-one",
  1423  							Type: "bare",
  1424  							Size: quantity.SizeMiB,
  1425  							Content: []gadget.VolumeContent{
  1426  								{Image: "bare.img"},
  1427  							},
  1428  						}},
  1429  					},
  1430  				},
  1431  			},
  1432  			RootDir: currentGadgetInfo.MountDir(),
  1433  		})
  1434  		c.Check(update, DeepEquals, gadget.GadgetData{
  1435  			Info: &gadget.Info{
  1436  				Volumes: map[string]*gadget.Volume{
  1437  					"pc": {
  1438  						Bootloader: "grub",
  1439  						Schema:     "gpt",
  1440  						Structure: []gadget.VolumeStructure{{
  1441  							Name:       "foo",
  1442  							Type:       "00000000-0000-0000-0000-0000deadcafe",
  1443  							Size:       10 * quantity.SizeMiB,
  1444  							Filesystem: "ext4",
  1445  							Content: []gadget.VolumeContent{
  1446  								{UnresolvedSource: "new-foo-content", Target: "/"},
  1447  							},
  1448  						}, {
  1449  							Name: "bare-one",
  1450  							Type: "bare",
  1451  							Size: quantity.SizeMiB,
  1452  							Content: []gadget.VolumeContent{
  1453  								{Image: "new-bare-content.img"},
  1454  							},
  1455  						}},
  1456  					},
  1457  				},
  1458  			},
  1459  			RootDir: newGadgetInfo.MountDir(),
  1460  		})
  1461  		return nil
  1462  	})
  1463  	defer restore()
  1464  
  1465  	chg, err := devicestate.Remodel(s.state, new)
  1466  	c.Assert(err, IsNil)
  1467  	s.state.Unlock()
  1468  
  1469  	s.settle(c)
  1470  
  1471  	s.state.Lock()
  1472  	defer s.state.Unlock()
  1473  	c.Check(chg.IsReady(), Equals, true)
  1474  	c.Check(chg.Err(), IsNil)
  1475  	c.Check(gadgetUpdateCalled, Equals, true)
  1476  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystem})
  1477  }
  1478  
  1479  func (s *deviceMgrRemodelSuite) TestRemodelGadgetAssetsParanoidCheck(c *C) {
  1480  	s.state.Lock()
  1481  	s.state.Set("seeded", true)
  1482  	s.state.Set("refresh-privacy-key", "some-privacy-key")
  1483  
  1484  	nopHandler := func(task *state.Task, _ *tomb.Tomb) error {
  1485  		return nil
  1486  	}
  1487  	s.o.TaskRunner().AddHandler("fake-download", nopHandler, nil)
  1488  	s.o.TaskRunner().AddHandler("validate-snap", nopHandler, nil)
  1489  	s.o.TaskRunner().AddHandler("set-model", nopHandler, nil)
  1490  
  1491  	// set a model assertion we remodel from
  1492  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
  1493  		"architecture": "amd64",
  1494  		"kernel":       "pc-kernel",
  1495  		"gadget":       "pc",
  1496  		"base":         "core18",
  1497  	})
  1498  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial")
  1499  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1500  		Brand:  "canonical",
  1501  		Model:  "pc-model",
  1502  		Serial: "serial",
  1503  	})
  1504  
  1505  	// the target model
  1506  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
  1507  		"architecture": "amd64",
  1508  		"kernel":       "pc-kernel",
  1509  		"base":         "core18",
  1510  		"revision":     "1",
  1511  		// remodel to new gadget
  1512  		"gadget": "new-gadget",
  1513  	})
  1514  
  1515  	// current gadget
  1516  	siModelGadget := &snap.SideInfo{
  1517  		RealName: "pc",
  1518  		Revision: snap.R(33),
  1519  		SnapID:   "foo-id",
  1520  	}
  1521  	snapstate.Set(s.state, "pc", &snapstate.SnapState{
  1522  		SnapType: "gadget",
  1523  		Sequence: []*snap.SideInfo{siModelGadget},
  1524  		Current:  siModelGadget.Revision,
  1525  		Active:   true,
  1526  	})
  1527  
  1528  	// new gadget snap, name does not match the new model
  1529  	siUnexpectedModelGadget := &snap.SideInfo{
  1530  		RealName: "new-gadget-unexpected",
  1531  		Revision: snap.R(34),
  1532  	}
  1533  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
  1534  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
  1535  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
  1536  		tValidate.WaitFor(tDownload)
  1537  		tGadgetUpdate := s.state.NewTask("update-gadget-assets", fmt.Sprintf("Update gadget %s", name))
  1538  		tGadgetUpdate.Set("snap-setup", &snapstate.SnapSetup{
  1539  			SideInfo: siUnexpectedModelGadget,
  1540  			Type:     snap.TypeGadget,
  1541  		})
  1542  		tGadgetUpdate.WaitFor(tValidate)
  1543  		ts := state.NewTaskSet(tDownload, tValidate, tGadgetUpdate)
  1544  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
  1545  		return ts, nil
  1546  	})
  1547  	defer restore()
  1548  	restore = release.MockOnClassic(false)
  1549  	defer restore()
  1550  
  1551  	gadgetUpdateCalled := false
  1552  	restore = devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error {
  1553  		return errors.New("unexpected call")
  1554  	})
  1555  	defer restore()
  1556  
  1557  	chg, err := devicestate.Remodel(s.state, new)
  1558  	c.Assert(err, IsNil)
  1559  	s.state.Unlock()
  1560  
  1561  	s.settle(c)
  1562  
  1563  	s.state.Lock()
  1564  	defer s.state.Unlock()
  1565  	c.Check(chg.IsReady(), Equals, true)
  1566  	c.Assert(chg.Err(), ErrorMatches, `(?s).*\(cannot apply gadget assets update from non-model gadget snap "new-gadget-unexpected", expected "new-gadget" snap\)`)
  1567  	c.Check(gadgetUpdateCalled, Equals, false)
  1568  	c.Check(s.restartRequests, HasLen, 0)
  1569  }
  1570  
  1571  func (s *deviceMgrSuite) TestRemodelSwitchBase(c *C) {
  1572  	s.state.Lock()
  1573  	defer s.state.Unlock()
  1574  	s.state.Set("seeded", true)
  1575  	s.state.Set("refresh-privacy-key", "some-privacy-key")
  1576  
  1577  	var testDeviceCtx snapstate.DeviceContext
  1578  
  1579  	var snapstateInstallWithDeviceContextCalled int
  1580  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
  1581  		snapstateInstallWithDeviceContextCalled++
  1582  		c.Check(name, Equals, "core20")
  1583  
  1584  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
  1585  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
  1586  		tValidate.WaitFor(tDownload)
  1587  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
  1588  		tInstall.WaitFor(tValidate)
  1589  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
  1590  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
  1591  		return ts, nil
  1592  	})
  1593  	defer restore()
  1594  
  1595  	// set a model assertion
  1596  	current := s.brands.Model("canonical", "pc-model", map[string]interface{}{
  1597  		"architecture": "amd64",
  1598  		"kernel":       "pc-kernel",
  1599  		"gadget":       "pc",
  1600  		"base":         "core18",
  1601  	})
  1602  	err := assertstate.Add(s.state, current)
  1603  	c.Assert(err, IsNil)
  1604  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1605  		Brand: "canonical",
  1606  		Model: "pc-model",
  1607  	})
  1608  
  1609  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
  1610  		"architecture": "amd64",
  1611  		"kernel":       "pc-kernel",
  1612  		"gadget":       "pc",
  1613  		"base":         "core20",
  1614  		"revision":     "1",
  1615  	})
  1616  
  1617  	testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true}
  1618  
  1619  	tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99")
  1620  	c.Assert(err, IsNil)
  1621  	// 1 switch to a new base plus the remodel task
  1622  	c.Assert(tss, HasLen, 2)
  1623  	// API was hit
  1624  	c.Assert(snapstateInstallWithDeviceContextCalled, Equals, 1)
  1625  }