From a322061fe9345eb43766b135bb68e02c1a957b84 Mon Sep 17 00:00:00 2001 From: xu yang Date: Tue, 16 Apr 2024 10:55:23 -0600 Subject: [PATCH] Add initramfs stage to warewulfd The initramfs stage supports serving an initramfs image from within the container image, typically generated by dracut. Signed-off-by: xu yang Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 1 + internal/pkg/container/config.go | 27 ++++++++ internal/pkg/container/config_test.go | 88 ++++++++++++++++++++++++ internal/pkg/warewulfd/parser.go | 2 + internal/pkg/warewulfd/provision.go | 26 +++++-- internal/pkg/warewulfd/provision_test.go | 6 ++ 6 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 internal/pkg/container/config_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index bea5495a0..1e4effd50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Add examples for building overlays in parallel to documentation +- Add `stage=initramfs` to warewulfd provision to serve initramfs from container image. #1115 ### Changed diff --git a/internal/pkg/container/config.go b/internal/pkg/container/config.go index 350cef247..902827eaf 100644 --- a/internal/pkg/container/config.go +++ b/internal/pkg/container/config.go @@ -1,11 +1,25 @@ package container import ( + "fmt" "path" + "github.com/warewulf/warewulf/internal/pkg/util" + "github.com/warewulf/warewulf/internal/pkg/wwlog" + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" ) +var ( + initramfsSearchPaths = []string{ + // This is a printf format where the %s will be the kernel version + "boot/initramfs-%s", + "boot/initramfs-%s.img", + "boot/initrd-%s", + "boot/initrd-%s.img", + } +) + func SourceParentDir() string { conf := warewulfconf.Get() return conf.Paths.WWChrootdir @@ -27,3 +41,16 @@ func ImageParentDir() string { func ImageFile(name string) string { return path.Join(ImageParentDir(), name+".img") } + +// InitramfsBootPath returns the dracut built initramfs path, as dracut built initramfs inside container +// the function returns host path of the built file +func InitramfsBootPath(image, kver string) (string, error) { + for _, searchPath := range initramfsSearchPaths { + initramfs_path := path.Join(RootFsDir(image), fmt.Sprintf(searchPath, kver)) + wwlog.Debug("Looking for initramfs at: %s", initramfs_path) + if util.IsFile(initramfs_path) { + return initramfs_path, nil + } + } + return "", fmt.Errorf("Failed to find a target kernel version initramfs") +} diff --git a/internal/pkg/container/config_test.go b/internal/pkg/container/config_test.go new file mode 100644 index 000000000..904bc6b76 --- /dev/null +++ b/internal/pkg/container/config_test.go @@ -0,0 +1,88 @@ +package container + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" +) + +func TestInitramfsBootPath(t *testing.T) { + conf := warewulfconf.Get() + temp, err := os.MkdirTemp(os.TempDir(), "ww-conf-*") + assert.NoError(t, err) + defer os.RemoveAll(temp) + conf.Paths.WWChrootdir = temp + + assert.NoError(t, os.MkdirAll(filepath.Join(RootFsDir("image"), "boot"), 0700)) + + tests := []struct { + name string + initramfs []string + ver string + err error + retName string + }{ + { + name: "ok case 1", + initramfs: []string{"initramfs-1.1.1.aarch64.img"}, + ver: "1.1.1.aarch64", + err: nil, + }, + { + name: "ok case 2", + initramfs: []string{"initrd-1.1.1.aarch64"}, + ver: "1.1.1.aarch64", + err: nil, + }, + { + name: "ok case 3", + initramfs: []string{"initramfs-1.1.1.aarch64"}, + ver: "1.1.1.aarch64", + err: nil, + }, + { + name: "ok case 4", + initramfs: []string{"initrd-1.1.1.aarch64.img"}, + ver: "1.1.1.aarch64", + err: nil, + }, + { + name: "error case, wrong init name", + initramfs: []string{"initrr-1.1.1.aarch64.img"}, + ver: "1.1.1.aarch64", + err: fmt.Errorf("Failed to find a target kernel version initramfs"), + }, + { + name: "error case, wrong ver", + initramfs: []string{"initrr-1.1.1.aarch64.img"}, + ver: "1.1.2.aarch64", + err: fmt.Errorf("Failed to find a target kernel version initramfs"), + }, + } + + for _, tt := range tests { + t.Logf("running test: %s", tt.name) + for _, init := range tt.initramfs { + assert.NoError(t, os.WriteFile(filepath.Join(RootFsDir("image"), "boot", init), []byte(""), 0600)) + } + initPath, err := InitramfsBootPath("image", tt.ver) + assert.Equal(t, tt.err, err) + if err == nil { + assert.NotEmpty(t, initPath) + } else { + assert.Empty(t, initPath) + } + + if tt.retName != "" { + assert.Equal(t, filepath.Base(initPath), tt.retName) + } + // remove the file + for _, init := range tt.initramfs { + assert.NoError(t, os.Remove(filepath.Join(RootFsDir("image"), "boot", init))) + } + } +} diff --git a/internal/pkg/warewulfd/parser.go b/internal/pkg/warewulfd/parser.go index b91b9cdc0..e542982f8 100644 --- a/internal/pkg/warewulfd/parser.go +++ b/internal/pkg/warewulfd/parser.go @@ -73,6 +73,8 @@ func parseReq(req *http.Request) (parserInfo, error) { ret.stage = "runtime" } else if stage == "efiboot" { ret.stage = "efiboot" + } else if stage == "initramfs" { + ret.stage = "initramfs" } } diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index 0d544d69a..8861cd999 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -56,12 +56,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } status_stages := map[string]string{ - "efiboot": "EFI", - "ipxe": "IPXE", - "kernel": "KERNEL", - "kmods": "KMODS_OVERLAY", - "system": "SYSTEM_OVERLAY", - "runtime": "RUNTIME_OVERLAY"} + "efiboot": "EFI", + "ipxe": "IPXE", + "kernel": "KERNEL", + "kmods": "KMODS_OVERLAY", + "system": "SYSTEM_OVERLAY", + "runtime": "RUNTIME_OVERLAY", + "initramfs": "INITRAMFS"} status_stage := status_stages[rinfo.stage] var stage_file string @@ -213,6 +214,19 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } else { wwlog.Warn("No conainer set for node %s", node.Id.Get()) } + } else if rinfo.stage == "initramfs" { + if node.ContainerName.Defined() { + _, kver, err := kernel.FindKernel(container.RootFsDir(node.ContainerName.Get())) + if err != nil { + wwlog.Error("No kernel found for initramfs for container %s: %s", node.ContainerName.Get(), err) + } + stage_file, err = container.InitramfsBootPath(node.ContainerName.Get(), kver) + if err != nil { + wwlog.Error("No initramfs found for container %s: %s", node.ContainerName.Get(), err) + } + } else { + wwlog.Warn("No container set for node %s", node.Id.Get()) + } } wwlog.Serv("stage_file '%s'", stage_file) diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go index 1b1446a90..27ad6d339 100644 --- a/internal/pkg/warewulfd/provision_test.go +++ b/internal/pkg/warewulfd/provision_test.go @@ -30,6 +30,7 @@ var provisionSendTests = []struct { {"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"}, {"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"}, {"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"}, + {"find initramfs", "/provision/00:00:00:ff:ff:ff?stage=initramfs", "", 200, "10.10.10.10:9873"}, } func Test_ProvisionSend(t *testing.T) { @@ -84,6 +85,11 @@ nodes: _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi")) assert.NoError(t, err) } + assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/boot"), 0700)) + { + _, err := os.Create(path.Join(containerDir, "suse/rootfs/boot", "initramfs-.img")) + assert.NoError(t, err) + } dbErr := LoadNodeDB() assert.NoError(t, dbErr)