github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/snapstate/backend/setup_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 backend_test
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/boot/boottest"
    31  	"github.com/snapcore/snapd/bootloader"
    32  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    36  	"github.com/snapcore/snapd/progress"
    37  	"github.com/snapcore/snapd/snap"
    38  	"github.com/snapcore/snapd/snap/snaptest"
    39  	"github.com/snapcore/snapd/systemd"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  type setupSuite struct {
    44  	testutil.BaseTest
    45  	be                backend.Backend
    46  	umount            *testutil.MockCmd
    47  	systemctlRestorer func()
    48  }
    49  
    50  var _ = Suite(&setupSuite{})
    51  
    52  func (s *setupSuite) SetUpTest(c *C) {
    53  	s.BaseTest.SetUpTest(c)
    54  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    55  
    56  	dirs.SetRootDir(c.MkDir())
    57  
    58  	// needed for system key generation
    59  	restore := osutil.MockMountInfo("")
    60  	s.AddCleanup(restore)
    61  
    62  	err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc", "systemd", "system", "multi-user.target.wants"), 0755)
    63  	c.Assert(err, IsNil)
    64  
    65  	s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
    66  		return []byte("ActiveState=inactive\n"), nil
    67  	})
    68  
    69  	s.umount = testutil.MockCommand(c, "umount", "")
    70  }
    71  
    72  func (s *setupSuite) TearDownTest(c *C) {
    73  	s.BaseTest.TearDownTest(c)
    74  	dirs.SetRootDir("")
    75  	bootloader.Force(nil)
    76  	s.umount.Restore()
    77  	s.systemctlRestorer()
    78  }
    79  
    80  var (
    81  	mockClassicDev    = boottest.MockDevice("")
    82  	mockDev           = boottest.MockDevice("boot-snap")
    83  	mockDevWithKernel = boottest.MockDevice("kernel")
    84  )
    85  
    86  func (s *setupSuite) TestSetupDoUndoSimple(c *C) {
    87  	snapPath := makeTestSnap(c, helloYaml1)
    88  
    89  	si := snap.SideInfo{
    90  		RealName: "hello",
    91  		Revision: snap.R(14),
    92  	}
    93  
    94  	snapType, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null)
    95  	c.Assert(err, IsNil)
    96  	c.Assert(installRecord, NotNil)
    97  	c.Check(snapType, Equals, snap.TypeApp)
    98  
    99  	// after setup the snap file is in the right dir
   100  	c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_14.snap")), Equals, true)
   101  
   102  	// ensure the right unit is created
   103  	mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello/14"))
   104  	c.Assert(mup, testutil.FileMatches, fmt.Sprintf("(?ms).*^Where=%s", filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello/14")))
   105  	c.Assert(mup, testutil.FileMatches, "(?ms).*^What=/var/lib/snapd/snaps/hello_14.snap")
   106  
   107  	minInfo := snap.MinimalPlaceInfo("hello", snap.R(14))
   108  	// mount dir was created
   109  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true)
   110  
   111  	// undo undoes the mount unit and the instdir creation
   112  	err = s.be.UndoSetupSnap(minInfo, "app", nil, mockDev, progress.Null)
   113  	c.Assert(err, IsNil)
   114  
   115  	l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
   116  	c.Assert(l, HasLen, 0)
   117  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false)
   118  
   119  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false)
   120  
   121  }
   122  
   123  func (s *setupSuite) TestSetupDoUndoInstance(c *C) {
   124  	snapPath := makeTestSnap(c, helloYaml1)
   125  
   126  	si := snap.SideInfo{
   127  		RealName: "hello",
   128  		Revision: snap.R(14),
   129  	}
   130  
   131  	snapType, installRecord, err := s.be.SetupSnap(snapPath, "hello_instance", &si, mockDev, progress.Null)
   132  	c.Assert(err, IsNil)
   133  	c.Assert(installRecord, NotNil)
   134  	c.Check(snapType, Equals, snap.TypeApp)
   135  
   136  	// after setup the snap file is in the right dir
   137  	c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_instance_14.snap")), Equals, true)
   138  
   139  	// ensure the right unit is created
   140  	mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello_instance/14"))
   141  	c.Assert(mup, testutil.FileMatches, fmt.Sprintf("(?ms).*^Where=%s", filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello_instance/14")))
   142  	c.Assert(mup, testutil.FileMatches, "(?ms).*^What=/var/lib/snapd/snaps/hello_instance_14.snap")
   143  
   144  	minInfo := snap.MinimalPlaceInfo("hello_instance", snap.R(14))
   145  	// mount dir was created
   146  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true)
   147  
   148  	// undo undoes the mount unit and the instdir creation
   149  	err = s.be.UndoSetupSnap(minInfo, "app", nil, mockDev, progress.Null)
   150  	c.Assert(err, IsNil)
   151  
   152  	l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
   153  	c.Assert(l, HasLen, 0)
   154  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false)
   155  
   156  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false)
   157  }
   158  
   159  func (s *setupSuite) TestSetupDoUndoKernel(c *C) {
   160  	bloader := bootloadertest.Mock("mock", c.MkDir())
   161  	bootloader.Force(bloader)
   162  
   163  	// we don't get real mounting
   164  	os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
   165  	defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
   166  
   167  	testFiles := [][]string{
   168  		{"kernel.img", "kernel"},
   169  		{"initrd.img", "initrd"},
   170  		{"modules/4.4.0-14-generic/foo.ko", "a module"},
   171  		{"firmware/bar.bin", "some firmware"},
   172  		{"meta/kernel.yaml", "version: 4.2"},
   173  	}
   174  	snapPath := snaptest.MakeTestSnapWithFiles(c, `name: kernel
   175  version: 1.0
   176  type: kernel
   177  `, testFiles)
   178  
   179  	si := snap.SideInfo{
   180  		RealName: "kernel",
   181  		Revision: snap.R(140),
   182  	}
   183  
   184  	snapType, installRecord, err := s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null)
   185  	c.Assert(err, IsNil)
   186  	c.Check(snapType, Equals, snap.TypeKernel)
   187  	c.Assert(installRecord, NotNil)
   188  	c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1)
   189  	c.Assert(bloader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
   190  	minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
   191  
   192  	// undo deletes the kernel assets again
   193  	err = s.be.UndoSetupSnap(minInfo, "kernel", nil, mockDevWithKernel, progress.Null)
   194  	c.Assert(err, IsNil)
   195  	c.Assert(bloader.RemoveKernelAssetsCalls, HasLen, 1)
   196  	c.Assert(bloader.RemoveKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
   197  }
   198  
   199  func (s *setupSuite) TestSetupDoIdempotent(c *C) {
   200  	// make sure that a retry wouldn't stumble on partial work
   201  	// use a kernel because that does and need to do strictly more
   202  
   203  	// this cannot check systemd own behavior though around mounts!
   204  
   205  	bloader := bootloadertest.Mock("mock", c.MkDir())
   206  	bootloader.Force(bloader)
   207  	// we don't get real mounting
   208  	os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
   209  	defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
   210  
   211  	testFiles := [][]string{
   212  		{"kernel.img", "kernel"},
   213  		{"initrd.img", "initrd"},
   214  		{"modules/4.4.0-14-generic/foo.ko", "a module"},
   215  		{"firmware/bar.bin", "some firmware"},
   216  		{"meta/kernel.yaml", "version: 4.2"},
   217  	}
   218  	snapPath := snaptest.MakeTestSnapWithFiles(c, `name: kernel
   219  version: 1.0
   220  type: kernel
   221  `, testFiles)
   222  
   223  	si := snap.SideInfo{
   224  		RealName: "kernel",
   225  		Revision: snap.R(140),
   226  	}
   227  
   228  	_, installRecord, err := s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null)
   229  	c.Assert(err, IsNil)
   230  	c.Assert(installRecord, NotNil)
   231  	c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1)
   232  	c.Assert(bloader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
   233  
   234  	// retry run
   235  	_, installRecord, err = s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null)
   236  	c.Assert(err, IsNil)
   237  	c.Assert(installRecord, NotNil)
   238  	c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 2)
   239  	c.Assert(bloader.ExtractKernelAssetsCalls[1].InstanceName(), Equals, "kernel")
   240  	minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
   241  
   242  	// sanity checks
   243  	l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
   244  	c.Assert(l, HasLen, 1)
   245  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true)
   246  
   247  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true)
   248  }
   249  
   250  func (s *setupSuite) TestSetupUndoIdempotent(c *C) {
   251  	// make sure that a retry wouldn't stumble on partial work
   252  	// use a kernel because that does and need to do strictly more
   253  
   254  	// this cannot check systemd own behavior though around mounts!
   255  
   256  	bloader := bootloadertest.Mock("mock", c.MkDir())
   257  	bootloader.Force(bloader)
   258  	// we don't get real mounting
   259  	os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
   260  	defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
   261  
   262  	testFiles := [][]string{
   263  		{"kernel.img", "kernel"},
   264  		{"initrd.img", "initrd"},
   265  		{"modules/4.4.0-14-generic/foo.ko", "a module"},
   266  		{"firmware/bar.bin", "some firmware"},
   267  		{"meta/kernel.yaml", "version: 4.2"},
   268  	}
   269  	snapPath := snaptest.MakeTestSnapWithFiles(c, `name: kernel
   270  version: 1.0
   271  type: kernel
   272  `, testFiles)
   273  
   274  	si := snap.SideInfo{
   275  		RealName: "kernel",
   276  		Revision: snap.R(140),
   277  	}
   278  
   279  	_, installRecord, err := s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null)
   280  	c.Assert(err, IsNil)
   281  	c.Assert(installRecord, NotNil)
   282  
   283  	minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
   284  
   285  	err = s.be.UndoSetupSnap(minInfo, "kernel", nil, mockDevWithKernel, progress.Null)
   286  	c.Assert(err, IsNil)
   287  
   288  	// retry run
   289  	err = s.be.UndoSetupSnap(minInfo, "kernel", nil, mockDevWithKernel, progress.Null)
   290  	c.Assert(err, IsNil)
   291  
   292  	// sanity checks
   293  	l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
   294  	c.Assert(l, HasLen, 0)
   295  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false)
   296  
   297  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false)
   298  
   299  	// assets got extracted and then removed again
   300  	c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1)
   301  	c.Assert(bloader.RemoveKernelAssetsCalls, HasLen, 1)
   302  }
   303  
   304  func (s *setupSuite) TestSetupUndoKeepsTargetSnapIfSymlink(c *C) {
   305  	snapPath := makeTestSnap(c, helloYaml1)
   306  	c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), IsNil)
   307  	// symlink the test snap under target blob dir where SetupSnap would normally
   308  	// install it, so that it realizes there is nothing to do.
   309  	tmpPath := filepath.Join(dirs.SnapBlobDir, "hello_14.snap")
   310  	c.Assert(os.Symlink(snapPath, tmpPath), IsNil)
   311  
   312  	si := snap.SideInfo{RealName: "hello", Revision: snap.R(14)}
   313  	_, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null)
   314  	c.Assert(err, IsNil)
   315  	c.Assert(installRecord, NotNil)
   316  	c.Check(installRecord.TargetSnapExisted, Equals, true)
   317  
   318  	minInfo := snap.MinimalPlaceInfo("hello", snap.R(14))
   319  
   320  	// after setup the snap file is in the right dir
   321  	c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_14.snap")), Equals, true)
   322  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true)
   323  	// sanity
   324  	c.Assert(osutil.IsSymlink(minInfo.MountFile()), Equals, true)
   325  
   326  	// undo keeps the target .snap file intact if requested
   327  	installRecord = &backend.InstallRecord{TargetSnapExisted: true}
   328  	c.Assert(s.be.UndoSetupSnap(minInfo, "app", installRecord, mockDev, progress.Null), IsNil)
   329  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true)
   330  }
   331  
   332  func (s *setupSuite) TestSetupUndoKeepsTargetSnapIgnoredIfNotSymlink(c *C) {
   333  	snapPath := makeTestSnap(c, helloYaml1)
   334  	c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), IsNil)
   335  	// copy test snap to target blob dir where SetupSnap would normally install it,
   336  	// so that it realizes there is nothing to do.
   337  	tmpPath := filepath.Join(dirs.SnapBlobDir, "hello_14.snap")
   338  	c.Assert(osutil.CopyFile(snapPath, tmpPath, 0), IsNil)
   339  
   340  	si := snap.SideInfo{RealName: "hello", Revision: snap.R(14)}
   341  	_, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null)
   342  	c.Assert(err, IsNil)
   343  	c.Assert(installRecord, NotNil)
   344  	c.Check(installRecord.TargetSnapExisted, Equals, true)
   345  
   346  	minInfo := snap.MinimalPlaceInfo("hello", snap.R(14))
   347  
   348  	// after setup the snap file is in the right dir
   349  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true)
   350  
   351  	installRecord = &backend.InstallRecord{TargetSnapExisted: true}
   352  	c.Assert(s.be.UndoSetupSnap(minInfo, "app", installRecord, mockDev, progress.Null), IsNil)
   353  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false)
   354  }
   355  
   356  func (s *setupSuite) TestSetupCleanupAfterFail(c *C) {
   357  	snapPath := makeTestSnap(c, helloYaml1)
   358  
   359  	si := snap.SideInfo{
   360  		RealName: "hello",
   361  		Revision: snap.R(14),
   362  	}
   363  
   364  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
   365  		// mount unit start fails
   366  		if len(cmd) >= 2 && cmd[0] == "start" && strings.HasSuffix(cmd[1], ".mount") {
   367  			return nil, fmt.Errorf("failed")
   368  		}
   369  		return []byte("ActiveState=inactive\n"), nil
   370  	})
   371  	defer r()
   372  
   373  	_, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null)
   374  	c.Assert(err, ErrorMatches, "failed")
   375  	c.Check(installRecord, IsNil)
   376  
   377  	// everything is gone
   378  	l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
   379  	c.Check(l, HasLen, 0)
   380  
   381  	minInfo := snap.MinimalPlaceInfo("hello", snap.R(14))
   382  	c.Check(osutil.FileExists(minInfo.MountDir()), Equals, false)
   383  	c.Check(osutil.FileExists(minInfo.MountFile()), Equals, false)
   384  	c.Check(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_14.snap")), Equals, false)
   385  }
   386  
   387  func (s *setupSuite) TestRemoveSnapFilesDir(c *C) {
   388  	snapPath := makeTestSnap(c, helloYaml1)
   389  
   390  	si := snap.SideInfo{
   391  		RealName: "hello",
   392  		Revision: snap.R(14),
   393  	}
   394  
   395  	snapType, installRecord, err := s.be.SetupSnap(snapPath, "hello_instance", &si, mockDev, progress.Null)
   396  	c.Assert(err, IsNil)
   397  	c.Assert(installRecord, NotNil)
   398  	c.Check(snapType, Equals, snap.TypeApp)
   399  
   400  	minInfo := snap.MinimalPlaceInfo("hello_instance", snap.R(14))
   401  	// mount dir was created
   402  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true)
   403  
   404  	installRecord = &backend.InstallRecord{}
   405  	s.be.RemoveSnapFiles(minInfo, snapType, installRecord, mockDev, progress.Null)
   406  	c.Assert(err, IsNil)
   407  
   408  	l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
   409  	c.Assert(l, HasLen, 0)
   410  	c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false)
   411  	c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false)
   412  	c.Assert(osutil.FileExists(snap.BaseDir(minInfo.InstanceName())), Equals, true)
   413  	c.Assert(osutil.FileExists(snap.BaseDir(minInfo.SnapName())), Equals, true)
   414  
   415  	// /snap/hello is kept as other instances exist
   416  	err = s.be.RemoveSnapDir(minInfo, true)
   417  	c.Assert(err, IsNil)
   418  	c.Assert(osutil.FileExists(snap.BaseDir(minInfo.InstanceName())), Equals, false)
   419  	c.Assert(osutil.FileExists(snap.BaseDir(minInfo.SnapName())), Equals, true)
   420  
   421  	// /snap/hello is removed when there are no more instances
   422  	err = s.be.RemoveSnapDir(minInfo, false)
   423  	c.Assert(err, IsNil)
   424  	c.Assert(osutil.FileExists(snap.BaseDir(minInfo.SnapName())), Equals, false)
   425  }