github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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  	"strings"
    31  	"time"
    32  
    33  	. "gopkg.in/check.v1"
    34  	"gopkg.in/tomb.v2"
    35  
    36  	"github.com/snapcore/snapd/asserts"
    37  	"github.com/snapcore/snapd/asserts/assertstest"
    38  	"github.com/snapcore/snapd/boot"
    39  	"github.com/snapcore/snapd/gadget"
    40  	"github.com/snapcore/snapd/gadget/quantity"
    41  	"github.com/snapcore/snapd/logger"
    42  	"github.com/snapcore/snapd/overlord/assertstate"
    43  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    44  	"github.com/snapcore/snapd/overlord/auth"
    45  	"github.com/snapcore/snapd/overlord/devicestate"
    46  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    47  	"github.com/snapcore/snapd/overlord/snapstate"
    48  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    49  	"github.com/snapcore/snapd/overlord/state"
    50  	"github.com/snapcore/snapd/overlord/storecontext"
    51  	"github.com/snapcore/snapd/release"
    52  	"github.com/snapcore/snapd/snap"
    53  	"github.com/snapcore/snapd/snap/snapfile"
    54  	"github.com/snapcore/snapd/snap/snaptest"
    55  	"github.com/snapcore/snapd/store/storetest"
    56  	"github.com/snapcore/snapd/testutil"
    57  )
    58  
    59  type deviceMgrRemodelSuite struct {
    60  	deviceMgrBaseSuite
    61  }
    62  
    63  var _ = Suite(&deviceMgrRemodelSuite{})
    64  
    65  func (s *deviceMgrRemodelSuite) TestRemodelUnhappyNotSeeded(c *C) {
    66  	s.state.Lock()
    67  	defer s.state.Unlock()
    68  	s.state.Set("seeded", false)
    69  
    70  	newModel := s.brands.Model("canonical", "pc", map[string]interface{}{
    71  		"architecture": "amd64",
    72  		"kernel":       "pc-kernel",
    73  		"gadget":       "pc",
    74  	})
    75  	_, err := devicestate.Remodel(s.state, newModel)
    76  	c.Assert(err, ErrorMatches, "cannot remodel until fully seeded")
    77  }
    78  
    79  var mockCore20ModelHeaders = map[string]interface{}{
    80  	"brand":        "canonical",
    81  	"model":        "pc-model-20",
    82  	"architecture": "amd64",
    83  	"grade":        "dangerous",
    84  	"base":         "core20",
    85  	"snaps":        mockCore20ModelSnaps,
    86  }
    87  
    88  var mockCore20ModelSnaps = []interface{}{
    89  	map[string]interface{}{
    90  		"name":            "pc-kernel",
    91  		"id":              "pckernelidididididididididididid",
    92  		"type":            "kernel",
    93  		"default-channel": "20",
    94  	},
    95  	map[string]interface{}{
    96  		"name":            "pc",
    97  		"id":              "pcididididididididididididididid",
    98  		"type":            "gadget",
    99  		"default-channel": "20",
   100  	},
   101  }
   102  
   103  // copy current model unless new model test data is different
   104  // and delete nil keys in new model
   105  func mergeMockModelHeaders(cur, new map[string]interface{}) {
   106  	for k, v := range cur {
   107  		if v, ok := new[k]; ok {
   108  			if v == nil {
   109  				delete(new, k)
   110  			}
   111  			continue
   112  		}
   113  		new[k] = v
   114  	}
   115  }
   116  
   117  func (s *deviceMgrRemodelSuite) TestRemodelUnhappy(c *C) {
   118  	s.state.Lock()
   119  	defer s.state.Unlock()
   120  	s.state.Set("seeded", true)
   121  
   122  	// set a model assertion
   123  	cur := map[string]interface{}{
   124  		"brand":        "canonical",
   125  		"model":        "pc-model",
   126  		"architecture": "amd64",
   127  		"kernel":       "pc-kernel",
   128  		"gadget":       "pc",
   129  	}
   130  	s.makeModelAssertionInState(c, cur["brand"].(string), cur["model"].(string), map[string]interface{}{
   131  		"architecture": cur["architecture"],
   132  		"kernel":       cur["kernel"],
   133  		"gadget":       cur["gadget"],
   134  	})
   135  	s.makeSerialAssertionInState(c, cur["brand"].(string), cur["model"].(string), "orig-serial")
   136  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   137  		Brand:  cur["brand"].(string),
   138  		Model:  cur["model"].(string),
   139  		Serial: "orig-serial",
   140  	})
   141  
   142  	restore := devicestate.AllowUC20RemodelTesting(false)
   143  	defer restore()
   144  	// ensure all error cases are checked
   145  	for _, t := range []struct {
   146  		new    map[string]interface{}
   147  		errStr string
   148  	}{
   149  		{map[string]interface{}{"architecture": "pdp-7"}, "cannot remodel to different architectures yet"},
   150  		{map[string]interface{}{"base": "core18"}, "cannot remodel from core to bases yet"},
   151  		{map[string]interface{}{"base": "core20", "kernel": nil, "gadget": nil, "snaps": mockCore20ModelSnaps}, "cannot remodel to Ubuntu Core 20 models yet"},
   152  	} {
   153  		mergeMockModelHeaders(cur, t.new)
   154  		new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new)
   155  		chg, err := devicestate.Remodel(s.state, new)
   156  		c.Check(chg, IsNil)
   157  		c.Check(err, ErrorMatches, t.errStr)
   158  	}
   159  
   160  	restore = devicestate.AllowUC20RemodelTesting(true)
   161  	defer restore()
   162  
   163  	for _, t := range []struct {
   164  		new    map[string]interface{}
   165  		errStr string
   166  	}{
   167  		// pre-UC20 to UC20
   168  		{map[string]interface{}{"base": "core20", "kernel": nil, "gadget": nil, "snaps": mockCore20ModelSnaps}, "cannot remodel from grade unset to grade signed"},
   169  	} {
   170  		mergeMockModelHeaders(cur, t.new)
   171  		new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new)
   172  		chg, err := devicestate.Remodel(s.state, new)
   173  		c.Check(chg, IsNil)
   174  		c.Check(err, ErrorMatches, t.errStr)
   175  	}
   176  
   177  }
   178  
   179  func (s *deviceMgrRemodelSuite) TestRemodelCheckGrade(c *C) {
   180  	s.state.Lock()
   181  	defer s.state.Unlock()
   182  	s.state.Set("seeded", true)
   183  
   184  	restore := devicestate.AllowUC20RemodelTesting(true)
   185  	defer restore()
   186  
   187  	// set a model assertion
   188  	cur := mockCore20ModelHeaders
   189  	s.makeModelAssertionInState(c, cur["brand"].(string), cur["model"].(string), map[string]interface{}{
   190  		"architecture": cur["architecture"],
   191  		"base":         cur["base"],
   192  		"grade":        cur["grade"],
   193  		"snaps":        cur["snaps"],
   194  	})
   195  	s.makeSerialAssertionInState(c, cur["brand"].(string), cur["model"].(string), "orig-serial")
   196  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   197  		Brand:  cur["brand"].(string),
   198  		Model:  cur["model"].(string),
   199  		Serial: "orig-serial",
   200  	})
   201  
   202  	// ensure all error cases are checked
   203  	for idx, t := range []struct {
   204  		new    map[string]interface{}
   205  		errStr string
   206  	}{
   207  		// uc20 model
   208  		{map[string]interface{}{"grade": "signed"}, "cannot remodel from grade dangerous to grade signed"},
   209  		{map[string]interface{}{"grade": "secured"}, "cannot remodel from grade dangerous to grade secured"},
   210  		// non-uc20 model
   211  		{map[string]interface{}{"snaps": nil, "grade": nil, "base": "core", "gadget": "pc", "kernel": "pc-kernel"}, "cannot remodel from grade dangerous to grade unset"},
   212  	} {
   213  		c.Logf("tc: %v", idx)
   214  		mergeMockModelHeaders(cur, t.new)
   215  		new := s.brands.Model(t.new["brand"].(string), t.new["model"].(string), t.new)
   216  		chg, err := devicestate.Remodel(s.state, new)
   217  		c.Check(chg, IsNil)
   218  		c.Check(err, ErrorMatches, t.errStr)
   219  	}
   220  }
   221  
   222  func (s *deviceMgrRemodelSuite) TestRemodelRequiresSerial(c *C) {
   223  	s.state.Lock()
   224  	defer s.state.Unlock()
   225  	s.state.Set("seeded", true)
   226  
   227  	// set a model assertion
   228  	cur := map[string]interface{}{
   229  		"brand":        "canonical",
   230  		"model":        "pc-model",
   231  		"architecture": "amd64",
   232  		"kernel":       "pc-kernel",
   233  		"gadget":       "pc",
   234  	}
   235  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   236  		"architecture": "amd64",
   237  		"kernel":       "pc-kernel",
   238  		"gadget":       "pc",
   239  	})
   240  	// no serial assertion, no serial in state
   241  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   242  		Brand: "canonical",
   243  		Model: "pc-model",
   244  	})
   245  
   246  	newModelHdrs := map[string]interface{}{
   247  		"revision": "2",
   248  	}
   249  	mergeMockModelHeaders(cur, newModelHdrs)
   250  	new := s.brands.Model("canonical", "pc-model", newModelHdrs)
   251  	chg, err := devicestate.Remodel(s.state, new)
   252  	c.Check(chg, IsNil)
   253  	c.Check(err, ErrorMatches, "cannot remodel without a serial")
   254  }
   255  
   256  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchGadgetTrack(c *C) {
   257  	s.testRemodelTasksSwitchTrack(c, "pc", map[string]interface{}{
   258  		"gadget": "pc=18",
   259  	})
   260  }
   261  
   262  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchKernelTrack(c *C) {
   263  	s.testRemodelTasksSwitchTrack(c, "pc-kernel", map[string]interface{}{
   264  		"kernel": "pc-kernel=18",
   265  	})
   266  }
   267  
   268  func (s *deviceMgrRemodelSuite) testRemodelTasksSwitchTrack(c *C, whatRefreshes string, newModelOverrides map[string]interface{}) {
   269  	s.state.Lock()
   270  	defer s.state.Unlock()
   271  	s.state.Set("seeded", true)
   272  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   273  
   274  	var testDeviceCtx snapstate.DeviceContext
   275  
   276  	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) {
   277  		c.Check(flags.Required, Equals, true)
   278  		c.Check(deviceCtx, Equals, testDeviceCtx)
   279  		c.Check(fromChange, Equals, "99")
   280  
   281  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   282  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   283  		tValidate.WaitFor(tDownload)
   284  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   285  		tInstall.WaitFor(tValidate)
   286  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   287  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   288  		return ts, nil
   289  	})
   290  	defer restore()
   291  
   292  	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) {
   293  		c.Check(flags.Required, Equals, false)
   294  		c.Check(flags.NoReRefresh, Equals, true)
   295  		c.Check(deviceCtx, Equals, testDeviceCtx)
   296  		c.Check(fromChange, Equals, "99")
   297  		c.Check(name, Equals, whatRefreshes)
   298  		c.Check(opts.Channel, Equals, "18")
   299  
   300  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s to track %s", name, opts.Channel))
   301  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   302  		tValidate.WaitFor(tDownload)
   303  		tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
   304  		tUpdate.WaitFor(tValidate)
   305  		ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
   306  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   307  		return ts, nil
   308  	})
   309  	defer restore()
   310  
   311  	// set a model assertion
   312  	current := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   313  		"architecture": "amd64",
   314  		"kernel":       "pc-kernel",
   315  		"gadget":       "pc",
   316  		"base":         "core18",
   317  	})
   318  	err := assertstate.Add(s.state, current)
   319  	c.Assert(err, IsNil)
   320  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   321  		Brand: "canonical",
   322  		Model: "pc-model",
   323  	})
   324  
   325  	headers := map[string]interface{}{
   326  		"architecture":   "amd64",
   327  		"kernel":         "pc-kernel",
   328  		"gadget":         "pc",
   329  		"base":           "core18",
   330  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   331  		"revision":       "1",
   332  	}
   333  	for k, v := range newModelOverrides {
   334  		headers[k] = v
   335  	}
   336  	new := s.brands.Model("canonical", "pc-model", headers)
   337  
   338  	testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true}
   339  
   340  	tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99")
   341  	c.Assert(err, IsNil)
   342  	// 2 snaps, plus one track switch plus the remodel task, the
   343  	// wait chain is tested in TestRemodel*
   344  	c.Assert(tss, HasLen, 4)
   345  }
   346  
   347  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchGadget(c *C) {
   348  	s.testRemodelSwitchTasks(c, "other-gadget", "18", map[string]interface{}{
   349  		"gadget": "other-gadget=18",
   350  	})
   351  }
   352  
   353  func (s *deviceMgrRemodelSuite) TestRemodelTasksSwitchKernel(c *C) {
   354  	s.testRemodelSwitchTasks(c, "other-kernel", "18", map[string]interface{}{
   355  		"kernel": "other-kernel=18",
   356  	})
   357  }
   358  
   359  func (s *deviceMgrRemodelSuite) testRemodelSwitchTasks(c *C, whatsNew, whatNewTrack string, newModelOverrides map[string]interface{}) {
   360  	c.Check(newModelOverrides, HasLen, 1, Commentf("test expects a single model property to change"))
   361  	s.state.Lock()
   362  	defer s.state.Unlock()
   363  	s.state.Set("seeded", true)
   364  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   365  
   366  	var testDeviceCtx snapstate.DeviceContext
   367  
   368  	var snapstateInstallWithDeviceContextCalled int
   369  	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) {
   370  		snapstateInstallWithDeviceContextCalled++
   371  		c.Check(name, Equals, whatsNew)
   372  		if whatNewTrack != "" {
   373  			c.Check(opts.Channel, Equals, whatNewTrack)
   374  		}
   375  
   376  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   377  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   378  		tValidate.WaitFor(tDownload)
   379  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   380  		tInstall.WaitFor(tValidate)
   381  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   382  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   383  		return ts, nil
   384  	})
   385  	defer restore()
   386  
   387  	// set a model assertion
   388  	current := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   389  		"architecture": "amd64",
   390  		"kernel":       "pc-kernel",
   391  		"gadget":       "pc",
   392  		"base":         "core18",
   393  	})
   394  	err := assertstate.Add(s.state, current)
   395  	c.Assert(err, IsNil)
   396  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   397  		Brand: "canonical",
   398  		Model: "pc-model",
   399  	})
   400  
   401  	headers := map[string]interface{}{
   402  		"architecture": "amd64",
   403  		"kernel":       "pc-kernel",
   404  		"gadget":       "pc",
   405  		"base":         "core18",
   406  		"revision":     "1",
   407  	}
   408  	for k, v := range newModelOverrides {
   409  		headers[k] = v
   410  	}
   411  	new := s.brands.Model("canonical", "pc-model", headers)
   412  
   413  	testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true}
   414  
   415  	tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99")
   416  	c.Assert(err, IsNil)
   417  	// 1 of switch-kernel/base/gadget plus the remodel task
   418  	c.Assert(tss, HasLen, 2)
   419  	// API was hit
   420  	c.Assert(snapstateInstallWithDeviceContextCalled, Equals, 1)
   421  }
   422  
   423  func (s *deviceMgrRemodelSuite) TestRemodelRequiredSnaps(c *C) {
   424  	s.state.Lock()
   425  	defer s.state.Unlock()
   426  	s.state.Set("seeded", true)
   427  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   428  
   429  	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) {
   430  		c.Check(flags.Required, Equals, true)
   431  		c.Check(deviceCtx, NotNil)
   432  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   433  
   434  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   435  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   436  		tValidate.WaitFor(tDownload)
   437  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   438  		tInstall.WaitFor(tValidate)
   439  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   440  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   441  		return ts, nil
   442  	})
   443  	defer restore()
   444  
   445  	// set a model assertion
   446  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   447  		"architecture": "amd64",
   448  		"kernel":       "pc-kernel",
   449  		"gadget":       "pc",
   450  		"base":         "core18",
   451  	})
   452  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   453  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   454  		Brand:  "canonical",
   455  		Model:  "pc-model",
   456  		Serial: "1234",
   457  	})
   458  
   459  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   460  		"architecture":   "amd64",
   461  		"kernel":         "pc-kernel",
   462  		"gadget":         "pc",
   463  		"base":           "core18",
   464  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   465  		"revision":       "1",
   466  	})
   467  	chg, err := devicestate.Remodel(s.state, new)
   468  	c.Assert(err, IsNil)
   469  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   470  
   471  	tl := chg.Tasks()
   472  	// 2 snaps,
   473  	c.Assert(tl, HasLen, 2*3+1)
   474  
   475  	deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil)
   476  	c.Assert(err, IsNil)
   477  	// deviceCtx is actually a remodelContext here
   478  	remodCtx, ok := deviceCtx.(devicestate.RemodelContext)
   479  	c.Assert(ok, Equals, true)
   480  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   481  	c.Check(remodCtx.Kind(), Equals, devicestate.UpdateRemodel)
   482  	c.Check(remodCtx.Model(), DeepEquals, new)
   483  	c.Check(remodCtx.Store(), IsNil)
   484  
   485  	// check the tasks
   486  	tDownloadSnap1 := tl[0]
   487  	tValidateSnap1 := tl[1]
   488  	tInstallSnap1 := tl[2]
   489  	tDownloadSnap2 := tl[3]
   490  	tValidateSnap2 := tl[4]
   491  	tInstallSnap2 := tl[5]
   492  	tSetModel := tl[6]
   493  
   494  	// check the tasks
   495  	c.Assert(tDownloadSnap1.Kind(), Equals, "fake-download")
   496  	c.Assert(tDownloadSnap1.Summary(), Equals, "Download new-required-snap-1")
   497  	c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0)
   498  	c.Assert(tValidateSnap1.Kind(), Equals, "validate-snap")
   499  	c.Assert(tValidateSnap1.Summary(), Equals, "Validate new-required-snap-1")
   500  	c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0)
   501  	c.Assert(tDownloadSnap2.Kind(), Equals, "fake-download")
   502  	c.Assert(tDownloadSnap2.Summary(), Equals, "Download new-required-snap-2")
   503  	// check the ordering, download/validate everything first, then install
   504  
   505  	// snap2 downloads wait for the downloads of snap1
   506  	c.Assert(tDownloadSnap1.WaitTasks(), HasLen, 0)
   507  	c.Assert(tValidateSnap1.WaitTasks(), DeepEquals, []*state.Task{
   508  		tDownloadSnap1,
   509  	})
   510  	c.Assert(tDownloadSnap2.WaitTasks(), DeepEquals, []*state.Task{
   511  		tValidateSnap1,
   512  	})
   513  	c.Assert(tValidateSnap2.WaitTasks(), DeepEquals, []*state.Task{
   514  		tDownloadSnap2,
   515  	})
   516  	c.Assert(tInstallSnap1.WaitTasks(), DeepEquals, []*state.Task{
   517  		// wait for own check-snap
   518  		tValidateSnap1,
   519  		// and also the last check-snap of the download chain
   520  		tValidateSnap2,
   521  	})
   522  	c.Assert(tInstallSnap2.WaitTasks(), DeepEquals, []*state.Task{
   523  		// last snap of the download chain
   524  		tValidateSnap2,
   525  		// previous install chain
   526  		tInstallSnap1,
   527  	})
   528  
   529  	c.Assert(tSetModel.Kind(), Equals, "set-model")
   530  	c.Assert(tSetModel.Summary(), Equals, "Set new model assertion")
   531  	// setModel waits for everything in the change
   532  	c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{tDownloadSnap1, tValidateSnap1, tInstallSnap1, tDownloadSnap2, tValidateSnap2, tInstallSnap2})
   533  }
   534  
   535  func (s *deviceMgrRemodelSuite) TestRemodelSwitchKernelTrack(c *C) {
   536  	s.state.Lock()
   537  	defer s.state.Unlock()
   538  	s.state.Set("seeded", true)
   539  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   540  
   541  	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) {
   542  		c.Check(flags.Required, Equals, true)
   543  		c.Check(deviceCtx, NotNil)
   544  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   545  
   546  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   547  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   548  		tValidate.WaitFor(tDownload)
   549  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   550  		tInstall.WaitFor(tValidate)
   551  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   552  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   553  		return ts, nil
   554  	})
   555  	defer restore()
   556  
   557  	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) {
   558  		c.Check(flags.Required, Equals, false)
   559  		c.Check(flags.NoReRefresh, Equals, true)
   560  		c.Check(deviceCtx, NotNil)
   561  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   562  
   563  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s to track %s", name, opts.Channel))
   564  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   565  		tValidate.WaitFor(tDownload)
   566  		tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
   567  		tUpdate.WaitFor(tValidate)
   568  		ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
   569  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   570  		return ts, nil
   571  	})
   572  	defer restore()
   573  
   574  	// set a model assertion
   575  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   576  		"architecture": "amd64",
   577  		"kernel":       "pc-kernel",
   578  		"gadget":       "pc",
   579  		"base":         "core18",
   580  	})
   581  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   582  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   583  		Brand:  "canonical",
   584  		Model:  "pc-model",
   585  		Serial: "1234",
   586  	})
   587  
   588  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   589  		"architecture":   "amd64",
   590  		"kernel":         "pc-kernel=18",
   591  		"gadget":         "pc",
   592  		"base":           "core18",
   593  		"required-snaps": []interface{}{"new-required-snap-1"},
   594  		"revision":       "1",
   595  	})
   596  	chg, err := devicestate.Remodel(s.state, new)
   597  	c.Assert(err, IsNil)
   598  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   599  
   600  	tl := chg.Tasks()
   601  	c.Assert(tl, HasLen, 2*3+1)
   602  
   603  	tDownloadKernel := tl[0]
   604  	tValidateKernel := tl[1]
   605  	tUpdateKernel := tl[2]
   606  	tDownloadSnap1 := tl[3]
   607  	tValidateSnap1 := tl[4]
   608  	tInstallSnap1 := tl[5]
   609  	tSetModel := tl[6]
   610  
   611  	c.Assert(tDownloadKernel.Kind(), Equals, "fake-download")
   612  	c.Assert(tDownloadKernel.Summary(), Equals, "Download pc-kernel to track 18")
   613  	c.Assert(tValidateKernel.Kind(), Equals, "validate-snap")
   614  	c.Assert(tValidateKernel.Summary(), Equals, "Validate pc-kernel")
   615  	c.Assert(tUpdateKernel.Kind(), Equals, "fake-update")
   616  	c.Assert(tUpdateKernel.Summary(), Equals, "Update pc-kernel to track 18")
   617  	c.Assert(tDownloadSnap1.Kind(), Equals, "fake-download")
   618  	c.Assert(tDownloadSnap1.Summary(), Equals, "Download new-required-snap-1")
   619  	c.Assert(tValidateSnap1.Kind(), Equals, "validate-snap")
   620  	c.Assert(tValidateSnap1.Summary(), Equals, "Validate new-required-snap-1")
   621  	c.Assert(tInstallSnap1.Kind(), Equals, "fake-install")
   622  	c.Assert(tInstallSnap1.Summary(), Equals, "Install new-required-snap-1")
   623  
   624  	c.Assert(tSetModel.Kind(), Equals, "set-model")
   625  	c.Assert(tSetModel.Summary(), Equals, "Set new model assertion")
   626  
   627  	// check the ordering
   628  	c.Assert(tDownloadSnap1.WaitTasks(), DeepEquals, []*state.Task{
   629  		// previous download finished
   630  		tValidateKernel,
   631  	})
   632  	c.Assert(tInstallSnap1.WaitTasks(), DeepEquals, []*state.Task{
   633  		// last download in the chain finished
   634  		tValidateSnap1,
   635  		// and kernel got updated
   636  		tUpdateKernel,
   637  	})
   638  	c.Assert(tUpdateKernel.WaitTasks(), DeepEquals, []*state.Task{
   639  		// kernel is valid
   640  		tValidateKernel,
   641  		// and last download in the chain finished
   642  		tValidateSnap1,
   643  	})
   644  }
   645  
   646  func (s *deviceMgrRemodelSuite) TestRemodelLessRequiredSnaps(c *C) {
   647  	s.state.Lock()
   648  	defer s.state.Unlock()
   649  	s.state.Set("seeded", true)
   650  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   651  
   652  	// set a model assertion
   653  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   654  		"architecture":   "amd64",
   655  		"kernel":         "pc-kernel",
   656  		"gadget":         "pc",
   657  		"base":           "core18",
   658  		"required-snaps": []interface{}{"some-required-snap"},
   659  	})
   660  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   661  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   662  		Brand:  "canonical",
   663  		Model:  "pc-model",
   664  		Serial: "1234",
   665  	})
   666  
   667  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   668  		"architecture": "amd64",
   669  		"kernel":       "pc-kernel",
   670  		"gadget":       "pc",
   671  		"base":         "core18",
   672  		"revision":     "1",
   673  	})
   674  	chg, err := devicestate.Remodel(s.state, new)
   675  	c.Assert(err, IsNil)
   676  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   677  
   678  	tl := chg.Tasks()
   679  	c.Assert(tl, HasLen, 1)
   680  	tSetModel := tl[0]
   681  	c.Assert(tSetModel.Kind(), Equals, "set-model")
   682  	c.Assert(tSetModel.Summary(), Equals, "Set new model assertion")
   683  }
   684  
   685  type freshSessionStore struct {
   686  	storetest.Store
   687  
   688  	ensureDeviceSession int
   689  }
   690  
   691  func (sto *freshSessionStore) EnsureDeviceSession() (*auth.DeviceState, error) {
   692  	sto.ensureDeviceSession += 1
   693  	return nil, nil
   694  }
   695  
   696  func (s *deviceMgrRemodelSuite) TestRemodelStoreSwitch(c *C) {
   697  	s.state.Lock()
   698  	defer s.state.Unlock()
   699  	s.state.Set("seeded", true)
   700  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   701  
   702  	var testStore snapstate.StoreService
   703  
   704  	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) {
   705  		c.Check(flags.Required, Equals, true)
   706  		c.Check(deviceCtx, NotNil)
   707  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   708  
   709  		c.Check(deviceCtx.Store(), Equals, testStore)
   710  
   711  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   712  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   713  		tValidate.WaitFor(tDownload)
   714  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   715  		tInstall.WaitFor(tValidate)
   716  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   717  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   718  		return ts, nil
   719  	})
   720  	defer restore()
   721  
   722  	// set a model assertion
   723  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   724  		"architecture": "amd64",
   725  		"kernel":       "pc-kernel",
   726  		"gadget":       "pc",
   727  		"base":         "core18",
   728  	})
   729  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   730  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   731  		Brand:  "canonical",
   732  		Model:  "pc-model",
   733  		Serial: "1234",
   734  	})
   735  
   736  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   737  		"architecture":   "amd64",
   738  		"kernel":         "pc-kernel",
   739  		"gadget":         "pc",
   740  		"base":           "core18",
   741  		"store":          "switched-store",
   742  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   743  		"revision":       "1",
   744  	})
   745  
   746  	freshStore := &freshSessionStore{}
   747  	testStore = freshStore
   748  
   749  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   750  		mod, err := devBE.Model()
   751  		c.Check(err, IsNil)
   752  		if err == nil {
   753  			c.Check(mod, DeepEquals, new)
   754  		}
   755  		return testStore
   756  	}
   757  
   758  	chg, err := devicestate.Remodel(s.state, new)
   759  	c.Assert(err, IsNil)
   760  	c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
   761  
   762  	c.Check(freshStore.ensureDeviceSession, Equals, 1)
   763  
   764  	tl := chg.Tasks()
   765  	// 2 snaps * 3 tasks (from the mock install above) +
   766  	// 1 "set-model" task at the end
   767  	c.Assert(tl, HasLen, 2*3+1)
   768  
   769  	deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil)
   770  	c.Assert(err, IsNil)
   771  	// deviceCtx is actually a remodelContext here
   772  	remodCtx, ok := deviceCtx.(devicestate.RemodelContext)
   773  	c.Assert(ok, Equals, true)
   774  	c.Check(remodCtx.ForRemodeling(), Equals, true)
   775  	c.Check(remodCtx.Kind(), Equals, devicestate.StoreSwitchRemodel)
   776  	c.Check(remodCtx.Model(), DeepEquals, new)
   777  	c.Check(remodCtx.Store(), Equals, testStore)
   778  }
   779  
   780  func (s *deviceMgrRemodelSuite) TestRemodelRereg(c *C) {
   781  	s.state.Lock()
   782  	defer s.state.Unlock()
   783  	s.state.Set("seeded", true)
   784  
   785  	// set a model assertion
   786  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   787  		"architecture": "amd64",
   788  		"kernel":       "pc-kernel",
   789  		"gadget":       "pc",
   790  		"base":         "core18",
   791  	})
   792  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial")
   793  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   794  		Brand:           "canonical",
   795  		Model:           "pc-model",
   796  		Serial:          "orig-serial",
   797  		SessionMacaroon: "old-session",
   798  	})
   799  
   800  	new := s.brands.Model("canonical", "rereg-model", map[string]interface{}{
   801  		"architecture":   "amd64",
   802  		"kernel":         "pc-kernel",
   803  		"gadget":         "pc",
   804  		"base":           "core18",
   805  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   806  	})
   807  
   808  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   809  		mod, err := devBE.Model()
   810  		c.Check(err, IsNil)
   811  		if err == nil {
   812  			c.Check(mod, DeepEquals, new)
   813  		}
   814  		return nil
   815  	}
   816  
   817  	chg, err := devicestate.Remodel(s.state, new)
   818  	c.Assert(err, IsNil)
   819  
   820  	c.Assert(chg.Summary(), Equals, "Remodel device to canonical/rereg-model (0)")
   821  
   822  	tl := chg.Tasks()
   823  	c.Assert(tl, HasLen, 2)
   824  
   825  	// check the tasks
   826  	tRequestSerial := tl[0]
   827  	tPrepareRemodeling := tl[1]
   828  
   829  	// check the tasks
   830  	c.Assert(tRequestSerial.Kind(), Equals, "request-serial")
   831  	c.Assert(tRequestSerial.Summary(), Equals, "Request new device serial")
   832  	c.Assert(tRequestSerial.WaitTasks(), HasLen, 0)
   833  
   834  	c.Assert(tPrepareRemodeling.Kind(), Equals, "prepare-remodeling")
   835  	c.Assert(tPrepareRemodeling.Summary(), Equals, "Prepare remodeling")
   836  	c.Assert(tPrepareRemodeling.WaitTasks(), DeepEquals, []*state.Task{tRequestSerial})
   837  }
   838  
   839  func (s *deviceMgrRemodelSuite) TestRemodelClash(c *C) {
   840  	s.state.Lock()
   841  	defer s.state.Unlock()
   842  	s.state.Set("seeded", true)
   843  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   844  
   845  	var clashing *asserts.Model
   846  
   847  	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) {
   848  		// simulate things changing under our feet
   849  		assertstatetest.AddMany(st, clashing)
   850  		devicestatetest.SetDevice(s.state, &auth.DeviceState{
   851  			Brand: "canonical",
   852  			Model: clashing.Model(),
   853  		})
   854  
   855  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   856  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   857  		tValidate.WaitFor(tDownload)
   858  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   859  		tInstall.WaitFor(tValidate)
   860  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   861  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   862  		return ts, nil
   863  	})
   864  	defer restore()
   865  
   866  	// set a model assertion
   867  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   868  		"architecture": "amd64",
   869  		"kernel":       "pc-kernel",
   870  		"gadget":       "pc",
   871  		"base":         "core18",
   872  	})
   873  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   874  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   875  		Brand:  "canonical",
   876  		Model:  "pc-model",
   877  		Serial: "1234",
   878  	})
   879  
   880  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   881  		"architecture":   "amd64",
   882  		"kernel":         "pc-kernel",
   883  		"gadget":         "pc",
   884  		"base":           "core18",
   885  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   886  		"revision":       "1",
   887  	})
   888  	other := s.brands.Model("canonical", "pc-model-other", map[string]interface{}{
   889  		"architecture":   "amd64",
   890  		"kernel":         "pc-kernel",
   891  		"gadget":         "pc",
   892  		"base":           "core18",
   893  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   894  	})
   895  
   896  	clashing = other
   897  	_, err := devicestate.Remodel(s.state, new)
   898  	c.Check(err, DeepEquals, &snapstate.ChangeConflictError{
   899  		Message: "cannot start remodel, clashing with concurrent remodel to canonical/pc-model-other (0)",
   900  	})
   901  
   902  	// reset
   903  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   904  		Brand:  "canonical",
   905  		Model:  "pc-model",
   906  		Serial: "1234",
   907  	})
   908  	clashing = new
   909  	_, err = devicestate.Remodel(s.state, new)
   910  	c.Check(err, DeepEquals, &snapstate.ChangeConflictError{
   911  		Message: "cannot start remodel, clashing with concurrent remodel to canonical/pc-model (1)",
   912  	})
   913  }
   914  
   915  func (s *deviceMgrRemodelSuite) TestRemodelClashInProgress(c *C) {
   916  	s.state.Lock()
   917  	defer s.state.Unlock()
   918  	s.state.Set("seeded", true)
   919  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   920  
   921  	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) {
   922  		// simulate another started remodeling
   923  		st.NewChange("remodel", "other remodel")
   924  
   925  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   926  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   927  		tValidate.WaitFor(tDownload)
   928  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   929  		tInstall.WaitFor(tValidate)
   930  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   931  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   932  		return ts, nil
   933  	})
   934  	defer restore()
   935  
   936  	// set a model assertion
   937  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   938  		"architecture": "amd64",
   939  		"kernel":       "pc-kernel",
   940  		"gadget":       "pc",
   941  		"base":         "core18",
   942  	})
   943  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "1234")
   944  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   945  		Brand:  "canonical",
   946  		Model:  "pc-model",
   947  		Serial: "1234",
   948  	})
   949  
   950  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   951  		"architecture":   "amd64",
   952  		"kernel":         "pc-kernel",
   953  		"gadget":         "pc",
   954  		"base":           "core18",
   955  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   956  		"revision":       "1",
   957  	})
   958  
   959  	_, err := devicestate.Remodel(s.state, new)
   960  	c.Check(err, DeepEquals, &snapstate.ChangeConflictError{
   961  		Message: "cannot start remodel, clashing with concurrent one",
   962  	})
   963  }
   964  
   965  func (s *deviceMgrRemodelSuite) TestReregRemodelClashAnyChange(c *C) {
   966  	s.state.Lock()
   967  	defer s.state.Unlock()
   968  	s.state.Set("seeded", true)
   969  
   970  	// set a model assertion
   971  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   972  		"architecture": "amd64",
   973  		"kernel":       "pc-kernel",
   974  		"gadget":       "pc",
   975  		"base":         "core18",
   976  	})
   977  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial")
   978  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   979  		Brand:           "canonical",
   980  		Model:           "pc-model",
   981  		Serial:          "orig-serial",
   982  		SessionMacaroon: "old-session",
   983  	})
   984  
   985  	new := s.brands.Model("canonical", "pc-model-2", map[string]interface{}{
   986  		"architecture":   "amd64",
   987  		"kernel":         "pc-kernel",
   988  		"gadget":         "pc",
   989  		"base":           "core18",
   990  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   991  		"revision":       "1",
   992  	})
   993  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   994  		// we never reach the place where this gets called
   995  		c.Fatalf("unexpected call")
   996  		return nil
   997  	}
   998  
   999  	// simulate any other change
  1000  	chg := s.state.NewChange("chg", "other change")
  1001  	chg.SetStatus(state.DoingStatus)
  1002  
  1003  	_, err := devicestate.Remodel(s.state, new)
  1004  	c.Assert(err, NotNil)
  1005  	c.Assert(err, DeepEquals, &snapstate.ChangeConflictError{
  1006  		ChangeKind: "chg",
  1007  		Message:    `other changes in progress (conflicting change "chg"), change "remodel" not allowed until they are done`,
  1008  	})
  1009  }
  1010  
  1011  func (s *deviceMgrRemodelSuite) TestRemodeling(c *C) {
  1012  	s.state.Lock()
  1013  	defer s.state.Unlock()
  1014  
  1015  	// no changes
  1016  	c.Check(devicestate.Remodeling(s.state), Equals, false)
  1017  
  1018  	// other change
  1019  	s.state.NewChange("other", "...")
  1020  	c.Check(devicestate.Remodeling(s.state), Equals, false)
  1021  
  1022  	// remodel change
  1023  	chg := s.state.NewChange("remodel", "...")
  1024  	c.Check(devicestate.Remodeling(s.state), Equals, true)
  1025  
  1026  	// done
  1027  	chg.SetStatus(state.DoneStatus)
  1028  	c.Check(devicestate.Remodeling(s.state), Equals, false)
  1029  }
  1030  
  1031  func (s *deviceMgrRemodelSuite) TestDeviceCtxNoTask(c *C) {
  1032  	s.state.Lock()
  1033  	defer s.state.Unlock()
  1034  	// nothing in the state
  1035  
  1036  	_, err := devicestate.DeviceCtx(s.state, nil, nil)
  1037  	c.Check(err, Equals, state.ErrNoState)
  1038  
  1039  	// have a model assertion
  1040  	model := s.brands.Model("canonical", "pc", map[string]interface{}{
  1041  		"gadget":       "pc",
  1042  		"kernel":       "kernel",
  1043  		"architecture": "amd64",
  1044  	})
  1045  	assertstatetest.AddMany(s.state, model)
  1046  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1047  		Brand: "canonical",
  1048  		Model: "pc",
  1049  	})
  1050  
  1051  	deviceCtx, err := devicestate.DeviceCtx(s.state, nil, nil)
  1052  	c.Assert(err, IsNil)
  1053  	c.Assert(deviceCtx.Model().BrandID(), Equals, "canonical")
  1054  
  1055  	c.Check(deviceCtx.Classic(), Equals, false)
  1056  	c.Check(deviceCtx.Kernel(), Equals, "kernel")
  1057  	c.Check(deviceCtx.Base(), Equals, "")
  1058  	c.Check(deviceCtx.RunMode(), Equals, true)
  1059  	// not a uc20 model, so no modeenv
  1060  	c.Check(deviceCtx.HasModeenv(), Equals, false)
  1061  }
  1062  
  1063  func (s *deviceMgrRemodelSuite) TestDeviceCtxGroundContext(c *C) {
  1064  	s.state.Lock()
  1065  	defer s.state.Unlock()
  1066  
  1067  	// have a model assertion
  1068  	model := s.brands.Model("canonical", "pc", map[string]interface{}{
  1069  		"gadget":       "pc",
  1070  		"kernel":       "kernel",
  1071  		"architecture": "amd64",
  1072  	})
  1073  	assertstatetest.AddMany(s.state, model)
  1074  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1075  		Brand: "canonical",
  1076  		Model: "pc",
  1077  	})
  1078  
  1079  	deviceCtx, err := devicestate.DeviceCtx(s.state, nil, nil)
  1080  	c.Assert(err, IsNil)
  1081  	c.Assert(deviceCtx.Model().BrandID(), Equals, "canonical")
  1082  	groundCtx := deviceCtx.GroundContext()
  1083  	c.Check(groundCtx.ForRemodeling(), Equals, false)
  1084  	c.Check(groundCtx.Model().Model(), Equals, "pc")
  1085  	c.Check(groundCtx.Store, PanicMatches, `retrieved ground context is not intended to drive store operations`)
  1086  }
  1087  
  1088  func (s *deviceMgrRemodelSuite) TestDeviceCtxProvided(c *C) {
  1089  	s.state.Lock()
  1090  	defer s.state.Unlock()
  1091  
  1092  	model := assertstest.FakeAssertion(map[string]interface{}{
  1093  		"type":         "model",
  1094  		"authority-id": "canonical",
  1095  		"series":       "16",
  1096  		"brand-id":     "canonical",
  1097  		"model":        "pc",
  1098  		"gadget":       "pc",
  1099  		"kernel":       "kernel",
  1100  		"architecture": "amd64",
  1101  	}).(*asserts.Model)
  1102  
  1103  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model}
  1104  
  1105  	deviceCtx1, err := devicestate.DeviceCtx(s.state, nil, deviceCtx)
  1106  	c.Assert(err, IsNil)
  1107  	c.Assert(deviceCtx1, Equals, deviceCtx)
  1108  }
  1109  
  1110  func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatible(c *C) {
  1111  	s.state.Lock()
  1112  	defer s.state.Unlock()
  1113  
  1114  	currentSnapYaml := `
  1115  name: gadget
  1116  type: gadget
  1117  version: 123
  1118  `
  1119  	remodelSnapYaml := `
  1120  name: new-gadget
  1121  type: gadget
  1122  version: 123
  1123  `
  1124  	mockGadget := `
  1125  type: gadget
  1126  name: gadget
  1127  volumes:
  1128    volume:
  1129      schema: gpt
  1130      bootloader: grub
  1131  `
  1132  	siCurrent := &snap.SideInfo{Revision: snap.R(123), RealName: "gadget"}
  1133  	// so that we get a directory
  1134  	currInfo := snaptest.MockSnapWithFiles(c, currentSnapYaml, siCurrent, nil)
  1135  	info := snaptest.MockSnapWithFiles(c, remodelSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, nil)
  1136  	snapf, err := snapfile.Open(info.MountDir())
  1137  	c.Assert(err, IsNil)
  1138  
  1139  	s.setupBrands(c)
  1140  
  1141  	oldModel := fakeMyModel(map[string]interface{}{
  1142  		"architecture": "amd64",
  1143  		"gadget":       "gadget",
  1144  		"kernel":       "kernel",
  1145  	})
  1146  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: oldModel}
  1147  
  1148  	// model assertion in device context
  1149  	newModel := fakeMyModel(map[string]interface{}{
  1150  		"architecture": "amd64",
  1151  		"gadget":       "new-gadget",
  1152  		"kernel":       "kernel",
  1153  	})
  1154  	remodelCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: newModel, Remodeling: true, OldDeviceModel: oldModel}
  1155  
  1156  	restore := devicestate.MockGadgetIsCompatible(func(current, update *gadget.Info) error {
  1157  		c.Assert(current.Volumes, HasLen, 1)
  1158  		c.Assert(update.Volumes, HasLen, 1)
  1159  		return errors.New("fail")
  1160  	})
  1161  	defer restore()
  1162  
  1163  	// not on classic
  1164  	release.OnClassic = true
  1165  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1166  	c.Check(err, IsNil)
  1167  	release.OnClassic = false
  1168  
  1169  	// nothing if not remodeling
  1170  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, deviceCtx)
  1171  	c.Check(err, IsNil)
  1172  
  1173  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1174  	c.Check(err, ErrorMatches, "cannot read new gadget metadata: .*/new-gadget/1/meta/gadget.yaml: no such file or directory")
  1175  
  1176  	// drop gadget.yaml to the new gadget
  1177  	err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(mockGadget), 0644)
  1178  	c.Assert(err, IsNil)
  1179  
  1180  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1181  	c.Check(err, ErrorMatches, "cannot read current gadget metadata: .*/gadget/123/meta/gadget.yaml: no such file or directory")
  1182  
  1183  	// drop gadget.yaml to the current gadget
  1184  	err = ioutil.WriteFile(filepath.Join(currInfo.MountDir(), "meta/gadget.yaml"), []byte(mockGadget), 0644)
  1185  	c.Assert(err, IsNil)
  1186  
  1187  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1188  	c.Check(err, ErrorMatches, "cannot remodel to an incompatible gadget: fail")
  1189  
  1190  	restore = devicestate.MockGadgetIsCompatible(func(current, update *gadget.Info) error {
  1191  		c.Assert(current.Volumes, HasLen, 1)
  1192  		c.Assert(update.Volumes, HasLen, 1)
  1193  		return nil
  1194  	})
  1195  	defer restore()
  1196  
  1197  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1198  	c.Check(err, IsNil)
  1199  
  1200  	// when remodeling to completely new gadget snap, there is no current
  1201  	// snap passed to the check callback
  1202  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx)
  1203  	c.Check(err, ErrorMatches, "cannot identify the current gadget snap")
  1204  
  1205  	// mock data to obtain current gadget info
  1206  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1207  		Brand: "canonical",
  1208  		Model: "gadget",
  1209  	})
  1210  	s.makeModelAssertionInState(c, "canonical", "gadget", map[string]interface{}{
  1211  		"architecture": "amd64",
  1212  		"kernel":       "kernel",
  1213  		"gadget":       "gadget",
  1214  	})
  1215  
  1216  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx)
  1217  	c.Check(err, ErrorMatches, "cannot identify the current gadget snap")
  1218  
  1219  	snapstate.Set(s.state, "gadget", &snapstate.SnapState{
  1220  		SnapType: "gadget",
  1221  		Sequence: []*snap.SideInfo{siCurrent},
  1222  		Current:  siCurrent.Revision,
  1223  		Active:   true,
  1224  	})
  1225  
  1226  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, nil, snapf, snapstate.Flags{}, remodelCtx)
  1227  	c.Check(err, IsNil)
  1228  }
  1229  
  1230  var (
  1231  	compatibleTestMockOkGadget = `
  1232  type: gadget
  1233  name: gadget
  1234  volumes:
  1235    volume:
  1236      schema: gpt
  1237      bootloader: grub
  1238      structure:
  1239        - name: foo
  1240          size: 10M
  1241          type: 00000000-0000-0000-0000-0000deadbeef
  1242  `
  1243  )
  1244  
  1245  func (s *deviceMgrRemodelSuite) testCheckGadgetRemodelCompatibleWithYaml(c *C, currentGadgetYaml, newGadgetYaml string, expErr string) {
  1246  	s.state.Lock()
  1247  	defer s.state.Unlock()
  1248  
  1249  	currentSnapYaml := `
  1250  name: gadget
  1251  type: gadget
  1252  version: 123
  1253  `
  1254  	remodelSnapYaml := `
  1255  name: new-gadget
  1256  type: gadget
  1257  version: 123
  1258  `
  1259  
  1260  	currInfo := snaptest.MockSnapWithFiles(c, currentSnapYaml, &snap.SideInfo{Revision: snap.R(123)}, [][]string{
  1261  		{"meta/gadget.yaml", currentGadgetYaml},
  1262  	})
  1263  	// gadget we're remodeling to is identical
  1264  	info := snaptest.MockSnapWithFiles(c, remodelSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, [][]string{
  1265  		{"meta/gadget.yaml", newGadgetYaml},
  1266  	})
  1267  	snapf, err := snapfile.Open(info.MountDir())
  1268  	c.Assert(err, IsNil)
  1269  
  1270  	s.setupBrands(c)
  1271  	// model assertion in device context
  1272  	oldModel := fakeMyModel(map[string]interface{}{
  1273  		"architecture": "amd64",
  1274  		"gadget":       "new-gadget",
  1275  		"kernel":       "krnl",
  1276  	})
  1277  	model := fakeMyModel(map[string]interface{}{
  1278  		"architecture": "amd64",
  1279  		"gadget":       "new-gadget",
  1280  		"kernel":       "krnl",
  1281  	})
  1282  	remodelCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: model, Remodeling: true, OldDeviceModel: oldModel}
  1283  
  1284  	err = devicestate.CheckGadgetRemodelCompatible(s.state, info, currInfo, snapf, snapstate.Flags{}, remodelCtx)
  1285  	if expErr == "" {
  1286  		c.Check(err, IsNil)
  1287  	} else {
  1288  		c.Check(err, ErrorMatches, expErr)
  1289  	}
  1290  
  1291  }
  1292  
  1293  func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatibleWithYamlHappy(c *C) {
  1294  	s.testCheckGadgetRemodelCompatibleWithYaml(c, compatibleTestMockOkGadget, compatibleTestMockOkGadget, "")
  1295  }
  1296  
  1297  func (s *deviceMgrRemodelSuite) TestCheckGadgetRemodelCompatibleWithYamlBad(c *C) {
  1298  	mockBadGadgetYaml := `
  1299  type: gadget
  1300  name: gadget
  1301  volumes:
  1302    volume:
  1303      schema: gpt
  1304      bootloader: grub
  1305      structure:
  1306        - name: foo
  1307          size: 20M
  1308          type: 00000000-0000-0000-0000-0000deadbeef
  1309  `
  1310  
  1311  	errMatch := `cannot remodel to an incompatible gadget: incompatible layout change: incompatible structure #0 \("foo"\) change: cannot change structure size from 10485760 to 20971520`
  1312  	s.testCheckGadgetRemodelCompatibleWithYaml(c, compatibleTestMockOkGadget, mockBadGadgetYaml, errMatch)
  1313  }
  1314  
  1315  func (s *deviceMgrRemodelSuite) mockTasksNopHandler(kinds ...string) {
  1316  	nopHandler := func(task *state.Task, _ *tomb.Tomb) error { return nil }
  1317  	for _, kind := range kinds {
  1318  		s.o.TaskRunner().AddHandler(kind, nopHandler, nil)
  1319  	}
  1320  }
  1321  
  1322  func (s *deviceMgrRemodelSuite) TestRemodelGadgetAssetsUpdate(c *C) {
  1323  	var currentGadgetYaml = `
  1324  volumes:
  1325    pc:
  1326      bootloader: grub
  1327      structure:
  1328         - name: foo
  1329           type: 00000000-0000-0000-0000-0000deadcafe
  1330           filesystem: ext4
  1331           size: 10M
  1332           content:
  1333              - source: foo-content
  1334                target: /
  1335         - name: bare-one
  1336           type: bare
  1337           size: 1M
  1338           content:
  1339              - image: bare.img
  1340  `
  1341  
  1342  	var remodelGadgetYaml = `
  1343  volumes:
  1344    pc:
  1345      bootloader: grub
  1346      structure:
  1347         - name: foo
  1348           type: 00000000-0000-0000-0000-0000deadcafe
  1349           filesystem: ext4
  1350           size: 10M
  1351           content:
  1352              - source: new-foo-content
  1353                target: /
  1354         - name: bare-one
  1355           type: bare
  1356           size: 1M
  1357           content:
  1358              - image: new-bare-content.img
  1359  `
  1360  
  1361  	s.state.Lock()
  1362  	s.state.Set("seeded", true)
  1363  	s.state.Set("refresh-privacy-key", "some-privacy-key")
  1364  
  1365  	s.mockTasksNopHandler("fake-download", "validate-snap", "set-model")
  1366  
  1367  	// set a model assertion we remodel from
  1368  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
  1369  		"architecture": "amd64",
  1370  		"kernel":       "pc-kernel",
  1371  		"gadget":       "pc",
  1372  		"base":         "core18",
  1373  	})
  1374  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial")
  1375  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1376  		Brand:  "canonical",
  1377  		Model:  "pc-model",
  1378  		Serial: "serial",
  1379  	})
  1380  
  1381  	// the target model
  1382  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
  1383  		"architecture": "amd64",
  1384  		"kernel":       "pc-kernel",
  1385  		"base":         "core18",
  1386  		"revision":     "1",
  1387  		// remodel to new gadget
  1388  		"gadget": "new-gadget",
  1389  	})
  1390  
  1391  	// current gadget
  1392  	siModelGadget := &snap.SideInfo{
  1393  		RealName: "pc",
  1394  		Revision: snap.R(33),
  1395  		SnapID:   "foo-id",
  1396  	}
  1397  	currentGadgetInfo := snaptest.MockSnapWithFiles(c, snapYaml, siModelGadget, [][]string{
  1398  		{"meta/gadget.yaml", currentGadgetYaml},
  1399  	})
  1400  	snapstate.Set(s.state, "pc", &snapstate.SnapState{
  1401  		SnapType: "gadget",
  1402  		Sequence: []*snap.SideInfo{siModelGadget},
  1403  		Current:  siModelGadget.Revision,
  1404  		Active:   true,
  1405  	})
  1406  
  1407  	// new gadget snap
  1408  	siNewModelGadget := &snap.SideInfo{
  1409  		RealName: "new-gadget",
  1410  		Revision: snap.R(34),
  1411  	}
  1412  	newGadgetInfo := snaptest.MockSnapWithFiles(c, snapYaml, siNewModelGadget, [][]string{
  1413  		{"meta/gadget.yaml", remodelGadgetYaml},
  1414  	})
  1415  
  1416  	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) {
  1417  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
  1418  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
  1419  		tValidate.WaitFor(tDownload)
  1420  		tGadgetUpdate := s.state.NewTask("update-gadget-assets", fmt.Sprintf("Update gadget %s", name))
  1421  		tGadgetUpdate.Set("snap-setup", &snapstate.SnapSetup{
  1422  			SideInfo: siNewModelGadget,
  1423  			Type:     snap.TypeGadget,
  1424  		})
  1425  		tGadgetUpdate.WaitFor(tValidate)
  1426  		ts := state.NewTaskSet(tDownload, tValidate, tGadgetUpdate)
  1427  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
  1428  		return ts, nil
  1429  	})
  1430  	defer restore()
  1431  	restore = release.MockOnClassic(false)
  1432  	defer restore()
  1433  
  1434  	gadgetUpdateCalled := false
  1435  	restore = devicestate.MockGadgetUpdate(func(current, update gadget.GadgetData, path string, policy gadget.UpdatePolicyFunc, _ gadget.ContentUpdateObserver) error {
  1436  		gadgetUpdateCalled = true
  1437  		c.Check(policy, NotNil)
  1438  		c.Check(reflect.ValueOf(policy).Pointer(), Equals, reflect.ValueOf(gadget.RemodelUpdatePolicy).Pointer())
  1439  		c.Check(current, DeepEquals, gadget.GadgetData{
  1440  			Info: &gadget.Info{
  1441  				Volumes: map[string]*gadget.Volume{
  1442  					"pc": {
  1443  						Bootloader: "grub",
  1444  						Schema:     "gpt",
  1445  						Structure: []gadget.VolumeStructure{{
  1446  							Name:       "foo",
  1447  							Type:       "00000000-0000-0000-0000-0000deadcafe",
  1448  							Size:       10 * quantity.SizeMiB,
  1449  							Filesystem: "ext4",
  1450  							Content: []gadget.VolumeContent{
  1451  								{UnresolvedSource: "foo-content", Target: "/"},
  1452  							},
  1453  						}, {
  1454  							Name: "bare-one",
  1455  							Type: "bare",
  1456  							Size: quantity.SizeMiB,
  1457  							Content: []gadget.VolumeContent{
  1458  								{Image: "bare.img"},
  1459  							},
  1460  						}},
  1461  					},
  1462  				},
  1463  			},
  1464  			RootDir: currentGadgetInfo.MountDir(),
  1465  		})
  1466  		c.Check(update, DeepEquals, gadget.GadgetData{
  1467  			Info: &gadget.Info{
  1468  				Volumes: map[string]*gadget.Volume{
  1469  					"pc": {
  1470  						Bootloader: "grub",
  1471  						Schema:     "gpt",
  1472  						Structure: []gadget.VolumeStructure{{
  1473  							Name:       "foo",
  1474  							Type:       "00000000-0000-0000-0000-0000deadcafe",
  1475  							Size:       10 * quantity.SizeMiB,
  1476  							Filesystem: "ext4",
  1477  							Content: []gadget.VolumeContent{
  1478  								{UnresolvedSource: "new-foo-content", Target: "/"},
  1479  							},
  1480  						}, {
  1481  							Name: "bare-one",
  1482  							Type: "bare",
  1483  							Size: quantity.SizeMiB,
  1484  							Content: []gadget.VolumeContent{
  1485  								{Image: "new-bare-content.img"},
  1486  							},
  1487  						}},
  1488  					},
  1489  				},
  1490  			},
  1491  			RootDir: newGadgetInfo.MountDir(),
  1492  		})
  1493  		return nil
  1494  	})
  1495  	defer restore()
  1496  
  1497  	chg, err := devicestate.Remodel(s.state, new)
  1498  	c.Assert(err, IsNil)
  1499  	s.state.Unlock()
  1500  
  1501  	s.settle(c)
  1502  
  1503  	s.state.Lock()
  1504  	defer s.state.Unlock()
  1505  	c.Check(chg.IsReady(), Equals, true)
  1506  	c.Check(chg.Err(), IsNil)
  1507  	c.Check(gadgetUpdateCalled, Equals, true)
  1508  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystem})
  1509  }
  1510  
  1511  func (s *deviceMgrRemodelSuite) TestRemodelGadgetAssetsParanoidCheck(c *C) {
  1512  	s.state.Lock()
  1513  	s.state.Set("seeded", true)
  1514  	s.state.Set("refresh-privacy-key", "some-privacy-key")
  1515  
  1516  	s.mockTasksNopHandler("fake-download", "validate-snap", "set-model")
  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  }
  1978  
  1979  type uc20RemodelSetModelTestCase struct {
  1980  	// errors on consecutive reseals
  1981  	resealErr    []error
  1982  	taskLogMatch string
  1983  	logMatch     string
  1984  }
  1985  
  1986  func (s *deviceMgrRemodelSuite) testUC20RemodelSetModel(c *C, tc uc20RemodelSetModelTestCase) {
  1987  	s.state.Lock()
  1988  	defer s.state.Unlock()
  1989  	s.state.Set("seeded", true)
  1990  	s.state.Set("refresh-privacy-key", "some-privacy-key")
  1991  
  1992  	devicestate.SetBootOkRan(s.mgr, true)
  1993  
  1994  	c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755), IsNil)
  1995  
  1996  	s.mockTasksNopHandler("fake-download", "validate-snap", "fake-install",
  1997  		// create recovery system requests are boot, which is not done here
  1998  		"create-recovery-system", "finalize-recovery-system")
  1999  
  2000  	// set a model assertion we remodel from
  2001  	model := s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
  2002  		"architecture": "amd64",
  2003  		"base":         "core20",
  2004  		"grade":        "dangerous",
  2005  		"snaps": []interface{}{
  2006  			map[string]interface{}{
  2007  				"name":            "pc-kernel",
  2008  				"id":              snaptest.AssertedSnapID("pc-kernel"),
  2009  				"type":            "kernel",
  2010  				"default-channel": "20",
  2011  			},
  2012  			map[string]interface{}{
  2013  				"name":            "pc",
  2014  				"id":              snaptest.AssertedSnapID("pc"),
  2015  				"type":            "gadget",
  2016  				"default-channel": "20",
  2017  			},
  2018  		},
  2019  	})
  2020  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial")
  2021  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  2022  		Brand:  "canonical",
  2023  		Model:  "pc-model",
  2024  		Serial: "serial",
  2025  	})
  2026  
  2027  	oldSeededTs := time.Now().AddDate(0, 0, -1)
  2028  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
  2029  		{
  2030  			System:    "0000",
  2031  			Model:     model.Model(),
  2032  			BrandID:   model.BrandID(),
  2033  			Timestamp: model.Timestamp(),
  2034  			SeedTime:  oldSeededTs,
  2035  		},
  2036  	})
  2037  
  2038  	// the target model
  2039  	new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
  2040  		"architecture": "amd64",
  2041  		"base":         "core20",
  2042  		"grade":        "dangerous",
  2043  		"revision":     "1",
  2044  		"snaps": []interface{}{
  2045  			map[string]interface{}{
  2046  				"name":            "pc-kernel",
  2047  				"id":              snaptest.AssertedSnapID("pc-kernel"),
  2048  				"type":            "kernel",
  2049  				"default-channel": "20",
  2050  			},
  2051  			map[string]interface{}{
  2052  				"name":            "pc",
  2053  				"id":              snaptest.AssertedSnapID("pc"),
  2054  				"type":            "gadget",
  2055  				"default-channel": "20",
  2056  			},
  2057  		},
  2058  	})
  2059  
  2060  	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) {
  2061  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
  2062  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
  2063  		tValidate.WaitFor(tDownload)
  2064  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
  2065  		tInstall.WaitFor(tValidate)
  2066  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
  2067  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
  2068  		return ts, nil
  2069  	})
  2070  	defer restore()
  2071  	restore = release.MockOnClassic(false)
  2072  	defer restore()
  2073  
  2074  	buf, restore := logger.MockLogger()
  2075  	defer restore()
  2076  
  2077  	restore = devicestate.AllowUC20RemodelTesting(true)
  2078  	defer restore()
  2079  
  2080  	m := boot.Modeenv{
  2081  		Mode: "run",
  2082  
  2083  		GoodRecoverySystems:    []string{"0000"},
  2084  		CurrentRecoverySystems: []string{"0000"},
  2085  
  2086  		Model:          model.Model(),
  2087  		BrandID:        model.BrandID(),
  2088  		Grade:          string(model.Grade()),
  2089  		ModelSignKeyID: model.SignKeyID(),
  2090  	}
  2091  	c.Assert(m.WriteTo(""), IsNil)
  2092  
  2093  	now := time.Now()
  2094  	expectedLabel := now.Format("20060102")
  2095  	restore = devicestate.MockTimeNow(func() time.Time { return now })
  2096  	defer restore()
  2097  	s.state.Set("tried-systems", []string{expectedLabel})
  2098  
  2099  	resealKeyCalls := 0
  2100  	restore = boot.MockResealKeyToModeenv(func(rootdir string, modeenv *boot.Modeenv, expectReseal bool) error {
  2101  		resealKeyCalls++
  2102  		c.Assert(len(tc.resealErr) >= resealKeyCalls, Equals, true)
  2103  		c.Check(modeenv.GoodRecoverySystems, DeepEquals, []string{"0000", expectedLabel})
  2104  		c.Check(modeenv.CurrentRecoverySystems, DeepEquals, []string{"0000", expectedLabel})
  2105  		return tc.resealErr[resealKeyCalls-1]
  2106  	})
  2107  	defer restore()
  2108  
  2109  	chg, err := devicestate.Remodel(s.state, new)
  2110  	c.Assert(err, IsNil)
  2111  	var setModelTask *state.Task
  2112  	for _, tsk := range chg.Tasks() {
  2113  		if tsk.Kind() == "set-model" {
  2114  			setModelTask = tsk
  2115  			break
  2116  		}
  2117  	}
  2118  	s.state.Unlock()
  2119  
  2120  	s.settle(c)
  2121  
  2122  	s.state.Lock()
  2123  	c.Check(chg.IsReady(), Equals, true)
  2124  	c.Check(chg.Err(), IsNil)
  2125  	c.Check(resealKeyCalls, Equals, len(tc.resealErr))
  2126  	// even if errors occur during reseal, set-model is a point of no return
  2127  	c.Check(setModelTask.Status(), Equals, state.DoneStatus)
  2128  	var seededSystems []devicestate.SeededSystem
  2129  	c.Assert(s.state.Get("seeded-systems", &seededSystems), IsNil)
  2130  	hasError := false
  2131  	for _, err := range tc.resealErr {
  2132  		if err != nil {
  2133  			hasError = true
  2134  			break
  2135  		}
  2136  	}
  2137  	if !hasError {
  2138  		c.Check(setModelTask.Log(), HasLen, 0)
  2139  
  2140  		c.Assert(seededSystems, HasLen, 2)
  2141  		// the system was seeded after our mocked 'now' or at the same
  2142  		// time if clock resolution is very low, but not before it
  2143  		c.Check(seededSystems[0].SeedTime.Before(now), Equals, false)
  2144  		seededSystems[0].SeedTime = time.Time{}
  2145  		c.Check(seededSystems[1].SeedTime.Equal(oldSeededTs), Equals, true)
  2146  		seededSystems[1].SeedTime = time.Time{}
  2147  		c.Check(seededSystems, DeepEquals, []devicestate.SeededSystem{
  2148  			{
  2149  				System:    expectedLabel,
  2150  				Model:     new.Model(),
  2151  				BrandID:   new.BrandID(),
  2152  				Revision:  new.Revision(),
  2153  				Timestamp: new.Timestamp(),
  2154  			},
  2155  			{
  2156  				System:    "0000",
  2157  				Model:     model.Model(),
  2158  				BrandID:   model.BrandID(),
  2159  				Timestamp: model.Timestamp(),
  2160  				Revision:  model.Revision(),
  2161  			},
  2162  		})
  2163  	} else {
  2164  		// however, error is still logged, both to the task and the logger
  2165  		c.Check(strings.Join(setModelTask.Log(), "\n"), Matches, tc.taskLogMatch)
  2166  		c.Check(buf.String(), Matches, tc.logMatch)
  2167  
  2168  		c.Assert(seededSystems, HasLen, 1)
  2169  		c.Check(seededSystems[0].SeedTime.Equal(oldSeededTs), Equals, true)
  2170  		seededSystems[0].SeedTime = time.Time{}
  2171  		c.Check(seededSystems, DeepEquals, []devicestate.SeededSystem{
  2172  			{
  2173  				System:    "0000",
  2174  				Model:     model.Model(),
  2175  				BrandID:   model.BrandID(),
  2176  				Timestamp: model.Timestamp(),
  2177  				Revision:  model.Revision(),
  2178  			},
  2179  		})
  2180  	}
  2181  }
  2182  
  2183  func (s *deviceMgrRemodelSuite) TestUC20RemodelSetModelHappy(c *C) {
  2184  	s.testUC20RemodelSetModel(c, uc20RemodelSetModelTestCase{
  2185  		resealErr: []error{
  2186  			nil, // promote recovery system
  2187  			nil, // device change pre model write
  2188  			nil, // device change post model write
  2189  		},
  2190  	})
  2191  }
  2192  
  2193  func (s *deviceMgrRemodelSuite) TestUC20RemodelSetModelErr(c *C) {
  2194  	s.testUC20RemodelSetModel(c, uc20RemodelSetModelTestCase{
  2195  		resealErr: []error{
  2196  			nil, // promote tried recovery system
  2197  			// keep this comment so that gofmt does not complain
  2198  			fmt.Errorf("mock reseal error"), // device change pre model write
  2199  		},
  2200  		taskLogMatch: `.* cannot complete remodel: \[cannot switch device: mock reseal error\]`,
  2201  		logMatch:     `(?s).* cannot complete remodel: \[cannot switch device: mock reseal error\].`,
  2202  	})
  2203  }
  2204  
  2205  func (s *deviceMgrRemodelSuite) TestUC20RemodelSetModelWithReboot(c *C) {
  2206  	// check that set-model does the right thing even if it is restarted
  2207  	// after an unexpected reboot; this gets complicated as we cannot
  2208  	// panic() at a random place in the task runner, so we set up the state
  2209  	// such that the set-model task completes once and is re-run again
  2210  
  2211  	s.state.Lock()
  2212  	defer s.state.Unlock()
  2213  	s.state.Set("seeded", true)
  2214  	s.state.Set("refresh-privacy-key", "some-privacy-key")
  2215  
  2216  	devicestate.SetBootOkRan(s.mgr, true)
  2217  
  2218  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
  2219  		return &freshSessionStore{}
  2220  	}
  2221  
  2222  	s.mockTasksNopHandler("fake-download", "validate-snap", "fake-install",
  2223  		"check-snap", "request-serial",
  2224  		// create recovery system requests are boot, which is not done
  2225  		// here
  2226  		"create-recovery-system", "finalize-recovery-system")
  2227  
  2228  	// set a model assertion we remodel from
  2229  	model := s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
  2230  		"architecture": "amd64",
  2231  		"base":         "core20",
  2232  		"grade":        "dangerous",
  2233  		"snaps": []interface{}{
  2234  			map[string]interface{}{
  2235  				"name":            "pc-kernel",
  2236  				"id":              snaptest.AssertedSnapID("pc-kernel"),
  2237  				"type":            "kernel",
  2238  				"default-channel": "20",
  2239  			},
  2240  			map[string]interface{}{
  2241  				"name":            "pc",
  2242  				"id":              snaptest.AssertedSnapID("pc"),
  2243  				"type":            "gadget",
  2244  				"default-channel": "20",
  2245  			},
  2246  		},
  2247  	})
  2248  	writeDeviceModelToUbuntuBoot(c, model)
  2249  	// the gadget needs to be mocked
  2250  	info := snaptest.MakeSnapFileAndDir(c, "name: pc\nversion: 1\ntype: gadget\n", nil, &snap.SideInfo{
  2251  		SnapID:   snaptest.AssertedSnapID("pc"),
  2252  		Revision: snap.R(1),
  2253  		RealName: "pc",
  2254  	})
  2255  	snapstate.Set(s.state, info.InstanceName(), &snapstate.SnapState{
  2256  		SnapType: string(info.Type()),
  2257  		Active:   true,
  2258  		Sequence: []*snap.SideInfo{&info.SideInfo},
  2259  		Current:  info.Revision,
  2260  	})
  2261  
  2262  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "serial")
  2263  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  2264  		Brand:  "canonical",
  2265  		Model:  "pc-model",
  2266  		Serial: "serial",
  2267  	})
  2268  	oldSeededTs := time.Now().AddDate(0, 0, -1)
  2269  	s.state.Set("seeded-systems", []devicestate.SeededSystem{
  2270  		{
  2271  			System:    "0000",
  2272  			Model:     model.Model(),
  2273  			BrandID:   model.BrandID(),
  2274  			Timestamp: model.Timestamp(),
  2275  			SeedTime:  oldSeededTs,
  2276  		},
  2277  	})
  2278  
  2279  	// the target model, since it's a new model altogether a reregistration
  2280  	// will be triggered
  2281  	new := s.brands.Model("canonical", "pc-new-model", map[string]interface{}{
  2282  		"architecture": "amd64",
  2283  		"base":         "core20",
  2284  		"grade":        "dangerous",
  2285  		"revision":     "1",
  2286  		"snaps": []interface{}{
  2287  			map[string]interface{}{
  2288  				"name":            "pc-kernel",
  2289  				"id":              snaptest.AssertedSnapID("pc-kernel"),
  2290  				"type":            "kernel",
  2291  				"default-channel": "20",
  2292  			},
  2293  			map[string]interface{}{
  2294  				"name":            "pc",
  2295  				"id":              snaptest.AssertedSnapID("pc"),
  2296  				"type":            "gadget",
  2297  				"default-channel": "20",
  2298  			},
  2299  		},
  2300  	})
  2301  
  2302  	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) {
  2303  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
  2304  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
  2305  		tValidate.WaitFor(tDownload)
  2306  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
  2307  		tInstall.WaitFor(tValidate)
  2308  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
  2309  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
  2310  		return ts, nil
  2311  	})
  2312  	defer restore()
  2313  	restore = release.MockOnClassic(false)
  2314  	defer restore()
  2315  
  2316  	restore = devicestate.AllowUC20RemodelTesting(true)
  2317  	defer restore()
  2318  
  2319  	m := boot.Modeenv{
  2320  		Mode: "run",
  2321  
  2322  		GoodRecoverySystems:    []string{"0000"},
  2323  		CurrentRecoverySystems: []string{"0000"},
  2324  
  2325  		Model:          model.Model(),
  2326  		BrandID:        model.BrandID(),
  2327  		Grade:          string(model.Grade()),
  2328  		ModelSignKeyID: model.SignKeyID(),
  2329  	}
  2330  	c.Assert(m.WriteTo(""), IsNil)
  2331  
  2332  	now := time.Now()
  2333  	restore = devicestate.MockTimeNow(func() time.Time { return now })
  2334  	defer restore()
  2335  	expectedLabel := now.Format("20060102")
  2336  	s.state.Set("tried-systems", []string{expectedLabel})
  2337  
  2338  	resealKeyCalls := 0
  2339  	restore = boot.MockResealKeyToModeenv(func(rootdir string, modeenv *boot.Modeenv, expectReseal bool) error {
  2340  		resealKeyCalls++
  2341  		// calls:
  2342  		// 1 - promote recovery system
  2343  		// 2 - reseal with both models
  2344  		// 3 - reseal with new model as current
  2345  		// (mocked reboot)
  2346  		// 4 - promote recovery system
  2347  		// 5 - reseal with new model as current and try; before reboot
  2348  		//     set-model changed the model in the state, the new model
  2349  		//     replaced the old one, and thus the remodel context
  2350  		//     carries the new model in ground context
  2351  		// 6 - reseal with new model as current
  2352  		c.Check(modeenv.GoodRecoverySystems, DeepEquals, []string{"0000", expectedLabel})
  2353  		c.Check(modeenv.CurrentRecoverySystems, DeepEquals, []string{"0000", expectedLabel})
  2354  		switch resealKeyCalls {
  2355  		case 2:
  2356  			c.Check(modeenv.Model, Equals, model.Model())
  2357  			c.Check(modeenv.TryModel, Equals, new.Model())
  2358  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2359  				testutil.FileContains, fmt.Sprintf("model: %s\n", model.Model()))
  2360  			// old model's revision is 0
  2361  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2362  				Not(testutil.FileContains), "revision:")
  2363  		case 3:
  2364  			c.Check(modeenv.Model, Equals, new.Model())
  2365  			c.Check(modeenv.TryModel, Equals, "")
  2366  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2367  				testutil.FileContains, fmt.Sprintf("model: %s\n", new.Model()))
  2368  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2369  				testutil.FileContains, fmt.Sprintf("revision: %v\n", new.Revision()))
  2370  		case 5:
  2371  			c.Check(modeenv.Model, Equals, model.Model())
  2372  			c.Check(modeenv.TryModel, Equals, new.Model())
  2373  			// we are in an after reboot scenario, the file contains
  2374  			// the new model
  2375  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2376  				testutil.FileContains, fmt.Sprintf("model: %s\n", new.Model()))
  2377  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2378  				testutil.FileContains, fmt.Sprintf("revision: %v\n", new.Revision()))
  2379  		case 6:
  2380  			c.Check(modeenv.Model, Equals, new.Model())
  2381  			c.Check(modeenv.TryModel, Equals, "")
  2382  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2383  				testutil.FileContains, fmt.Sprintf("model: %s\n", new.Model()))
  2384  			c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"),
  2385  				testutil.FileContains, fmt.Sprintf("revision: %v\n", new.Revision()))
  2386  		}
  2387  		if resealKeyCalls > 6 {
  2388  			c.Fatalf("unexpected #%v call to reseal key to modeenv", resealKeyCalls)
  2389  		}
  2390  		return nil
  2391  	})
  2392  	defer restore()
  2393  
  2394  	chg, err := devicestate.Remodel(s.state, new)
  2395  	c.Assert(err, IsNil)
  2396  
  2397  	// since we cannot panic in random place in code that runs under
  2398  	// taskrunner, we reset the task status and retry the change again, but
  2399  	// we cannot do that once a change has become ready, thus inject a task
  2400  	// that will request a reboot and keep retrying, thus stopping execution
  2401  	// and keeping the change in a not ready state
  2402  	fakeRebootCalls := 0
  2403  	fakeRebootCallsReady := false
  2404  	s.o.TaskRunner().AddHandler("fake-reboot-and-stall", func(task *state.Task, _ *tomb.Tomb) error {
  2405  		fakeRebootCalls++
  2406  		if fakeRebootCalls == 1 {
  2407  			st := task.State()
  2408  			st.Lock()
  2409  			defer st.Unlock()
  2410  			// not strictly needed, but underlines there's a reboot
  2411  			// happening
  2412  			st.RequestRestart(state.RestartSystemNow)
  2413  		}
  2414  		if fakeRebootCallsReady {
  2415  			return nil
  2416  		}
  2417  		// we're not ready, so that the change does not complete yet
  2418  		return &state.Retry{}
  2419  	}, nil)
  2420  	fakeRebootTask := s.state.NewTask("fake-reboot-and-stall", "fake reboot and stalling injected by tests")
  2421  	chg.AddTask(fakeRebootTask)
  2422  	var setModelTask *state.Task
  2423  	for _, tsk := range chg.Tasks() {
  2424  		if tsk.Kind() == "set-model" {
  2425  			c.Fatalf("set-model present too early")
  2426  		}
  2427  		// make fake-reboot run after all tasks
  2428  		if tsk.Kind() != "fake-reboot-and-stall" {
  2429  			fakeRebootTask.WaitFor(tsk)
  2430  		}
  2431  	}
  2432  	s.state.Unlock()
  2433  
  2434  	s.settle(c)
  2435  
  2436  	s.state.Lock()
  2437  	// set model was injected by prepare-remodeling
  2438  	for _, tsk := range chg.Tasks() {
  2439  		if tsk.Kind() == "set-model" {
  2440  			setModelTask = tsk
  2441  			break
  2442  		}
  2443  	}
  2444  	c.Check(chg.IsReady(), Equals, false)
  2445  	c.Check(chg.Err(), IsNil)
  2446  	// injected by fake restart
  2447  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
  2448  	// 3 calls: promote tried system, old & new model, just the new model
  2449  	c.Check(resealKeyCalls, Equals, 3)
  2450  	// even if errors occur during reseal, set-model is done
  2451  	c.Check(setModelTask.Status(), Equals, state.DoneStatus)
  2452  
  2453  	// reset the set-model state back to do, simulating a task restart after a reboot
  2454  	setModelTask.SetStatus(state.DoStatus)
  2455  
  2456  	// the seeded systems has already been populated
  2457  	var seededSystems []devicestate.SeededSystem
  2458  	c.Assert(s.state.Get("seeded-systems", &seededSystems), IsNil)
  2459  	c.Assert(seededSystems, HasLen, 2)
  2460  	// we need to be smart about checking seed time, also verify
  2461  	// timestamps separately to avoid timezone problems
  2462  	newSeededTs := seededSystems[0].SeedTime
  2463  	// the system was seeded after our mocked 'now' or at the same
  2464  	// time if clock resolution is very low, but not before it
  2465  	c.Check(newSeededTs.Before(now), Equals, false)
  2466  	seededSystems[0].SeedTime = time.Time{}
  2467  	c.Check(seededSystems[1].SeedTime.Equal(oldSeededTs), Equals, true)
  2468  	seededSystems[1].SeedTime = time.Time{}
  2469  	expectedSeededSystems := []devicestate.SeededSystem{
  2470  		{
  2471  			System:    expectedLabel,
  2472  			Model:     new.Model(),
  2473  			BrandID:   new.BrandID(),
  2474  			Revision:  new.Revision(),
  2475  			Timestamp: new.Timestamp(),
  2476  		},
  2477  		{
  2478  			System:    "0000",
  2479  			Model:     model.Model(),
  2480  			BrandID:   model.BrandID(),
  2481  			Timestamp: model.Timestamp(),
  2482  			Revision:  model.Revision(),
  2483  		},
  2484  	}
  2485  	c.Check(seededSystems, DeepEquals, expectedSeededSystems)
  2486  
  2487  	fakeRebootCallsReady = true
  2488  	// now redo the task again
  2489  	s.state.Unlock()
  2490  
  2491  	s.settle(c)
  2492  
  2493  	s.state.Lock()
  2494  	c.Check(chg.IsReady(), Equals, true)
  2495  	c.Check(chg.Err(), IsNil)
  2496  	c.Check(resealKeyCalls, Equals, 6)
  2497  	c.Check(setModelTask.Status(), Equals, state.DoneStatus)
  2498  
  2499  	c.Assert(s.state.Get("seeded-systems", &seededSystems), IsNil)
  2500  	c.Assert(seededSystems, HasLen, 2)
  2501  	// seed time should be unchanged
  2502  	c.Check(seededSystems[0].SeedTime.Equal(newSeededTs), Equals, true)
  2503  	seededSystems[0].SeedTime = time.Time{}
  2504  	c.Check(seededSystems[1].SeedTime.Equal(oldSeededTs), Equals, true)
  2505  	seededSystems[1].SeedTime = time.Time{}
  2506  	c.Check(seededSystems, DeepEquals, []devicestate.SeededSystem{
  2507  		{
  2508  			System:    expectedLabel,
  2509  			Model:     new.Model(),
  2510  			BrandID:   new.BrandID(),
  2511  			Revision:  new.Revision(),
  2512  			Timestamp: new.Timestamp(),
  2513  		},
  2514  		{
  2515  			System:    "0000",
  2516  			Model:     model.Model(),
  2517  			BrandID:   model.BrandID(),
  2518  			Timestamp: model.Timestamp(),
  2519  			Revision:  model.Revision(),
  2520  		},
  2521  	})
  2522  }