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