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