Skip to content

Commit

Permalink
many: build drivers tree on install (canonical#13923)
Browse files Browse the repository at this point in the history
Build drivers tree on installation so kernel modules can be found on first boot after installation (otherwise the modules would be available too late, after initial installation of the kernel has happened while seeding).

Also, make sure that the tree is not re-created when seeding, and add a spread test for kernel-modules components.

* dirs: add function to get root of drivers trees

* kernel: do not recreate drivers tree if it already exists

On installation, a drivers tree has already been copied to the data
partition. Do not re-create if found when seeding happens (note though
that on preseeding we will not find the tree and we will still create
it).

* gadget/install: create device tree on installations

Create a device tree on installation so we have it on first boot.

* o/snapstate: fix task message

* tests/nested/manual: add kernel-modules-components test

This test installs a kernel-modules component and verifies that the
shipped kernel module is installed and can be loaded.

* tests/lib: do not error out if cloud-init finished with

'recoverable error' status.

* kernel: have destination as argument when building drivers tree

We need to tackle the generic installation case as opposed to creating
the drivers tree when seeding the system. In that case the destination
tree can vary depending on installation type.

* overlord: pass kernel information to install package

so the drivers tree can be created.

* cmd/snap-bootstrap: build drivers tree on installation from initramfs

* tests: adapt to additional parameters in install.Run

* snap/naming: add ancillary method to split a full component name

into snap and component names.

* gadget/install: fix nosecboot tests
  • Loading branch information
alfonsosanchezbeato authored May 29, 2024
1 parent a279396 commit b052830
Show file tree
Hide file tree
Showing 27 changed files with 542 additions and 175 deletions.
16 changes: 15 additions & 1 deletion cmd/snap-bootstrap/cmd_initramfs_mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,21 @@ func doInstall(mst *initramfsMountsState, model *asserts.Model, sysSnaps map[sna
}

bootDevice := ""
installedSystem, err := gadgetInstallRun(model, gadgetMountDir, kernelMountDir, bootDevice, options, installObserver, timings.New(nil))
kernelSnapInfo := &gadgetInstall.KernelSnapInfo{
Name: kernelSnap.SnapName(),
MountPoint: kernelMountDir,
Revision: kernelSnap.Revision,
// Should be true always anyway
IsCore: !model.Classic(),
}
switch model.Base() {
case "core20", "core22", "core22-desktop":
kernelSnapInfo.NeedsDriversTree = false
default:
kernelSnapInfo.NeedsDriversTree = true
}

installedSystem, err := gadgetInstallRun(model, gadgetMountDir, kernelSnapInfo, bootDevice, options, installObserver, timings.New(nil))
if err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8150,15 +8150,15 @@ echo '{"features":[]}'
saveKey := keys.EncryptionKey{'s', 'a', 'v', 'e', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}

gadgetInstallCalled := false
restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) {
restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot string, kernelSnapInfo *gadgetInstall.KernelSnapInfo, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) {
gadgetInstallCalled = true
c.Assert(options.Mount, Equals, true)
c.Assert(string(options.EncryptionType), Equals, "cryptsetup")
c.Assert(bootDevice, Equals, "")
c.Assert(model.Classic(), Equals, false)
c.Assert(string(model.Grade()), Equals, "signed")
c.Assert(gadgetRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "gadget"))
c.Assert(kernelRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel"))
c.Assert(kernelSnapInfo.MountPoint, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel"))

keyForRole := map[string]keys.EncryptionKey{
gadget.SystemData: dataKey,
Expand Down Expand Up @@ -8284,15 +8284,15 @@ func (s *initramfsMountsSuite) TestInitramfsMountsInstallAndRunFdeSetupNotPresen
writeGadget(c, "ubuntu-seed", "system-seed", "")

gadgetInstallCalled := false
restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) {
restoreGadgetInstall := main.MockGadgetInstallRun(func(model gadget.Model, gadgetRoot string, kernelSnapInfo *gadgetInstall.KernelSnapInfo, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error) {
gadgetInstallCalled = true
c.Assert(options.Mount, Equals, true)
c.Assert(string(options.EncryptionType), Equals, "")
c.Assert(bootDevice, Equals, "")
c.Assert(model.Classic(), Equals, false)
c.Assert(string(model.Grade()), Equals, "signed")
c.Assert(gadgetRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "gadget"))
c.Assert(kernelRoot, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel"))
c.Assert(kernelSnapInfo.MountPoint, Equals, filepath.Join(boot.InitramfsRunMntDir, "kernel"))
return &gadgetInstall.InstalledSystemSideData{}, nil
})
defer restoreGadgetInstall()
Expand Down
2 changes: 1 addition & 1 deletion cmd/snap-bootstrap/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func MockWaitFile(f func(string, time.Duration, int) error) (restore func()) {

var WaitFile = waitFile

func MockGadgetInstallRun(f func(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error)) (restore func()) {
func MockGadgetInstallRun(f func(model gadget.Model, gadgetRoot string, kernelSnapInfo *gadgetInstall.KernelSnapInfo, bootDevice string, options gadgetInstall.Options, observer gadget.ContentObserver, perfTimings timings.Measurer) (*gadgetInstall.InstalledSystemSideData, error)) (restore func()) {
old := gadgetInstallRun
gadgetInstallRun = f
return func() {
Expand Down
6 changes: 6 additions & 0 deletions dirs/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ func SnapRepairConfigFileUnder(rootdir string) string {
return filepath.Join(rootdir, snappyDir, "repair.json")
}

// SnapKernelTreesDirUnder returns the path to the snap kernel drivers trees
// dir under rootdir.
func SnapKernelDriversTreesDirUnder(rootdir string) string {
return filepath.Join(rootdir, snappyDir, "kernel")
}

// AddRootDirCallback registers a callback for whenever the global root
// directory (set by SetRootDir) is changed to enable updates to variables in
// other packages that depend on its location.
Expand Down
39 changes: 37 additions & 2 deletions gadget/install/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,30 @@ import (
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/kernel"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil/mkfs"
"github.com/snapcore/snapd/snap"
)

var mkfsImpl = mkfs.Make
var (
mkfsImpl = mkfs.Make
kernelEnsureKernelDriversTree = kernel.EnsureKernelDriversTree
)

// KernelSnapInfo includes information from the kernel snap that is
// needed to build a drivers tree. Defin
type KernelSnapInfo struct {
Name string
Revision snap.Revision
// MountPoint is the root of the files from the kernel snap
MountPoint string
// NeedsDriversTree will be set if a drivers tree needs to be
// build on installation
NeedsDriversTree bool
// IsCore is set if this is UC
IsCore bool
}

type mkfsParams struct {
Type string
Expand Down Expand Up @@ -83,7 +102,7 @@ func unmountWithFallbackToLazy(mntPt, operationMsg string) error {
// writeContent populates the given on-disk filesystem structure with a
// corresponding filesystem device, according to the contents defined in the
// gadget.
func writeFilesystemContent(laidOut *gadget.LaidOutStructure, fsDevice string, observer gadget.ContentObserver) (err error) {
func writeFilesystemContent(laidOut *gadget.LaidOutStructure, kSnapInfo *KernelSnapInfo, fsDevice string, observer gadget.ContentObserver) (err error) {
mountpoint := filepath.Join(dirs.SnapRunDir, "gadget-install", strings.ReplaceAll(strings.Trim(fsDevice, "/"), "/", "-"))
if err := os.MkdirAll(mountpoint, 0755); err != nil {
return err
Expand All @@ -110,5 +129,21 @@ func writeFilesystemContent(laidOut *gadget.LaidOutStructure, fsDevice string, o
return fmt.Errorf("cannot create filesystem image: %v", err)
}

// For data partition, build drivers tree if required, so kernel
// drivers are available on first boot of the installed system.
if laidOut.Role() == gadget.SystemData && kSnapInfo != nil && kSnapInfo.NeedsDriversTree {
destRoot := mountpoint
if kSnapInfo.IsCore {
destRoot = filepath.Join(mountpoint, "system-data")
}
destDir := kernel.DriversTreeDir(destRoot, kSnapInfo.Name, kSnapInfo.Revision)
logger.Noticef("building drivers tree in %s", destDir)

if err := kernelEnsureKernelDriversTree(kSnapInfo.MountPoint, destDir, nil,
&kernel.KernelDriversTreeOptions{KernelInstall: true}); err != nil {
return err
}
}

return nil
}
87 changes: 85 additions & 2 deletions gadget/install/content_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ import (
"github.com/snapcore/snapd/gadget/gadgettest"
"github.com/snapcore/snapd/gadget/install"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/kernel"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)

Expand Down Expand Up @@ -88,6 +90,7 @@ func mockOnDiskStructureSystemSeed(gadgetRoot string) *gadget.LaidOutStructure {
return &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "vfat",
Role: gadget.SystemSeed,
Content: []gadget.VolumeContent{
{
UnresolvedSource: "grubx64.efi",
Expand All @@ -108,6 +111,16 @@ func mockOnDiskStructureSystemSeed(gadgetRoot string) *gadget.LaidOutStructure {
}
}

func mockOnDiskStructureSystemData() *gadget.LaidOutStructure {
return &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "ext4",
Role: gadget.SystemData,
YamlIndex: 1000, // to demonstrate we do not use the laid out index
},
}
}

const gadgetContent = `volumes:
pc:
bootloader: grub
Expand Down Expand Up @@ -211,7 +224,7 @@ func (s *contentTestSuite) TestWriteFilesystemContent(c *C) {
observeErr: tc.observeErr,
expectedRole: m.Role(),
}
err := install.WriteFilesystemContent(m, "/dev/node2", obs)
err := install.WriteFilesystemContent(m, nil, "/dev/node2", obs)
if tc.err == "" {
c.Assert(err, IsNil)
} else {
Expand All @@ -235,6 +248,76 @@ func (s *contentTestSuite) TestWriteFilesystemContent(c *C) {
}
}

func (s *contentTestSuite) testWriteFilesystemContentDriversTree(c *C, isCore bool) {
defer dirs.SetRootDir(dirs.GlobalRootDir)
dirs.SetRootDir(c.MkDir())

dataMntPoint := filepath.Join(dirs.SnapRunDir, "gadget-install/dev-node2")
restore := install.MockSysMount(func(source, target, fstype string, flags uintptr, data string) error {
c.Check(source, Equals, "/dev/node2")
c.Check(fstype, Equals, "ext4")
c.Check(target, Equals, filepath.Join(dirs.SnapRunDir, "gadget-install/dev-node2"))
return nil
})
defer restore()

restore = install.MockSysUnmount(func(target string, flags int) error {
return nil
})
defer restore()

// copy existing mock
m := mockOnDiskStructureSystemData()
obs := &mockWriteObserver{
c: c,
observeErr: nil,
expectedRole: m.Role(),
}
// mock drivers tree
treesDir := dirs.SnapKernelDriversTreesDirUnder(dirs.GlobalRootDir)
modsSubDir := "pc-kernel/111/lib/modules/6.8.0-31-generic"
modsDir := filepath.Join(treesDir, modsSubDir)
c.Assert(os.MkdirAll(modsDir, 0755), IsNil)
someFile := filepath.Join(modsDir, "modules.alias")
c.Assert(os.WriteFile(someFile, []byte("blah"), 0644), IsNil)

kMntPoint := filepath.Join(dirs.GlobalRootDir, "snap/pc-kernel/111")
kInfo := &install.KernelSnapInfo{
Name: "pc-kernel",
Revision: snap.R(111),
MountPoint: kMntPoint,
NeedsDriversTree: true,
IsCore: isCore,
}

restore = install.MockKernelEnsureKernelDriversTree(func(kSnapRoot, destDir string, kmodsConts []snap.ContainerPlaceInfo, opts *kernel.KernelDriversTreeOptions) (err error) {
c.Check(kSnapRoot, Equals, kMntPoint)
if isCore {
c.Check(destDir, Equals, filepath.Join(dataMntPoint,
"system-data/var/lib/snapd/kernel/pc-kernel/111"))
} else {
c.Check(destDir, Equals, filepath.Join(dataMntPoint,
"var/lib/snapd/kernel/pc-kernel/111"))
}
return nil
})
defer restore()

err := install.WriteFilesystemContent(m, kInfo, "/dev/node2", obs)
c.Assert(err, IsNil)

}

func (s *contentTestSuite) TestWriteFilesystemContentDriversTreeCore(c *C) {
isCore := true
s.testWriteFilesystemContentDriversTree(c, isCore)
}

func (s *contentTestSuite) TestWriteFilesystemContentDriversTreeHybrid(c *C) {
isCore := false
s.testWriteFilesystemContentDriversTree(c, isCore)
}

func (s *contentTestSuite) TestWriteFilesystemContentUnmountErrHandling(c *C) {
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir(dirs.GlobalRootDir)
Expand Down Expand Up @@ -295,7 +378,7 @@ func (s *contentTestSuite) TestWriteFilesystemContentUnmountErrHandling(c *C) {
})
defer restore()

err := install.WriteFilesystemContent(m, "/dev/node2", obs)
err := install.WriteFilesystemContent(m, nil, "/dev/node2", obs)
if tc.expectedErr == "" {
c.Assert(err, IsNil)
} else {
Expand Down
10 changes: 10 additions & 0 deletions gadget/install/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (

"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/kernel"
"github.com/snapcore/snapd/snap"
)

type MkfsParams = mkfsParams
Expand Down Expand Up @@ -74,6 +76,14 @@ func MockMkfsMake(f func(typ, img, label string, devSize, sectorSize quantity.Si
}
}

func MockKernelEnsureKernelDriversTree(f func(kSnapRoot, destDir string, kmodsConts []snap.ContainerPlaceInfo, opts *kernel.KernelDriversTreeOptions) (err error)) (restore func()) {
old := kernelEnsureKernelDriversTree
kernelEnsureKernelDriversTree = f
return func() {
kernelEnsureKernelDriversTree = old
}
}

func CheckEncryptionSetupData(encryptSetup *EncryptionSetupData, labelToEncDevice map[string]string) error {
for label, part := range encryptSetup.parts {
switch part.role {
Expand Down
Loading

0 comments on commit b052830

Please sign in to comment.