github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	"errors"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/boot"
    33  	"github.com/snapcore/snapd/boot/boottest"
    34  	"github.com/snapcore/snapd/bootloader"
    35  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/overlord"
    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 bootedSuite struct {
    48  	testutil.BaseTest
    49  
    50  	bootloader *boottest.Bootenv16
    51  
    52  	o           *overlord.Overlord
    53  	state       *state.State
    54  	snapmgr     *snapstate.SnapManager
    55  	fakeBackend *fakeSnappyBackend
    56  	restore     func()
    57  }
    58  
    59  var _ = Suite(&bootedSuite{})
    60  
    61  func (bs *bootedSuite) SetUpTest(c *C) {
    62  	bs.BaseTest.SetUpTest(c)
    63  
    64  	dirs.SetRootDir(c.MkDir())
    65  	err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
    66  	c.Assert(err, IsNil)
    67  
    68  	bs.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    69  
    70  	// booted is not running on classic
    71  	release.MockOnClassic(false)
    72  
    73  	bs.bootloader = boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
    74  	bs.bootloader.SetBootKernel("canonical-pc-linux_2.snap")
    75  	bs.bootloader.SetBootBase("core_2.snap")
    76  	bootloader.Force(bs.bootloader)
    77  
    78  	bs.fakeBackend = &fakeSnappyBackend{}
    79  	bs.o = overlord.Mock()
    80  	bs.state = bs.o.State()
    81  	bs.snapmgr, err = snapstate.Manager(bs.state, bs.o.TaskRunner())
    82  	c.Assert(err, IsNil)
    83  
    84  	AddForeignTaskHandlers(bs.o.TaskRunner(), bs.fakeBackend)
    85  
    86  	bs.o.AddManager(bs.snapmgr)
    87  	bs.o.AddManager(bs.o.TaskRunner())
    88  
    89  	c.Assert(bs.o.StartUp(), IsNil)
    90  
    91  	snapstate.SetSnapManagerBackend(bs.snapmgr, bs.fakeBackend)
    92  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
    93  		return nil, nil
    94  	}
    95  	bs.restore = snapstatetest.MockDeviceModel(DefaultModel())
    96  }
    97  
    98  func (bs *bootedSuite) TearDownTest(c *C) {
    99  	bs.BaseTest.TearDownTest(c)
   100  	snapstate.AutoAliases = nil
   101  	bs.restore()
   102  	release.MockOnClassic(true)
   103  	dirs.SetRootDir("")
   104  	bootloader.Force(nil)
   105  }
   106  
   107  var osSI1 = &snap.SideInfo{RealName: "core", Revision: snap.R(1)}
   108  var osSI2 = &snap.SideInfo{RealName: "core", Revision: snap.R(2)}
   109  var kernelSI1 = &snap.SideInfo{RealName: "canonical-pc-linux", Revision: snap.R(1)}
   110  var kernelSI2 = &snap.SideInfo{RealName: "canonical-pc-linux", Revision: snap.R(2)}
   111  
   112  func (bs *bootedSuite) settle() {
   113  	bs.o.Settle(5 * time.Second)
   114  }
   115  
   116  func (bs *bootedSuite) makeInstalledKernelOS(c *C, st *state.State) {
   117  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", osSI1)
   118  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", osSI2)
   119  	snapstate.Set(st, "core", &snapstate.SnapState{
   120  		SnapType: "os",
   121  		Active:   true,
   122  		Sequence: []*snap.SideInfo{osSI1, osSI2},
   123  		Current:  snap.R(2),
   124  	})
   125  
   126  	snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 1", kernelSI1)
   127  	snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", kernelSI2)
   128  	snapstate.Set(st, "canonical-pc-linux", &snapstate.SnapState{
   129  		SnapType: "kernel",
   130  		Active:   true,
   131  		Sequence: []*snap.SideInfo{kernelSI1, kernelSI2},
   132  		Current:  snap.R(2),
   133  	})
   134  
   135  }
   136  
   137  func (bs *bootedSuite) TestUpdateBootRevisionsOSSimple(c *C) {
   138  	st := bs.state
   139  	st.Lock()
   140  	defer st.Unlock()
   141  
   142  	bs.makeInstalledKernelOS(c, st)
   143  
   144  	bs.bootloader.SetBootBase("core_1.snap")
   145  	err := snapstate.UpdateBootRevisions(st)
   146  	c.Assert(err, IsNil)
   147  
   148  	st.Unlock()
   149  	bs.settle()
   150  	st.Lock()
   151  
   152  	c.Assert(st.Changes(), HasLen, 1)
   153  	chg := st.Changes()[0]
   154  	c.Assert(chg.Err(), IsNil)
   155  	c.Assert(chg.Kind(), Equals, "update-revisions")
   156  	c.Assert(chg.IsReady(), Equals, true)
   157  
   158  	// core "current" got reverted but canonical-pc-linux did not
   159  	var snapst snapstate.SnapState
   160  	err = snapstate.Get(st, "core", &snapst)
   161  	c.Assert(err, IsNil)
   162  	c.Assert(snapst.Current, Equals, snap.R(1))
   163  	c.Assert(snapst.Active, Equals, true)
   164  
   165  	err = snapstate.Get(st, "canonical-pc-linux", &snapst)
   166  	c.Assert(err, IsNil)
   167  	c.Assert(snapst.Current, Equals, snap.R(2))
   168  	c.Assert(snapst.Active, Equals, true)
   169  }
   170  
   171  func (bs *bootedSuite) TestUpdateBootRevisionsKernelSimple(c *C) {
   172  	st := bs.state
   173  	st.Lock()
   174  	defer st.Unlock()
   175  
   176  	bs.makeInstalledKernelOS(c, st)
   177  
   178  	bs.bootloader.SetBootKernel("canonical-pc-linux_1.snap")
   179  	err := snapstate.UpdateBootRevisions(st)
   180  	c.Assert(err, IsNil)
   181  
   182  	st.Unlock()
   183  	bs.settle()
   184  	st.Lock()
   185  
   186  	c.Assert(st.Changes(), HasLen, 1)
   187  	chg := st.Changes()[0]
   188  	c.Assert(chg.Err(), IsNil)
   189  	c.Assert(chg.Kind(), Equals, "update-revisions")
   190  	c.Assert(chg.IsReady(), Equals, true)
   191  
   192  	// canonical-pc-linux "current" got reverted but core did not
   193  	var snapst snapstate.SnapState
   194  	err = snapstate.Get(st, "canonical-pc-linux", &snapst)
   195  	c.Assert(err, IsNil)
   196  	c.Assert(snapst.Current, Equals, snap.R(1))
   197  	c.Assert(snapst.Active, Equals, true)
   198  
   199  	err = snapstate.Get(st, "core", &snapst)
   200  	c.Assert(err, IsNil)
   201  	c.Assert(snapst.Current, Equals, snap.R(2))
   202  	c.Assert(snapst.Active, Equals, true)
   203  }
   204  
   205  func (bs *bootedSuite) TestUpdateBootRevisionsDeviceCtxErrors(c *C) {
   206  	st := bs.state
   207  	st.Lock()
   208  	defer st.Unlock()
   209  
   210  	bs.makeInstalledKernelOS(c, st)
   211  
   212  	errBoom := errors.New("boom")
   213  
   214  	r := snapstatetest.ReplaceDeviceCtxHook(func(*state.State, *state.Task, snapstate.DeviceContext) (snapstate.DeviceContext, error) {
   215  		return nil, errBoom
   216  	})
   217  	defer r()
   218  
   219  	err := snapstate.UpdateBootRevisions(st)
   220  	c.Assert(err, Equals, errBoom)
   221  }
   222  
   223  func (bs *bootedSuite) TestUpdateBootRevisionsKernelErrorsEarly(c *C) {
   224  	st := bs.state
   225  	st.Lock()
   226  	defer st.Unlock()
   227  
   228  	bs.makeInstalledKernelOS(c, st)
   229  
   230  	bs.bootloader.SetBootKernel("canonical-pc-linux_99.snap")
   231  	err := snapstate.UpdateBootRevisions(st)
   232  	c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "canonical-pc-linux"`)
   233  }
   234  
   235  func (bs *bootedSuite) TestUpdateBootRevisionsOSErrorsEarly(c *C) {
   236  	st := bs.state
   237  	st.Lock()
   238  	defer st.Unlock()
   239  
   240  	bs.makeInstalledKernelOS(c, st)
   241  
   242  	bs.bootloader.SetBootBase("core_99.snap")
   243  	err := snapstate.UpdateBootRevisions(st)
   244  	c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "core"`)
   245  }
   246  
   247  func (bs *bootedSuite) TestUpdateBootRevisionsOSErrorsLate(c *C) {
   248  	st := bs.state
   249  	st.Lock()
   250  	defer st.Unlock()
   251  
   252  	// have a kernel
   253  	snaptest.MockSnap(c, "name: canonical-pc-linux\ntype: os\nversion: 2", kernelSI2)
   254  	snapstate.Set(st, "canonical-pc-linux", &snapstate.SnapState{
   255  		SnapType: "kernel",
   256  		Active:   true,
   257  		Sequence: []*snap.SideInfo{kernelSI2},
   258  		Current:  snap.R(2),
   259  	})
   260  
   261  	// put core into the state but add no files on disk
   262  	// will break in the tasks
   263  	snapstate.Set(st, "core", &snapstate.SnapState{
   264  		SnapType: "os",
   265  		Active:   true,
   266  		Sequence: []*snap.SideInfo{osSI1, osSI2},
   267  		Current:  snap.R(2),
   268  	})
   269  	bs.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/core/1")
   270  
   271  	bs.bootloader.SetBootBase("core_1.snap")
   272  	err := snapstate.UpdateBootRevisions(st)
   273  	c.Assert(err, IsNil)
   274  
   275  	st.Unlock()
   276  	bs.settle()
   277  	st.Lock()
   278  
   279  	c.Assert(st.Changes(), HasLen, 1)
   280  	chg := st.Changes()[0]
   281  	c.Assert(chg.Kind(), Equals, "update-revisions")
   282  	c.Assert(chg.IsReady(), Equals, true)
   283  	c.Assert(chg.Err(), ErrorMatches, `(?ms).*Make snap "core" \(1\) available to the system \(fail\).*`)
   284  }
   285  
   286  func (bs *bootedSuite) TestWaitRestartCore(c *C) {
   287  	st := bs.state
   288  	st.Lock()
   289  	defer st.Unlock()
   290  
   291  	task := st.NewTask("auto-connect", "...")
   292  
   293  	// not core snap
   294  	si := &snap.SideInfo{RealName: "some-app"}
   295  	snaptest.MockSnap(c, "name: some-app\nversion: 1", si)
   296  	err := snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si})
   297  	c.Check(err, IsNil)
   298  
   299  	si = &snap.SideInfo{RealName: "core"}
   300  	snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeOS}
   301  
   302  	// core snap, restarting ... wait
   303  	state.MockRestarting(st, state.RestartSystem)
   304  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", si)
   305  	err = snapstate.WaitRestart(task, snapsup)
   306  	c.Check(err, FitsTypeOf, &state.Retry{})
   307  
   308  	// core snap, restarted, waiting for current core revision
   309  	state.MockRestarting(st, state.RestartUnset)
   310  	bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus
   311  	err = snapstate.WaitRestart(task, snapsup)
   312  	c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
   313  
   314  	// core snap updated
   315  	si.Revision = snap.R(2)
   316  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 2", si)
   317  
   318  	// core snap, restarted, right core revision, no rollback
   319  	bs.bootloader.BootVars["snap_mode"] = ""
   320  	err = snapstate.WaitRestart(task, snapsup)
   321  	c.Check(err, IsNil)
   322  
   323  	// core snap, restarted, wrong core revision, rollback!
   324  	bs.bootloader.SetBootBase("core_1.snap")
   325  	err = snapstate.WaitRestart(task, snapsup)
   326  	c.Check(err, ErrorMatches, `cannot finish core installation, there was a rollback across reboot`)
   327  }
   328  
   329  func (bs *bootedSuite) TestWaitRestartBootableBase(c *C) {
   330  	r := snapstatetest.MockDeviceModel(ModelWithBase("core18"))
   331  	defer r()
   332  
   333  	st := bs.state
   334  	st.Lock()
   335  	defer st.Unlock()
   336  
   337  	task := st.NewTask("auto-connect", "...")
   338  
   339  	// not core snap
   340  	si := &snap.SideInfo{RealName: "some-app", Revision: snap.R(1)}
   341  	snaptest.MockSnap(c, "name: some-app\nversion: 1", si)
   342  	err := snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si})
   343  	c.Check(err, IsNil)
   344  
   345  	// core snap but we are on a model with a different base
   346  	si = &snap.SideInfo{RealName: "core"}
   347  	snaptest.MockSnap(c, "name: core\ntype: os\nversion: 1", si)
   348  	err = snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeOS})
   349  	c.Check(err, IsNil)
   350  
   351  	si = &snap.SideInfo{RealName: "core18"}
   352  	snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeBase}
   353  	snaptest.MockSnap(c, "name: core18\ntype: base\nversion: 1", si)
   354  	// core snap, restarting ... wait
   355  	state.MockRestarting(st, state.RestartSystem)
   356  	err = snapstate.WaitRestart(task, snapsup)
   357  	c.Check(err, FitsTypeOf, &state.Retry{})
   358  
   359  	// core snap, restarted, waiting for current core revision
   360  	state.MockRestarting(st, state.RestartUnset)
   361  	bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus
   362  	err = snapstate.WaitRestart(task, snapsup)
   363  	c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
   364  
   365  	// core18 snap updated
   366  	si.Revision = snap.R(2)
   367  	snaptest.MockSnap(c, "name: core18\ntype: base\nversion: 2", si)
   368  
   369  	// core snap, restarted, right core revision, no rollback
   370  	bs.bootloader.BootVars["snap_mode"] = ""
   371  	bs.bootloader.SetBootBase("core18_2.snap")
   372  	err = snapstate.WaitRestart(task, snapsup)
   373  	c.Check(err, IsNil)
   374  
   375  	// core snap, restarted, wrong core revision, rollback!
   376  	bs.bootloader.SetBootBase("core18_1.snap")
   377  	err = snapstate.WaitRestart(task, snapsup)
   378  	c.Check(err, ErrorMatches, `cannot finish core18 installation, there was a rollback across reboot`)
   379  }
   380  
   381  func (bs *bootedSuite) TestWaitRestartKernel(c *C) {
   382  	r := snapstatetest.MockDeviceModel(DefaultModel())
   383  	defer r()
   384  
   385  	st := bs.state
   386  	st.Lock()
   387  	defer st.Unlock()
   388  
   389  	task := st.NewTask("auto-connect", "...")
   390  
   391  	// not kernel snap
   392  	si := &snap.SideInfo{RealName: "some-app", Revision: snap.R(1)}
   393  	snaptest.MockSnap(c, "name: some-app\nversion: 1", si)
   394  	err := snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si})
   395  	c.Check(err, IsNil)
   396  
   397  	// different kernel (may happen with remodel)
   398  	si = &snap.SideInfo{RealName: "other-kernel"}
   399  	snaptest.MockSnap(c, "name: other-kernel\ntype: kernel\nversion: 1", si)
   400  	err = snapstate.WaitRestart(task, &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeKernel})
   401  	c.Check(err, IsNil)
   402  
   403  	si = &snap.SideInfo{RealName: "kernel"}
   404  	snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeKernel}
   405  	snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 1", si)
   406  	// kernel snap, restarting ... wait
   407  	state.MockRestarting(st, state.RestartSystem)
   408  	err = snapstate.WaitRestart(task, snapsup)
   409  	c.Check(err, FitsTypeOf, &state.Retry{})
   410  
   411  	// kernel snap, restarted, waiting for current core revision
   412  	state.MockRestarting(st, state.RestartUnset)
   413  	bs.bootloader.BootVars["snap_mode"] = boot.TryingStatus
   414  	err = snapstate.WaitRestart(task, snapsup)
   415  	c.Check(err, DeepEquals, &state.Retry{After: 5 * time.Second})
   416  
   417  	// kernel snap updated
   418  	si.Revision = snap.R(2)
   419  	snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 2", si)
   420  
   421  	// kernel snap, restarted, right kernel revision, no rollback
   422  	bs.bootloader.BootVars["snap_mode"] = ""
   423  	bs.bootloader.SetBootKernel("kernel_2.snap")
   424  	err = snapstate.WaitRestart(task, snapsup)
   425  	c.Check(err, IsNil)
   426  
   427  	// kernel snap, restarted, wrong core revision, rollback!
   428  	bs.bootloader.SetBootKernel("kernel_1.snap")
   429  	err = snapstate.WaitRestart(task, snapsup)
   430  	c.Check(err, ErrorMatches, `cannot finish kernel installation, there was a rollback across reboot`)
   431  }
   432  
   433  func (bs *bootedSuite) TestWaitRestartEphemeralModeSkipsRollbackDetection(c *C) {
   434  	r := snapstatetest.MockDeviceModel(DefaultModel())
   435  	defer r()
   436  
   437  	st := bs.state
   438  	st.Lock()
   439  	defer st.Unlock()
   440  
   441  	task := st.NewTask("auto-connect", "...")
   442  
   443  	si := &snap.SideInfo{RealName: "kernel"}
   444  	snapsup := &snapstate.SnapSetup{SideInfo: si, Type: snap.TypeKernel}
   445  	snaptest.MockSnap(c, "name: kernel\ntype: kernel\nversion: 1", si)
   446  	// kernel snap, restarted, wrong core revision, rollback detected!
   447  	bs.bootloader.SetBootKernel("kernel_1.snap")
   448  	err := snapstate.WaitRestart(task, snapsup)
   449  	c.Check(err, ErrorMatches, `cannot finish kernel installation, there was a rollback across reboot`)
   450  
   451  	// but *not* in an ephemeral mode like "recover" - we skip the rollback
   452  	// detection here
   453  	r = snapstatetest.MockDeviceModelAndMode(DefaultModel(), "install")
   454  	defer r()
   455  	err = snapstate.WaitRestart(task, snapsup)
   456  	c.Check(err, IsNil)
   457  }