github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapstate/booted_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  // test the boot related code
    23  
    24  import (
    25  	"os"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/bootloader"
    32  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/overlord"
    35  	"github.com/snapcore/snapd/overlord/snapstate"
    36  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    37  	"github.com/snapcore/snapd/overlord/state"
    38  	"github.com/snapcore/snapd/release"
    39  	"github.com/snapcore/snapd/snap"
    40  	"github.com/snapcore/snapd/snap/snaptest"
    41  	"github.com/snapcore/snapd/testutil"
    42  )
    43  
    44  type bootedSuite struct {
    45  	testutil.BaseTest
    46  	bootloader *bootloadertest.MockBootloader
    47  
    48  	o           *overlord.Overlord
    49  	state       *state.State
    50  	snapmgr     *snapstate.SnapManager
    51  	fakeBackend *fakeSnappyBackend
    52  	restore     func()
    53  }
    54  
    55  var _ = Suite(&bootedSuite{})
    56  
    57  func (bs *bootedSuite) SetUpTest(c *C) {
    58  	bs.BaseTest.SetUpTest(c)
    59  
    60  	dirs.SetRootDir(c.MkDir())
    61  	err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
    62  	c.Assert(err, IsNil)
    63  
    64  	bs.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    65  
    66  	// booted is not running on classic
    67  	release.MockOnClassic(false)
    68  
    69  	bs.bootloader = bootloadertest.Mock("mock", c.MkDir())
    70  	bs.bootloader.SetBootKernel("canonical-pc-linux_2.snap")
    71  	bs.bootloader.SetBootBase("core_2.snap")
    72  	bootloader.Force(bs.bootloader)
    73  
    74  	bs.fakeBackend = &fakeSnappyBackend{}
    75  	bs.o = overlord.Mock()
    76  	bs.state = bs.o.State()
    77  	bs.snapmgr, err = snapstate.Manager(bs.state, bs.o.TaskRunner())
    78  	c.Assert(err, IsNil)
    79  
    80  	AddForeignTaskHandlers(bs.o.TaskRunner(), bs.fakeBackend)
    81  
    82  	bs.o.AddManager(bs.snapmgr)
    83  	bs.o.AddManager(bs.o.TaskRunner())
    84  
    85  	c.Assert(bs.o.StartUp(), IsNil)
    86  
    87  	snapstate.SetSnapManagerBackend(bs.snapmgr, bs.fakeBackend)
    88  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
    89  		return nil, nil
    90  	}
    91  	bs.restore = snapstatetest.MockDeviceModel(DefaultModel())
    92  }
    93  
    94  func (bs *bootedSuite) TearDownTest(c *C) {
    95  	bs.BaseTest.TearDownTest(c)
    96  	snapstate.AutoAliases = nil
    97  	bs.restore()
    98  	release.MockOnClassic(true)
    99  	dirs.SetRootDir("")
   100  	bootloader.Force(nil)
   101  }
   102  
   103  var osSI1 = &snap.SideInfo{RealName: "core", Revision: snap.R(1)}
   104  var osSI2 = &snap.SideInfo{RealName: "core", Revision: snap.R(2)}
   105  var kernelSI1 = &snap.SideInfo{RealName: "canonical-pc-linux", Revision: snap.R(1)}
   106  var kernelSI2 = &snap.SideInfo{RealName: "canonical-pc-linux", Revision: snap.R(2)}
   107  
   108  func (bs *bootedSuite) settle() {
   109  	bs.o.Settle(5 * time.Second)
   110  }
   111  
   112  func (bs *bootedSuite) makeInstalledKernelOS(c *C, st *state.State) {
   113  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", osSI1)
   114  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", osSI2)
   115  	snapstate.Set(st, "core", &snapstate.SnapState{
   116  		SnapType: "os",
   117  		Active:   true,
   118  		Sequence: []*snap.SideInfo{osSI1, osSI2},
   119  		Current:  snap.R(2),
   120  	})
   121  
   122  	snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 1", kernelSI1)
   123  	snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", kernelSI2)
   124  	snapstate.Set(st, "canonical-pc-linux", &snapstate.SnapState{
   125  		SnapType: "kernel",
   126  		Active:   true,
   127  		Sequence: []*snap.SideInfo{kernelSI1, kernelSI2},
   128  		Current:  snap.R(2),
   129  	})
   130  
   131  }
   132  
   133  func (bs *bootedSuite) TestUpdateBootRevisionsOSSimple(c *C) {
   134  	st := bs.state
   135  	st.Lock()
   136  	defer st.Unlock()
   137  
   138  	bs.makeInstalledKernelOS(c, st)
   139  
   140  	bs.bootloader.SetBootBase("core_1.snap")
   141  	err := snapstate.UpdateBootRevisions(st)
   142  	c.Assert(err, IsNil)
   143  
   144  	st.Unlock()
   145  	bs.settle()
   146  	st.Lock()
   147  
   148  	c.Assert(st.Changes(), HasLen, 1)
   149  	chg := st.Changes()[0]
   150  	c.Assert(chg.Err(), IsNil)
   151  	c.Assert(chg.Kind(), Equals, "update-revisions")
   152  	c.Assert(chg.IsReady(), Equals, true)
   153  
   154  	// core "current" got reverted but canonical-pc-linux did not
   155  	var snapst snapstate.SnapState
   156  	err = snapstate.Get(st, "core", &snapst)
   157  	c.Assert(err, IsNil)
   158  	c.Assert(snapst.Current, Equals, snap.R(1))
   159  	c.Assert(snapst.Active, Equals, true)
   160  
   161  	err = snapstate.Get(st, "canonical-pc-linux", &snapst)
   162  	c.Assert(err, IsNil)
   163  	c.Assert(snapst.Current, Equals, snap.R(2))
   164  	c.Assert(snapst.Active, Equals, true)
   165  }
   166  
   167  func (bs *bootedSuite) TestUpdateBootRevisionsKernelSimple(c *C) {
   168  	st := bs.state
   169  	st.Lock()
   170  	defer st.Unlock()
   171  
   172  	bs.makeInstalledKernelOS(c, st)
   173  
   174  	bs.bootloader.SetBootKernel("canonical-pc-linux_1.snap")
   175  	err := snapstate.UpdateBootRevisions(st)
   176  	c.Assert(err, IsNil)
   177  
   178  	st.Unlock()
   179  	bs.settle()
   180  	st.Lock()
   181  
   182  	c.Assert(st.Changes(), HasLen, 1)
   183  	chg := st.Changes()[0]
   184  	c.Assert(chg.Err(), IsNil)
   185  	c.Assert(chg.Kind(), Equals, "update-revisions")
   186  	c.Assert(chg.IsReady(), Equals, true)
   187  
   188  	// canonical-pc-linux "current" got reverted but core did not
   189  	var snapst snapstate.SnapState
   190  	err = snapstate.Get(st, "canonical-pc-linux", &snapst)
   191  	c.Assert(err, IsNil)
   192  	c.Assert(snapst.Current, Equals, snap.R(1))
   193  	c.Assert(snapst.Active, Equals, true)
   194  
   195  	err = snapstate.Get(st, "core", &snapst)
   196  	c.Assert(err, IsNil)
   197  	c.Assert(snapst.Current, Equals, snap.R(2))
   198  	c.Assert(snapst.Active, Equals, true)
   199  }
   200  
   201  func (bs *bootedSuite) TestUpdateBootRevisionsKernelErrorsEarly(c *C) {
   202  	st := bs.state
   203  	st.Lock()
   204  	defer st.Unlock()
   205  
   206  	bs.makeInstalledKernelOS(c, st)
   207  
   208  	bs.bootloader.SetBootKernel("canonical-pc-linux_99.snap")
   209  	err := snapstate.UpdateBootRevisions(st)
   210  	c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "canonical-pc-linux"`)
   211  }
   212  
   213  func (bs *bootedSuite) TestUpdateBootRevisionsOSErrorsEarly(c *C) {
   214  	st := bs.state
   215  	st.Lock()
   216  	defer st.Unlock()
   217  
   218  	bs.makeInstalledKernelOS(c, st)
   219  
   220  	bs.bootloader.SetBootBase("core_99.snap")
   221  	err := snapstate.UpdateBootRevisions(st)
   222  	c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "core"`)
   223  }
   224  
   225  func (bs *bootedSuite) TestUpdateBootRevisionsOSErrorsLate(c *C) {
   226  	st := bs.state
   227  	st.Lock()
   228  	defer st.Unlock()
   229  
   230  	// have a kernel
   231  	snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", kernelSI2)
   232  	snapstate.Set(st, "canonical-pc-linux", &snapstate.SnapState{
   233  		SnapType: "kernel",
   234  		Active:   true,
   235  		Sequence: []*snap.SideInfo{kernelSI2},
   236  		Current:  snap.R(2),
   237  	})
   238  
   239  	// put core into the state but add no files on disk
   240  	// will break in the tasks
   241  	snapstate.Set(st, "core", &snapstate.SnapState{
   242  		SnapType: "os",
   243  		Active:   true,
   244  		Sequence: []*snap.SideInfo{osSI1, osSI2},
   245  		Current:  snap.R(2),
   246  	})
   247  	bs.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/core/1")
   248  
   249  	bs.bootloader.SetBootBase("core_1.snap")
   250  	err := snapstate.UpdateBootRevisions(st)
   251  	c.Assert(err, IsNil)
   252  
   253  	st.Unlock()
   254  	bs.settle()
   255  	st.Lock()
   256  
   257  	c.Assert(st.Changes(), HasLen, 1)
   258  	chg := st.Changes()[0]
   259  	c.Assert(chg.Kind(), Equals, "update-revisions")
   260  	c.Assert(chg.IsReady(), Equals, true)
   261  	c.Assert(chg.Err(), ErrorMatches, `(?ms).*Make snap "core" \(1\) available to the system \(fail\).*`)
   262  }
   263  
   264  func (bs *bootedSuite) TestWaitRestartCore(c *C) {
   265  	st := bs.state
   266  	st.Lock()
   267  	defer st.Unlock()
   268  
   269  	task := st.NewTask("auto-connect", "...")
   270  
   271  	// not core snap
   272  	si := &snap.SideInfo{RealName: "some-app"}
   273  	snaptest.MockSnap(c, "name: some-app\nversion: 1", si)
   274  	err := snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si})
   275  	c.Check(err, IsNil)
   276  
   277  	si = &snap.SideInfo{RealName: "core"}
   278  	snapsup := &snapstate.SnapSetup{SideInfo: si}
   279  
   280  	// core snap, restarting ... wait
   281  	state.MockRestarting(st, state.RestartSystem)
   282  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", si)
   283  	err = snapstate.WaitRestart(task, snapsup)
   284  	c.Check(err, FitsTypeOf, &state.Retry{})
   285  
   286  	// core snap, restarted, waiting for current core revision
   287  	state.MockRestarting(st, state.RestartUnset)
   288  	bs.bootloader.BootVars["snap_mode"] = "trying"
   289  	err = snapstate.WaitRestart(task, snapsup)
   290  	c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
   291  
   292  	// core snap updated
   293  	si.Revision = snap.R(2)
   294  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", si)
   295  
   296  	// core snap, restarted, right core revision, no rollback
   297  	bs.bootloader.BootVars["snap_mode"] = ""
   298  	err = snapstate.WaitRestart(task, snapsup)
   299  	c.Check(err, IsNil)
   300  
   301  	// core snap, restarted, wrong core revision, rollback!
   302  	bs.bootloader.SetBootBase("core_1.snap")
   303  	err = snapstate.WaitRestart(task, snapsup)
   304  	c.Check(err, ErrorMatches, `cannot finish core installation, there was a rollback across reboot`)
   305  }
   306  
   307  func (bs *bootedSuite) TestWaitRestartBootableBase(c *C) {
   308  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   309  	defer r()
   310  
   311  	st := bs.state
   312  	st.Lock()
   313  	defer st.Unlock()
   314  
   315  	task := st.NewTask("auto-connect", "...")
   316  
   317  	// not core snap
   318  	si := &snap.SideInfo{RealName: "some-app", Revision: snap.R(1)}
   319  	snaptest.MockSnap(c, "name: some-app\nversion: 1", si)
   320  	err := snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si})
   321  	c.Check(err, IsNil)
   322  
   323  	// core snap but we are on a model with a different base
   324  	si = &snap.SideInfo{RealName: "core"}
   325  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", si)
   326  	err = snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si})
   327  	c.Check(err, IsNil)
   328  
   329  	si = &snap.SideInfo{RealName: "core18"}
   330  	snapsup := &snapstate.SnapSetup{SideInfo: si}
   331  	snaptest.MockSnap(c, "name: core18\ntype: base\nversion: 1", si)
   332  	// core snap, restarting ... wait
   333  	state.MockRestarting(st, state.RestartSystem)
   334  	err = snapstate.WaitRestart(task, snapsup)
   335  	c.Check(err, FitsTypeOf, &state.Retry{})
   336  
   337  	// core snap, restarted, waiting for current core revision
   338  	state.MockRestarting(st, state.RestartUnset)
   339  	bs.bootloader.BootVars["snap_mode"] = "trying"
   340  	err = snapstate.WaitRestart(task, snapsup)
   341  	c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
   342  
   343  	// core18 snap updated
   344  	si.Revision = snap.R(2)
   345  	snaptest.MockSnap(c, "name: core18\ntype: base\nversion: 2", si)
   346  
   347  	// core snap, restarted, right core revision, no rollback
   348  	bs.bootloader.BootVars["snap_mode"] = ""
   349  	bs.bootloader.SetBootBase("core18_2.snap")
   350  	err = snapstate.WaitRestart(task, snapsup)
   351  	c.Check(err, IsNil)
   352  
   353  	// core snap, restarted, wrong core revision, rollback!
   354  	bs.bootloader.SetBootBase("core18_1.snap")
   355  	err = snapstate.WaitRestart(task, snapsup)
   356  	c.Check(err, ErrorMatches, `cannot finish core18 installation, there was a rollback across reboot`)
   357  }