github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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/boot"
    32  	"github.com/snapcore/snapd/boot/boottest"
    33  	"github.com/snapcore/snapd/bootloader"
    34  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/overlord/auth"
    37  	"github.com/snapcore/snapd/overlord/configstate/config"
    38  	"github.com/snapcore/snapd/overlord/snapstate"
    39  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    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/snaptest"
    44  	"github.com/snapcore/snapd/testutil"
    45  )
    46  
    47  type linkSnapSuite struct {
    48  	baseHandlerSuite
    49  
    50  	stateBackend *witnessRestartReqStateBackend
    51  }
    52  
    53  var _ = Suite(&linkSnapSuite{})
    54  
    55  type witnessRestartReqStateBackend struct {
    56  	restartRequested []state.RestartType
    57  }
    58  
    59  func (b *witnessRestartReqStateBackend) Checkpoint([]byte) error {
    60  	return nil
    61  }
    62  
    63  func (b *witnessRestartReqStateBackend) RequestRestart(t state.RestartType) {
    64  	b.restartRequested = append(b.restartRequested, t)
    65  }
    66  
    67  func (b *witnessRestartReqStateBackend) EnsureBefore(time.Duration) {}
    68  
    69  func (s *linkSnapSuite) SetUpTest(c *C) {
    70  	s.stateBackend = &witnessRestartReqStateBackend{}
    71  
    72  	s.setup(c, s.stateBackend)
    73  
    74  	s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel()))
    75  }
    76  
    77  func checkHasCookieForSnap(c *C, st *state.State, instanceName string) {
    78  	var contexts map[string]interface{}
    79  	err := st.Get("snap-cookies", &contexts)
    80  	c.Assert(err, IsNil)
    81  	c.Check(contexts, HasLen, 1)
    82  
    83  	for _, snap := range contexts {
    84  		if instanceName == snap {
    85  			return
    86  		}
    87  	}
    88  	panic(fmt.Sprintf("Cookie missing for snap %q", instanceName))
    89  }
    90  
    91  func (s *linkSnapSuite) TestDoLinkSnapSuccess(c *C) {
    92  	// we start without the auxiliary store info
    93  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
    94  
    95  	s.state.Lock()
    96  	t := s.state.NewTask("link-snap", "test")
    97  	t.Set("snap-setup", &snapstate.SnapSetup{
    98  		SideInfo: &snap.SideInfo{
    99  			RealName: "foo",
   100  			Revision: snap.R(33),
   101  			SnapID:   "foo-id",
   102  		},
   103  		Channel: "beta",
   104  		UserID:  2,
   105  	})
   106  	s.state.NewChange("dummy", "...").AddTask(t)
   107  
   108  	s.state.Unlock()
   109  
   110  	s.se.Ensure()
   111  	s.se.Wait()
   112  
   113  	s.state.Lock()
   114  	defer s.state.Unlock()
   115  	var snapst snapstate.SnapState
   116  	err := snapstate.Get(s.state, "foo", &snapst)
   117  	c.Assert(err, IsNil)
   118  
   119  	checkHasCookieForSnap(c, s.state, "foo")
   120  
   121  	typ, err := snapst.Type()
   122  	c.Check(err, IsNil)
   123  	c.Check(typ, Equals, snap.TypeApp)
   124  
   125  	c.Check(snapst.Active, Equals, true)
   126  	c.Check(snapst.Sequence, HasLen, 1)
   127  	c.Check(snapst.Current, Equals, snap.R(33))
   128  	c.Check(snapst.TrackingChannel, Equals, "latest/beta")
   129  	c.Check(snapst.UserID, Equals, 2)
   130  	c.Check(snapst.CohortKey, Equals, "")
   131  	c.Check(t.Status(), Equals, state.DoneStatus)
   132  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
   133  
   134  	// we end with the auxiliary store info
   135  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent)
   136  }
   137  
   138  func (s *linkSnapSuite) TestDoLinkSnapSuccessWithCohort(c *C) {
   139  	// we start without the auxiliary store info
   140  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
   141  
   142  	s.state.Lock()
   143  	t := s.state.NewTask("link-snap", "test")
   144  	t.Set("snap-setup", &snapstate.SnapSetup{
   145  		SideInfo: &snap.SideInfo{
   146  			RealName: "foo",
   147  			Revision: snap.R(33),
   148  			SnapID:   "foo-id",
   149  		},
   150  		Channel:   "beta",
   151  		UserID:    2,
   152  		CohortKey: "wobbling",
   153  	})
   154  	s.state.NewChange("dummy", "...").AddTask(t)
   155  
   156  	s.state.Unlock()
   157  
   158  	s.se.Ensure()
   159  	s.se.Wait()
   160  
   161  	s.state.Lock()
   162  	defer s.state.Unlock()
   163  	var snapst snapstate.SnapState
   164  	err := snapstate.Get(s.state, "foo", &snapst)
   165  	c.Assert(err, IsNil)
   166  
   167  	checkHasCookieForSnap(c, s.state, "foo")
   168  
   169  	typ, err := snapst.Type()
   170  	c.Check(err, IsNil)
   171  	c.Check(typ, Equals, snap.TypeApp)
   172  
   173  	c.Check(snapst.Active, Equals, true)
   174  	c.Check(snapst.Sequence, HasLen, 1)
   175  	c.Check(snapst.Current, Equals, snap.R(33))
   176  	c.Check(snapst.TrackingChannel, Equals, "latest/beta")
   177  	c.Check(snapst.UserID, Equals, 2)
   178  	c.Check(snapst.CohortKey, Equals, "wobbling")
   179  	c.Check(t.Status(), Equals, state.DoneStatus)
   180  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
   181  
   182  	// we end with the auxiliary store info
   183  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent)
   184  }
   185  
   186  func (s *linkSnapSuite) TestDoLinkSnapSuccessNoUserID(c *C) {
   187  	s.state.Lock()
   188  	t := s.state.NewTask("link-snap", "test")
   189  	t.Set("snap-setup", &snapstate.SnapSetup{
   190  		SideInfo: &snap.SideInfo{
   191  			RealName: "foo",
   192  			Revision: snap.R(33),
   193  		},
   194  		Channel: "beta",
   195  	})
   196  	s.state.NewChange("dummy", "...").AddTask(t)
   197  
   198  	s.state.Unlock()
   199  	s.se.Ensure()
   200  	s.se.Wait()
   201  	s.state.Lock()
   202  	defer s.state.Unlock()
   203  
   204  	// check that snapst.UserID does not get set
   205  	var snapst snapstate.SnapState
   206  	err := snapstate.Get(s.state, "foo", &snapst)
   207  	c.Assert(err, IsNil)
   208  	c.Check(snapst.UserID, Equals, 0)
   209  
   210  	var snaps map[string]*json.RawMessage
   211  	err = s.state.Get("snaps", &snaps)
   212  	c.Assert(err, IsNil)
   213  	raw := []byte(*snaps["foo"])
   214  	c.Check(string(raw), Not(testutil.Contains), "user-id")
   215  }
   216  
   217  func (s *linkSnapSuite) TestDoLinkSnapSuccessUserIDAlreadySet(c *C) {
   218  	s.state.Lock()
   219  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   220  		Sequence: []*snap.SideInfo{
   221  			{RealName: "foo", Revision: snap.R(1)},
   222  		},
   223  		Current: snap.R(1),
   224  		UserID:  1,
   225  	})
   226  	// the user
   227  	user, err := auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"})
   228  	c.Assert(err, IsNil)
   229  	c.Assert(user.ID, Equals, 1)
   230  
   231  	t := s.state.NewTask("link-snap", "test")
   232  	t.Set("snap-setup", &snapstate.SnapSetup{
   233  		SideInfo: &snap.SideInfo{
   234  			RealName: "foo",
   235  			Revision: snap.R(33),
   236  		},
   237  		Channel: "beta",
   238  		UserID:  2,
   239  	})
   240  	s.state.NewChange("dummy", "...").AddTask(t)
   241  
   242  	s.state.Unlock()
   243  	s.se.Ensure()
   244  	s.se.Wait()
   245  	s.state.Lock()
   246  	defer s.state.Unlock()
   247  
   248  	// check that snapst.UserID was not "transferred"
   249  	var snapst snapstate.SnapState
   250  	err = snapstate.Get(s.state, "foo", &snapst)
   251  	c.Assert(err, IsNil)
   252  	c.Check(snapst.UserID, Equals, 1)
   253  }
   254  
   255  func (s *linkSnapSuite) TestDoLinkSnapSuccessUserLoggedOut(c *C) {
   256  	s.state.Lock()
   257  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   258  		Sequence: []*snap.SideInfo{
   259  			{RealName: "foo", Revision: snap.R(1)},
   260  		},
   261  		Current: snap.R(1),
   262  		UserID:  1,
   263  	})
   264  
   265  	t := s.state.NewTask("link-snap", "test")
   266  	t.Set("snap-setup", &snapstate.SnapSetup{
   267  		SideInfo: &snap.SideInfo{
   268  			RealName: "foo",
   269  			Revision: snap.R(33),
   270  		},
   271  		Channel: "beta",
   272  		UserID:  2,
   273  	})
   274  	s.state.NewChange("dummy", "...").AddTask(t)
   275  
   276  	s.state.Unlock()
   277  	s.se.Ensure()
   278  	s.se.Wait()
   279  	s.state.Lock()
   280  	defer s.state.Unlock()
   281  
   282  	// check that snapst.UserID was transferred
   283  	// given that user 1 doesn't exist anymore
   284  	var snapst snapstate.SnapState
   285  	err := snapstate.Get(s.state, "foo", &snapst)
   286  	c.Assert(err, IsNil)
   287  	c.Check(snapst.UserID, Equals, 2)
   288  }
   289  
   290  func (s *linkSnapSuite) TestDoLinkSnapSeqFile(c *C) {
   291  	s.state.Lock()
   292  	// pretend we have an installed snap
   293  	si11 := &snap.SideInfo{
   294  		RealName: "foo",
   295  		Revision: snap.R(11),
   296  	}
   297  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   298  		Sequence: []*snap.SideInfo{si11},
   299  		Current:  si11.Revision,
   300  	})
   301  	// add a new one
   302  	t := s.state.NewTask("link-snap", "test")
   303  	t.Set("snap-setup", &snapstate.SnapSetup{
   304  		SideInfo: &snap.SideInfo{
   305  			RealName: "foo",
   306  			Revision: snap.R(33),
   307  		},
   308  		Channel: "beta",
   309  	})
   310  	s.state.NewChange("dummy", "...").AddTask(t)
   311  	s.state.Unlock()
   312  
   313  	s.se.Ensure()
   314  	s.se.Wait()
   315  
   316  	s.state.Lock()
   317  	defer s.state.Unlock()
   318  	var snapst snapstate.SnapState
   319  	err := snapstate.Get(s.state, "foo", &snapst)
   320  	c.Assert(err, IsNil)
   321  
   322  	// and check that the sequence file got updated
   323  	seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json"))
   324  	c.Assert(err, IsNil)
   325  	c.Check(string(seqContent), Equals, `{"sequence":[{"name":"foo","snap-id":"","revision":"11"},{"name":"foo","snap-id":"","revision":"33"}],"current":"33"}`)
   326  }
   327  
   328  func (s *linkSnapSuite) TestDoUndoLinkSnap(c *C) {
   329  	s.state.Lock()
   330  	defer s.state.Unlock()
   331  	// a hook might have set some config
   332  	cfg := json.RawMessage(`{"c":true}`)
   333  	err := config.SetSnapConfig(s.state, "foo", &cfg)
   334  	c.Assert(err, IsNil)
   335  
   336  	si := &snap.SideInfo{
   337  		RealName: "foo",
   338  		Revision: snap.R(33),
   339  	}
   340  	t := s.state.NewTask("link-snap", "test")
   341  	t.Set("snap-setup", &snapstate.SnapSetup{
   342  		SideInfo: si,
   343  		Channel:  "beta",
   344  	})
   345  	chg := s.state.NewChange("dummy", "...")
   346  	chg.AddTask(t)
   347  
   348  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   349  	terr.WaitFor(t)
   350  	chg.AddTask(terr)
   351  
   352  	s.state.Unlock()
   353  
   354  	for i := 0; i < 6; i++ {
   355  		s.se.Ensure()
   356  		s.se.Wait()
   357  	}
   358  
   359  	s.state.Lock()
   360  	var snapst snapstate.SnapState
   361  	err = snapstate.Get(s.state, "foo", &snapst)
   362  	c.Assert(err, Equals, state.ErrNoState)
   363  	c.Check(t.Status(), Equals, state.UndoneStatus)
   364  
   365  	// and check that the sequence file got updated
   366  	seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json"))
   367  	c.Assert(err, IsNil)
   368  	c.Check(string(seqContent), Equals, `{"sequence":[],"current":"unset"}`)
   369  
   370  	// nothing in config
   371  	var config map[string]*json.RawMessage
   372  	err = s.state.Get("config", &config)
   373  	c.Assert(err, IsNil)
   374  	c.Check(config, HasLen, 1)
   375  	_, ok := config["core"]
   376  	c.Check(ok, Equals, true)
   377  }
   378  
   379  func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapWithVitalityScore(c *C) {
   380  	s.state.Lock()
   381  	defer s.state.Unlock()
   382  	// foo has a vitality-hint
   383  	cfg := json.RawMessage(`{"resilience":{"vitality-hint":"bar,foo,baz"}}`)
   384  	err := config.SetSnapConfig(s.state, "core", &cfg)
   385  	c.Assert(err, IsNil)
   386  
   387  	si1 := &snap.SideInfo{
   388  		RealName: "foo",
   389  		Revision: snap.R(11),
   390  	}
   391  	si2 := &snap.SideInfo{
   392  		RealName: "foo",
   393  		Revision: snap.R(33),
   394  	}
   395  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   396  		Sequence: []*snap.SideInfo{si1},
   397  		Current:  si1.Revision,
   398  		Active:   true,
   399  	})
   400  	t := s.state.NewTask("unlink-current-snap", "test")
   401  	t.Set("snap-setup", &snapstate.SnapSetup{
   402  		SideInfo: si2,
   403  	})
   404  	chg := s.state.NewChange("dummy", "...")
   405  	chg.AddTask(t)
   406  
   407  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   408  	terr.WaitFor(t)
   409  	chg.AddTask(terr)
   410  
   411  	s.state.Unlock()
   412  
   413  	for i := 0; i < 3; i++ {
   414  		s.se.Ensure()
   415  		s.se.Wait()
   416  	}
   417  
   418  	s.state.Lock()
   419  	var snapst snapstate.SnapState
   420  	err = snapstate.Get(s.state, "foo", &snapst)
   421  	c.Assert(err, IsNil)
   422  	c.Check(snapst.Active, Equals, true)
   423  	c.Check(snapst.Sequence, HasLen, 1)
   424  	c.Check(snapst.Current, Equals, snap.R(11))
   425  	c.Check(t.Status(), Equals, state.UndoneStatus)
   426  
   427  	expected := fakeOps{
   428  		{
   429  			op:   "unlink-snap",
   430  			path: filepath.Join(dirs.SnapMountDir, "foo/11"),
   431  		},
   432  		{
   433  			op:           "link-snap",
   434  			path:         filepath.Join(dirs.SnapMountDir, "foo/11"),
   435  			vitalityRank: 2,
   436  		},
   437  	}
   438  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   439  }
   440  
   441  func (s *linkSnapSuite) TestDoLinkSnapWithVitalityScore(c *C) {
   442  	s.state.Lock()
   443  	defer s.state.Unlock()
   444  	// a hook might have set some config
   445  	cfg := json.RawMessage(`{"resilience":{"vitality-hint":"bar,foo,baz"}}`)
   446  	err := config.SetSnapConfig(s.state, "core", &cfg)
   447  	c.Assert(err, IsNil)
   448  
   449  	si := &snap.SideInfo{
   450  		RealName: "foo",
   451  		Revision: snap.R(33),
   452  	}
   453  	t := s.state.NewTask("link-snap", "test")
   454  	t.Set("snap-setup", &snapstate.SnapSetup{
   455  		SideInfo: si,
   456  	})
   457  	chg := s.state.NewChange("dummy", "...")
   458  	chg.AddTask(t)
   459  
   460  	s.state.Unlock()
   461  
   462  	for i := 0; i < 6; i++ {
   463  		s.se.Ensure()
   464  		s.se.Wait()
   465  	}
   466  
   467  	s.state.Lock()
   468  	expected := fakeOps{
   469  		{
   470  			op:    "candidate",
   471  			sinfo: *si,
   472  		},
   473  		{
   474  			op:           "link-snap",
   475  			path:         filepath.Join(dirs.SnapMountDir, "foo/33"),
   476  			vitalityRank: 2,
   477  		},
   478  	}
   479  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   480  }
   481  
   482  func (s *linkSnapSuite) TestDoLinkSnapTryToCleanupOnError(c *C) {
   483  	s.state.Lock()
   484  	defer s.state.Unlock()
   485  	si := &snap.SideInfo{
   486  		RealName: "foo",
   487  		Revision: snap.R(35),
   488  	}
   489  	t := s.state.NewTask("link-snap", "test")
   490  	t.Set("snap-setup", &snapstate.SnapSetup{
   491  		SideInfo: si,
   492  		Channel:  "beta",
   493  	})
   494  
   495  	s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "foo/35")
   496  	s.state.NewChange("dummy", "...").AddTask(t)
   497  	s.state.Unlock()
   498  
   499  	s.se.Ensure()
   500  	s.se.Wait()
   501  
   502  	s.state.Lock()
   503  
   504  	// state as expected
   505  	var snapst snapstate.SnapState
   506  	err := snapstate.Get(s.state, "foo", &snapst)
   507  	c.Assert(err, Equals, state.ErrNoState)
   508  
   509  	// tried to cleanup
   510  	expected := fakeOps{
   511  		{
   512  			op:    "candidate",
   513  			sinfo: *si,
   514  		},
   515  		{
   516  			op:   "link-snap.failed",
   517  			path: filepath.Join(dirs.SnapMountDir, "foo/35"),
   518  		},
   519  		{
   520  			op:   "unlink-snap",
   521  			path: filepath.Join(dirs.SnapMountDir, "foo/35"),
   522  
   523  			unlinkFirstInstallUndo: true,
   524  		},
   525  	}
   526  
   527  	// start with an easier-to-read error if this fails:
   528  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   529  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   530  }
   531  
   532  func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreRestarts(c *C) {
   533  	restore := release.MockOnClassic(true)
   534  	defer restore()
   535  
   536  	s.state.Lock()
   537  	si := &snap.SideInfo{
   538  		RealName: "core",
   539  		Revision: snap.R(33),
   540  	}
   541  	t := s.state.NewTask("link-snap", "test")
   542  	t.Set("snap-setup", &snapstate.SnapSetup{
   543  		SideInfo: si,
   544  	})
   545  	s.state.NewChange("dummy", "...").AddTask(t)
   546  
   547  	s.state.Unlock()
   548  
   549  	s.se.Ensure()
   550  	s.se.Wait()
   551  
   552  	s.state.Lock()
   553  	defer s.state.Unlock()
   554  
   555  	var snapst snapstate.SnapState
   556  	err := snapstate.Get(s.state, "core", &snapst)
   557  	c.Assert(err, IsNil)
   558  
   559  	typ, err := snapst.Type()
   560  	c.Check(err, IsNil)
   561  	c.Check(typ, Equals, snap.TypeOS)
   562  
   563  	c.Check(t.Status(), Equals, state.DoneStatus)
   564  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   565  	c.Check(t.Log(), HasLen, 1)
   566  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart\.`)
   567  }
   568  
   569  func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnCoreWithBase(c *C) {
   570  	restore := release.MockOnClassic(false)
   571  	defer restore()
   572  
   573  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   574  	defer r()
   575  
   576  	s.state.Lock()
   577  	si := &snap.SideInfo{
   578  		RealName: "snapd",
   579  		SnapID:   "snapd-snap-id",
   580  		Revision: snap.R(22),
   581  	}
   582  	t := s.state.NewTask("link-snap", "test")
   583  	t.Set("snap-setup", &snapstate.SnapSetup{
   584  		SideInfo: si,
   585  		Type:     snap.TypeSnapd,
   586  	})
   587  	s.state.NewChange("dummy", "...").AddTask(t)
   588  
   589  	s.state.Unlock()
   590  
   591  	s.se.Ensure()
   592  	s.se.Wait()
   593  
   594  	s.state.Lock()
   595  	defer s.state.Unlock()
   596  
   597  	var snapst snapstate.SnapState
   598  	err := snapstate.Get(s.state, "snapd", &snapst)
   599  	c.Assert(err, IsNil)
   600  
   601  	typ, err := snapst.Type()
   602  	c.Check(err, IsNil)
   603  	c.Check(typ, Equals, snap.TypeSnapd)
   604  
   605  	c.Check(t.Status(), Equals, state.DoneStatus)
   606  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   607  	c.Check(t.Log(), HasLen, 1)
   608  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
   609  }
   610  
   611  func (s *linkSnapSuite) TestDoLinkSnapSuccessRebootForCoreBase(c *C) {
   612  	restore := release.MockOnClassic(false)
   613  	defer restore()
   614  
   615  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   616  	defer r()
   617  
   618  	s.fakeBackend.linkSnapMaybeReboot = true
   619  
   620  	s.state.Lock()
   621  	defer s.state.Unlock()
   622  
   623  	// we need to init the boot-id
   624  	err := s.state.VerifyReboot("some-boot-id")
   625  	c.Assert(err, IsNil)
   626  
   627  	si := &snap.SideInfo{
   628  		RealName: "core18",
   629  		SnapID:   "core18-id",
   630  		Revision: snap.R(22),
   631  	}
   632  	t := s.state.NewTask("link-snap", "test")
   633  	t.Set("snap-setup", &snapstate.SnapSetup{
   634  		SideInfo: si,
   635  	})
   636  	s.state.NewChange("dummy", "...").AddTask(t)
   637  
   638  	s.state.Unlock()
   639  	s.se.Ensure()
   640  	s.se.Wait()
   641  	s.state.Lock()
   642  
   643  	c.Check(t.Status(), Equals, state.DoneStatus)
   644  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartSystem})
   645  	c.Assert(t.Log(), HasLen, 1)
   646  	c.Check(t.Log()[0], Matches, `.*INFO Requested system restart.*`)
   647  }
   648  
   649  func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnClassic(c *C) {
   650  	restore := release.MockOnClassic(true)
   651  	defer restore()
   652  
   653  	s.state.Lock()
   654  	si := &snap.SideInfo{
   655  		RealName: "snapd",
   656  		SnapID:   "snapd-snap-id",
   657  		Revision: snap.R(22),
   658  	}
   659  	t := s.state.NewTask("link-snap", "test")
   660  	t.Set("snap-setup", &snapstate.SnapSetup{
   661  		SideInfo: si,
   662  		Type:     snap.TypeSnapd,
   663  	})
   664  	s.state.NewChange("dummy", "...").AddTask(t)
   665  
   666  	s.state.Unlock()
   667  
   668  	s.se.Ensure()
   669  	s.se.Wait()
   670  
   671  	s.state.Lock()
   672  	defer s.state.Unlock()
   673  
   674  	var snapst snapstate.SnapState
   675  	err := snapstate.Get(s.state, "snapd", &snapst)
   676  	c.Assert(err, IsNil)
   677  
   678  	typ, err := snapst.Type()
   679  	c.Check(err, IsNil)
   680  	c.Check(typ, Equals, snap.TypeSnapd)
   681  
   682  	c.Check(t.Status(), Equals, state.DoneStatus)
   683  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   684  	c.Check(t.Log(), HasLen, 1)
   685  }
   686  
   687  func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreAndSnapdNoCoreRestart(c *C) {
   688  	restore := release.MockOnClassic(true)
   689  	defer restore()
   690  
   691  	s.state.Lock()
   692  	siSnapd := &snap.SideInfo{
   693  		RealName: "snapd",
   694  		Revision: snap.R(64),
   695  	}
   696  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   697  		Sequence: []*snap.SideInfo{siSnapd},
   698  		Current:  siSnapd.Revision,
   699  		Active:   true,
   700  		SnapType: "snapd",
   701  	})
   702  
   703  	si := &snap.SideInfo{
   704  		RealName: "core",
   705  		Revision: snap.R(33),
   706  	}
   707  	t := s.state.NewTask("link-snap", "test")
   708  	t.Set("snap-setup", &snapstate.SnapSetup{
   709  		SideInfo: si,
   710  	})
   711  	s.state.NewChange("dummy", "...").AddTask(t)
   712  
   713  	s.state.Unlock()
   714  
   715  	s.se.Ensure()
   716  	s.se.Wait()
   717  
   718  	s.state.Lock()
   719  	defer s.state.Unlock()
   720  
   721  	var snapst snapstate.SnapState
   722  	err := snapstate.Get(s.state, "core", &snapst)
   723  	c.Assert(err, IsNil)
   724  
   725  	typ, err := snapst.Type()
   726  	c.Check(err, IsNil)
   727  	c.Check(typ, Equals, snap.TypeOS)
   728  
   729  	c.Check(t.Status(), Equals, state.DoneStatus)
   730  	c.Check(s.stateBackend.restartRequested, IsNil)
   731  	c.Check(t.Log(), HasLen, 0)
   732  }
   733  
   734  func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceDidNotHaveCandidate(c *C) {
   735  	s.state.Lock()
   736  	defer s.state.Unlock()
   737  	si1 := &snap.SideInfo{
   738  		RealName: "foo",
   739  		Revision: snap.R(1),
   740  	}
   741  	si2 := &snap.SideInfo{
   742  		RealName: "foo",
   743  		Revision: snap.R(2),
   744  	}
   745  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   746  		Sequence: []*snap.SideInfo{si1},
   747  		Current:  si1.Revision,
   748  	})
   749  	t := s.state.NewTask("link-snap", "test")
   750  	t.Set("snap-setup", &snapstate.SnapSetup{
   751  		SideInfo: si2,
   752  		Channel:  "beta",
   753  	})
   754  	chg := s.state.NewChange("dummy", "...")
   755  	chg.AddTask(t)
   756  
   757  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   758  	terr.WaitFor(t)
   759  	chg.AddTask(terr)
   760  
   761  	s.state.Unlock()
   762  
   763  	for i := 0; i < 6; i++ {
   764  		s.se.Ensure()
   765  		s.se.Wait()
   766  	}
   767  
   768  	s.state.Lock()
   769  	var snapst snapstate.SnapState
   770  	err := snapstate.Get(s.state, "foo", &snapst)
   771  	c.Assert(err, IsNil)
   772  	c.Check(snapst.Active, Equals, false)
   773  	c.Check(snapst.Sequence, HasLen, 1)
   774  	c.Check(snapst.Current, Equals, snap.R(1))
   775  	c.Check(t.Status(), Equals, state.UndoneStatus)
   776  }
   777  
   778  func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceHadCandidate(c *C) {
   779  	s.state.Lock()
   780  	defer s.state.Unlock()
   781  	si1 := &snap.SideInfo{
   782  		RealName: "foo",
   783  		Revision: snap.R(1),
   784  	}
   785  	si2 := &snap.SideInfo{
   786  		RealName: "foo",
   787  		Revision: snap.R(2),
   788  	}
   789  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   790  		Sequence: []*snap.SideInfo{si1, si2},
   791  		Current:  si2.Revision,
   792  	})
   793  	t := s.state.NewTask("link-snap", "test")
   794  	t.Set("snap-setup", &snapstate.SnapSetup{
   795  		SideInfo: si1,
   796  		Channel:  "beta",
   797  	})
   798  	chg := s.state.NewChange("dummy", "...")
   799  	chg.AddTask(t)
   800  
   801  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   802  	terr.WaitFor(t)
   803  	chg.AddTask(terr)
   804  
   805  	s.state.Unlock()
   806  
   807  	for i := 0; i < 6; i++ {
   808  		s.se.Ensure()
   809  		s.se.Wait()
   810  	}
   811  
   812  	s.state.Lock()
   813  	var snapst snapstate.SnapState
   814  	err := snapstate.Get(s.state, "foo", &snapst)
   815  	c.Assert(err, IsNil)
   816  	c.Check(snapst.Active, Equals, false)
   817  	c.Check(snapst.Sequence, HasLen, 2)
   818  	c.Check(snapst.Current, Equals, snap.R(2))
   819  	c.Check(t.Status(), Equals, state.UndoneStatus)
   820  }
   821  
   822  func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapCore(c *C) {
   823  	restore := release.MockOnClassic(true)
   824  	defer restore()
   825  
   826  	s.state.Lock()
   827  	defer s.state.Unlock()
   828  	si1 := &snap.SideInfo{
   829  		RealName: "core",
   830  		Revision: snap.R(1),
   831  	}
   832  	si2 := &snap.SideInfo{
   833  		RealName: "core",
   834  		Revision: snap.R(2),
   835  	}
   836  	snapstate.Set(s.state, "core", &snapstate.SnapState{
   837  		Sequence: []*snap.SideInfo{si1},
   838  		Current:  si1.Revision,
   839  		Active:   true,
   840  		SnapType: "os",
   841  	})
   842  	t := s.state.NewTask("unlink-current-snap", "test")
   843  	t.Set("snap-setup", &snapstate.SnapSetup{
   844  		SideInfo: si2,
   845  	})
   846  	chg := s.state.NewChange("dummy", "...")
   847  	chg.AddTask(t)
   848  
   849  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   850  	terr.WaitFor(t)
   851  	chg.AddTask(terr)
   852  
   853  	s.state.Unlock()
   854  
   855  	for i := 0; i < 3; i++ {
   856  		s.se.Ensure()
   857  		s.se.Wait()
   858  	}
   859  
   860  	s.state.Lock()
   861  	var snapst snapstate.SnapState
   862  	err := snapstate.Get(s.state, "core", &snapst)
   863  	c.Assert(err, IsNil)
   864  	c.Check(snapst.Active, Equals, true)
   865  	c.Check(snapst.Sequence, HasLen, 1)
   866  	c.Check(snapst.Current, Equals, snap.R(1))
   867  	c.Check(t.Status(), Equals, state.UndoneStatus)
   868  
   869  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   870  }
   871  
   872  func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapCoreBase(c *C) {
   873  	restore := release.MockOnClassic(false)
   874  	defer restore()
   875  
   876  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   877  	defer r()
   878  
   879  	s.fakeBackend.linkSnapMaybeReboot = true
   880  
   881  	s.state.Lock()
   882  	defer s.state.Unlock()
   883  	// we need to init the boot-id
   884  	err := s.state.VerifyReboot("some-boot-id")
   885  	c.Assert(err, IsNil)
   886  
   887  	si1 := &snap.SideInfo{
   888  		RealName: "core18",
   889  		Revision: snap.R(1),
   890  	}
   891  	si2 := &snap.SideInfo{
   892  		RealName: "core18",
   893  		Revision: snap.R(2),
   894  	}
   895  	snapstate.Set(s.state, "core18", &snapstate.SnapState{
   896  		Sequence: []*snap.SideInfo{si1},
   897  		Current:  si1.Revision,
   898  		Active:   true,
   899  		SnapType: "base",
   900  	})
   901  	t := s.state.NewTask("unlink-current-snap", "test")
   902  	t.Set("snap-setup", &snapstate.SnapSetup{
   903  		SideInfo: si2,
   904  	})
   905  	chg := s.state.NewChange("dummy", "...")
   906  	chg.AddTask(t)
   907  
   908  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   909  	terr.WaitFor(t)
   910  	chg.AddTask(terr)
   911  
   912  	s.state.Unlock()
   913  	for i := 0; i < 3; i++ {
   914  		s.se.Ensure()
   915  		s.se.Wait()
   916  	}
   917  	s.state.Lock()
   918  
   919  	var snapst snapstate.SnapState
   920  	err = snapstate.Get(s.state, "core18", &snapst)
   921  	c.Assert(err, IsNil)
   922  	c.Check(snapst.Active, Equals, true)
   923  	c.Check(snapst.Sequence, HasLen, 1)
   924  	c.Check(snapst.Current, Equals, snap.R(1))
   925  	c.Check(t.Status(), Equals, state.UndoneStatus)
   926  
   927  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartSystem})
   928  }
   929  
   930  func (s *linkSnapSuite) TestDoUndoLinkSnapCoreClassic(c *C) {
   931  	restore := release.MockOnClassic(true)
   932  	defer restore()
   933  
   934  	s.state.Lock()
   935  	defer s.state.Unlock()
   936  
   937  	// no previous core snap and an error on link, in this
   938  	// case we need to restart on classic back into the distro
   939  	// package version
   940  	si1 := &snap.SideInfo{
   941  		RealName: "core",
   942  		Revision: snap.R(1),
   943  	}
   944  	t := s.state.NewTask("link-snap", "test")
   945  	t.Set("snap-setup", &snapstate.SnapSetup{
   946  		SideInfo: si1,
   947  	})
   948  	chg := s.state.NewChange("dummy", "...")
   949  	chg.AddTask(t)
   950  
   951  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   952  	terr.WaitFor(t)
   953  	chg.AddTask(terr)
   954  
   955  	s.state.Unlock()
   956  
   957  	for i := 0; i < 3; i++ {
   958  		s.se.Ensure()
   959  		s.se.Wait()
   960  	}
   961  
   962  	s.state.Lock()
   963  	var snapst snapstate.SnapState
   964  	err := snapstate.Get(s.state, "core", &snapst)
   965  	c.Assert(err, Equals, state.ErrNoState)
   966  	c.Check(t.Status(), Equals, state.UndoneStatus)
   967  
   968  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon})
   969  
   970  }
   971  
   972  func (s *linkSnapSuite) TestLinkSnapInjectsAutoConnectIfMissing(c *C) {
   973  	si1 := &snap.SideInfo{
   974  		RealName: "snap1",
   975  		Revision: snap.R(1),
   976  	}
   977  	sup1 := &snapstate.SnapSetup{SideInfo: si1}
   978  	si2 := &snap.SideInfo{
   979  		RealName: "snap2",
   980  		Revision: snap.R(1),
   981  	}
   982  	sup2 := &snapstate.SnapSetup{SideInfo: si2}
   983  
   984  	s.state.Lock()
   985  	defer s.state.Unlock()
   986  
   987  	task0 := s.state.NewTask("setup-profiles", "")
   988  	task1 := s.state.NewTask("link-snap", "")
   989  	task1.WaitFor(task0)
   990  	task0.Set("snap-setup", sup1)
   991  	task1.Set("snap-setup", sup1)
   992  
   993  	task2 := s.state.NewTask("setup-profiles", "")
   994  	task3 := s.state.NewTask("link-snap", "")
   995  	task2.WaitFor(task1)
   996  	task3.WaitFor(task2)
   997  	task2.Set("snap-setup", sup2)
   998  	task3.Set("snap-setup", sup2)
   999  
  1000  	chg := s.state.NewChange("test", "")
  1001  	chg.AddTask(task0)
  1002  	chg.AddTask(task1)
  1003  	chg.AddTask(task2)
  1004  	chg.AddTask(task3)
  1005  
  1006  	s.state.Unlock()
  1007  
  1008  	for i := 0; i < 10; i++ {
  1009  		s.se.Ensure()
  1010  		s.se.Wait()
  1011  	}
  1012  
  1013  	s.state.Lock()
  1014  
  1015  	// ensure all our tasks ran
  1016  	c.Assert(chg.Err(), IsNil)
  1017  	c.Assert(chg.Tasks(), HasLen, 6)
  1018  
  1019  	// sanity checks
  1020  	t := chg.Tasks()[1]
  1021  	c.Assert(t.Kind(), Equals, "link-snap")
  1022  	t = chg.Tasks()[3]
  1023  	c.Assert(t.Kind(), Equals, "link-snap")
  1024  
  1025  	// check that auto-connect tasks were added and have snap-setup
  1026  	var autoconnectSup snapstate.SnapSetup
  1027  	t = chg.Tasks()[4]
  1028  	c.Assert(t.Kind(), Equals, "auto-connect")
  1029  	c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil)
  1030  	c.Assert(autoconnectSup.InstanceName(), Equals, "snap1")
  1031  
  1032  	t = chg.Tasks()[5]
  1033  	c.Assert(t.Kind(), Equals, "auto-connect")
  1034  	c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil)
  1035  	c.Assert(autoconnectSup.InstanceName(), Equals, "snap2")
  1036  }
  1037  
  1038  func (s *linkSnapSuite) TestDoLinkSnapFailureCleansUpAux(c *C) {
  1039  	// this is very chummy with the order of LinkSnap
  1040  	c.Assert(ioutil.WriteFile(dirs.SnapSeqDir, nil, 0644), IsNil)
  1041  
  1042  	// we start without the auxiliary store info
  1043  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
  1044  
  1045  	s.state.Lock()
  1046  	t := s.state.NewTask("link-snap", "test")
  1047  	t.Set("snap-setup", &snapstate.SnapSetup{
  1048  		SideInfo: &snap.SideInfo{
  1049  			RealName: "foo",
  1050  			Revision: snap.R(33),
  1051  			SnapID:   "foo-id",
  1052  		},
  1053  		Channel: "beta",
  1054  		UserID:  2,
  1055  	})
  1056  	s.state.NewChange("dummy", "...").AddTask(t)
  1057  
  1058  	s.state.Unlock()
  1059  
  1060  	s.se.Ensure()
  1061  	s.se.Wait()
  1062  
  1063  	s.state.Lock()
  1064  	defer s.state.Unlock()
  1065  
  1066  	c.Check(t.Status(), Equals, state.ErrorStatus)
  1067  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
  1068  
  1069  	// we end without the auxiliary store info
  1070  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
  1071  }
  1072  
  1073  func (s *linkSnapSuite) TestLinkSnapResetsRefreshInhibitedTime(c *C) {
  1074  	// When a snap is linked the refresh-inhibited-time is reset to zero
  1075  	// to indicate a successful refresh. The old value is stored in task
  1076  	// state for task undo logic.
  1077  	s.state.Lock()
  1078  	defer s.state.Unlock()
  1079  
  1080  	instant := time.Now()
  1081  
  1082  	si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)}
  1083  	sup := &snapstate.SnapSetup{SideInfo: si}
  1084  	snapstate.Set(s.state, "snap", &snapstate.SnapState{
  1085  		Sequence:             []*snap.SideInfo{si},
  1086  		Current:              si.Revision,
  1087  		RefreshInhibitedTime: &instant,
  1088  	})
  1089  
  1090  	task := s.state.NewTask("link-snap", "")
  1091  	task.Set("snap-setup", sup)
  1092  	chg := s.state.NewChange("test", "")
  1093  	chg.AddTask(task)
  1094  
  1095  	s.state.Unlock()
  1096  
  1097  	for i := 0; i < 10; i++ {
  1098  		s.se.Ensure()
  1099  		s.se.Wait()
  1100  	}
  1101  
  1102  	s.state.Lock()
  1103  
  1104  	c.Assert(chg.Err(), IsNil)
  1105  	c.Assert(chg.Tasks(), HasLen, 1)
  1106  
  1107  	var snapst snapstate.SnapState
  1108  	err := snapstate.Get(s.state, "snap", &snapst)
  1109  	c.Assert(err, IsNil)
  1110  	c.Check(snapst.RefreshInhibitedTime, IsNil)
  1111  
  1112  	var oldTime time.Time
  1113  	c.Assert(task.Get("old-refresh-inhibited-time", &oldTime), IsNil)
  1114  	c.Check(oldTime.Equal(instant), Equals, true)
  1115  }
  1116  
  1117  func (s *linkSnapSuite) TestDoUndoLinkSnapRestoresRefreshInhibitedTime(c *C) {
  1118  	s.state.Lock()
  1119  	defer s.state.Unlock()
  1120  
  1121  	instant := time.Now()
  1122  
  1123  	si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)}
  1124  	sup := &snapstate.SnapSetup{SideInfo: si}
  1125  	snapstate.Set(s.state, "snap", &snapstate.SnapState{
  1126  		Sequence:             []*snap.SideInfo{si},
  1127  		Current:              si.Revision,
  1128  		RefreshInhibitedTime: &instant,
  1129  	})
  1130  
  1131  	task := s.state.NewTask("link-snap", "")
  1132  	task.Set("snap-setup", sup)
  1133  	chg := s.state.NewChange("test", "")
  1134  	chg.AddTask(task)
  1135  
  1136  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1137  	terr.WaitFor(task)
  1138  	chg.AddTask(terr)
  1139  
  1140  	s.state.Unlock()
  1141  
  1142  	for i := 0; i < 6; i++ {
  1143  		s.se.Ensure()
  1144  		s.se.Wait()
  1145  	}
  1146  
  1147  	s.state.Lock()
  1148  
  1149  	c.Assert(chg.Err(), NotNil)
  1150  	c.Assert(chg.Tasks(), HasLen, 2)
  1151  	c.Check(task.Status(), Equals, state.UndoneStatus)
  1152  
  1153  	var snapst snapstate.SnapState
  1154  	err := snapstate.Get(s.state, "snap", &snapst)
  1155  	c.Assert(err, IsNil)
  1156  	c.Check(snapst.RefreshInhibitedTime.Equal(instant), Equals, true)
  1157  }
  1158  
  1159  func (s *linkSnapSuite) TestUndoLinkSnapdFirstInstall(c *C) {
  1160  	restore := release.MockOnClassic(false)
  1161  	defer restore()
  1162  
  1163  	s.state.Lock()
  1164  	si := &snap.SideInfo{
  1165  		RealName: "snapd",
  1166  		SnapID:   "snapd-snap-id",
  1167  		Revision: snap.R(22),
  1168  	}
  1169  	chg := s.state.NewChange("dummy", "...")
  1170  	t := s.state.NewTask("link-snap", "test")
  1171  	t.Set("snap-setup", &snapstate.SnapSetup{
  1172  		SideInfo: si,
  1173  		Type:     snap.TypeSnapd,
  1174  	})
  1175  	chg.AddTask(t)
  1176  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1177  	terr.WaitFor(t)
  1178  	chg.AddTask(terr)
  1179  
  1180  	s.state.Unlock()
  1181  
  1182  	for i := 0; i < 6; i++ {
  1183  		s.se.Ensure()
  1184  		s.se.Wait()
  1185  	}
  1186  
  1187  	s.state.Lock()
  1188  	defer s.state.Unlock()
  1189  
  1190  	var snapst snapstate.SnapState
  1191  	err := snapstate.Get(s.state, "snapd", &snapst)
  1192  	c.Assert(err, Equals, state.ErrNoState)
  1193  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1194  
  1195  	expected := fakeOps{
  1196  		{
  1197  			op:    "candidate",
  1198  			sinfo: *si,
  1199  		},
  1200  		{
  1201  			op:   "link-snap",
  1202  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1203  		},
  1204  		{
  1205  			op:   "discard-namespace",
  1206  			name: "snapd",
  1207  		},
  1208  		{
  1209  			op:   "unlink-snap",
  1210  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1211  
  1212  			unlinkFirstInstallUndo: true,
  1213  		},
  1214  	}
  1215  
  1216  	// start with an easier-to-read error if this fails:
  1217  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
  1218  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
  1219  
  1220  	// 2 restarts, one from link snap, another one from undo
  1221  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon})
  1222  	c.Check(t.Log(), HasLen, 3)
  1223  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
  1224  	c.Check(t.Log()[2], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
  1225  
  1226  }
  1227  
  1228  func (s *linkSnapSuite) TestUndoLinkSnapdNthInstall(c *C) {
  1229  	restore := release.MockOnClassic(false)
  1230  	defer restore()
  1231  
  1232  	s.state.Lock()
  1233  	si := &snap.SideInfo{
  1234  		RealName: "snapd",
  1235  		SnapID:   "snapd-snap-id",
  1236  		Revision: snap.R(22),
  1237  	}
  1238  	siOld := *si
  1239  	siOld.Revision = snap.R(20)
  1240  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
  1241  		Sequence: []*snap.SideInfo{&siOld},
  1242  		Current:  siOld.Revision,
  1243  		Active:   true,
  1244  		SnapType: "snapd",
  1245  	})
  1246  	chg := s.state.NewChange("dummy", "...")
  1247  	t := s.state.NewTask("link-snap", "test")
  1248  	t.Set("snap-setup", &snapstate.SnapSetup{
  1249  		SideInfo: si,
  1250  		Type:     snap.TypeSnapd,
  1251  	})
  1252  	chg.AddTask(t)
  1253  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1254  	terr.WaitFor(t)
  1255  	chg.AddTask(terr)
  1256  
  1257  	s.state.Unlock()
  1258  
  1259  	for i := 0; i < 6; i++ {
  1260  		s.se.Ensure()
  1261  		s.se.Wait()
  1262  	}
  1263  
  1264  	s.state.Lock()
  1265  	defer s.state.Unlock()
  1266  
  1267  	var snapst snapstate.SnapState
  1268  	err := snapstate.Get(s.state, "snapd", &snapst)
  1269  	c.Assert(err, IsNil)
  1270  	snapst.Current = siOld.Revision
  1271  	c.Check(t.Status(), Equals, state.UndoneStatus)
  1272  
  1273  	expected := fakeOps{
  1274  		{
  1275  			op:    "candidate",
  1276  			sinfo: *si,
  1277  		},
  1278  		{
  1279  			op:   "link-snap",
  1280  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1281  		},
  1282  		{
  1283  			op: "current-snap-service-states",
  1284  		},
  1285  		{
  1286  			op:   "unlink-snap",
  1287  			path: filepath.Join(dirs.SnapMountDir, "snapd/22"),
  1288  		},
  1289  	}
  1290  
  1291  	// start with an easier-to-read error if this fails:
  1292  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
  1293  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
  1294  
  1295  	// 1 restart from link snap, the other restart happens
  1296  	// in undoUnlinkCurrentSnap (not tested here)
  1297  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
  1298  	c.Check(t.Log(), HasLen, 2)
  1299  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
  1300  	c.Check(t.Log()[1], Matches, `.*INFO unlink`)
  1301  }
  1302  
  1303  func (s *linkSnapSuite) TestDoUnlinkSnapRefreshAwarenessHardCheck(c *C) {
  1304  	s.state.Lock()
  1305  	defer s.state.Unlock()
  1306  
  1307  	tr := config.NewTransaction(s.state)
  1308  	tr.Set("core", "experimental.refresh-app-awareness", true)
  1309  	tr.Commit()
  1310  
  1311  	chg := s.testDoUnlinkSnapRefreshAwareness(c)
  1312  
  1313  	c.Check(chg.Err(), ErrorMatches, `(?ms).*^- some-change-descr \(snap "some-snap" has running apps \(some-app\)\).*`)
  1314  }
  1315  
  1316  func (s *linkSnapSuite) TestDoUnlinkSnapRefreshHardCheckOff(c *C) {
  1317  	s.state.Lock()
  1318  	defer s.state.Unlock()
  1319  
  1320  	chg := s.testDoUnlinkSnapRefreshAwareness(c)
  1321  
  1322  	c.Check(chg.Err(), IsNil)
  1323  }
  1324  
  1325  func (s *linkSnapSuite) testDoUnlinkSnapRefreshAwareness(c *C) *state.Change {
  1326  	restore := release.MockOnClassic(true)
  1327  	defer restore()
  1328  
  1329  	dirs.SetRootDir(c.MkDir())
  1330  	defer dirs.SetRootDir("/")
  1331  
  1332  	snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) {
  1333  		info := &snap.Info{SuggestedName: name, SideInfo: *si, SnapType: snap.TypeApp}
  1334  		info.Apps = map[string]*snap.AppInfo{
  1335  			"some-app": {Snap: info, Name: "some-app"},
  1336  		}
  1337  		return info, nil
  1338  	})
  1339  	// mock that "some-snap" has an app and that this app has pids running
  1340  	restore = snapstate.MockPidsOfSnap(func(instanceName string) (map[string][]int, error) {
  1341  		c.Assert(instanceName, Equals, "some-snap")
  1342  		return map[string][]int{
  1343  			"snap.some-snap.some-app": {1234},
  1344  		}, nil
  1345  	})
  1346  	defer restore()
  1347  
  1348  	si1 := &snap.SideInfo{
  1349  		RealName: "some-snap",
  1350  		Revision: snap.R(1),
  1351  	}
  1352  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
  1353  		Sequence: []*snap.SideInfo{si1},
  1354  		Current:  si1.Revision,
  1355  		Active:   true,
  1356  	})
  1357  	t := s.state.NewTask("unlink-current-snap", "some-change-descr")
  1358  	t.Set("snap-setup", &snapstate.SnapSetup{
  1359  		SideInfo: si1,
  1360  	})
  1361  	chg := s.state.NewChange("dummy", "...")
  1362  	chg.AddTask(t)
  1363  
  1364  	s.state.Unlock()
  1365  	defer s.state.Lock()
  1366  
  1367  	for i := 0; i < 3; i++ {
  1368  		s.se.Ensure()
  1369  		s.se.Wait()
  1370  	}
  1371  
  1372  	return chg
  1373  }
  1374  
  1375  func (s *linkSnapSuite) setMockKernelRemodelCtx(c *C, oldKernel, newKernel string) {
  1376  	newModel := MakeModel(map[string]interface{}{"kernel": newKernel})
  1377  	oldModel := MakeModel(map[string]interface{}{"kernel": oldKernel})
  1378  	mockRemodelCtx := &snapstatetest.TrivialDeviceContext{
  1379  		DeviceModel:    newModel,
  1380  		OldDeviceModel: oldModel,
  1381  		Remodeling:     true,
  1382  	}
  1383  	restore := snapstatetest.MockDeviceContext(mockRemodelCtx)
  1384  	s.AddCleanup(restore)
  1385  }
  1386  
  1387  func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesUnrelatedAppDoesNothing(c *C) {
  1388  	s.state.Lock()
  1389  	defer s.state.Unlock()
  1390  
  1391  	s.setMockKernelRemodelCtx(c, "kernel", "new-kernel")
  1392  	t := s.state.NewTask("link-snap", "...")
  1393  	t.Set("snap-setup", &snapstate.SnapSetup{
  1394  		SideInfo: &snap.SideInfo{
  1395  			RealName: "some-app",
  1396  			Revision: snap.R(1),
  1397  		},
  1398  	})
  1399  
  1400  	err := s.snapmgr.MaybeUndoRemodelBootChanges(t)
  1401  	c.Assert(err, IsNil)
  1402  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
  1403  }
  1404  
  1405  func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesSameKernel(c *C) {
  1406  	s.state.Lock()
  1407  	defer s.state.Unlock()
  1408  
  1409  	s.setMockKernelRemodelCtx(c, "kernel", "kernel")
  1410  	t := s.state.NewTask("link-snap", "...")
  1411  	t.Set("snap-setup", &snapstate.SnapSetup{
  1412  		SideInfo: &snap.SideInfo{
  1413  			RealName: "kernel",
  1414  			Revision: snap.R(1),
  1415  		},
  1416  		Type: "kernel",
  1417  	})
  1418  
  1419  	err := s.snapmgr.MaybeUndoRemodelBootChanges(t)
  1420  	c.Assert(err, IsNil)
  1421  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
  1422  }
  1423  
  1424  func (s *linkSnapSuite) TestMaybeUndoRemodelBootChangesNeedsUndo(c *C) {
  1425  	s.state.Lock()
  1426  	defer s.state.Unlock()
  1427  
  1428  	// undoing remodel bootenv changes is only relevant on !classic
  1429  	restore := release.MockOnClassic(false)
  1430  	defer restore()
  1431  
  1432  	// using "snaptest.MockSnap()" is more convenient here so we avoid
  1433  	// the (default) mocking of snapReadInfo()
  1434  	restore = snapstate.MockSnapReadInfo(snap.ReadInfo)
  1435  	defer restore()
  1436  
  1437  	// we need to init the boot-id
  1438  	err := s.state.VerifyReboot("some-boot-id")
  1439  	c.Assert(err, IsNil)
  1440  
  1441  	// we pretend we do a remodel from kernel -> new-kernel
  1442  	s.setMockKernelRemodelCtx(c, "kernel", "new-kernel")
  1443  
  1444  	// and we pretend that we booted into the "new-kernel" already
  1445  	// and now that needs to be undone
  1446  	bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
  1447  	bootloader.Force(bloader)
  1448  	bloader.SetBootKernel("new-kernel_1.snap")
  1449  
  1450  	// both kernel and new-kernel are instaleld at this point
  1451  	si := &snap.SideInfo{RealName: "kernel", Revision: snap.R(1)}
  1452  	snapstate.Set(s.state, "kernel", &snapstate.SnapState{
  1453  		Sequence: []*snap.SideInfo{si},
  1454  		SnapType: "kernel",
  1455  		Current:  snap.R(1),
  1456  	})
  1457  	snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 1.0", si)
  1458  	si2 := &snap.SideInfo{RealName: "new-kernel", Revision: snap.R(1)}
  1459  	snapstate.Set(s.state, "new-kernel", &snapstate.SnapState{
  1460  		Sequence: []*snap.SideInfo{si2},
  1461  		SnapType: "kernel",
  1462  		Current:  snap.R(1),
  1463  	})
  1464  	snaptest.MockSnap(c, "name: new-kernel\ntype: kernel\nversion: 1.0", si)
  1465  
  1466  	t := s.state.NewTask("link-snap", "...")
  1467  	t.Set("snap-setup", &snapstate.SnapSetup{
  1468  		SideInfo: &snap.SideInfo{
  1469  			RealName: "new-kernel",
  1470  			Revision: snap.R(1),
  1471  			SnapID:   "new-kernel-id",
  1472  		},
  1473  		Type: "kernel",
  1474  	})
  1475  
  1476  	// now we simulate that the new kernel is getting undone
  1477  	err = s.snapmgr.MaybeUndoRemodelBootChanges(t)
  1478  	c.Assert(err, IsNil)
  1479  
  1480  	// that will schedule a boot into the previous kernel
  1481  	c.Assert(bloader.BootVars, DeepEquals, map[string]string{
  1482  		"snap_mode":       boot.TryStatus,
  1483  		"snap_kernel":     "new-kernel_1.snap",
  1484  		"snap_try_kernel": "kernel_1.snap",
  1485  	})
  1486  	c.Check(s.stateBackend.restartRequested, HasLen, 1)
  1487  	c.Check(s.stateBackend.restartRequested[0], Equals, state.RestartSystem)
  1488  }