github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/devicestate/devicestate.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // Package devicestate implements the manager and state aspects responsible
    21  // for the device identity and policies.
    22  package devicestate
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"sync"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/gadget"
    31  	"github.com/snapcore/snapd/i18n"
    32  	"github.com/snapcore/snapd/logger"
    33  	"github.com/snapcore/snapd/netutil"
    34  	"github.com/snapcore/snapd/overlord/assertstate"
    35  	"github.com/snapcore/snapd/overlord/auth"
    36  	"github.com/snapcore/snapd/overlord/configstate/config"
    37  	"github.com/snapcore/snapd/overlord/devicestate/internal"
    38  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    39  	"github.com/snapcore/snapd/overlord/snapstate"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/release"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/snap/naming"
    44  )
    45  
    46  var (
    47  	snapstateInstallWithDeviceContext = snapstate.InstallWithDeviceContext
    48  	snapstateUpdateWithDeviceContext  = snapstate.UpdateWithDeviceContext
    49  )
    50  
    51  // findModel returns the device model assertion.
    52  func findModel(st *state.State) (*asserts.Model, error) {
    53  	device, err := internal.Device(st)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	if device.Brand == "" || device.Model == "" {
    59  		return nil, state.ErrNoState
    60  	}
    61  
    62  	a, err := assertstate.DB(st).Find(asserts.ModelType, map[string]string{
    63  		"series":   release.Series,
    64  		"brand-id": device.Brand,
    65  		"model":    device.Model,
    66  	})
    67  	if asserts.IsNotFound(err) {
    68  		return nil, state.ErrNoState
    69  	}
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	return a.(*asserts.Model), nil
    75  }
    76  
    77  // findSerial returns the device serial assertion. device is optional and used instead of the global state if provided.
    78  func findSerial(st *state.State, device *auth.DeviceState) (*asserts.Serial, error) {
    79  	if device == nil {
    80  		var err error
    81  		device, err = internal.Device(st)
    82  		if err != nil {
    83  			return nil, err
    84  		}
    85  	}
    86  
    87  	if device.Serial == "" {
    88  		return nil, state.ErrNoState
    89  	}
    90  
    91  	a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{
    92  		"brand-id": device.Brand,
    93  		"model":    device.Model,
    94  		"serial":   device.Serial,
    95  	})
    96  	if asserts.IsNotFound(err) {
    97  		return nil, state.ErrNoState
    98  	}
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	return a.(*asserts.Serial), nil
   104  }
   105  
   106  // auto-refresh
   107  func canAutoRefresh(st *state.State) (bool, error) {
   108  	// we need to be seeded first
   109  	var seeded bool
   110  	st.Get("seeded", &seeded)
   111  	if !seeded {
   112  		return false, nil
   113  	}
   114  
   115  	// Either we have a serial or we try anyway if we attempted
   116  	// for a while to get a serial, this would allow us to at
   117  	// least upgrade core if that can help.
   118  	if ensureOperationalAttempts(st) >= 3 {
   119  		return true, nil
   120  	}
   121  
   122  	// Check model exists, for sanity. We always have a model, either
   123  	// seeded or a generic one that ships with snapd.
   124  	_, err := findModel(st)
   125  	if err == state.ErrNoState {
   126  		return false, nil
   127  	}
   128  	if err != nil {
   129  		return false, err
   130  	}
   131  
   132  	_, err = findSerial(st, nil)
   133  	if err == state.ErrNoState {
   134  		return false, nil
   135  	}
   136  	if err != nil {
   137  		return false, err
   138  	}
   139  
   140  	return true, nil
   141  }
   142  
   143  func checkGadgetOrKernel(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error {
   144  	kind := ""
   145  	var snapType snap.Type
   146  	var getName func(*asserts.Model) string
   147  	switch snapInfo.Type() {
   148  	case snap.TypeGadget:
   149  		kind = "gadget"
   150  		snapType = snap.TypeGadget
   151  		getName = (*asserts.Model).Gadget
   152  	case snap.TypeKernel:
   153  		if release.OnClassic {
   154  			return fmt.Errorf("cannot install a kernel snap on classic")
   155  		}
   156  
   157  		kind = "kernel"
   158  		snapType = snap.TypeKernel
   159  		getName = (*asserts.Model).Kernel
   160  	default:
   161  		// not a relevant check
   162  		return nil
   163  	}
   164  
   165  	model := deviceCtx.Model()
   166  
   167  	if snapInfo.SnapID != "" {
   168  		snapDecl, err := assertstate.SnapDeclaration(st, snapInfo.SnapID)
   169  		if err != nil {
   170  			return fmt.Errorf("internal error: cannot find snap declaration for %q: %v", snapInfo.InstanceName(), err)
   171  		}
   172  		publisher := snapDecl.PublisherID()
   173  		if publisher != "canonical" && publisher != model.BrandID() {
   174  			return fmt.Errorf("cannot install %s %q published by %q for model by %q", kind, snapInfo.InstanceName(), publisher, model.BrandID())
   175  		}
   176  	} else {
   177  		logger.Noticef("installing unasserted %s %q", kind, snapInfo.InstanceName())
   178  	}
   179  
   180  	found, err := snapstate.HasSnapOfType(st, snapType)
   181  	if err != nil {
   182  		return fmt.Errorf("cannot detect original %s snap: %v", kind, err)
   183  	}
   184  	if found {
   185  		// already installed, snapstate takes care
   186  		return nil
   187  	}
   188  	// first installation of a gadget/kernel
   189  
   190  	expectedName := getName(model)
   191  	if expectedName == "" { // can happen only on classic
   192  		return fmt.Errorf("cannot install %s snap on classic if not requested by the model", kind)
   193  	}
   194  
   195  	if snapInfo.InstanceName() != snapInfo.SnapName() {
   196  		return fmt.Errorf("cannot install %q, parallel installation of kernel or gadget snaps is not supported", snapInfo.InstanceName())
   197  	}
   198  
   199  	if snapInfo.InstanceName() != expectedName {
   200  		return fmt.Errorf("cannot install %s %q, model assertion requests %q", kind, snapInfo.InstanceName(), expectedName)
   201  	}
   202  
   203  	return nil
   204  }
   205  
   206  func checkGadgetValid(st *state.State, snapInfo, _ *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error {
   207  	if snapInfo.Type() != snap.TypeGadget {
   208  		// not a gadget, nothing to do
   209  		return nil
   210  	}
   211  	if deviceCtx.ForRemodeling() {
   212  		// in this case the gadget is checked by
   213  		// checkGadgetRemodelCompatible
   214  		return nil
   215  	}
   216  
   217  	// do basic validity checks on the gadget against its model constraints
   218  	_, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model())
   219  	return err
   220  }
   221  
   222  var once sync.Once
   223  
   224  func delayedCrossMgrInit() {
   225  	once.Do(func() {
   226  		snapstate.AddCheckSnapCallback(checkGadgetOrKernel)
   227  		snapstate.AddCheckSnapCallback(checkGadgetValid)
   228  		snapstate.AddCheckSnapCallback(checkGadgetRemodelCompatible)
   229  	})
   230  	snapstate.CanAutoRefresh = canAutoRefresh
   231  	snapstate.CanManageRefreshes = CanManageRefreshes
   232  	snapstate.IsOnMeteredConnection = netutil.IsOnMeteredConnection
   233  	snapstate.DeviceCtx = DeviceCtx
   234  	snapstate.Remodeling = Remodeling
   235  }
   236  
   237  // proxyStore returns the store assertion for the proxy store if one is set.
   238  func proxyStore(st *state.State, tr *config.Transaction) (*asserts.Store, error) {
   239  	var proxyStore string
   240  	err := tr.GetMaybe("core", "proxy.store", &proxyStore)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	if proxyStore == "" {
   245  		return nil, state.ErrNoState
   246  	}
   247  
   248  	a, err := assertstate.DB(st).Find(asserts.StoreType, map[string]string{
   249  		"store": proxyStore,
   250  	})
   251  	if asserts.IsNotFound(err) {
   252  		return nil, state.ErrNoState
   253  	}
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	return a.(*asserts.Store), nil
   259  }
   260  
   261  // interfaceConnected returns true if the given snap/interface names
   262  // are connected
   263  func interfaceConnected(st *state.State, snapName, ifName string) bool {
   264  	conns, err := ifacerepo.Get(st).Connected(snapName, ifName)
   265  	return err == nil && len(conns) > 0
   266  }
   267  
   268  // CanManageRefreshes returns true if the device can be
   269  // switched to the "core.refresh.schedule=managed" mode.
   270  //
   271  // TODO:
   272  // - Move the CanManageRefreshes code into the ifstate
   273  // - Look at the connections and find the connection for snapd-control
   274  //   with the managed attribute
   275  // - Take the snap from this connection and look at the snapstate to see
   276  //   if that snap has a snap declaration (to ensure it comes from the store)
   277  func CanManageRefreshes(st *state.State) bool {
   278  	snapStates, err := snapstate.All(st)
   279  	if err != nil {
   280  		return false
   281  	}
   282  	for _, snapst := range snapStates {
   283  		// Always get the current info even if the snap is currently
   284  		// being operated on or if its disabled.
   285  		info, err := snapst.CurrentInfo()
   286  		if err != nil {
   287  			continue
   288  		}
   289  		if info.Broken != "" {
   290  			continue
   291  		}
   292  		// The snap must have a snap declaration (implies that
   293  		// its from the store)
   294  		if _, err := assertstate.SnapDeclaration(st, info.SideInfo.SnapID); err != nil {
   295  			continue
   296  		}
   297  
   298  		for _, plugInfo := range info.Plugs {
   299  			if plugInfo.Interface == "snapd-control" && plugInfo.Attrs["refresh-schedule"] == "managed" {
   300  				snapName := info.InstanceName()
   301  				plugName := plugInfo.Name
   302  				if interfaceConnected(st, snapName, plugName) {
   303  					return true
   304  				}
   305  			}
   306  		}
   307  	}
   308  
   309  	return false
   310  }
   311  
   312  func getAllRequiredSnapsForModel(model *asserts.Model) *naming.SnapSet {
   313  	reqSnaps := model.RequiredWithEssentialSnaps()
   314  	return naming.NewSnapSet(reqSnaps)
   315  }
   316  
   317  // extractDownloadInstallEdgesFromTs extracts the first, last download
   318  // phase and install phase tasks from a TaskSet
   319  func extractDownloadInstallEdgesFromTs(ts *state.TaskSet) (firstDl, lastDl, firstInst, lastInst *state.Task, err error) {
   320  	edgeTask, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge)
   321  	if err != nil {
   322  		return nil, nil, nil, nil, err
   323  	}
   324  	tasks := ts.Tasks()
   325  	// we know we always start with downloads
   326  	firstDl = tasks[0]
   327  	// and always end with installs
   328  	lastInst = tasks[len(tasks)-1]
   329  
   330  	var edgeTaskIndex int
   331  	for i, task := range tasks {
   332  		if task == edgeTask {
   333  			edgeTaskIndex = i
   334  			break
   335  		}
   336  	}
   337  	return firstDl, tasks[edgeTaskIndex], tasks[edgeTaskIndex+1], lastInst, nil
   338  }
   339  
   340  func notInstalled(st *state.State, name string) (bool, error) {
   341  	_, err := snapstate.CurrentInfo(st, name)
   342  	_, isNotInstalled := err.(*snap.NotInstalledError)
   343  	if isNotInstalled {
   344  		return true, nil
   345  	}
   346  	return false, err
   347  }
   348  
   349  func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Model, deviceCtx snapstate.DeviceContext, fromChange string) ([]*state.TaskSet, error) {
   350  	userID := 0
   351  	var tss []*state.TaskSet
   352  
   353  	// kernel
   354  	if current.Kernel() == new.Kernel() && current.KernelTrack() != new.KernelTrack() {
   355  		ts, err := snapstateUpdateWithDeviceContext(st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange)
   356  		if err != nil {
   357  			return nil, err
   358  		}
   359  		tss = append(tss, ts)
   360  	}
   361  
   362  	var ts *state.TaskSet
   363  	if current.Kernel() != new.Kernel() {
   364  		needsInstall, err := notInstalled(st, new.Kernel())
   365  		if err != nil {
   366  			return nil, err
   367  		}
   368  		if needsInstall {
   369  			ts, err = snapstateInstallWithDeviceContext(ctx, st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{}, deviceCtx, fromChange)
   370  		} else {
   371  			ts, err = snapstate.LinkNewBaseOrKernel(st, new.Base())
   372  		}
   373  		if err != nil {
   374  			return nil, err
   375  		}
   376  		tss = append(tss, ts)
   377  	}
   378  	if current.Base() != new.Base() {
   379  		needsInstall, err := notInstalled(st, new.Base())
   380  		if err != nil {
   381  			return nil, err
   382  		}
   383  		if needsInstall {
   384  			ts, err = snapstateInstallWithDeviceContext(ctx, st, new.Base(), nil, userID, snapstate.Flags{}, deviceCtx, fromChange)
   385  		} else {
   386  			ts, err = snapstate.LinkNewBaseOrKernel(st, new.Base())
   387  		}
   388  		if err != nil {
   389  			return nil, err
   390  		}
   391  		tss = append(tss, ts)
   392  	}
   393  	// gadget
   394  	if current.Gadget() == new.Gadget() && current.GadgetTrack() != new.GadgetTrack() {
   395  		ts, err := snapstateUpdateWithDeviceContext(st, new.Gadget(), &snapstate.RevisionOptions{Channel: new.GadgetTrack()}, userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange)
   396  		if err != nil {
   397  			return nil, err
   398  		}
   399  		tss = append(tss, ts)
   400  	}
   401  	if current.Gadget() != new.Gadget() {
   402  		ts, err := snapstateInstallWithDeviceContext(ctx, st, new.Gadget(), &snapstate.RevisionOptions{Channel: new.GadgetTrack()}, userID, snapstate.Flags{}, deviceCtx, fromChange)
   403  		if err != nil {
   404  			return nil, err
   405  		}
   406  		tss = append(tss, ts)
   407  	}
   408  
   409  	// add new required-snaps, no longer required snaps will be cleaned
   410  	// in "set-model"
   411  	for _, snapRef := range new.RequiredNoEssentialSnaps() {
   412  		// TODO|XXX: have methods that take refs directly
   413  		// to respect the snap ids
   414  		needsInstall, err := notInstalled(st, snapRef.SnapName())
   415  		if err != nil {
   416  			return nil, err
   417  		}
   418  		if needsInstall {
   419  			// If the snap is not installed we need to install it now.
   420  			ts, err := snapstateInstallWithDeviceContext(ctx, st, snapRef.SnapName(), nil, userID, snapstate.Flags{Required: true}, deviceCtx, fromChange)
   421  			if err != nil {
   422  				return nil, err
   423  			}
   424  			tss = append(tss, ts)
   425  		}
   426  	}
   427  	// TODO: Validate that all bases and default-providers are part
   428  	//       of the install tasksets and error if not. If the
   429  	//       prereq task handler check starts adding installs into
   430  	//       our remodel change our carefully constructed wait chain
   431  	//       breaks down.
   432  
   433  	// Ensure all download/check tasks are run *before* the install
   434  	// tasks. During a remodel the network may not be available so
   435  	// we need to ensure we have everything local.
   436  	var lastDownloadInChain, firstInstallInChain *state.Task
   437  	var prevDownload, prevInstall *state.Task
   438  	for _, ts := range tss {
   439  		// make sure all things happen sequentially
   440  		// Terminology
   441  		// A <- B means B waits for A
   442  		// "download,verify" are part of the "Download" phase
   443  		// "link,start" is part of "Install" phase
   444  		//
   445  		// - all tasks inside ts{Download,Install} already wait for
   446  		//   each other so the chains look something like this:
   447  		//     download1 <- verify1 <- install1
   448  		//     download2 <- verify2 <- install2
   449  		//     download3 <- verify3 <- install3
   450  		// - add wait of each first ts{Download,Install} task for
   451  		//   the last previous ts{Download,Install} task
   452  		//   Our chains now looks like:
   453  		//     download1 <- verify1 <- install1 (as before)
   454  		//     download2 <- verify2 <- install2 (as before)
   455  		//     download3 <- verify3 <- install3 (as before)
   456  		//     verify1 <- download2 (added)
   457  		//     verify2 <- download3 (added)
   458  		//     install1  <- install2 (added)
   459  		//     install2  <- install3 (added)
   460  		downloadStart, downloadLast, installFirst, installLast, err := extractDownloadInstallEdgesFromTs(ts)
   461  		if err != nil {
   462  			return nil, fmt.Errorf("cannot remodel: %v", err)
   463  		}
   464  		if prevDownload != nil {
   465  			// XXX: we don't strictly need to serialize the download
   466  			downloadStart.WaitFor(prevDownload)
   467  		}
   468  		if prevInstall != nil {
   469  			installFirst.WaitFor(prevInstall)
   470  		}
   471  		prevDownload = downloadLast
   472  		prevInstall = installLast
   473  		// update global state
   474  		lastDownloadInChain = downloadLast
   475  		if firstInstallInChain == nil {
   476  			firstInstallInChain = installFirst
   477  		}
   478  	}
   479  	// Make sure the first install waits for the last download. With this
   480  	// our (simplified) wait chain looks like:
   481  	// download1 <- verify1 <- download2 <- verify2 <- download3 <- verify3 <- install1 <- install2 <- install3
   482  	if firstInstallInChain != nil && lastDownloadInChain != nil {
   483  		firstInstallInChain.WaitFor(lastDownloadInChain)
   484  	}
   485  
   486  	// Set the new model assertion - this *must* be the last thing done
   487  	// by the change.
   488  	setModel := st.NewTask("set-model", i18n.G("Set new model assertion"))
   489  	for _, tsPrev := range tss {
   490  		setModel.WaitAll(tsPrev)
   491  	}
   492  	tss = append(tss, state.NewTaskSet(setModel))
   493  
   494  	return tss, nil
   495  }
   496  
   497  // Remodel takes a new model assertion and generates a change that
   498  // takes the device from the old to the new model or an error if the
   499  // transition is not possible.
   500  //
   501  // TODO:
   502  // - Check estimated disk size delta
   503  // - Check all relevant snaps exist in new store
   504  //   (need to check that even unchanged snaps are accessible)
   505  // - Make sure this works with Core 20 as well, in the Core 20 case
   506  //   we must enforce the default-channels from the model as well
   507  func Remodel(st *state.State, new *asserts.Model) (*state.Change, error) {
   508  	var seeded bool
   509  	err := st.Get("seeded", &seeded)
   510  	if err != nil && err != state.ErrNoState {
   511  		return nil, err
   512  	}
   513  	if !seeded {
   514  		return nil, fmt.Errorf("cannot remodel until fully seeded")
   515  	}
   516  
   517  	current, err := findModel(st)
   518  	if err != nil {
   519  		return nil, err
   520  	}
   521  	if current.Series() != new.Series() {
   522  		return nil, fmt.Errorf("cannot remodel to different series yet")
   523  	}
   524  
   525  	// TODO:UC20: support remodel, also ensure we never remodel to a lower
   526  	// grade
   527  	if current.Grade() != asserts.ModelGradeUnset {
   528  		return nil, fmt.Errorf("cannot remodel Ubuntu Core 20 models yet")
   529  	}
   530  	if new.Grade() != asserts.ModelGradeUnset {
   531  		return nil, fmt.Errorf("cannot remodel to Ubuntu Core 20 models yet")
   532  	}
   533  
   534  	// TODO: we need dedicated assertion language to permit for
   535  	// model transitions before we allow cross vault
   536  	// transitions.
   537  
   538  	remodelKind := ClassifyRemodel(current, new)
   539  
   540  	// TODO: should we restrict remodel from one arch to another?
   541  	// There are valid use-cases here though, i.e. amd64 machine that
   542  	// remodels itself to/from i386 (if the HW can do both 32/64 bit)
   543  	if current.Architecture() != new.Architecture() {
   544  		return nil, fmt.Errorf("cannot remodel to different architectures yet")
   545  	}
   546  
   547  	// calculate snap differences between the two models
   548  	// FIXME: this needs work to switch from core->bases
   549  	if current.Base() == "" && new.Base() != "" {
   550  		return nil, fmt.Errorf("cannot remodel from core to bases yet")
   551  	}
   552  
   553  	// TODO: should we run a remodel only while no other change is
   554  	// running?  do we add a task upfront that waits for that to be
   555  	// true? Do we do this only for the more complicated cases
   556  	// (anything more than adding required-snaps really)?
   557  
   558  	remodCtx, err := remodelCtx(st, current, new)
   559  	if err != nil {
   560  		return nil, err
   561  	}
   562  
   563  	var tss []*state.TaskSet
   564  	switch remodelKind {
   565  	case ReregRemodel:
   566  		// nothing else can be in-flight
   567  		for _, chg := range st.Changes() {
   568  			if !chg.IsReady() {
   569  				return nil, &snapstate.ChangeConflictError{Message: "cannot start complete remodel, other changes are in progress"}
   570  			}
   571  		}
   572  
   573  		requestSerial := st.NewTask("request-serial", i18n.G("Request new device serial"))
   574  
   575  		prepare := st.NewTask("prepare-remodeling", i18n.G("Prepare remodeling"))
   576  		prepare.WaitFor(requestSerial)
   577  		ts := state.NewTaskSet(requestSerial, prepare)
   578  		tss = []*state.TaskSet{ts}
   579  	case StoreSwitchRemodel:
   580  		sto := remodCtx.Store()
   581  		if sto == nil {
   582  			return nil, fmt.Errorf("internal error: a store switch remodeling should have built a store")
   583  		}
   584  		// ensure a new session accounting for the new brand store
   585  		st.Unlock()
   586  		_, err := sto.EnsureDeviceSession()
   587  		st.Lock()
   588  		if err != nil {
   589  			return nil, fmt.Errorf("cannot get a store session based on the new model assertion: %v", err)
   590  		}
   591  		fallthrough
   592  	case UpdateRemodel:
   593  		var err error
   594  		tss, err = remodelTasks(context.TODO(), st, current, new, remodCtx, "")
   595  		if err != nil {
   596  			return nil, err
   597  		}
   598  	}
   599  
   600  	// we potentially released the lock a couple of times here:
   601  	// make sure the current model is essentially the same as when
   602  	// we started
   603  	current1, err := findModel(st)
   604  	if err != nil {
   605  		return nil, err
   606  	}
   607  	if current.BrandID() != current1.BrandID() || current.Model() != current1.Model() || current.Revision() != current1.Revision() {
   608  		return nil, &snapstate.ChangeConflictError{Message: fmt.Sprintf("cannot start remodel, clashing with concurrent remodel to %v/%v (%v)", current1.BrandID(), current1.Model(), current1.Revision())}
   609  	}
   610  	// make sure another unfinished remodel wasn't already setup either
   611  	if Remodeling(st) {
   612  		return nil, &snapstate.ChangeConflictError{Message: "cannot start remodel, clashing with concurrent one"}
   613  	}
   614  
   615  	var msg string
   616  	if current.BrandID() == new.BrandID() && current.Model() == new.Model() {
   617  		msg = fmt.Sprintf(i18n.G("Refresh model assertion from revision %v to %v"), current.Revision(), new.Revision())
   618  	} else {
   619  		msg = fmt.Sprintf(i18n.G("Remodel device to %v/%v (%v)"), new.BrandID(), new.Model(), new.Revision())
   620  	}
   621  
   622  	chg := st.NewChange("remodel", msg)
   623  	remodCtx.Init(chg)
   624  	for _, ts := range tss {
   625  		chg.AddAll(ts)
   626  	}
   627  
   628  	return chg, nil
   629  }
   630  
   631  // Remodeling returns true whether there's a remodeling in progress
   632  func Remodeling(st *state.State) bool {
   633  	for _, chg := range st.Changes() {
   634  		if !chg.IsReady() && chg.Kind() == "remodel" {
   635  			return true
   636  		}
   637  	}
   638  	return false
   639  }