github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/overlord/devicestate/devicestate_remodel_test.go (about)

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