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 <xyang@ciq.com>
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
xu yang
2024-04-16 10:55:23 -06:00
committed by Jonathon Anderson
parent 892db0f089
commit a322061fe9
6 changed files with 144 additions and 6 deletions

View File

@@ -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

View File

@@ -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")
}

View File

@@ -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)))
}
}
}

View File

@@ -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"
}
}

View File

@@ -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)

View File

@@ -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)