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