github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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/dirs"
    32  	"github.com/snapcore/snapd/overlord/auth"
    33  	"github.com/snapcore/snapd/overlord/configstate/config"
    34  	"github.com/snapcore/snapd/overlord/snapstate"
    35  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    36  	"github.com/snapcore/snapd/overlord/state"
    37  	"github.com/snapcore/snapd/release"
    38  	"github.com/snapcore/snapd/snap"
    39  	"github.com/snapcore/snapd/testutil"
    40  )
    41  
    42  type linkSnapSuite struct {
    43  	baseHandlerSuite
    44  
    45  	stateBackend *witnessRestartReqStateBackend
    46  }
    47  
    48  var _ = Suite(&linkSnapSuite{})
    49  
    50  type witnessRestartReqStateBackend struct {
    51  	restartRequested []state.RestartType
    52  }
    53  
    54  func (b *witnessRestartReqStateBackend) Checkpoint([]byte) error {
    55  	return nil
    56  }
    57  
    58  func (b *witnessRestartReqStateBackend) RequestRestart(t state.RestartType) {
    59  	b.restartRequested = append(b.restartRequested, t)
    60  }
    61  
    62  func (b *witnessRestartReqStateBackend) EnsureBefore(time.Duration) {}
    63  
    64  func (s *linkSnapSuite) SetUpTest(c *C) {
    65  	s.stateBackend = &witnessRestartReqStateBackend{}
    66  
    67  	s.setup(c, s.stateBackend)
    68  
    69  	s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel()))
    70  	s.AddCleanup(snap.MockSnapdSnapID("snapd-snap-id"))
    71  }
    72  
    73  func checkHasCookieForSnap(c *C, st *state.State, instanceName string) {
    74  	var contexts map[string]interface{}
    75  	err := st.Get("snap-cookies", &contexts)
    76  	c.Assert(err, IsNil)
    77  	c.Check(contexts, HasLen, 1)
    78  
    79  	for _, snap := range contexts {
    80  		if instanceName == snap {
    81  			return
    82  		}
    83  	}
    84  	panic(fmt.Sprintf("Cookie missing for snap %q", instanceName))
    85  }
    86  
    87  func (s *linkSnapSuite) TestDoLinkSnapSuccess(c *C) {
    88  	// we start without the auxiliary store info
    89  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
    90  
    91  	s.state.Lock()
    92  	t := s.state.NewTask("link-snap", "test")
    93  	t.Set("snap-setup", &snapstate.SnapSetup{
    94  		SideInfo: &snap.SideInfo{
    95  			RealName: "foo",
    96  			Revision: snap.R(33),
    97  			SnapID:   "foo-id",
    98  		},
    99  		Channel: "beta",
   100  		UserID:  2,
   101  	})
   102  	s.state.NewChange("dummy", "...").AddTask(t)
   103  
   104  	s.state.Unlock()
   105  
   106  	s.se.Ensure()
   107  	s.se.Wait()
   108  
   109  	s.state.Lock()
   110  	defer s.state.Unlock()
   111  	var snapst snapstate.SnapState
   112  	err := snapstate.Get(s.state, "foo", &snapst)
   113  	c.Assert(err, IsNil)
   114  
   115  	checkHasCookieForSnap(c, s.state, "foo")
   116  
   117  	typ, err := snapst.Type()
   118  	c.Check(err, IsNil)
   119  	c.Check(typ, Equals, snap.TypeApp)
   120  
   121  	c.Check(snapst.Active, Equals, true)
   122  	c.Check(snapst.Sequence, HasLen, 1)
   123  	c.Check(snapst.Current, Equals, snap.R(33))
   124  	c.Check(snapst.Channel, Equals, "beta")
   125  	c.Check(snapst.UserID, Equals, 2)
   126  	c.Check(snapst.CohortKey, Equals, "")
   127  	c.Check(t.Status(), Equals, state.DoneStatus)
   128  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
   129  
   130  	// we end with the auxiliary store info
   131  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent)
   132  }
   133  
   134  func (s *linkSnapSuite) TestDoLinkSnapSuccessWithCohort(c *C) {
   135  	// we start without the auxiliary store info
   136  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
   137  
   138  	s.state.Lock()
   139  	t := s.state.NewTask("link-snap", "test")
   140  	t.Set("snap-setup", &snapstate.SnapSetup{
   141  		SideInfo: &snap.SideInfo{
   142  			RealName: "foo",
   143  			Revision: snap.R(33),
   144  			SnapID:   "foo-id",
   145  		},
   146  		Channel:   "beta",
   147  		UserID:    2,
   148  		CohortKey: "wobbling",
   149  	})
   150  	s.state.NewChange("dummy", "...").AddTask(t)
   151  
   152  	s.state.Unlock()
   153  
   154  	s.se.Ensure()
   155  	s.se.Wait()
   156  
   157  	s.state.Lock()
   158  	defer s.state.Unlock()
   159  	var snapst snapstate.SnapState
   160  	err := snapstate.Get(s.state, "foo", &snapst)
   161  	c.Assert(err, IsNil)
   162  
   163  	checkHasCookieForSnap(c, s.state, "foo")
   164  
   165  	typ, err := snapst.Type()
   166  	c.Check(err, IsNil)
   167  	c.Check(typ, Equals, snap.TypeApp)
   168  
   169  	c.Check(snapst.Active, Equals, true)
   170  	c.Check(snapst.Sequence, HasLen, 1)
   171  	c.Check(snapst.Current, Equals, snap.R(33))
   172  	c.Check(snapst.Channel, Equals, "beta")
   173  	c.Check(snapst.UserID, Equals, 2)
   174  	c.Check(snapst.CohortKey, Equals, "wobbling")
   175  	c.Check(t.Status(), Equals, state.DoneStatus)
   176  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
   177  
   178  	// we end with the auxiliary store info
   179  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FilePresent)
   180  }
   181  
   182  func (s *linkSnapSuite) TestDoLinkSnapSuccessNoUserID(c *C) {
   183  	s.state.Lock()
   184  	t := s.state.NewTask("link-snap", "test")
   185  	t.Set("snap-setup", &snapstate.SnapSetup{
   186  		SideInfo: &snap.SideInfo{
   187  			RealName: "foo",
   188  			Revision: snap.R(33),
   189  		},
   190  		Channel: "beta",
   191  	})
   192  	s.state.NewChange("dummy", "...").AddTask(t)
   193  
   194  	s.state.Unlock()
   195  	s.se.Ensure()
   196  	s.se.Wait()
   197  	s.state.Lock()
   198  	defer s.state.Unlock()
   199  
   200  	// check that snapst.UserID does not get set
   201  	var snapst snapstate.SnapState
   202  	err := snapstate.Get(s.state, "foo", &snapst)
   203  	c.Assert(err, IsNil)
   204  	c.Check(snapst.UserID, Equals, 0)
   205  
   206  	var snaps map[string]*json.RawMessage
   207  	err = s.state.Get("snaps", &snaps)
   208  	c.Assert(err, IsNil)
   209  	raw := []byte(*snaps["foo"])
   210  	c.Check(string(raw), Not(testutil.Contains), "user-id")
   211  }
   212  
   213  func (s *linkSnapSuite) TestDoLinkSnapSuccessUserIDAlreadySet(c *C) {
   214  	s.state.Lock()
   215  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   216  		Sequence: []*snap.SideInfo{
   217  			{RealName: "foo", Revision: snap.R(1)},
   218  		},
   219  		Current: snap.R(1),
   220  		UserID:  1,
   221  	})
   222  	// the user
   223  	user, err := auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"})
   224  	c.Assert(err, IsNil)
   225  	c.Assert(user.ID, Equals, 1)
   226  
   227  	t := s.state.NewTask("link-snap", "test")
   228  	t.Set("snap-setup", &snapstate.SnapSetup{
   229  		SideInfo: &snap.SideInfo{
   230  			RealName: "foo",
   231  			Revision: snap.R(33),
   232  		},
   233  		Channel: "beta",
   234  		UserID:  2,
   235  	})
   236  	s.state.NewChange("dummy", "...").AddTask(t)
   237  
   238  	s.state.Unlock()
   239  	s.se.Ensure()
   240  	s.se.Wait()
   241  	s.state.Lock()
   242  	defer s.state.Unlock()
   243  
   244  	// check that snapst.UserID was not "transferred"
   245  	var snapst snapstate.SnapState
   246  	err = snapstate.Get(s.state, "foo", &snapst)
   247  	c.Assert(err, IsNil)
   248  	c.Check(snapst.UserID, Equals, 1)
   249  }
   250  
   251  func (s *linkSnapSuite) TestDoLinkSnapSuccessUserLoggedOut(c *C) {
   252  	s.state.Lock()
   253  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   254  		Sequence: []*snap.SideInfo{
   255  			{RealName: "foo", Revision: snap.R(1)},
   256  		},
   257  		Current: snap.R(1),
   258  		UserID:  1,
   259  	})
   260  
   261  	t := s.state.NewTask("link-snap", "test")
   262  	t.Set("snap-setup", &snapstate.SnapSetup{
   263  		SideInfo: &snap.SideInfo{
   264  			RealName: "foo",
   265  			Revision: snap.R(33),
   266  		},
   267  		Channel: "beta",
   268  		UserID:  2,
   269  	})
   270  	s.state.NewChange("dummy", "...").AddTask(t)
   271  
   272  	s.state.Unlock()
   273  	s.se.Ensure()
   274  	s.se.Wait()
   275  	s.state.Lock()
   276  	defer s.state.Unlock()
   277  
   278  	// check that snapst.UserID was transferred
   279  	// given that user 1 doesn't exist anymore
   280  	var snapst snapstate.SnapState
   281  	err := snapstate.Get(s.state, "foo", &snapst)
   282  	c.Assert(err, IsNil)
   283  	c.Check(snapst.UserID, Equals, 2)
   284  }
   285  
   286  func (s *linkSnapSuite) TestDoLinkSnapSeqFile(c *C) {
   287  	s.state.Lock()
   288  	// pretend we have an installed snap
   289  	si11 := &snap.SideInfo{
   290  		RealName: "foo",
   291  		Revision: snap.R(11),
   292  	}
   293  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   294  		Sequence: []*snap.SideInfo{si11},
   295  		Current:  si11.Revision,
   296  	})
   297  	// add a new one
   298  	t := s.state.NewTask("link-snap", "test")
   299  	t.Set("snap-setup", &snapstate.SnapSetup{
   300  		SideInfo: &snap.SideInfo{
   301  			RealName: "foo",
   302  			Revision: snap.R(33),
   303  		},
   304  		Channel: "beta",
   305  	})
   306  	s.state.NewChange("dummy", "...").AddTask(t)
   307  	s.state.Unlock()
   308  
   309  	s.se.Ensure()
   310  	s.se.Wait()
   311  
   312  	s.state.Lock()
   313  	defer s.state.Unlock()
   314  	var snapst snapstate.SnapState
   315  	err := snapstate.Get(s.state, "foo", &snapst)
   316  	c.Assert(err, IsNil)
   317  
   318  	// and check that the sequence file got updated
   319  	seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json"))
   320  	c.Assert(err, IsNil)
   321  	c.Check(string(seqContent), Equals, `{"sequence":[{"name":"foo","snap-id":"","revision":"11"},{"name":"foo","snap-id":"","revision":"33"}],"current":"33"}`)
   322  }
   323  
   324  func (s *linkSnapSuite) TestDoUndoLinkSnap(c *C) {
   325  	s.state.Lock()
   326  	defer s.state.Unlock()
   327  	si := &snap.SideInfo{
   328  		RealName: "foo",
   329  		Revision: snap.R(33),
   330  	}
   331  	t := s.state.NewTask("link-snap", "test")
   332  	t.Set("snap-setup", &snapstate.SnapSetup{
   333  		SideInfo: si,
   334  		Channel:  "beta",
   335  	})
   336  	chg := s.state.NewChange("dummy", "...")
   337  	chg.AddTask(t)
   338  
   339  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   340  	terr.WaitFor(t)
   341  	chg.AddTask(terr)
   342  
   343  	s.state.Unlock()
   344  
   345  	for i := 0; i < 6; i++ {
   346  		s.se.Ensure()
   347  		s.se.Wait()
   348  	}
   349  
   350  	s.state.Lock()
   351  	var snapst snapstate.SnapState
   352  	err := snapstate.Get(s.state, "foo", &snapst)
   353  	c.Assert(err, Equals, state.ErrNoState)
   354  	c.Check(t.Status(), Equals, state.UndoneStatus)
   355  
   356  	// and check that the sequence file got updated
   357  	seqContent, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, "foo.json"))
   358  	c.Assert(err, IsNil)
   359  	c.Check(string(seqContent), Equals, `{"sequence":[],"current":"unset"}`)
   360  }
   361  
   362  func (s *linkSnapSuite) TestDoLinkSnapTryToCleanupOnError(c *C) {
   363  	s.state.Lock()
   364  	defer s.state.Unlock()
   365  	si := &snap.SideInfo{
   366  		RealName: "foo",
   367  		Revision: snap.R(35),
   368  	}
   369  	t := s.state.NewTask("link-snap", "test")
   370  	t.Set("snap-setup", &snapstate.SnapSetup{
   371  		SideInfo: si,
   372  		Channel:  "beta",
   373  	})
   374  
   375  	s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "foo/35")
   376  	s.state.NewChange("dummy", "...").AddTask(t)
   377  	s.state.Unlock()
   378  
   379  	s.se.Ensure()
   380  	s.se.Wait()
   381  
   382  	s.state.Lock()
   383  
   384  	// state as expected
   385  	var snapst snapstate.SnapState
   386  	err := snapstate.Get(s.state, "foo", &snapst)
   387  	c.Assert(err, Equals, state.ErrNoState)
   388  
   389  	// tried to cleanup
   390  	expected := fakeOps{
   391  		{
   392  			op:    "candidate",
   393  			sinfo: *si,
   394  		},
   395  		{
   396  			op:   "link-snap.failed",
   397  			path: filepath.Join(dirs.SnapMountDir, "foo/35"),
   398  		},
   399  		{
   400  			op:   "unlink-snap",
   401  			path: filepath.Join(dirs.SnapMountDir, "foo/35"),
   402  		},
   403  	}
   404  
   405  	// start with an easier-to-read error if this fails:
   406  	c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
   407  	c.Check(s.fakeBackend.ops, DeepEquals, expected)
   408  }
   409  
   410  func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreRestarts(c *C) {
   411  	restore := release.MockOnClassic(true)
   412  	defer restore()
   413  
   414  	s.state.Lock()
   415  	si := &snap.SideInfo{
   416  		RealName: "core",
   417  		Revision: snap.R(33),
   418  	}
   419  	t := s.state.NewTask("link-snap", "test")
   420  	t.Set("snap-setup", &snapstate.SnapSetup{
   421  		SideInfo: si,
   422  	})
   423  	s.state.NewChange("dummy", "...").AddTask(t)
   424  
   425  	s.state.Unlock()
   426  
   427  	s.se.Ensure()
   428  	s.se.Wait()
   429  
   430  	s.state.Lock()
   431  	defer s.state.Unlock()
   432  
   433  	var snapst snapstate.SnapState
   434  	err := snapstate.Get(s.state, "core", &snapst)
   435  	c.Assert(err, IsNil)
   436  
   437  	typ, err := snapst.Type()
   438  	c.Check(err, IsNil)
   439  	c.Check(typ, Equals, snap.TypeOS)
   440  
   441  	c.Check(t.Status(), Equals, state.DoneStatus)
   442  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   443  	c.Check(t.Log(), HasLen, 1)
   444  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart\.`)
   445  }
   446  
   447  func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnCoreWithBase(c *C) {
   448  	restore := release.MockOnClassic(false)
   449  	defer restore()
   450  
   451  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   452  	defer r()
   453  
   454  	s.state.Lock()
   455  	si := &snap.SideInfo{
   456  		RealName: "snapd",
   457  		SnapID:   "snapd-snap-id",
   458  		Revision: snap.R(22),
   459  	}
   460  	t := s.state.NewTask("link-snap", "test")
   461  	t.Set("snap-setup", &snapstate.SnapSetup{
   462  		SideInfo: si,
   463  	})
   464  	s.state.NewChange("dummy", "...").AddTask(t)
   465  
   466  	s.state.Unlock()
   467  
   468  	s.se.Ensure()
   469  	s.se.Wait()
   470  
   471  	s.state.Lock()
   472  	defer s.state.Unlock()
   473  
   474  	var snapst snapstate.SnapState
   475  	err := snapstate.Get(s.state, "snapd", &snapst)
   476  	c.Assert(err, IsNil)
   477  
   478  	typ, err := snapst.Type()
   479  	c.Check(err, IsNil)
   480  	c.Check(typ, Equals, snap.TypeSnapd)
   481  
   482  	c.Check(t.Status(), Equals, state.DoneStatus)
   483  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   484  	c.Check(t.Log(), HasLen, 1)
   485  	c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart \(snapd snap\)\.`)
   486  }
   487  
   488  func (s *linkSnapSuite) TestDoLinkSnapSuccessSnapdRestartsOnClassic(c *C) {
   489  	restore := release.MockOnClassic(true)
   490  	defer restore()
   491  
   492  	s.state.Lock()
   493  	si := &snap.SideInfo{
   494  		RealName: "snapd",
   495  		SnapID:   "snapd-snap-id",
   496  		Revision: snap.R(22),
   497  	}
   498  	t := s.state.NewTask("link-snap", "test")
   499  	t.Set("snap-setup", &snapstate.SnapSetup{
   500  		SideInfo: si,
   501  	})
   502  	s.state.NewChange("dummy", "...").AddTask(t)
   503  
   504  	s.state.Unlock()
   505  
   506  	s.se.Ensure()
   507  	s.se.Wait()
   508  
   509  	s.state.Lock()
   510  	defer s.state.Unlock()
   511  
   512  	var snapst snapstate.SnapState
   513  	err := snapstate.Get(s.state, "snapd", &snapst)
   514  	c.Assert(err, IsNil)
   515  
   516  	typ, err := snapst.Type()
   517  	c.Check(err, IsNil)
   518  	c.Check(typ, Equals, snap.TypeSnapd)
   519  
   520  	c.Check(t.Status(), Equals, state.DoneStatus)
   521  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   522  	c.Check(t.Log(), HasLen, 1)
   523  }
   524  
   525  func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreAndSnapdNoCoreRestart(c *C) {
   526  	restore := release.MockOnClassic(true)
   527  	defer restore()
   528  
   529  	s.state.Lock()
   530  	siSnapd := &snap.SideInfo{
   531  		RealName: "snapd",
   532  		Revision: snap.R(64),
   533  	}
   534  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   535  		Sequence: []*snap.SideInfo{siSnapd},
   536  		Current:  siSnapd.Revision,
   537  		Active:   true,
   538  	})
   539  
   540  	si := &snap.SideInfo{
   541  		RealName: "core",
   542  		Revision: snap.R(33),
   543  	}
   544  	t := s.state.NewTask("link-snap", "test")
   545  	t.Set("snap-setup", &snapstate.SnapSetup{
   546  		SideInfo: si,
   547  	})
   548  	s.state.NewChange("dummy", "...").AddTask(t)
   549  
   550  	s.state.Unlock()
   551  
   552  	s.se.Ensure()
   553  	s.se.Wait()
   554  
   555  	s.state.Lock()
   556  	defer s.state.Unlock()
   557  
   558  	var snapst snapstate.SnapState
   559  	err := snapstate.Get(s.state, "core", &snapst)
   560  	c.Assert(err, IsNil)
   561  
   562  	typ, err := snapst.Type()
   563  	c.Check(err, IsNil)
   564  	c.Check(typ, Equals, snap.TypeOS)
   565  
   566  	c.Check(t.Status(), Equals, state.DoneStatus)
   567  	c.Check(s.stateBackend.restartRequested, IsNil)
   568  	c.Check(t.Log(), HasLen, 0)
   569  }
   570  
   571  func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceDidNotHaveCandidate(c *C) {
   572  	s.state.Lock()
   573  	defer s.state.Unlock()
   574  	si1 := &snap.SideInfo{
   575  		RealName: "foo",
   576  		Revision: snap.R(1),
   577  	}
   578  	si2 := &snap.SideInfo{
   579  		RealName: "foo",
   580  		Revision: snap.R(2),
   581  	}
   582  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   583  		Sequence: []*snap.SideInfo{si1},
   584  		Current:  si1.Revision,
   585  	})
   586  	t := s.state.NewTask("link-snap", "test")
   587  	t.Set("snap-setup", &snapstate.SnapSetup{
   588  		SideInfo: si2,
   589  		Channel:  "beta",
   590  	})
   591  	chg := s.state.NewChange("dummy", "...")
   592  	chg.AddTask(t)
   593  
   594  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   595  	terr.WaitFor(t)
   596  	chg.AddTask(terr)
   597  
   598  	s.state.Unlock()
   599  
   600  	for i := 0; i < 6; i++ {
   601  		s.se.Ensure()
   602  		s.se.Wait()
   603  	}
   604  
   605  	s.state.Lock()
   606  	var snapst snapstate.SnapState
   607  	err := snapstate.Get(s.state, "foo", &snapst)
   608  	c.Assert(err, IsNil)
   609  	c.Check(snapst.Active, Equals, false)
   610  	c.Check(snapst.Sequence, HasLen, 1)
   611  	c.Check(snapst.Current, Equals, snap.R(1))
   612  	c.Check(t.Status(), Equals, state.UndoneStatus)
   613  }
   614  
   615  func (s *linkSnapSuite) TestDoUndoLinkSnapSequenceHadCandidate(c *C) {
   616  	s.state.Lock()
   617  	defer s.state.Unlock()
   618  	si1 := &snap.SideInfo{
   619  		RealName: "foo",
   620  		Revision: snap.R(1),
   621  	}
   622  	si2 := &snap.SideInfo{
   623  		RealName: "foo",
   624  		Revision: snap.R(2),
   625  	}
   626  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
   627  		Sequence: []*snap.SideInfo{si1, si2},
   628  		Current:  si2.Revision,
   629  	})
   630  	t := s.state.NewTask("link-snap", "test")
   631  	t.Set("snap-setup", &snapstate.SnapSetup{
   632  		SideInfo: si1,
   633  		Channel:  "beta",
   634  	})
   635  	chg := s.state.NewChange("dummy", "...")
   636  	chg.AddTask(t)
   637  
   638  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   639  	terr.WaitFor(t)
   640  	chg.AddTask(terr)
   641  
   642  	s.state.Unlock()
   643  
   644  	for i := 0; i < 6; i++ {
   645  		s.se.Ensure()
   646  		s.se.Wait()
   647  	}
   648  
   649  	s.state.Lock()
   650  	var snapst snapstate.SnapState
   651  	err := snapstate.Get(s.state, "foo", &snapst)
   652  	c.Assert(err, IsNil)
   653  	c.Check(snapst.Active, Equals, false)
   654  	c.Check(snapst.Sequence, HasLen, 2)
   655  	c.Check(snapst.Current, Equals, snap.R(2))
   656  	c.Check(t.Status(), Equals, state.UndoneStatus)
   657  }
   658  
   659  func (s *linkSnapSuite) TestDoUndoUnlinkCurrentSnapCore(c *C) {
   660  	restore := release.MockOnClassic(true)
   661  	defer restore()
   662  
   663  	s.state.Lock()
   664  	defer s.state.Unlock()
   665  	si1 := &snap.SideInfo{
   666  		RealName: "core",
   667  		Revision: snap.R(1),
   668  	}
   669  	si2 := &snap.SideInfo{
   670  		RealName: "core",
   671  		Revision: snap.R(2),
   672  	}
   673  	snapstate.Set(s.state, "core", &snapstate.SnapState{
   674  		Sequence: []*snap.SideInfo{si1},
   675  		Current:  si1.Revision,
   676  		Active:   true,
   677  		SnapType: "os",
   678  	})
   679  	t := s.state.NewTask("unlink-current-snap", "test")
   680  	t.Set("snap-setup", &snapstate.SnapSetup{
   681  		SideInfo: si2,
   682  	})
   683  	chg := s.state.NewChange("dummy", "...")
   684  	chg.AddTask(t)
   685  
   686  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   687  	terr.WaitFor(t)
   688  	chg.AddTask(terr)
   689  
   690  	s.state.Unlock()
   691  
   692  	for i := 0; i < 3; i++ {
   693  		s.se.Ensure()
   694  		s.se.Wait()
   695  	}
   696  
   697  	s.state.Lock()
   698  	var snapst snapstate.SnapState
   699  	err := snapstate.Get(s.state, "core", &snapst)
   700  	c.Assert(err, IsNil)
   701  	c.Check(snapst.Active, Equals, true)
   702  	c.Check(snapst.Sequence, HasLen, 1)
   703  	c.Check(snapst.Current, Equals, snap.R(1))
   704  	c.Check(t.Status(), Equals, state.UndoneStatus)
   705  
   706  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon})
   707  }
   708  
   709  func (s *linkSnapSuite) TestDoUndoLinkSnapCoreClassic(c *C) {
   710  	restore := release.MockOnClassic(true)
   711  	defer restore()
   712  
   713  	s.state.Lock()
   714  	defer s.state.Unlock()
   715  
   716  	// no previous core snap and an error on link, in this
   717  	// case we need to restart on classic back into the distro
   718  	// package version
   719  	si1 := &snap.SideInfo{
   720  		RealName: "core",
   721  		Revision: snap.R(1),
   722  	}
   723  	t := s.state.NewTask("link-snap", "test")
   724  	t.Set("snap-setup", &snapstate.SnapSetup{
   725  		SideInfo: si1,
   726  	})
   727  	chg := s.state.NewChange("dummy", "...")
   728  	chg.AddTask(t)
   729  
   730  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   731  	terr.WaitFor(t)
   732  	chg.AddTask(terr)
   733  
   734  	s.state.Unlock()
   735  
   736  	for i := 0; i < 3; i++ {
   737  		s.se.Ensure()
   738  		s.se.Wait()
   739  	}
   740  
   741  	s.state.Lock()
   742  	var snapst snapstate.SnapState
   743  	err := snapstate.Get(s.state, "core", &snapst)
   744  	c.Assert(err, Equals, state.ErrNoState)
   745  	c.Check(t.Status(), Equals, state.UndoneStatus)
   746  
   747  	c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon})
   748  
   749  }
   750  
   751  func (s *linkSnapSuite) TestLinkSnapInjectsAutoConnectIfMissing(c *C) {
   752  	si1 := &snap.SideInfo{
   753  		RealName: "snap1",
   754  		Revision: snap.R(1),
   755  	}
   756  	sup1 := &snapstate.SnapSetup{SideInfo: si1}
   757  	si2 := &snap.SideInfo{
   758  		RealName: "snap2",
   759  		Revision: snap.R(1),
   760  	}
   761  	sup2 := &snapstate.SnapSetup{SideInfo: si2}
   762  
   763  	s.state.Lock()
   764  	defer s.state.Unlock()
   765  
   766  	task0 := s.state.NewTask("setup-profiles", "")
   767  	task1 := s.state.NewTask("link-snap", "")
   768  	task1.WaitFor(task0)
   769  	task0.Set("snap-setup", sup1)
   770  	task1.Set("snap-setup", sup1)
   771  
   772  	task2 := s.state.NewTask("setup-profiles", "")
   773  	task3 := s.state.NewTask("link-snap", "")
   774  	task2.WaitFor(task1)
   775  	task3.WaitFor(task2)
   776  	task2.Set("snap-setup", sup2)
   777  	task3.Set("snap-setup", sup2)
   778  
   779  	chg := s.state.NewChange("test", "")
   780  	chg.AddTask(task0)
   781  	chg.AddTask(task1)
   782  	chg.AddTask(task2)
   783  	chg.AddTask(task3)
   784  
   785  	s.state.Unlock()
   786  
   787  	for i := 0; i < 10; i++ {
   788  		s.se.Ensure()
   789  		s.se.Wait()
   790  	}
   791  
   792  	s.state.Lock()
   793  
   794  	// ensure all our tasks ran
   795  	c.Assert(chg.Err(), IsNil)
   796  	c.Assert(chg.Tasks(), HasLen, 6)
   797  
   798  	// sanity checks
   799  	t := chg.Tasks()[1]
   800  	c.Assert(t.Kind(), Equals, "link-snap")
   801  	t = chg.Tasks()[3]
   802  	c.Assert(t.Kind(), Equals, "link-snap")
   803  
   804  	// check that auto-connect tasks were added and have snap-setup
   805  	var autoconnectSup snapstate.SnapSetup
   806  	t = chg.Tasks()[4]
   807  	c.Assert(t.Kind(), Equals, "auto-connect")
   808  	c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil)
   809  	c.Assert(autoconnectSup.InstanceName(), Equals, "snap1")
   810  
   811  	t = chg.Tasks()[5]
   812  	c.Assert(t.Kind(), Equals, "auto-connect")
   813  	c.Assert(t.Get("snap-setup", &autoconnectSup), IsNil)
   814  	c.Assert(autoconnectSup.InstanceName(), Equals, "snap2")
   815  }
   816  
   817  func (s *linkSnapSuite) TestDoLinkSnapFailureCleansUpAux(c *C) {
   818  	// this is very chummy with the order of LinkSnap
   819  	c.Assert(ioutil.WriteFile(dirs.SnapSeqDir, nil, 0644), IsNil)
   820  
   821  	// we start without the auxiliary store info
   822  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
   823  
   824  	s.state.Lock()
   825  	t := s.state.NewTask("link-snap", "test")
   826  	t.Set("snap-setup", &snapstate.SnapSetup{
   827  		SideInfo: &snap.SideInfo{
   828  			RealName: "foo",
   829  			Revision: snap.R(33),
   830  			SnapID:   "foo-id",
   831  		},
   832  		Channel: "beta",
   833  		UserID:  2,
   834  	})
   835  	s.state.NewChange("dummy", "...").AddTask(t)
   836  
   837  	s.state.Unlock()
   838  
   839  	s.se.Ensure()
   840  	s.se.Wait()
   841  
   842  	s.state.Lock()
   843  	defer s.state.Unlock()
   844  
   845  	c.Check(t.Status(), Equals, state.ErrorStatus)
   846  	c.Check(s.stateBackend.restartRequested, HasLen, 0)
   847  
   848  	// we end without the auxiliary store info
   849  	c.Check(snapstate.AuxStoreInfoFilename("foo-id"), testutil.FileAbsent)
   850  }
   851  
   852  func (s *linkSnapSuite) TestLinkSnapResetsRefreshInhibitedTime(c *C) {
   853  	// When a snap is linked the refresh-inhibited-time is reset to zero
   854  	// to indicate a successful refresh. The old value is stored in task
   855  	// state for task undo logic.
   856  	s.state.Lock()
   857  	defer s.state.Unlock()
   858  
   859  	instant := time.Now()
   860  
   861  	si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)}
   862  	sup := &snapstate.SnapSetup{SideInfo: si}
   863  	snapstate.Set(s.state, "snap", &snapstate.SnapState{
   864  		Sequence:             []*snap.SideInfo{si},
   865  		Current:              si.Revision,
   866  		RefreshInhibitedTime: &instant,
   867  	})
   868  
   869  	task := s.state.NewTask("link-snap", "")
   870  	task.Set("snap-setup", sup)
   871  	chg := s.state.NewChange("test", "")
   872  	chg.AddTask(task)
   873  
   874  	s.state.Unlock()
   875  
   876  	for i := 0; i < 10; i++ {
   877  		s.se.Ensure()
   878  		s.se.Wait()
   879  	}
   880  
   881  	s.state.Lock()
   882  
   883  	c.Assert(chg.Err(), IsNil)
   884  	c.Assert(chg.Tasks(), HasLen, 1)
   885  
   886  	var snapst snapstate.SnapState
   887  	err := snapstate.Get(s.state, "snap", &snapst)
   888  	c.Assert(err, IsNil)
   889  	c.Check(snapst.RefreshInhibitedTime, IsNil)
   890  
   891  	var oldTime time.Time
   892  	c.Assert(task.Get("old-refresh-inhibited-time", &oldTime), IsNil)
   893  	c.Check(oldTime.Equal(instant), Equals, true)
   894  }
   895  
   896  func (s *linkSnapSuite) TestDoUndoLinkSnapRestoresRefreshInhibitedTime(c *C) {
   897  	s.state.Lock()
   898  	defer s.state.Unlock()
   899  
   900  	instant := time.Now()
   901  
   902  	si := &snap.SideInfo{RealName: "snap", Revision: snap.R(1)}
   903  	sup := &snapstate.SnapSetup{SideInfo: si}
   904  	snapstate.Set(s.state, "snap", &snapstate.SnapState{
   905  		Sequence:             []*snap.SideInfo{si},
   906  		Current:              si.Revision,
   907  		RefreshInhibitedTime: &instant,
   908  	})
   909  
   910  	task := s.state.NewTask("link-snap", "")
   911  	task.Set("snap-setup", sup)
   912  	chg := s.state.NewChange("test", "")
   913  	chg.AddTask(task)
   914  
   915  	terr := s.state.NewTask("error-trigger", "provoking total undo")
   916  	terr.WaitFor(task)
   917  	chg.AddTask(terr)
   918  
   919  	s.state.Unlock()
   920  
   921  	for i := 0; i < 6; i++ {
   922  		s.se.Ensure()
   923  		s.se.Wait()
   924  	}
   925  
   926  	s.state.Lock()
   927  
   928  	c.Assert(chg.Err(), NotNil)
   929  	c.Assert(chg.Tasks(), HasLen, 2)
   930  	c.Check(task.Status(), Equals, state.UndoneStatus)
   931  
   932  	var snapst snapstate.SnapState
   933  	err := snapstate.Get(s.state, "snap", &snapst)
   934  	c.Assert(err, IsNil)
   935  	c.Check(snapst.RefreshInhibitedTime.Equal(instant), Equals, true)
   936  }
   937  
   938  func (s *linkSnapSuite) TestDoUnlinkSnapRefreshAwarenessHardCheck(c *C) {
   939  	s.state.Lock()
   940  	defer s.state.Unlock()
   941  
   942  	tr := config.NewTransaction(s.state)
   943  	tr.Set("core", "experimental.refresh-app-awareness", true)
   944  	tr.Commit()
   945  
   946  	chg := s.testDoUnlinkSnapRefreshAwareness(c)
   947  
   948  	c.Check(chg.Err(), ErrorMatches, `(?ms).*^- some-change-descr \(snap "some-snap" has running apps \(some-app\)\).*`)
   949  }
   950  
   951  func (s *linkSnapSuite) TestDoUnlinkSnapRefreshHardCheckOff(c *C) {
   952  	s.state.Lock()
   953  	defer s.state.Unlock()
   954  
   955  	chg := s.testDoUnlinkSnapRefreshAwareness(c)
   956  
   957  	c.Check(chg.Err(), IsNil)
   958  }
   959  
   960  func (s *linkSnapSuite) testDoUnlinkSnapRefreshAwareness(c *C) *state.Change {
   961  	restore := release.MockOnClassic(true)
   962  	defer restore()
   963  
   964  	// mock that "some-snap" has an app and that this app has pids running
   965  	writePids(c, filepath.Join(dirs.PidsCgroupDir, "snap.some-snap.some-app"), []int{1234})
   966  	snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) {
   967  		info := &snap.Info{SuggestedName: name, SideInfo: *si, SnapType: snap.TypeApp}
   968  		info.Apps = map[string]*snap.AppInfo{
   969  			"some-app": {Snap: info, Name: "some-app"},
   970  		}
   971  		return info, nil
   972  	})
   973  
   974  	si1 := &snap.SideInfo{
   975  		RealName: "some-snap",
   976  		Revision: snap.R(1),
   977  	}
   978  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
   979  		Sequence: []*snap.SideInfo{si1},
   980  		Current:  si1.Revision,
   981  		Active:   true,
   982  	})
   983  	t := s.state.NewTask("unlink-current-snap", "some-change-descr")
   984  	t.Set("snap-setup", &snapstate.SnapSetup{
   985  		SideInfo: si1,
   986  	})
   987  	chg := s.state.NewChange("dummy", "...")
   988  	chg.AddTask(t)
   989  
   990  	s.state.Unlock()
   991  	defer s.state.Lock()
   992  
   993  	for i := 0; i < 3; i++ {
   994  		s.se.Ensure()
   995  		s.se.Wait()
   996  	}
   997  
   998  	return chg
   999  }