github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/handlers_link_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 snapstate_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/boot"
    33  	"github.com/snapcore/snapd/boot/boottest"
    34  	"github.com/snapcore/snapd/bootloader"
    35  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/overlord/auth"
    38  	"github.com/snapcore/snapd/overlord/configstate/config"
    39  	"github.com/snapcore/snapd/overlord/servicestate"
    40  	"github.com/snapcore/snapd/overlord/snapstate"
    41  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    42  	"github.com/snapcore/snapd/overlord/state"
    43  	"github.com/snapcore/snapd/release"
    44  	"github.com/snapcore/snapd/snap"
    45  	"github.com/snapcore/snapd/snap/snaptest"
    46  	"github.com/snapcore/snapd/testutil"
    47  )
    48  
    49  type linkSnapSuite struct {
    50  	baseHandlerSuite
    51  
    52  	stateBackend *witnessRestartReqStateBackend
    53  }
    54  
    55  var _ = Suite(&linkSnapSuite{})
    56  
    57  type witnessRestartReqStateBackend struct {
    58  	restartRequested []state.RestartType
    59  }
    60  
    61  func (b *witnessRestartReqStateBackend) Checkpoint([]byte) error {
    62  	return nil
    63  }
    64  
    65  func (b *witnessRestartReqStateBackend) RequestRestart(t state.RestartType) {
    66  	b.restartRequested = append(b.restartRequested, t)
    67  }
    68  
    69  func (b *witnessRestartReqStateBackend) EnsureBefore(time.Duration) {}
    70  
    71  func (s *linkSnapSuite) SetUpTest(c *C) {
    72  	s.stateBackend = &witnessRestartReqStateBackend{}
    73  
    74  	s.setup(c, s.stateBackend)
    75  
    76  	s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel()))
    77  
    78  	oldSnapServiceOptions := snapstate.SnapServiceOptions
    79  	snapstate.SnapServiceOptions = servicestate.SnapServiceOptions
    80  	s.AddCleanup(func() {
    81  		snapstate.SnapServiceOptions = oldSnapServiceOptions
    82  	})
    83  }
    84  
    85  func checkHasCookieForSnap(c *C, st *state.State, instanceName string) {
    86  	var contexts map[string]interface{}
    87  	err := st.Get("snap-cookies", &contexts)
    88  	c.Assert(err, IsNil)
    89  	c.Check(contexts, HasLen, 1)
    90  
    91  	for _, snap := range contexts {
    92  		if instanceName == snap {
    93  			return
    94  		}
    95  	}
    96  	panic(fmt.Sprintf("Cookie missing for snap %q", instanceName))
    97  }
    98  
    99  func (s *linkSnapSuite) TestDoLinkSnapSuccess(c *C) {
   100  	// we start without the auxiliary store info
   101  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
   102  
   103  	lp := &testLinkParticipant{}
   104  	restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp})
   105  	defer restore()
   106  
   107  	s.state.Lock()
   108  	t := s.state.NewTask("link-snap", "test")
   109  	t.Set("snap-setup", &snapstate.SnapSetup{
   110  		SideInfo: &snap.SideInfo{
   111  			RealName: "foo",
   112  			Revision: snap.R(33),
   113  			SnapID:   "foo-id",
   114  		},
   115  		Channel: "beta",
   116  		UserID:  2,
   117  	})
   118  	s.state.NewChange("dummy", "...").AddTask(t)
   119  
   120  	s.state.Unlock()
   121  
   122  	s.se.Ensure()
   123  	s.se.Wait()
   124  
   125  	s.state.Lock()
   126  	defer s.state.Unlock()
   127  	var snapst snapstate.SnapState
   128  	err := snapstate.Get(s.state, "foo", &snapst)
   129  	c.Assert(err, IsNil)
   130  
   131  	checkHasCookieForSnap(c, s.state, "foo")
   132  
   133  	typ, err := snapst.Type()
   134  	c.Check(err, IsNil)
   135  	c.Check(typ, Equals, snap.TypeApp)
   136  
   137  	c.Check(snapst.Active, Equals, true)
   138  	c.Check(snapst.Sequence, HasLen, 1)
   139  	c.Check(snapst.Current, Equals, snap.R(33))
   140  	c.Check(snapst.TrackingChannel, Equals, "latest/beta")
   141  	c.Check(snapst.UserID, Equals, 2)
   142  	c.Check(snapst.CohortKey, Equals, "")
   143  	c.Check(t.Status(), Equals, state.DoneStatus)
   144  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
   145  
   146  	// we end with the auxiliary store info
   147  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent)
   148  
   149  	// link snap participant was invoked
   150  	c.Check(lp.instanceNames, DeepEquals, []string{"foo"})
   151  }
   152  
   153  func (s *linkSnapSuite) TestDoLinkSnapSuccessWithCohort(c *C) {
   154  	// we start without the auxiliary store info
   155  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
   156  
   157  	s.state.Lock()
   158  	t := s.state.NewTask("link-snap", "test")
   159  	t.Set("snap-setup", &snapstate.SnapSetup{
   160  		SideInfo: &snap.SideInfo{
   161  			RealName: "foo",
   162  			Revision: snap.R(33),
   163  			SnapID:   "foo-id",
   164  		},
   165  		Channel:   "beta",
   166  		UserID:    2,
   167  		CohortKey: "wobbling",
   168  	})
   169  	s.state.NewChange("dummy", "...").AddTask(t)
   170  
   171  	s.state.Unlock()
   172  
   173  	s.se.Ensure()
   174  	s.se.Wait()
   175  
   176  	s.state.Lock()
   177  	defer s.state.Unlock()
   178  	var snapst snapstate.SnapState
   179  	err := snapstate.Get(s.state, "foo", &snapst)
   180  	c.Assert(err, IsNil)
   181  
   182  	checkHasCookieForSnap(c, s.state, "foo")
   183  
   184  	typ, err := snapst.Type()
   185  	c.Check(err, IsNil)
   186  	c.Check(typ, Equals, snap.TypeApp)
   187  
   188  	c.Check(snapst.Active, Equals, true)
   189  	c.Check(snapst.Sequence, HasLen, 1)
   190  	c.Check(snapst.Current, Equals, snap.R(33))
   191  	c.Check(snapst.TrackingChannel, Equals, "latest/beta")
   192  	c.Check(snapst.UserID, Equals, 2)
   193  	c.Check(snapst.CohortKey, Equals, "wobbling")
   194  	c.Check(t.Status(), Equals, state.DoneStatus)
   195  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
   196  
   197  	// we end with the auxiliary store info
   198  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent)
   199  }
   200  
   201  func (s *linkSnapSuite) TestDoLinkSnapSuccessNoUserID(c *C) {
   202  	s.state.Lock()
   203  	t := s.state.NewTask("link-snap", "test")
   204  	t.Set("snap-setup", &snapstate.SnapSetup{
   205  		SideInfo: &snap.SideInfo{
   206  			RealName: "foo",
   207  			Revision: snap.R(33),
   208  		},
   209  		Channel: "beta",
   210  	})
   211  	s.state.NewChange("dummy", "...").AddTask(t)
   212  
   213  	s.state.Unlock()
   214  	s.se.Ensure()
   215  	s.se.Wait()
   216  	s.state.Lock()
   217  	defer s.state.Unlock()
   218  
   219  	// check that snapst.UserID does not get set
   220  	var snapst snapstate.SnapState
   221  	err := snapstate.Get(s.state, "foo", &snapst)
   222  	c.Assert(err, IsNil)
   223  	c.Check(snapst.UserID, Equals, 0)
   224  
   225  	var snaps map[string]*json.RawMessage
   226  	err = s.state.Get("snaps", &snaps)
   227  	c.Assert(err, IsNil)
   228  	raw := []byte(*snaps["foo"])
   229  	c.Check(string(raw), Not(testutil.Contains), "user-id")
   230  }
   231  
   232  func (s *linkSnapSuite) TestDoLinkSnapSuccessUserIDAlreadySet(c *C) {
   233  	s.state.Lock()
   234  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   235  		Sequence: []*snap.SideInfo{
   236  			{RealName: "foo", Revision: snap.R(1)},
   237  		},
   238  		Current: snap.R(1),
   239  		UserID:  1,
   240  	})
   241  	// the user
   242  	user, err := auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"})
   243  	c.Assert(err, IsNil)
   244  	c.Assert(user.ID, Equals, 1)
   245  
   246  	t := s.state.NewTask("link-snap", "test")
   247  	t.Set("snap-setup", &snapstate.SnapSetup{
   248  		SideInfo: &snap.SideInfo{
   249  			RealName: "foo",
   250  			Revision: snap.R(33),
   251  		},
   252  		Channel: "beta",
   253  		UserID:  2,
   254  	})
   255  	s.state.NewChange("dummy", "...").AddTask(t)
   256  
   257  	s.state.Unlock()
   258  	s.se.Ensure()
   259  	s.se.Wait()
   260  	s.state.Lock()
   261  	defer s.state.Unlock()
   262  
   263  	// check that snapst.UserID was not "transferred"
   264  	var snapst snapstate.SnapState
   265  	err = snapstate.Get(s.state, "foo", &snapst)
   266  	c.Assert(err, IsNil)
   267  	c.Check(snapst.UserID, Equals, 1)
   268  }
   269  
   270  func (s *linkSnapSuite) TestDoLinkSnapSuccessUserLoggedOut(c *C) {
   271  	s.state.Lock()
   272  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   273  		Sequence: []*snap.SideInfo{
   274  			{RealName: "foo", Revision: snap.R(1)},
   275  		},
   276  		Current: snap.R(1),
   277  		UserID:  1,
   278  	})
   279  
   280  	t := s.state.NewTask("link-snap", "test")
   281  	t.Set("snap-setup", &snapstate.SnapSetup{
   282  		SideInfo: &snap.SideInfo{
   283  			RealName: "foo",
   284  			Revision: snap.R(33),
   285  		},
   286  		Channel: "beta",
   287  		UserID:  2,
   288  	})
   289  	s.state.NewChange("dummy", "...").AddTask(t)
   290  
   291  	s.state.Unlock()
   292  	s.se.Ensure()
   293  	s.se.Wait()
   294  	s.state.Lock()
   295  	defer s.state.Unlock()
   296  
   297  	// check that snapst.UserID was transferred
   298  	// given that user 1 doesn't exist anymore
   299  	var snapst snapstate.SnapState
   300  	err := snapstate.Get(s.state, "foo", &snapst)
   301  	c.Assert(err, IsNil)
   302  	c.Check(snapst.UserID, Equals, 2)
   303  }
   304  
   305  func (s *linkSnapSuite) TestDoLinkSnapSeqFile(c *C) {
   306  	s.state.Lock()
   307  	// pretend we have an installed snap
   308  	si11 := &snap.SideInfo{
   309  		RealName: "foo",
   310  		Revision: snap.R(11),
   311  	}
   312  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   313  		Sequence: []*snap.SideInfo{si11},
   314  		Current:  si11.Revision,
   315  	})
   316  	// add a new one
   317  	t := s.state.NewTask("link-snap", "test")
   318  	t.Set("snap-setup", &snapstate.SnapSetup{
   319  		SideInfo: &snap.SideInfo{
   320  			RealName: "foo",
   321  			Revision: snap.R(33),
   322  		},
   323  		Channel: "beta",
   324  	})
   325  	s.state.NewChange("dummy", "...").AddTask(t)
   326  	s.state.Unlock()
   327  
   328  	s.se.Ensure()
   329  	s.se.Wait()
   330  
   331  	s.state.Lock()
   332  	defer s.state.Unlock()
   333  	var snapst snapstate.SnapState
   334  	err := snapstate.Get(s.state, "foo", &snapst)
   335  	c.Assert(err, IsNil)
   336  
   337  	// and check that the sequence file got updated
   338  	seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json"))
   339  	c.Assert(err, IsNil)
   340  	c.Check(string(seqContent), Equals, `{"sequence":[{"name":"foo","snap-id":"","revision":"11"},{"name":"foo","snap-id":"","revision":"33"}],"current":"33"}`)
   341  }
   342  
   343  func (s *linkSnapSuite) TestDoUndoLinkSnap(c *C) {
   344  	s.state.Lock()
   345  	defer s.state.Unlock()
   346  
   347  	linkChangeCount := 0
   348  	lp := &testLinkParticipant{
   349  		linkageChanged: func(st *state.State, instanceName string) error {
   350  			var snapst snapstate.SnapState
   351  			err := snapstate.Get(st, instanceName, &snapst)
   352  			linkChangeCount++
   353  			switch linkChangeCount {
   354  			case 1:
   355  				// Initially the snap gets linked.
   356  				c.Check(err, IsNil)
   357  				c.Check(snapst.Active, Equals, true)
   358  			case 2:
   359  				// Then link-snap is undone and the snap gets unlinked.
   360  				c.Check(err, Equals, state.ErrNoState)
   361  			}
   362  			return nil
   363  		},
   364  	}
   365  	restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp})
   366  	defer restore()
   367  
   368  	// a hook might have set some config
   369  	cfg := json.RawMessage(`{"c":true}`)
   370  	err := config.SetSnapConfig(s.state, "foo", &cfg)
   371  	c.Assert(err, IsNil)
   372  
   373  	si := &snap.SideInfo{
   374  		RealName: "foo",
   375  		Revision: snap.R(33),
   376  	}
   377  	t := s.state.NewTask("link-snap", "test")
   378  	t.Set("snap-setup", &snapstate.SnapSetup{
   379  		SideInfo: si,
   380  		Channel:  "beta",
   381  	})
   382  	chg := s.state.NewChange("dummy", "...")
   383  	chg.AddTask(t)
   384  
   385  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   386  	terr.WaitFor(t)
   387  	chg.AddTask(terr)
   388  
   389  	s.state.Unlock()
   390  
   391  	for i := 0; i < 6; i++ {
   392  		s.se.Ensure()
   393  		s.se.Wait()
   394  	}
   395  
   396  	s.state.Lock()
   397  	var snapst snapstate.SnapState
   398  	err = snapstate.Get(s.state, "foo", &snapst)
   399  	c.Assert(err, Equals, state.ErrNoState)
   400  	c.Check(t.Status(), Equals, state.UndoneStatus)
   401  
   402  	// and check that the sequence file got updated
   403  	seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json"))
   404  	c.Assert(err, IsNil)
   405  	c.Check(string(seqContent), Equals, `{"sequence":[],"current":"unset"}`)
   406  
   407  	// nothing in config
   408  	var config map[string]*json.RawMessage
   409  	err = s.state.Get("config", &config)
   410  	c.Assert(err, IsNil)
   411  	c.Check(config, HasLen, 1)
   412  	_, ok := config["core"]
   413  	c.Check(ok, Equals, true)
   414  
   415  	// link snap participant was invoked, once for do, once for undo.
   416  	c.Check(lp.instanceNames, DeepEquals, []string{"foo", "foo"})
   417  }
   418  
   419  func (s *linkSnapSuite) TestDoUnlinkCurrentSnapWithIgnoreRunning(c *C) {
   420  	s.state.Lock()
   421  	defer s.state.Unlock()
   422  
   423  	// With refresh-app-awareness enabled
   424  	tr := config.NewTransaction(s.state)
   425  	tr.Set("core", "experimental.refresh-app-awareness", true)
   426  	tr.Commit()
   427  
   428  	// With a snap "pkg" at revision 42
   429  	si := &snap.SideInfo{RealName: "pkg", Revision: snap.R(42)}
   430  	snapstate.Set(s.state, "pkg", &snapstate.SnapState{
   431  		Sequence: []*snap.SideInfo{si},
   432  		Current:  si.Revision,
   433  		Active:   true,
   434  	})
   435  
   436  	// With an app belonging to the snap that is apparently running.
   437  	snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) {
   438  		c.Assert(name, Equals, "pkg")
   439  		info := &snap.Info{SuggestedName: name, SideInfo: *si, SnapType: snap.TypeApp}
   440  		info.Apps = map[string]*snap.AppInfo{
   441  			"app": {Snap: info, Name: "app"},
   442  		}
   443  		return info, nil
   444  	})
   445  	restore := snapstate.MockPidsOfSnap(func(instanceName string) (map[string][]int, error) {
   446  		c.Assert(instanceName, Equals, "pkg")
   447  		return map[string][]int{"snap.pkg.app": {1234}}, nil
   448  	})
   449  	defer restore()
   450  
   451  	// We can unlink the current revision of that snap, by setting IgnoreRunning flag.
   452  	task := s.state.NewTask("unlink-current-snap", "")
   453  	task.Set("snap-setup", &snapstate.SnapSetup{
   454  		SideInfo: si,
   455  		Flags:    snapstate.Flags{IgnoreRunning: true},
   456  	})
   457  	chg := s.state.NewChange("dummy", "...")
   458  	chg.AddTask(task)
   459  
   460  	// Run the task we created
   461  	s.state.Unlock()
   462  	s.se.Ensure()
   463  	s.se.Wait()
   464  	s.state.Lock()
   465  
   466  	// And observe the results.
   467  	var snapst snapstate.SnapState
   468  	err := snapstate.Get(s.state, "pkg", &snapst)
   469  	c.Assert(err, IsNil)
   470  	c.Check(snapst.Active, Equals, false)
   471  	c.Check(snapst.Sequence, HasLen, 1)
   472  	c.Check(snapst.Current, Equals, snap.R(42))
   473  	c.Check(task.Status(), Equals, state.DoneStatus)
   474  	expected := fakeOps{{
   475  		op:   "unlink-snap",
   476  		path: filepath.Join(dirs.SnapMountDir, "pkg/42"),
   477  	}}
   478  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   479  }
   480  
   481  func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapWithVitalityScore(c *C) {
   482  	s.state.Lock()
   483  	defer s.state.Unlock()
   484  	// foo has a vitality-hint
   485  	cfg := json.RawMessage(`{"resilience":{"vitality-hint":"bar,foo,baz"}}`)
   486  	err := config.SetSnapConfig(s.state, "core", &cfg)
   487  	c.Assert(err, IsNil)
   488  
   489  	si1 := &snap.SideInfo{
   490  		RealName: "foo",
   491  		Revision: snap.R(11),
   492  	}
   493  	si2 := &snap.SideInfo{
   494  		RealName: "foo",
   495  		Revision: snap.R(33),
   496  	}
   497  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   498  		Sequence: []*snap.SideInfo{si1},
   499  		Current:  si1.Revision,
   500  		Active:   true,
   501  	})
   502  	t := s.state.NewTask("unlink-current-snap", "test")
   503  	t.Set("snap-setup", &snapstate.SnapSetup{
   504  		SideInfo: si2,
   505  	})
   506  	chg := s.state.NewChange("dummy", "...")
   507  	chg.AddTask(t)
   508  
   509  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   510  	terr.WaitFor(t)
   511  	chg.AddTask(terr)
   512  
   513  	s.state.Unlock()
   514  
   515  	for i := 0; i < 3; i++ {
   516  		s.se.Ensure()
   517  		s.se.Wait()
   518  	}
   519  
   520  	s.state.Lock()
   521  	var snapst snapstate.SnapState
   522  	err = snapstate.Get(s.state, "foo", &snapst)
   523  	c.Assert(err, IsNil)
   524  	c.Check(snapst.Active, Equals, true)
   525  	c.Check(snapst.Sequence, HasLen, 1)
   526  	c.Check(snapst.Current, Equals, snap.R(11))
   527  	c.Check(t.Status(), Equals, state.UndoneStatus)
   528  
   529  	expected := fakeOps{
   530  		{
   531  			op:   "unlink-snap",
   532  			path: filepath.Join(dirs.SnapMountDir, "foo/11"),
   533  		},
   534  		{
   535  			op:           "link-snap",
   536  			path:         filepath.Join(dirs.SnapMountDir, "foo/11"),
   537  			vitalityRank: 2,
   538  		},
   539  	}
   540  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   541  }
   542  
   543  func (s *linkSnapSuite) TestDoUnlinkCurrentSnapSnapdNop(c *C) {
   544  	s.state.Lock()
   545  	defer s.state.Unlock()
   546  
   547  	si := &snap.SideInfo{
   548  		RealName: "snapd",
   549  		SnapID:   "snapd-snap-id",
   550  		Revision: snap.R(22),
   551  	}
   552  	siOld := *si
   553  	siOld.Revision = snap.R(20)
   554  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   555  		Sequence: []*snap.SideInfo{&siOld},
   556  		Current:  siOld.Revision,
   557  		Active:   true,
   558  		SnapType: "snapd",
   559  	})
   560  
   561  	task := s.state.NewTask("unlink-current-snap", "")
   562  	task.Set("snap-setup", &snapstate.SnapSetup{
   563  		SideInfo: si,
   564  		Channel:  "beta",
   565  	})
   566  	chg := s.state.NewChange("dummy", "...")
   567  	chg.AddTask(task)
   568  
   569  	// Run the task we created
   570  	s.state.Unlock()
   571  	s.se.Ensure()
   572  	s.se.Wait()
   573  	s.state.Lock()
   574  
   575  	// And observe the results.
   576  	var snapst snapstate.SnapState
   577  	err := snapstate.Get(s.state, "snapd", &snapst)
   578  	c.Assert(err, IsNil)
   579  	c.Check(snapst.Active, Equals, false)
   580  	c.Check(snapst.Sequence, HasLen, 1)
   581  	c.Check(snapst.Current, Equals, snap.R(20))
   582  	c.Check(task.Status(), Equals, state.DoneStatus)
   583  	// backend was not called to unlink the snap
   584  	c.Check(s.fakeBackend.ops, HasLen, 0)
   585  }
   586  
   587  func (s *linkSnapSuite) TestDoUnlinkSnapdUnlinks(c *C) {
   588  	s.state.Lock()
   589  	defer s.state.Unlock()
   590  
   591  	si := &snap.SideInfo{
   592  		RealName: "snapd",
   593  		Revision: snap.R(20),
   594  	}
   595  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   596  		Sequence: []*snap.SideInfo{si},
   597  		Current:  si.Revision,
   598  		Active:   true,
   599  	})
   600  
   601  	task := s.state.NewTask("unlink-snap", "")
   602  	task.Set("snap-setup", &snapstate.SnapSetup{
   603  		SideInfo: si,
   604  		Channel:  "beta",
   605  	})
   606  	chg := s.state.NewChange("dummy", "...")
   607  	chg.AddTask(task)
   608  
   609  	// Run the task we created
   610  	s.state.Unlock()
   611  	s.se.Ensure()
   612  	s.se.Wait()
   613  	s.state.Lock()
   614  
   615  	// And observe the results.
   616  	var snapst snapstate.SnapState
   617  	err := snapstate.Get(s.state, "snapd", &snapst)
   618  	c.Assert(err, IsNil)
   619  	c.Check(snapst.Active, Equals, false)
   620  	c.Check(snapst.Sequence, HasLen, 1)
   621  	c.Check(snapst.Current, Equals, snap.R(20))
   622  	c.Check(task.Status(), Equals, state.DoneStatus)
   623  	// backend was called to unlink the snap
   624  	expected := fakeOps{{
   625  		op:   "unlink-snap",
   626  		path: filepath.Join(dirs.SnapMountDir, "snapd/20"),
   627  	}}
   628  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   629  }
   630  
   631  func (s *linkSnapSuite) TestDoLinkSnapWithVitalityScore(c *C) {
   632  	s.state.Lock()
   633  	defer s.state.Unlock()
   634  	// a hook might have set some config
   635  	cfg := json.RawMessage(`{"resilience":{"vitality-hint":"bar,foo,baz"}}`)
   636  	err := config.SetSnapConfig(s.state, "core", &cfg)
   637  	c.Assert(err, IsNil)
   638  
   639  	si := &snap.SideInfo{
   640  		RealName: "foo",
   641  		Revision: snap.R(33),
   642  	}
   643  	t := s.state.NewTask("link-snap", "test")
   644  	t.Set("snap-setup", &snapstate.SnapSetup{
   645  		SideInfo: si,
   646  	})
   647  	chg := s.state.NewChange("dummy", "...")
   648  	chg.AddTask(t)
   649  
   650  	s.state.Unlock()
   651  
   652  	for i := 0; i < 6; i++ {
   653  		s.se.Ensure()
   654  		s.se.Wait()
   655  	}
   656  
   657  	s.state.Lock()
   658  	expected := fakeOps{
   659  		{
   660  			op:    "candidate",
   661  			sinfo: *si,
   662  		},
   663  		{
   664  			op:           "link-snap",
   665  			path:         filepath.Join(dirs.SnapMountDir, "foo/33"),
   666  			vitalityRank: 2,
   667  		},
   668  	}
   669  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   670  }
   671  
   672  func (s *linkSnapSuite) TestDoLinkSnapTryToCleanupOnError(c *C) {
   673  	s.state.Lock()
   674  	defer s.state.Unlock()
   675  
   676  	lp := &testLinkParticipant{}
   677  	restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp})
   678  	defer restore()
   679  
   680  	si := &snap.SideInfo{
   681  		RealName: "foo",
   682  		Revision: snap.R(35),
   683  	}
   684  	t := s.state.NewTask("link-snap", "test")
   685  	t.Set("snap-setup", &snapstate.SnapSetup{
   686  		SideInfo: si,
   687  		Channel:  "beta",
   688  	})
   689  
   690  	s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "foo/35")
   691  	s.state.NewChange("dummy", "...").AddTask(t)
   692  	s.state.Unlock()
   693  
   694  	s.se.Ensure()
   695  	s.se.Wait()
   696  
   697  	s.state.Lock()
   698  
   699  	// state as expected
   700  	var snapst snapstate.SnapState
   701  	err := snapstate.Get(s.state, "foo", &snapst)
   702  	c.Assert(err, Equals, state.ErrNoState)
   703  
   704  	// tried to cleanup
   705  	expected := fakeOps{
   706  		{
   707  			op:    "candidate",
   708  			sinfo: *si,
   709  		},
   710  		{
   711  			op:   "link-snap.failed",
   712  			path: filepath.Join(dirs.SnapMountDir, "foo/35"),
   713  		},
   714  		{
   715  			op:   "unlink-snap",
   716  			path: filepath.Join(dirs.SnapMountDir, "foo/35"),
   717  
   718  			unlinkFirstInstallUndo: true,
   719  		},
   720  	}
   721  
   722  	// start with an easier-to-read error if this fails:
   723  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   724  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   725  
   726  	// link snap participant was invoked
   727  	c.Check(lp.instanceNames, DeepEquals, []string{"foo"})
   728  }
   729  
   730  func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreRestarts(c *C) {
   731  	restore := release.MockOnClassic(true)
   732  	defer restore()
   733  
   734  	s.state.Lock()
   735  	si := &snap.SideInfo{
   736  		RealName: "core",
   737  		Revision: snap.R(33),
   738  	}
   739  	t := s.state.NewTask("link-snap", "test")
   740  	t.Set("snap-setup", &snapstate.SnapSetup{
   741  		SideInfo: si,
   742  	})
   743  	s.state.NewChange("dummy", "...").AddTask(t)
   744  
   745  	s.state.Unlock()
   746  
   747  	s.se.Ensure()
   748  	s.se.Wait()
   749  
   750  	s.state.Lock()
   751  	defer s.state.Unlock()
   752  
   753  	var snapst snapstate.SnapState
   754  	err := snapstate.Get(s.state, "core", &snapst)
   755  	c.Assert(err, IsNil)
   756  
   757  	typ, err := snapst.Type()
   758  	c.Check(err, IsNil)
   759  	c.Check(typ, Equals, snap.TypeOS)
   760  
   761  	c.Check(t.Status(), Equals, state.DoneStatus)
   762  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   763  	c.Check(t.Log(), HasLen, 1)
   764  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart\.`)
   765  }
   766  
   767  func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnCoreWithBase(c *C) {
   768  	restore := release.MockOnClassic(false)
   769  	defer restore()
   770  
   771  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   772  	defer r()
   773  
   774  	s.state.Lock()
   775  	si := &snap.SideInfo{
   776  		RealName: "snapd",
   777  		SnapID:   "snapd-snap-id",
   778  		Revision: snap.R(22),
   779  	}
   780  	t := s.state.NewTask("link-snap", "test")
   781  	t.Set("snap-setup", &snapstate.SnapSetup{
   782  		SideInfo: si,
   783  		Type:     snap.TypeSnapd,
   784  	})
   785  	s.state.NewChange("dummy", "...").AddTask(t)
   786  
   787  	s.state.Unlock()
   788  
   789  	s.se.Ensure()
   790  	s.se.Wait()
   791  
   792  	s.state.Lock()
   793  	defer s.state.Unlock()
   794  
   795  	var snapst snapstate.SnapState
   796  	err := snapstate.Get(s.state, "snapd", &snapst)
   797  	c.Assert(err, IsNil)
   798  
   799  	typ, err := snapst.Type()
   800  	c.Check(err, IsNil)
   801  	c.Check(typ, Equals, snap.TypeSnapd)
   802  
   803  	c.Check(t.Status(), Equals, state.DoneStatus)
   804  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   805  	c.Check(t.Log(), HasLen, 1)
   806  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
   807  }
   808  
   809  func (s *linkSnapSuite) TestDoLinkSnapSuccessRebootForCoreBase(c *C) {
   810  	restore := release.MockOnClassic(false)
   811  	defer restore()
   812  
   813  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   814  	defer r()
   815  
   816  	s.fakeBackend.linkSnapMaybeReboot = true
   817  
   818  	s.state.Lock()
   819  	defer s.state.Unlock()
   820  
   821  	// we need to init the boot-id
   822  	err := s.state.VerifyReboot("some-boot-id")
   823  	c.Assert(err, IsNil)
   824  
   825  	si := &snap.SideInfo{
   826  		RealName: "core18",
   827  		SnapID:   "core18-id",
   828  		Revision: snap.R(22),
   829  	}
   830  	t := s.state.NewTask("link-snap", "test")
   831  	t.Set("snap-setup", &snapstate.SnapSetup{
   832  		SideInfo: si,
   833  	})
   834  	s.state.NewChange("dummy", "...").AddTask(t)
   835  
   836  	s.state.Unlock()
   837  	s.se.Ensure()
   838  	s.se.Wait()
   839  	s.state.Lock()
   840  
   841  	c.Check(t.Status(), Equals, state.DoneStatus)
   842  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartSystem})
   843  	c.Assert(t.Log(), HasLen, 1)
   844  	c.Check(t.Log()[0], Matches, `.*INFO Requested system restart.*`)
   845  }
   846  
   847  func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnClassic(c *C) {
   848  	restore := release.MockOnClassic(true)
   849  	defer restore()
   850  
   851  	s.state.Lock()
   852  	si := &snap.SideInfo{
   853  		RealName: "snapd",
   854  		SnapID:   "snapd-snap-id",
   855  		Revision: snap.R(22),
   856  	}
   857  	t := s.state.NewTask("link-snap", "test")
   858  	t.Set("snap-setup", &snapstate.SnapSetup{
   859  		SideInfo: si,
   860  		Type:     snap.TypeSnapd,
   861  	})
   862  	s.state.NewChange("dummy", "...").AddTask(t)
   863  
   864  	s.state.Unlock()
   865  
   866  	s.se.Ensure()
   867  	s.se.Wait()
   868  
   869  	s.state.Lock()
   870  	defer s.state.Unlock()
   871  
   872  	var snapst snapstate.SnapState
   873  	err := snapstate.Get(s.state, "snapd", &snapst)
   874  	c.Assert(err, IsNil)
   875  
   876  	typ, err := snapst.Type()
   877  	c.Check(err, IsNil)
   878  	c.Check(typ, Equals, snap.TypeSnapd)
   879  
   880  	c.Check(t.Status(), Equals, state.DoneStatus)
   881  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   882  	c.Check(t.Log(), HasLen, 1)
   883  }
   884  
   885  func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreAndSnapdNoCoreRestart(c *C) {
   886  	restore := release.MockOnClassic(true)
   887  	defer restore()
   888  
   889  	s.state.Lock()
   890  	siSnapd := &snap.SideInfo{
   891  		RealName: "snapd",
   892  		Revision: snap.R(64),
   893  	}
   894  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   895  		Sequence: []*snap.SideInfo{siSnapd},
   896  		Current:  siSnapd.Revision,
   897  		Active:   true,
   898  		SnapType: "snapd",
   899  	})
   900  
   901  	si := &snap.SideInfo{
   902  		RealName: "core",
   903  		Revision: snap.R(33),
   904  	}
   905  	t := s.state.NewTask("link-snap", "test")
   906  	t.Set("snap-setup", &snapstate.SnapSetup{
   907  		SideInfo: si,
   908  	})
   909  	s.state.NewChange("dummy", "...").AddTask(t)
   910  
   911  	s.state.Unlock()
   912  
   913  	s.se.Ensure()
   914  	s.se.Wait()
   915  
   916  	s.state.Lock()
   917  	defer s.state.Unlock()
   918  
   919  	var snapst snapstate.SnapState
   920  	err := snapstate.Get(s.state, "core", &snapst)
   921  	c.Assert(err, IsNil)
   922  
   923  	typ, err := snapst.Type()
   924  	c.Check(err, IsNil)
   925  	c.Check(typ, Equals, snap.TypeOS)
   926  
   927  	c.Check(t.Status(), Equals, state.DoneStatus)
   928  	c.Check(s.stateBackend.restartRequested, IsNil)
   929  	c.Check(t.Log(), HasLen, 0)
   930  }
   931  
   932  func (s *linkSnapSuite) TestDoLinkSnapdSnapCleanupOnErrorFirstInstall(c *C) {
   933  	s.state.Lock()
   934  	defer s.state.Unlock()
   935  
   936  	lp := &testLinkParticipant{}
   937  	restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp})
   938  	defer restore()
   939  
   940  	si := &snap.SideInfo{
   941  		RealName: "snapd",
   942  		SnapID:   "snapd-snap-id",
   943  		Revision: snap.R(22),
   944  	}
   945  	t := s.state.NewTask("link-snap", "test")
   946  	t.Set("snap-setup", &snapstate.SnapSetup{
   947  		SideInfo: si,
   948  		Channel:  "beta",
   949  	})
   950  
   951  	s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "snapd/22")
   952  	s.state.NewChange("dummy", "...").AddTask(t)
   953  	s.state.Unlock()
   954  
   955  	s.se.Ensure()
   956  	s.se.Wait()
   957  
   958  	s.state.Lock()
   959  
   960  	// state as expected
   961  	var snapst snapstate.SnapState
   962  	err := snapstate.Get(s.state, "foo", &snapst)
   963  	c.Assert(err, Equals, state.ErrNoState)
   964  
   965  	// tried to cleanup
   966  	expected := fakeOps{
   967  		{
   968  			op:    "candidate",
   969  			sinfo: *si,
   970  		},
   971  		{
   972  			op:   "link-snap.failed",
   973  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
   974  		},
   975  		{
   976  			op:   "unlink-snap",
   977  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
   978  
   979  			unlinkFirstInstallUndo: true,
   980  		},
   981  	}
   982  
   983  	// start with an easier-to-read error if this fails:
   984  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   985  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   986  
   987  	// link snap participant was invoked
   988  	c.Check(lp.instanceNames, DeepEquals, []string{"snapd"})
   989  }
   990  
   991  func (s *linkSnapSuite) TestDoLinkSnapdSnapCleanupOnErrorNthInstall(c *C) {
   992  	s.state.Lock()
   993  	defer s.state.Unlock()
   994  
   995  	lp := &testLinkParticipant{}
   996  	restore := snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp})
   997  	defer restore()
   998  
   999  	si := &snap.SideInfo{
  1000  		RealName: "snapd",
  1001  		SnapID:   "snapd-snap-id",
  1002  		Revision: snap.R(22),
  1003  	}
  1004  	siOld := *si
  1005  	siOld.Revision = snap.R(20)
  1006  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
  1007  		Sequence: []*snap.SideInfo{&siOld},
  1008  		Current:  siOld.Revision,
  1009  		Active:   true,
  1010  		SnapType: "snapd",
  1011  	})
  1012  	t := s.state.NewTask("link-snap", "test")
  1013  	t.Set("snap-setup", &snapstate.SnapSetup{
  1014  		SideInfo: si,
  1015  		Channel:  "beta",
  1016  	})
  1017  
  1018  	s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "snapd/22")
  1019  	s.state.NewChange("dummy", "...").AddTask(t)
  1020  	s.state.Unlock()
  1021  
  1022  	s.se.Ensure()
  1023  	s.se.Wait()
  1024  
  1025  	s.state.Lock()
  1026  
  1027  	// state as expected
  1028  	var snapst snapstate.SnapState
  1029  	err := snapstate.Get(s.state, "foo", &snapst)
  1030  	c.Assert(err, Equals, state.ErrNoState)
  1031  
  1032  	// tried to cleanup
  1033  	expected := fakeOps{
  1034  		{
  1035  			op:    "candidate",
  1036  			sinfo: *si,
  1037  		},
  1038  		{
  1039  			op:   "link-snap.failed",
  1040  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1041  		},
  1042  		{
  1043  			// we link the old revision
  1044  			op:   "link-snap",
  1045  			path: filepath.Join(dirs.SnapMountDir, "snapd/20"),
  1046  
  1047  			unlinkFirstInstallUndo: false,
  1048  		},
  1049  	}
  1050  
  1051  	// start with an easier-to-read error if this fails:
  1052  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
  1053  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
  1054  
  1055  	// link snap participant was invoked
  1056  	c.Check(lp.instanceNames, DeepEquals, []string{"snapd"})
  1057  }
  1058  
  1059  func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceDidNotHaveCandidate(c *C) {
  1060  	s.state.Lock()
  1061  	defer s.state.Unlock()
  1062  	si1 := &snap.SideInfo{
  1063  		RealName: "foo",
  1064  		Revision: snap.R(1),
  1065  	}
  1066  	si2 := &snap.SideInfo{
  1067  		RealName: "foo",
  1068  		Revision: snap.R(2),
  1069  	}
  1070  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
  1071  		Sequence: []*snap.SideInfo{si1},
  1072  		Current:  si1.Revision,
  1073  	})
  1074  	t := s.state.NewTask("link-snap", "test")
  1075  	t.Set("snap-setup", &snapstate.SnapSetup{
  1076  		SideInfo: si2,
  1077  		Channel:  "beta",
  1078  	})
  1079  	chg := s.state.NewChange("dummy", "...")
  1080  	chg.AddTask(t)
  1081  
  1082  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1083  	terr.WaitFor(t)
  1084  	chg.AddTask(terr)
  1085  
  1086  	s.state.Unlock()
  1087  
  1088  	for i := 0; i < 6; i++ {
  1089  		s.se.Ensure()
  1090  		s.se.Wait()
  1091  	}
  1092  
  1093  	s.state.Lock()
  1094  	var snapst snapstate.SnapState
  1095  	err := snapstate.Get(s.state, "foo", &snapst)
  1096  	c.Assert(err, IsNil)
  1097  	c.Check(snapst.Active, Equals, false)
  1098  	c.Check(snapst.Sequence, HasLen, 1)
  1099  	c.Check(snapst.Current, Equals, snap.R(1))
  1100  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1101  }
  1102  
  1103  func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceHadCandidate(c *C) {
  1104  	s.state.Lock()
  1105  	defer s.state.Unlock()
  1106  	si1 := &snap.SideInfo{
  1107  		RealName: "foo",
  1108  		Revision: snap.R(1),
  1109  	}
  1110  	si2 := &snap.SideInfo{
  1111  		RealName: "foo",
  1112  		Revision: snap.R(2),
  1113  	}
  1114  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
  1115  		Sequence: []*snap.SideInfo{si1, si2},
  1116  		Current:  si2.Revision,
  1117  	})
  1118  	t := s.state.NewTask("link-snap", "test")
  1119  	t.Set("snap-setup", &snapstate.SnapSetup{
  1120  		SideInfo: si1,
  1121  		Channel:  "beta",
  1122  	})
  1123  	chg := s.state.NewChange("dummy", "...")
  1124  	chg.AddTask(t)
  1125  
  1126  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1127  	terr.WaitFor(t)
  1128  	chg.AddTask(terr)
  1129  
  1130  	s.state.Unlock()
  1131  
  1132  	for i := 0; i < 6; i++ {
  1133  		s.se.Ensure()
  1134  		s.se.Wait()
  1135  	}
  1136  
  1137  	s.state.Lock()
  1138  	var snapst snapstate.SnapState
  1139  	err := snapstate.Get(s.state, "foo", &snapst)
  1140  	c.Assert(err, IsNil)
  1141  	c.Check(snapst.Active, Equals, false)
  1142  	c.Check(snapst.Sequence, HasLen, 2)
  1143  	c.Check(snapst.Current, Equals, snap.R(2))
  1144  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1145  }
  1146  
  1147  func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapCore(c *C) {
  1148  	restore := release.MockOnClassic(true)
  1149  	defer restore()
  1150  
  1151  	linkChangeCount := 0
  1152  	lp := &testLinkParticipant{
  1153  		linkageChanged: func(st *state.State, instanceName string) error {
  1154  			var snapst snapstate.SnapState
  1155  			err := snapstate.Get(st, instanceName, &snapst)
  1156  			linkChangeCount++
  1157  			switch linkChangeCount {
  1158  			case 1:
  1159  				// Initially the snap gets unlinked.
  1160  				c.Check(err, IsNil)
  1161  				c.Check(snapst.Active, Equals, false)
  1162  			case 2:
  1163  				// Then the undo handler re-links it.
  1164  				c.Check(err, IsNil)
  1165  				c.Check(snapst.Active, Equals, true)
  1166  			}
  1167  			return nil
  1168  		},
  1169  	}
  1170  	restore = snapstate.MockLinkSnapParticipants([]snapstate.LinkSnapParticipant{lp})
  1171  	defer restore()
  1172  
  1173  	s.state.Lock()
  1174  	defer s.state.Unlock()
  1175  	si1 := &snap.SideInfo{
  1176  		RealName: "core",
  1177  		Revision: snap.R(1),
  1178  	}
  1179  	si2 := &snap.SideInfo{
  1180  		RealName: "core",
  1181  		Revision: snap.R(2),
  1182  	}
  1183  	snapstate.Set(s.state, "core", &snapstate.SnapState{
  1184  		Sequence: []*snap.SideInfo{si1},
  1185  		Current:  si1.Revision,
  1186  		Active:   true,
  1187  		SnapType: "os",
  1188  	})
  1189  	t := s.state.NewTask("unlink-current-snap", "test")
  1190  	t.Set("snap-setup", &snapstate.SnapSetup{
  1191  		SideInfo: si2,
  1192  	})
  1193  	chg := s.state.NewChange("dummy", "...")
  1194  	chg.AddTask(t)
  1195  
  1196  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1197  	terr.WaitFor(t)
  1198  	chg.AddTask(terr)
  1199  
  1200  	s.state.Unlock()
  1201  
  1202  	for i := 0; i < 3; i++ {
  1203  		s.se.Ensure()
  1204  		s.se.Wait()
  1205  	}
  1206  
  1207  	s.state.Lock()
  1208  	var snapst snapstate.SnapState
  1209  	err := snapstate.Get(s.state, "core", &snapst)
  1210  	c.Assert(err, IsNil)
  1211  	c.Check(snapst.Active, Equals, true)
  1212  	c.Check(snapst.Sequence, HasLen, 1)
  1213  	c.Check(snapst.Current, Equals, snap.R(1))
  1214  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1215  
  1216  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
  1217  	c.Check(lp.instanceNames, DeepEquals, []string{"core", "core"})
  1218  }
  1219  
  1220  func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapCoreBase(c *C) {
  1221  	restore := release.MockOnClassic(false)
  1222  	defer restore()
  1223  
  1224  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
  1225  	defer r()
  1226  
  1227  	s.fakeBackend.linkSnapMaybeReboot = true
  1228  
  1229  	s.state.Lock()
  1230  	defer s.state.Unlock()
  1231  	// we need to init the boot-id
  1232  	err := s.state.VerifyReboot("some-boot-id")
  1233  	c.Assert(err, IsNil)
  1234  
  1235  	si1 := &snap.SideInfo{
  1236  		RealName: "core18",
  1237  		Revision: snap.R(1),
  1238  	}
  1239  	si2 := &snap.SideInfo{
  1240  		RealName: "core18",
  1241  		Revision: snap.R(2),
  1242  	}
  1243  	snapstate.Set(s.state, "core18", &snapstate.SnapState{
  1244  		Sequence: []*snap.SideInfo{si1},
  1245  		Current:  si1.Revision,
  1246  		Active:   true,
  1247  		SnapType: "base",
  1248  	})
  1249  	t := s.state.NewTask("unlink-current-snap", "test")
  1250  	t.Set("snap-setup", &snapstate.SnapSetup{
  1251  		SideInfo: si2,
  1252  	})
  1253  	chg := s.state.NewChange("dummy", "...")
  1254  	chg.AddTask(t)
  1255  
  1256  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1257  	terr.WaitFor(t)
  1258  	chg.AddTask(terr)
  1259  
  1260  	s.state.Unlock()
  1261  	for i := 0; i < 3; i++ {
  1262  		s.se.Ensure()
  1263  		s.se.Wait()
  1264  	}
  1265  	s.state.Lock()
  1266  
  1267  	var snapst snapstate.SnapState
  1268  	err = snapstate.Get(s.state, "core18", &snapst)
  1269  	c.Assert(err, IsNil)
  1270  	c.Check(snapst.Active, Equals, true)
  1271  	c.Check(snapst.Sequence, HasLen, 1)
  1272  	c.Check(snapst.Current, Equals, snap.R(1))
  1273  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1274  
  1275  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartSystem})
  1276  }
  1277  
  1278  func (s *linkSnapSuite) TestDoUndoLinkSnapCoreClassic(c *C) {
  1279  	restore := release.MockOnClassic(true)
  1280  	defer restore()
  1281  
  1282  	s.state.Lock()
  1283  	defer s.state.Unlock()
  1284  
  1285  	// no previous core snap and an error on link, in this
  1286  	// case we need to restart on classic back into the distro
  1287  	// package version
  1288  	si1 := &snap.SideInfo{
  1289  		RealName: "core",
  1290  		Revision: snap.R(1),
  1291  	}
  1292  	t := s.state.NewTask("link-snap", "test")
  1293  	t.Set("snap-setup", &snapstate.SnapSetup{
  1294  		SideInfo: si1,
  1295  	})
  1296  	chg := s.state.NewChange("dummy", "...")
  1297  	chg.AddTask(t)
  1298  
  1299  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1300  	terr.WaitFor(t)
  1301  	chg.AddTask(terr)
  1302  
  1303  	s.state.Unlock()
  1304  
  1305  	for i := 0; i < 3; i++ {
  1306  		s.se.Ensure()
  1307  		s.se.Wait()
  1308  	}
  1309  
  1310  	s.state.Lock()
  1311  	var snapst snapstate.SnapState
  1312  	err := snapstate.Get(s.state, "core", &snapst)
  1313  	c.Assert(err, Equals, state.ErrNoState)
  1314  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1315  
  1316  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon})
  1317  
  1318  }
  1319  
  1320  func (s *linkSnapSuite) TestLinkSnapInjectsAutoConnectIfMissing(c *C) {
  1321  	si1 := &snap.SideInfo{
  1322  		RealName: "snap1",
  1323  		Revision: snap.R(1),
  1324  	}
  1325  	sup1 := &snapstate.SnapSetup{SideInfo: si1}
  1326  	si2 := &snap.SideInfo{
  1327  		RealName: "snap2",
  1328  		Revision: snap.R(1),
  1329  	}
  1330  	sup2 := &snapstate.SnapSetup{SideInfo: si2}
  1331  
  1332  	s.state.Lock()
  1333  	defer s.state.Unlock()
  1334  
  1335  	task0 := s.state.NewTask("setup-profiles", "")
  1336  	task1 := s.state.NewTask("link-snap", "")
  1337  	task1.WaitFor(task0)
  1338  	task0.Set("snap-setup", sup1)
  1339  	task1.Set("snap-setup", sup1)
  1340  
  1341  	task2 := s.state.NewTask("setup-profiles", "")
  1342  	task3 := s.state.NewTask("link-snap", "")
  1343  	task2.WaitFor(task1)
  1344  	task3.WaitFor(task2)
  1345  	task2.Set("snap-setup", sup2)
  1346  	task3.Set("snap-setup", sup2)
  1347  
  1348  	chg := s.state.NewChange("test", "")
  1349  	chg.AddTask(task0)
  1350  	chg.AddTask(task1)
  1351  	chg.AddTask(task2)
  1352  	chg.AddTask(task3)
  1353  
  1354  	s.state.Unlock()
  1355  
  1356  	for i := 0; i < 10; i++ {
  1357  		s.se.Ensure()
  1358  		s.se.Wait()
  1359  	}
  1360  
  1361  	s.state.Lock()
  1362  
  1363  	// ensure all our tasks ran
  1364  	c.Assert(chg.Err(), IsNil)
  1365  	c.Assert(chg.Tasks(), HasLen, 6)
  1366  
  1367  	// sanity checks
  1368  	t := chg.Tasks()[1]
  1369  	c.Assert(t.Kind(), Equals, "link-snap")
  1370  	t = chg.Tasks()[3]
  1371  	c.Assert(t.Kind(), Equals, "link-snap")
  1372  
  1373  	// check that auto-connect tasks were added and have snap-setup
  1374  	var autoconnectSup snapstate.SnapSetup
  1375  	t = chg.Tasks()[4]
  1376  	c.Assert(t.Kind(), Equals, "auto-connect")
  1377  	c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil)
  1378  	c.Assert(autoconnectSup.InstanceName(), Equals, "snap1")
  1379  
  1380  	t = chg.Tasks()[5]
  1381  	c.Assert(t.Kind(), Equals, "auto-connect")
  1382  	c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil)
  1383  	c.Assert(autoconnectSup.InstanceName(), Equals, "snap2")
  1384  }
  1385  
  1386  func (s *linkSnapSuite) TestDoLinkSnapFailureCleansUpAux(c *C) {
  1387  	// this is very chummy with the order of LinkSnap
  1388  	c.Assert(ioutil.WriteFile(dirs.SnapSeqDir, nil, 0644), IsNil)
  1389  
  1390  	// we start without the auxiliary store info
  1391  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
  1392  
  1393  	s.state.Lock()
  1394  	t := s.state.NewTask("link-snap", "test")
  1395  	t.Set("snap-setup", &snapstate.SnapSetup{
  1396  		SideInfo: &snap.SideInfo{
  1397  			RealName: "foo",
  1398  			Revision: snap.R(33),
  1399  			SnapID:   "foo-id",
  1400  		},
  1401  		Channel: "beta",
  1402  		UserID:  2,
  1403  	})
  1404  	s.state.NewChange("dummy", "...").AddTask(t)
  1405  
  1406  	s.state.Unlock()
  1407  
  1408  	s.se.Ensure()
  1409  	s.se.Wait()
  1410  
  1411  	s.state.Lock()
  1412  	defer s.state.Unlock()
  1413  
  1414  	c.Check(t.Status(), Equals, state.ErrorStatus)
  1415  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
  1416  
  1417  	// we end without the auxiliary store info
  1418  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
  1419  }
  1420  
  1421  func (s *linkSnapSuite) TestLinkSnapResetsRefreshInhibitedTime(c *C) {
  1422  	// When a snap is linked the refresh-inhibited-time is reset to zero
  1423  	// to indicate a successful refresh. The old value is stored in task
  1424  	// state for task undo logic.
  1425  	s.state.Lock()
  1426  	defer s.state.Unlock()
  1427  
  1428  	instant := time.Now()
  1429  
  1430  	si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)}
  1431  	sup := &snapstate.SnapSetup{SideInfo: si}
  1432  	snapstate.Set(s.state, "snap", &snapstate.SnapState{
  1433  		Sequence:             []*snap.SideInfo{si},
  1434  		Current:              si.Revision,
  1435  		RefreshInhibitedTime: &instant,
  1436  	})
  1437  
  1438  	task := s.state.NewTask("link-snap", "")
  1439  	task.Set("snap-setup", sup)
  1440  	chg := s.state.NewChange("test", "")
  1441  	chg.AddTask(task)
  1442  
  1443  	s.state.Unlock()
  1444  
  1445  	for i := 0; i < 10; i++ {
  1446  		s.se.Ensure()
  1447  		s.se.Wait()
  1448  	}
  1449  
  1450  	s.state.Lock()
  1451  
  1452  	c.Assert(chg.Err(), IsNil)
  1453  	c.Assert(chg.Tasks(), HasLen, 1)
  1454  
  1455  	var snapst snapstate.SnapState
  1456  	err := snapstate.Get(s.state, "snap", &snapst)
  1457  	c.Assert(err, IsNil)
  1458  	c.Check(snapst.RefreshInhibitedTime, IsNil)
  1459  
  1460  	var oldTime time.Time
  1461  	c.Assert(task.Get("old-refresh-inhibited-time", &oldTime), IsNil)
  1462  	c.Check(oldTime.Equal(instant), Equals, true)
  1463  }
  1464  
  1465  func (s *linkSnapSuite) TestDoUndoLinkSnapRestoresRefreshInhibitedTime(c *C) {
  1466  	s.state.Lock()
  1467  	defer s.state.Unlock()
  1468  
  1469  	instant := time.Now()
  1470  
  1471  	si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)}
  1472  	sup := &snapstate.SnapSetup{SideInfo: si}
  1473  	snapstate.Set(s.state, "snap", &snapstate.SnapState{
  1474  		Sequence:             []*snap.SideInfo{si},
  1475  		Current:              si.Revision,
  1476  		RefreshInhibitedTime: &instant,
  1477  	})
  1478  
  1479  	task := s.state.NewTask("link-snap", "")
  1480  	task.Set("snap-setup", sup)
  1481  	chg := s.state.NewChange("test", "")
  1482  	chg.AddTask(task)
  1483  
  1484  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1485  	terr.WaitFor(task)
  1486  	chg.AddTask(terr)
  1487  
  1488  	s.state.Unlock()
  1489  
  1490  	for i := 0; i < 6; i++ {
  1491  		s.se.Ensure()
  1492  		s.se.Wait()
  1493  	}
  1494  
  1495  	s.state.Lock()
  1496  
  1497  	c.Assert(chg.Err(), NotNil)
  1498  	c.Assert(chg.Tasks(), HasLen, 2)
  1499  	c.Check(task.Status(), Equals, state.UndoneStatus)
  1500  
  1501  	var snapst snapstate.SnapState
  1502  	err := snapstate.Get(s.state, "snap", &snapst)
  1503  	c.Assert(err, IsNil)
  1504  	c.Check(snapst.RefreshInhibitedTime.Equal(instant), Equals, true)
  1505  }
  1506  
  1507  func (s *linkSnapSuite) TestUndoLinkSnapdFirstInstall(c *C) {
  1508  	restore := release.MockOnClassic(false)
  1509  	defer restore()
  1510  
  1511  	s.state.Lock()
  1512  	si := &snap.SideInfo{
  1513  		RealName: "snapd",
  1514  		SnapID:   "snapd-snap-id",
  1515  		Revision: snap.R(22),
  1516  	}
  1517  	chg := s.state.NewChange("dummy", "...")
  1518  	t := s.state.NewTask("link-snap", "test")
  1519  	t.Set("snap-setup", &snapstate.SnapSetup{
  1520  		SideInfo: si,
  1521  		Type:     snap.TypeSnapd,
  1522  	})
  1523  	chg.AddTask(t)
  1524  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1525  	terr.WaitFor(t)
  1526  	chg.AddTask(terr)
  1527  
  1528  	s.state.Unlock()
  1529  
  1530  	for i := 0; i < 6; i++ {
  1531  		s.se.Ensure()
  1532  		s.se.Wait()
  1533  	}
  1534  
  1535  	s.state.Lock()
  1536  	defer s.state.Unlock()
  1537  
  1538  	var snapst snapstate.SnapState
  1539  	err := snapstate.Get(s.state, "snapd", &snapst)
  1540  	c.Assert(err, Equals, state.ErrNoState)
  1541  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1542  
  1543  	expected := fakeOps{
  1544  		{
  1545  			op:    "candidate",
  1546  			sinfo: *si,
  1547  		},
  1548  		{
  1549  			op:   "link-snap",
  1550  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1551  		},
  1552  		{
  1553  			op:   "discard-namespace",
  1554  			name: "snapd",
  1555  		},
  1556  		{
  1557  			op:   "unlink-snap",
  1558  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1559  
  1560  			unlinkFirstInstallUndo: true,
  1561  		},
  1562  	}
  1563  
  1564  	// start with an easier-to-read error if this fails:
  1565  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
  1566  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
  1567  
  1568  	// 2 restarts, one from link snap, another one from undo
  1569  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon})
  1570  	c.Check(t.Log(), HasLen, 3)
  1571  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
  1572  	c.Check(t.Log()[2], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
  1573  
  1574  }
  1575  
  1576  func (s *linkSnapSuite) TestUndoLinkSnapdNthInstall(c *C) {
  1577  	restore := release.MockOnClassic(false)
  1578  	defer restore()
  1579  
  1580  	s.state.Lock()
  1581  	si := &snap.SideInfo{
  1582  		RealName: "snapd",
  1583  		SnapID:   "snapd-snap-id",
  1584  		Revision: snap.R(22),
  1585  	}
  1586  	siOld := *si
  1587  	siOld.Revision = snap.R(20)
  1588  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
  1589  		Sequence: []*snap.SideInfo{&siOld},
  1590  		Current:  siOld.Revision,
  1591  		Active:   true,
  1592  		SnapType: "snapd",
  1593  	})
  1594  	chg := s.state.NewChange("dummy", "...")
  1595  	t := s.state.NewTask("link-snap", "test")
  1596  	t.Set("snap-setup", &snapstate.SnapSetup{
  1597  		SideInfo: si,
  1598  		Type:     snap.TypeSnapd,
  1599  	})
  1600  	chg.AddTask(t)
  1601  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1602  	terr.WaitFor(t)
  1603  	chg.AddTask(terr)
  1604  
  1605  	s.state.Unlock()
  1606  
  1607  	for i := 0; i < 6; i++ {
  1608  		s.se.Ensure()
  1609  		s.se.Wait()
  1610  	}
  1611  
  1612  	s.state.Lock()
  1613  	defer s.state.Unlock()
  1614  
  1615  	var snapst snapstate.SnapState
  1616  	err := snapstate.Get(s.state, "snapd", &snapst)
  1617  	c.Assert(err, IsNil)
  1618  	snapst.Current = siOld.Revision
  1619  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1620  
  1621  	expected := fakeOps{
  1622  		{
  1623  			op:    "candidate",
  1624  			sinfo: *si,
  1625  		},
  1626  		{
  1627  			op:   "link-snap",
  1628  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1629  		},
  1630  		{
  1631  			op:   "link-snap",
  1632  			path: filepath.Join(dirs.SnapMountDir, "snapd/20"),
  1633  		},
  1634  	}
  1635  
  1636  	// start with an easier-to-read error if this fails:
  1637  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
  1638  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
  1639  
  1640  	// 1 restart from link snap, the other restart happens
  1641  	// in undoUnlinkCurrentSnap (not tested here)
  1642  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
  1643  	c.Assert(t.Log(), HasLen, 1)
  1644  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
  1645  }
  1646  
  1647  func (s *linkSnapSuite) TestDoUnlinkSnapRefreshAwarenessHardCheckOn(c *C) {
  1648  	s.state.Lock()
  1649  	defer s.state.Unlock()
  1650  
  1651  	tr := config.NewTransaction(s.state)
  1652  	tr.Set("core", "experimental.refresh-app-awareness", true)
  1653  	tr.Commit()
  1654  
  1655  	chg := s.testDoUnlinkSnapRefreshAwareness(c)
  1656  
  1657  	c.Check(chg.Err(), ErrorMatches, `(?ms).*^- some-change-descr \(snap "some-snap" has running apps \(some-app\)\).*`)
  1658  }
  1659  
  1660  func (s *linkSnapSuite) TestDoUnlinkSnapRefreshHardCheckOff(c *C) {
  1661  	s.state.Lock()
  1662  	defer s.state.Unlock()
  1663  
  1664  	tr := config.NewTransaction(s.state)
  1665  	tr.Set("core", "experimental.refresh-app-awareness", false)
  1666  	tr.Commit()
  1667  
  1668  	chg := s.testDoUnlinkSnapRefreshAwareness(c)
  1669  
  1670  	c.Check(chg.Err(), IsNil)
  1671  }
  1672  
  1673  func (s *linkSnapSuite) testDoUnlinkSnapRefreshAwareness(c *C) *state.Change {
  1674  	restore := release.MockOnClassic(true)
  1675  	defer restore()
  1676  
  1677  	dirs.SetRootDir(c.MkDir())
  1678  	defer dirs.SetRootDir("/")
  1679  
  1680  	snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) {
  1681  		info := &snap.Info{SuggestedName: name, SideInfo: *si, SnapType: snap.TypeApp}
  1682  		info.Apps = map[string]*snap.AppInfo{
  1683  			"some-app": {Snap: info, Name: "some-app"},
  1684  		}
  1685  		return info, nil
  1686  	})
  1687  	// mock that "some-snap" has an app and that this app has pids running
  1688  	restore = snapstate.MockPidsOfSnap(func(instanceName string) (map[string][]int, error) {
  1689  		c.Assert(instanceName, Equals, "some-snap")
  1690  		return map[string][]int{
  1691  			"snap.some-snap.some-app": {1234},
  1692  		}, nil
  1693  	})
  1694  	defer restore()
  1695  
  1696  	si1 := &snap.SideInfo{
  1697  		RealName: "some-snap",
  1698  		Revision: snap.R(1),
  1699  	}
  1700  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
  1701  		Sequence: []*snap.SideInfo{si1},
  1702  		Current:  si1.Revision,
  1703  		Active:   true,
  1704  	})
  1705  	t := s.state.NewTask("unlink-current-snap", "some-change-descr")
  1706  	t.Set("snap-setup", &snapstate.SnapSetup{
  1707  		SideInfo: si1,
  1708  	})
  1709  	chg := s.state.NewChange("dummy", "...")
  1710  	chg.AddTask(t)
  1711  
  1712  	s.state.Unlock()
  1713  	defer s.state.Lock()
  1714  
  1715  	for i := 0; i < 3; i++ {
  1716  		s.se.Ensure()
  1717  		s.se.Wait()
  1718  	}
  1719  
  1720  	return chg
  1721  }
  1722  
  1723  func (s *linkSnapSuite) setMockKernelRemodelCtx(c *C, oldKernel, newKernel string) {
  1724  	newModel := MakeModel(map[string]interface{}{"kernel": newKernel})
  1725  	oldModel := MakeModel(map[string]interface{}{"kernel": oldKernel})
  1726  	mockRemodelCtx := &snapstatetest.TrivialDeviceContext{
  1727  		DeviceModel:    newModel,
  1728  		OldDeviceModel: oldModel,
  1729  		Remodeling:     true,
  1730  	}
  1731  	restore := snapstatetest.MockDeviceContext(mockRemodelCtx)
  1732  	s.AddCleanup(restore)
  1733  }
  1734  
  1735  func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesUnrelatedAppDoesNothing(c *C) {
  1736  	s.state.Lock()
  1737  	defer s.state.Unlock()
  1738  
  1739  	s.setMockKernelRemodelCtx(c, "kernel", "new-kernel")
  1740  	t := s.state.NewTask("link-snap", "...")
  1741  	t.Set("snap-setup", &snapstate.SnapSetup{
  1742  		SideInfo: &snap.SideInfo{
  1743  			RealName: "some-app",
  1744  			Revision: snap.R(1),
  1745  		},
  1746  	})
  1747  
  1748  	err := s.snapmgr.MaybeUndoRemodelBootChanges(t)
  1749  	c.Assert(err, IsNil)
  1750  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
  1751  }
  1752  
  1753  func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesSameKernel(c *C) {
  1754  	s.state.Lock()
  1755  	defer s.state.Unlock()
  1756  
  1757  	s.setMockKernelRemodelCtx(c, "kernel", "kernel")
  1758  	t := s.state.NewTask("link-snap", "...")
  1759  	t.Set("snap-setup", &snapstate.SnapSetup{
  1760  		SideInfo: &snap.SideInfo{
  1761  			RealName: "kernel",
  1762  			Revision: snap.R(1),
  1763  		},
  1764  		Type: "kernel",
  1765  	})
  1766  
  1767  	err := s.snapmgr.MaybeUndoRemodelBootChanges(t)
  1768  	c.Assert(err, IsNil)
  1769  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
  1770  }
  1771  
  1772  func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesNeedsUndo(c *C) {
  1773  	s.state.Lock()
  1774  	defer s.state.Unlock()
  1775  
  1776  	// undoing remodel bootenv changes is only relevant on !classic
  1777  	restore := release.MockOnClassic(false)
  1778  	defer restore()
  1779  
  1780  	// using "snaptest.MockSnap()" is more convenient here so we avoid
  1781  	// the (default) mocking of snapReadInfo()
  1782  	restore = snapstate.MockSnapReadInfo(snap.ReadInfo)
  1783  	defer restore()
  1784  
  1785  	// we need to init the boot-id
  1786  	err := s.state.VerifyReboot("some-boot-id")
  1787  	c.Assert(err, IsNil)
  1788  
  1789  	// we pretend we do a remodel from kernel -> new-kernel
  1790  	s.setMockKernelRemodelCtx(c, "kernel", "new-kernel")
  1791  
  1792  	// and we pretend that we booted into the "new-kernel" already
  1793  	// and now that needs to be undone
  1794  	bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
  1795  	bootloader.Force(bloader)
  1796  	bloader.SetBootKernel("new-kernel_1.snap")
  1797  
  1798  	// both kernel and new-kernel are instaleld at this point
  1799  	si := &snap.SideInfo{RealName: "kernel", Revision: snap.R(1)}
  1800  	snapstate.Set(s.state, "kernel", &snapstate.SnapState{
  1801  		Sequence: []*snap.SideInfo{si},
  1802  		SnapType: "kernel",
  1803  		Current:  snap.R(1),
  1804  	})
  1805  	snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 1.0", si)
  1806  	si2 := &snap.SideInfo{RealName: "new-kernel", Revision: snap.R(1)}
  1807  	snapstate.Set(s.state, "new-kernel", &snapstate.SnapState{
  1808  		Sequence: []*snap.SideInfo{si2},
  1809  		SnapType: "kernel",
  1810  		Current:  snap.R(1),
  1811  	})
  1812  	snaptest.MockSnap(c, "name: new-kernel\ntype: kernel\nversion: 1.0", si)
  1813  
  1814  	t := s.state.NewTask("link-snap", "...")
  1815  	t.Set("snap-setup", &snapstate.SnapSetup{
  1816  		SideInfo: &snap.SideInfo{
  1817  			RealName: "new-kernel",
  1818  			Revision: snap.R(1),
  1819  			SnapID:   "new-kernel-id",
  1820  		},
  1821  		Type: "kernel",
  1822  	})
  1823  
  1824  	// now we simulate that the new kernel is getting undone
  1825  	err = s.snapmgr.MaybeUndoRemodelBootChanges(t)
  1826  	c.Assert(err, IsNil)
  1827  
  1828  	// that will schedule a boot into the previous kernel
  1829  	c.Assert(bloader.BootVars, DeepEquals, map[string]string{
  1830  		"snap_mode":       boot.TryStatus,
  1831  		"snap_kernel":     "new-kernel_1.snap",
  1832  		"snap_try_kernel": "kernel_1.snap",
  1833  	})
  1834  	c.Check(s.stateBackend.restartRequested, HasLen, 1)
  1835  	c.Check(s.stateBackend.restartRequested[0], Equals, state.RestartSystem)
  1836  }
  1837  
  1838  func (s *linkSnapSuite) testDoLinkSnapWithToolingDependency(c *C, classicOrBase string) {
  1839  	var model *asserts.Model
  1840  	var needsTooling bool
  1841  	switch classicOrBase {
  1842  	case "classic-system":
  1843  		model = ClassicModel()
  1844  	case "":
  1845  		model = DefaultModel()
  1846  	default:
  1847  		// the tooling mount is needed on UC18+
  1848  		needsTooling = true
  1849  		model = ModelWithBase(classicOrBase)
  1850  	}
  1851  	r := snapstatetest.MockDeviceModel(model)
  1852  	defer r()
  1853  
  1854  	s.state.Lock()
  1855  	si := &snap.SideInfo{
  1856  		RealName: "services-snap",
  1857  		SnapID:   "services-snap-id",
  1858  		Revision: snap.R(11),
  1859  	}
  1860  	t := s.state.NewTask("link-snap", "test")
  1861  	t.Set("snap-setup", &snapstate.SnapSetup{
  1862  		SideInfo: si,
  1863  		Type:     snap.TypeApp,
  1864  	})
  1865  	s.state.NewChange("dummy", "...").AddTask(t)
  1866  
  1867  	s.state.Unlock()
  1868  
  1869  	s.se.Ensure()
  1870  	s.se.Wait()
  1871  
  1872  	s.state.Lock()
  1873  	defer s.state.Unlock()
  1874  
  1875  	expected := fakeOps{
  1876  		{
  1877  			op:    "candidate",
  1878  			sinfo: *si,
  1879  		},
  1880  		{
  1881  			op:                  "link-snap",
  1882  			path:                filepath.Join(dirs.SnapMountDir, "services-snap/11"),
  1883  			requireSnapdTooling: needsTooling,
  1884  		},
  1885  	}
  1886  
  1887  	// start with an easier-to-read error if this fails:
  1888  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
  1889  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
  1890  }
  1891  
  1892  func (s *linkSnapSuite) TestDoLinkSnapWithToolingClassic(c *C) {
  1893  	s.testDoLinkSnapWithToolingDependency(c, "classic-system")
  1894  }
  1895  
  1896  func (s *linkSnapSuite) TestDoLinkSnapWithToolingCore(c *C) {
  1897  	s.testDoLinkSnapWithToolingDependency(c, "")
  1898  }
  1899  
  1900  func (s *linkSnapSuite) TestDoLinkSnapWithToolingCore18(c *C) {
  1901  	s.testDoLinkSnapWithToolingDependency(c, "core18")
  1902  }
  1903  
  1904  func (s *linkSnapSuite) TestDoLinkSnapWithToolingCore20(c *C) {
  1905  	s.testDoLinkSnapWithToolingDependency(c, "core20")
  1906  }