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