github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/overlord/devicestate/devicestate_remodel_test.go (about)

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