Merge pull request #1191 from anderbubble/dracut-initramfs-boot
Dracut initramfs boot
This commit is contained in:
@@ -38,11 +38,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add examples for building overlays in parallel to documentation
|
- Add examples for building overlays in parallel to documentation
|
||||||
|
- Add `stage=initramfs` to warewulfd provision to serve initramfs from container image. #1115
|
||||||
|
- Add `warewulf-dracut` package to support building Warewulf-compatible initramfs images with dracut. #1115
|
||||||
|
- Add iPXE template `dracut.ipxe` to boot a dracut initramfs. #1115
|
||||||
|
- Add dracut menuentry to `grub.cfg.ww` to boot a dracut initramfs. #1115
|
||||||
|
- Add `.NetDevs` variable to iPXE and GRUB templates, similar to overlay templates. #1115
|
||||||
|
- Add `.Tags` variable to iPXE and GRUB templates, similar to overlay templates. #1115
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Replace reference to docusaurus with Sphinx
|
- Replace reference to docusaurus with Sphinx
|
||||||
- `wwctl container import` now only runs syncuser if explicitly requested. #1212
|
- `wwctl container import` now only runs syncuser if explicitly requested. #1212
|
||||||
|
- wwinit now configures NetworkManager to not retain configurations from dracut. #1115
|
||||||
|
- Improved detection of SELinux capable root fs #1093
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -138,6 +138,8 @@ install: build docs
|
|||||||
install -m 0644 etc/bash_completion.d/wwctl $(DESTDIR)$(BASHCOMPDIR)/wwctl
|
install -m 0644 etc/bash_completion.d/wwctl $(DESTDIR)$(BASHCOMPDIR)/wwctl
|
||||||
for f in docs/man/man1/*.1.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man1/; done
|
for f in docs/man/man1/*.1.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man1/; done
|
||||||
for f in docs/man/man5/*.5.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man5/; done
|
for f in docs/man/man5/*.5.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man5/; done
|
||||||
|
install -pd -m 0755 $(DESTDIR)$(DRACUTMODDIR)/90wwinit
|
||||||
|
install -m 0644 dracut/modules.d/90wwinit/*.sh $(DESTDIR)$(DRACUTMODDIR)/90wwinit
|
||||||
|
|
||||||
.PHONY: installapi
|
.PHONY: installapi
|
||||||
installapi:
|
installapi:
|
||||||
|
|||||||
@@ -44,10 +44,11 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# OS-Specific Service Locations
|
# OS-Specific Service Locations
|
||||||
VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR
|
VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR DRACUTMODDIR
|
||||||
SYSTEMDDIR ?= /usr/lib/systemd/system
|
SYSTEMDDIR ?= /usr/lib/systemd/system
|
||||||
BASHCOMPDIR ?= /etc/bash_completion.d
|
BASHCOMPDIR ?= /etc/bash_completion.d
|
||||||
FIREWALLDDIR ?= /usr/lib/firewalld/services
|
FIREWALLDDIR ?= /usr/lib/firewalld/services
|
||||||
|
DRACUTMODDIR ?= /usr/lib/dracut/modules.d
|
||||||
ifeq ($(OS),suse)
|
ifeq ($(OS),suse)
|
||||||
TFTPDIR ?= /srv/tftpboot
|
TFTPDIR ?= /srv/tftpboot
|
||||||
endif
|
endif
|
||||||
|
|||||||
13
dracut/modules.d/90wwinit/load-wwinit.sh
Executable file
13
dracut/modules.d/90wwinit/load-wwinit.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
info "Mounting tmpfs at $NEWROOT"
|
||||||
|
mount -t tmpfs ${wwinit_tmpfs_size_option} tmpfs "$NEWROOT"
|
||||||
|
|
||||||
|
for archive in "${wwinit_container}" "${wwinit_kmods}" "${wwinit_system}" "${wwinit_runtime}"
|
||||||
|
do
|
||||||
|
if [ -n "${archive}" ]
|
||||||
|
then
|
||||||
|
info "Loading ${archive}"
|
||||||
|
(curl --silent -L "${archive}" | gzip -d | cpio -im --directory="${NEWROOT}") || die "Unable to load ${archive}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
20
dracut/modules.d/90wwinit/module-setup.sh
Executable file
20
dracut/modules.d/90wwinit/module-setup.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
check() {
|
||||||
|
# Don't include in hostonly mode
|
||||||
|
[[ $hostonly ]] && return 1
|
||||||
|
|
||||||
|
# Don't include by default
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
|
||||||
|
depends() {
|
||||||
|
echo network
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
install() {
|
||||||
|
inst_multiple cpio curl
|
||||||
|
inst_hook cmdline 30 "$moddir/parse-wwinit.sh"
|
||||||
|
inst_hook pre-mount 30 "$moddir/load-wwinit.sh"
|
||||||
|
}
|
||||||
28
dracut/modules.d/90wwinit/parse-wwinit.sh
Executable file
28
dracut/modules.d/90wwinit/parse-wwinit.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# root=wwinit
|
||||||
|
|
||||||
|
[ -z "$root" ] && root=$(getarg root=)
|
||||||
|
|
||||||
|
if [ "${root}" = "wwinit" ]
|
||||||
|
then
|
||||||
|
info "root=${root}"
|
||||||
|
export wwinit_container=$(getarg wwinit.container=); info "wwinit.container=${wwinit_container}"
|
||||||
|
export wwinit_system=$(getarg wwinit.system=); info "wwinit.system=${wwinit_system}"
|
||||||
|
export wwinit_runtime=$(getarg wwinit.runtime=); info "wwinit.runtime=${wwinit_runtime}"
|
||||||
|
export wwinit_kmods=$(getarg wwinit.kmods=); info "wwinit.kmods=${wwinit_kmods}"
|
||||||
|
|
||||||
|
wwinit_tmpfs_size=$(getarg wwinit.tmpfs.size=)
|
||||||
|
if [ -n "$wwinit_tmpfs_size" ]
|
||||||
|
then
|
||||||
|
info "wwinit.tmpfs.size=${wwinit_tmpfs_size}"
|
||||||
|
export wwinit_tmpfs_size_option="-o size=${wwinit_tmpfs_size}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${wwinit_container}" ]
|
||||||
|
then
|
||||||
|
info "Found root=${root} and a Warewulf container image. Will boot from Warewulf."
|
||||||
|
rootok=1
|
||||||
|
else
|
||||||
|
die "Found root=${root} but no container image. Cannot boot from Warewulf."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@@ -6,13 +6,16 @@ echo "Warewulf Controller: {{.Ipaddr}}"
|
|||||||
echo
|
echo
|
||||||
sleep 1
|
sleep 1
|
||||||
smbios --type1 --get-string 8 --set assetkey
|
smbios --type1 --get-string 8 --set assetkey
|
||||||
|
|
||||||
uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}"
|
uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}"
|
||||||
kernel="${uri}&stage=kernel"
|
kernel="${uri}&stage=kernel"
|
||||||
container="${uri}&stage=container&compress=gz"
|
container="${uri}&stage=container&compress=gz"
|
||||||
system="${uri}&stage=system&compress=gz"
|
system="${uri}&stage=system&compress=gz"
|
||||||
runtime="${uri}&stage=runtime&compress=gz"
|
runtime="${uri}&stage=runtime&compress=gz"
|
||||||
set default=ww4
|
|
||||||
|
set default={{ or .Tags.GrubMenuEntry "ww4" }}
|
||||||
set timeout=5
|
set timeout=5
|
||||||
|
|
||||||
menuentry "Network boot node: {{.Id}}" --id ww4 {
|
menuentry "Network boot node: {{.Id}}" --id ww4 {
|
||||||
{{if .KernelOverride }}
|
{{if .KernelOverride }}
|
||||||
echo "Kernel: {{.KernelOverride}}"
|
echo "Kernel: {{.KernelOverride}}"
|
||||||
@@ -34,17 +37,54 @@ menuentry "Network boot node: {{.Id}}" --id ww4 {
|
|||||||
reboot
|
reboot
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menuentry "Network boot node with dracut: {{.Id}}" --id dracut {
|
||||||
|
initramfs="${uri}&stage=initramfs"
|
||||||
|
|
||||||
|
uri="http://{{.Ipaddr}}:{{.Port}}/provision/${net_default_mac}?assetkey=${assetkey}"
|
||||||
|
container="${uri}&stage=container&compress=gz"
|
||||||
|
system="${uri}&stage=system&compress=gz"
|
||||||
|
runtime="${uri}&stage=runtime&compress=gz"
|
||||||
|
|
||||||
|
{{if .KernelOverride }}
|
||||||
|
echo "Kernel: {{.KernelOverride}}"
|
||||||
|
{{else}}
|
||||||
|
echo "Kernel: {{.ContainerName}} (container default)"
|
||||||
|
{{end}}
|
||||||
|
echo "KernelArgs: {{.KernelArgs}}"
|
||||||
|
|
||||||
|
net_args="rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}"
|
||||||
|
wwinit_args="root=wwinit wwinit.container=${container} wwinit.system=${system} wwinit.runtime=${runtime} init=/init"
|
||||||
|
linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args
|
||||||
|
|
||||||
|
if [ x$? = x0 ] ; then
|
||||||
|
echo "Loading Container: {{.ContainerName}}"
|
||||||
|
initrd $initramfs
|
||||||
|
boot
|
||||||
|
else
|
||||||
|
echo "MESSAGE: This node seems to be unconfigured. Please have your system administrator add a"
|
||||||
|
echo " configuration for this node with HW address: ${net_default_mac}"
|
||||||
|
echo ""
|
||||||
|
echo "Rebooting in 1 minute..."
|
||||||
|
sleep 60
|
||||||
|
reboot
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
menuentry "Chainload specific configfile" {
|
menuentry "Chainload specific configfile" {
|
||||||
conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg"
|
conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg"
|
||||||
configfile $conf
|
configfile $conf
|
||||||
}
|
}
|
||||||
|
|
||||||
menuentry "UEFI Firmware Settings" --id "uefi-firmware" {
|
menuentry "UEFI Firmware Settings" --id "uefi-firmware" {
|
||||||
fwsetup
|
fwsetup
|
||||||
}
|
}
|
||||||
|
|
||||||
menuentry "System restart" {
|
menuentry "System restart" {
|
||||||
echo "System rebooting..."
|
echo "System rebooting..."
|
||||||
reboot
|
reboot
|
||||||
}
|
}
|
||||||
|
|
||||||
menuentry "System shutdown" {
|
menuentry "System shutdown" {
|
||||||
echo "System shutting down..."
|
echo "System shutting down..."
|
||||||
halt
|
halt
|
||||||
|
|||||||
46
etc/ipxe/dracut.ipxe
Normal file
46
etc/ipxe/dracut.ipxe
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#!ipxe
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo ================================================================================
|
||||||
|
echo Warewulf v4 now booting via dracut: {{.Fqdn}} ({{.Hwaddr}})
|
||||||
|
echo
|
||||||
|
echo Container: {{.ContainerName}}
|
||||||
|
{{if .KernelOverride }}
|
||||||
|
echo Kernel: {{.KernelOverride}}
|
||||||
|
{{else}}
|
||||||
|
echo Kernel: {{.ContainerName}} (container default)
|
||||||
|
{{end}}
|
||||||
|
echo KernelArgs: {{.KernelArgs}}
|
||||||
|
echo
|
||||||
|
|
||||||
|
set uri http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid}
|
||||||
|
echo Warewulf Controller: {{.Ipaddr}}
|
||||||
|
|
||||||
|
echo Downloading Kernel Image:
|
||||||
|
kernel --name kernel ${uri}&stage=kernel || goto reboot
|
||||||
|
|
||||||
|
{{if ne .KernelOverride ""}}
|
||||||
|
echo Downloading Kernel Modules:
|
||||||
|
imgextract --name kmods ${uri}&stage=kmods&compress=gz || initrd --name kmods ${uri}&stage=kmods || goto reboot
|
||||||
|
set kernel_mods initrd=kmods
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
echo Downloading initramfs
|
||||||
|
initrd --name initramfs ${uri}&stage=initramfs || goto reboot
|
||||||
|
|
||||||
|
set dracut_net rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}
|
||||||
|
set dracut_wwinit root=wwinit wwinit.container=${uri}&stage=container&compress=gz wwinit.system=${uri}&stage=system&compress=gz wwinit.runtime=${uri}&stage=runtime&compress=gz {{if ne .KernelOverride ""}}wwinit.kmods=${uri}&stage=kmods&compress=gz{{end}} init=/init
|
||||||
|
|
||||||
|
echo Booting initramfs
|
||||||
|
#echo Network KernelArgs: ${dracut_net}
|
||||||
|
#echo Dracut wwinit KernelArgs: ${dracut_wwinit}
|
||||||
|
#sleep 15
|
||||||
|
boot kernel initrd=initramfs ${kernel_mods} ${dracut_net} ${dracut_wwinit} wwid={{.Hwaddr}} {{.KernelArgs}}
|
||||||
|
|
||||||
|
|
||||||
|
:reboot
|
||||||
|
echo
|
||||||
|
echo There was an error, rebooting in 15s...
|
||||||
|
echo
|
||||||
|
sleep 15
|
||||||
|
reboot
|
||||||
@@ -1,11 +1,25 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||||
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
|
|
||||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
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 {
|
func SourceParentDir() string {
|
||||||
conf := warewulfconf.Get()
|
conf := warewulfconf.Get()
|
||||||
return conf.Paths.WWChrootdir
|
return conf.Paths.WWChrootdir
|
||||||
@@ -27,3 +41,16 @@ func ImageParentDir() string {
|
|||||||
func ImageFile(name string) string {
|
func ImageFile(name string) string {
|
||||||
return path.Join(ImageParentDir(), name+".img")
|
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")
|
||||||
|
}
|
||||||
|
|||||||
88
internal/pkg/container/config_test.go
Normal file
88
internal/pkg/container/config_test.go
Normal 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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,8 @@ func parseReq(req *http.Request) (parserInfo, error) {
|
|||||||
ret.stage = "runtime"
|
ret.stage = "runtime"
|
||||||
} else if stage == "efiboot" {
|
} else if stage == "efiboot" {
|
||||||
ret.stage = "efiboot"
|
ret.stage = "efiboot"
|
||||||
|
} else if stage == "initramfs" {
|
||||||
|
ret.stage = "initramfs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/container"
|
"github.com/warewulf/warewulf/internal/pkg/container"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
||||||
|
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
"github.com/warewulf/warewulf/internal/pkg/overlay"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/util"
|
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
@@ -33,6 +34,8 @@ type templateVars struct {
|
|||||||
Port string
|
Port string
|
||||||
KernelArgs string
|
KernelArgs string
|
||||||
KernelOverride string
|
KernelOverride string
|
||||||
|
Tags map[string]string
|
||||||
|
NetDevs map[string]*node.NetDevs
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
||||||
@@ -56,12 +59,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status_stages := map[string]string{
|
status_stages := map[string]string{
|
||||||
"efiboot": "EFI",
|
"efiboot": "EFI",
|
||||||
"ipxe": "IPXE",
|
"ipxe": "IPXE",
|
||||||
"kernel": "KERNEL",
|
"kernel": "KERNEL",
|
||||||
"kmods": "KMODS_OVERLAY",
|
"kmods": "KMODS_OVERLAY",
|
||||||
"system": "SYSTEM_OVERLAY",
|
"system": "SYSTEM_OVERLAY",
|
||||||
"runtime": "RUNTIME_OVERLAY"}
|
"runtime": "RUNTIME_OVERLAY",
|
||||||
|
"initramfs": "INITRAMFS"}
|
||||||
|
|
||||||
status_stage := status_stages[rinfo.stage]
|
status_stage := status_stages[rinfo.stage]
|
||||||
var stage_file string
|
var stage_file string
|
||||||
@@ -93,6 +97,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
} else if rinfo.stage == "ipxe" {
|
} else if rinfo.stage == "ipxe" {
|
||||||
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe")
|
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe")
|
||||||
|
tstruct := overlay.InitStruct(&node)
|
||||||
tmpl_data = templateVars{
|
tmpl_data = templateVars{
|
||||||
Id: node.Id.Get(),
|
Id: node.Id.Get(),
|
||||||
Cluster: node.ClusterName.Get(),
|
Cluster: node.ClusterName.Get(),
|
||||||
@@ -103,7 +108,9 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
Hwaddr: rinfo.hwaddr,
|
Hwaddr: rinfo.hwaddr,
|
||||||
ContainerName: node.ContainerName.Get(),
|
ContainerName: node.ContainerName.Get(),
|
||||||
KernelArgs: node.Kernel.Args.Get(),
|
KernelArgs: node.Kernel.Args.Get(),
|
||||||
KernelOverride: node.Kernel.Override.Get()}
|
KernelOverride: node.Kernel.Override.Get(),
|
||||||
|
NetDevs: tstruct.NetDevs,
|
||||||
|
Tags: tstruct.Tags}
|
||||||
} else if rinfo.stage == "kernel" {
|
} else if rinfo.stage == "kernel" {
|
||||||
if node.Kernel.Override.Defined() {
|
if node.Kernel.Override.Defined() {
|
||||||
stage_file = kernel.KernelImage(node.Kernel.Override.Get())
|
stage_file = kernel.KernelImage(node.Kernel.Override.Get())
|
||||||
@@ -175,6 +182,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
case "grub.cfg":
|
case "grub.cfg":
|
||||||
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww")
|
stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww")
|
||||||
|
tstruct := overlay.InitStruct(&node)
|
||||||
tmpl_data = templateVars{
|
tmpl_data = templateVars{
|
||||||
Id: node.Id.Get(),
|
Id: node.Id.Get(),
|
||||||
Cluster: node.ClusterName.Get(),
|
Cluster: node.ClusterName.Get(),
|
||||||
@@ -185,7 +193,9 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
Hwaddr: rinfo.hwaddr,
|
Hwaddr: rinfo.hwaddr,
|
||||||
ContainerName: node.ContainerName.Get(),
|
ContainerName: node.ContainerName.Get(),
|
||||||
KernelArgs: node.Kernel.Args.Get(),
|
KernelArgs: node.Kernel.Args.Get(),
|
||||||
KernelOverride: node.Kernel.Override.Get()}
|
KernelOverride: node.Kernel.Override.Get(),
|
||||||
|
NetDevs: tstruct.NetDevs,
|
||||||
|
Tags: tstruct.Tags}
|
||||||
if stage_file == "" {
|
if stage_file == "" {
|
||||||
wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName)
|
wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
@@ -213,6 +223,19 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
wwlog.Warn("No conainer set for node %s", node.Id.Get())
|
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)
|
wwlog.Serv("stage_file '%s'", stage_file)
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ var provisionSendTests = []struct {
|
|||||||
{"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"},
|
{"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", "", 200, "10.10.10.10:9873"},
|
||||||
{"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11: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"},
|
||||||
|
{"ipxe test with NetDevs and KernelOverrides", "/provision/00:00:00:00:00:ff?stage=ipxe", "1.1.1 ifname=net:00:00:00:00:00:ff ", 200, "10.10.10.12:9873"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ProvisionSend(t *testing.T) {
|
func Test_ProvisionSend(t *testing.T) {
|
||||||
@@ -50,7 +52,15 @@ nodes:
|
|||||||
network devices:
|
network devices:
|
||||||
default:
|
default:
|
||||||
hwaddr: 00:00:00:00:ff:ff
|
hwaddr: 00:00:00:00:ff:ff
|
||||||
container name: none`)
|
container name: none
|
||||||
|
n3:
|
||||||
|
network devices:
|
||||||
|
default:
|
||||||
|
hwaddr: 00:00:00:00:00:ff
|
||||||
|
device: net
|
||||||
|
ipxe template: test
|
||||||
|
kernel:
|
||||||
|
override: 1.1.1`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
assert.NoError(t, conf_file.Sync())
|
assert.NoError(t, conf_file.Sync())
|
||||||
@@ -63,7 +73,8 @@ nodes:
|
|||||||
{
|
{
|
||||||
_, err := arp_file.WriteString(`IP address HW type Flags HW address Mask Device
|
_, err := arp_file.WriteString(`IP address HW type Flags HW address Mask Device
|
||||||
10.10.10.10 0x1 0x2 00:00:00:ff:ff:ff * dummy
|
10.10.10.10 0x1 0x2 00:00:00:ff:ff:ff * dummy
|
||||||
10.10.10.11 0x1 0x2 00:00:00:00:ff:ff * dummy`)
|
10.10.10.11 0x1 0x2 00:00:00:00:ff:ff * dummy
|
||||||
|
10.10.10.12 0x1 0x2 00:00:00:00:00:ff * dummy`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
assert.NoError(t, arp_file.Sync())
|
assert.NoError(t, arp_file.Sync())
|
||||||
@@ -74,6 +85,12 @@ nodes:
|
|||||||
assert.NoError(t, imageDirErr)
|
assert.NoError(t, imageDirErr)
|
||||||
defer os.RemoveAll(containerDir)
|
defer os.RemoveAll(containerDir)
|
||||||
conf.Paths.WWChrootdir = containerDir
|
conf.Paths.WWChrootdir = containerDir
|
||||||
|
|
||||||
|
sysConfDir, sysConfDirErr := os.MkdirTemp(os.TempDir(), "ww-test-sysconf-*")
|
||||||
|
assert.NoError(t, sysConfDirErr)
|
||||||
|
defer os.RemoveAll(sysConfDir)
|
||||||
|
conf.Paths.Sysconfdir = sysConfDir
|
||||||
|
|
||||||
assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/lib64/efi"), 0700))
|
assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/lib64/efi"), 0700))
|
||||||
{
|
{
|
||||||
_, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi"))
|
_, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi"))
|
||||||
@@ -84,6 +101,15 @@ nodes:
|
|||||||
_, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi"))
|
_, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi"))
|
||||||
assert.NoError(t, err)
|
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)
|
||||||
|
}
|
||||||
|
assert.NoError(t, os.MkdirAll(path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe"), 0700))
|
||||||
|
{
|
||||||
|
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe", "test.ipxe"), []byte("{{.KernelOverride}}{{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}"), 0600))
|
||||||
|
}
|
||||||
|
|
||||||
dbErr := LoadNodeDB()
|
dbErr := LoadNodeDB()
|
||||||
assert.NoError(t, dbErr)
|
assert.NoError(t, dbErr)
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[device]
|
||||||
|
keep-configuration=no
|
||||||
@@ -2,11 +2,6 @@
|
|||||||
|
|
||||||
. /warewulf/config
|
. /warewulf/config
|
||||||
|
|
||||||
if test -z "$WWROOT"; then
|
|
||||||
echo "Skipping SELinux configuration: Warewulf Root device not set"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test -f "/etc/sysconfig/selinux"; then
|
if test -f "/etc/sysconfig/selinux"; then
|
||||||
. /etc/sysconfig/selinux
|
. /etc/sysconfig/selinux
|
||||||
else
|
else
|
||||||
@@ -14,22 +9,25 @@ else
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test "$WWROOT" == "initramfs"; then
|
if test "$SELINUX" == "disabled"; then
|
||||||
echo "Skipping SELinux configuration: 'Root=initramfs'"
|
echo "Skipping SELinux setup per /etc/sysconfig/selinux"
|
||||||
if test "$SELINUX" != "disabled"; then
|
|
||||||
echo "WARNING: SELinux prep is being skipped, but SELinux is enabled on host! This may"
|
|
||||||
echo "WARNING: cause the system to not work properly. Try setting 'Root=tmpfs'"
|
|
||||||
sleep 5
|
|
||||||
fi
|
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test "$SELINUX" == "disabled"; then
|
if grep -q "selinux=0" /proc/cmdline; then
|
||||||
echo "Skipping SELinux setup per /etc/sysconfig/selinux"
|
|
||||||
elif grep -q "selinux=0" /proc/cmdline; then
|
|
||||||
echo "Skipping SELinux setup per kernel command line"
|
echo "Skipping SELinux setup per kernel command line"
|
||||||
else
|
exit
|
||||||
echo "Setting up SELinux"
|
|
||||||
/sbin/load_policy -i
|
|
||||||
/sbin/restorecon -r /
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ $(findmnt / --noheadings --output SOURCE) == 'rootfs' ]; then
|
||||||
|
echo "Skipping SELinux configuration: rootfs does not support SELinux contexts"
|
||||||
|
echo
|
||||||
|
echo "WARNING: SELinux prep is being skipped, but SELinux is enabled on host! This may"
|
||||||
|
echo "WARNING: cause the system to not work properly. Try setting 'Root=tmpfs'"
|
||||||
|
sleep 5
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Setting up SELinux"
|
||||||
|
/sbin/load_policy -i
|
||||||
|
/sbin/restorecon -e /sys -r /
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ Boot Management
|
|||||||
Warewulf uses iPXE to for network boot by default. As a tech preview, support
|
Warewulf uses iPXE to for network boot by default. As a tech preview, support
|
||||||
for GRUB is also available, which adds support for secure boot.
|
for GRUB is also available, which adds support for secure boot.
|
||||||
|
|
||||||
|
Also as a tech preview, Warewulf may also use iPXE to boot a dracut
|
||||||
|
initramfs as an initial stage before loading the container image.
|
||||||
|
|
||||||
Booting with iPXE
|
Booting with iPXE
|
||||||
=================
|
=================
|
||||||
|
|
||||||
@@ -29,6 +32,81 @@ Booting with iPXE
|
|||||||
ipxe_cfg->kernel[ltail=cluster0,label="http"];
|
ipxe_cfg->kernel[ltail=cluster0,label="http"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Booting with dracut
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Some systems, typically due to limitations in their BIOS or EFI
|
||||||
|
firmware, are unable to load container image of a certain size
|
||||||
|
directly with a traditional bootloader, either iPXE or GRUB. As a
|
||||||
|
workaround for such systems, Warewulf can be configured to load a
|
||||||
|
dracut initramfs from the container and to use that initramfs to load
|
||||||
|
the full container image.
|
||||||
|
|
||||||
|
Warewulf provides a dracut module to configure the dracut initramfs to
|
||||||
|
load the container image. This module is available in the
|
||||||
|
``warewulf-dracut`` subpackage, which must be installed in the
|
||||||
|
container image.
|
||||||
|
|
||||||
|
With the ``warewulf-dracut`` package installed, you can build an
|
||||||
|
initramfs inside the container.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
dnf -y install warewulf-dracut
|
||||||
|
dracut --force --no-hostonly --add wwinit --kver $(ls /lib/modules | head -n1)
|
||||||
|
|
||||||
|
Set the node's iPXE template to ``dracut`` to direct iPXE to fetch the
|
||||||
|
node's initramfs image and boot with dracut semantics, rather than
|
||||||
|
booting the node image directly.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Warewulf iPXE templates are located at ``/etc/warewulf/ipxe/`` when
|
||||||
|
Warewulf is installed via official packages. You can learn more
|
||||||
|
about how dracut booting works by inspecting its iPXE template at
|
||||||
|
``/etc/warewulf/ipxe/dracut.ipxe``.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
wwctl node set wwnode1 --ipxe dracut
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The iPXE template may be set at the node or profile level.
|
||||||
|
|
||||||
|
Alternatively, to direct GRUB to fetch the node's initramfs image and boot with
|
||||||
|
dracut semantics, set a ``GrubMenuEntry`` tag for the node.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Warewulf configures GRUB with a template located at
|
||||||
|
``/etc/warewulf/grub/grub.cfg.ww``. Inspect the template to learn more about
|
||||||
|
the dracut booting process.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
wwctl node set wwnode1 --tagadd GrubMenuEntry=dracut
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``GrubMenuEntry`` variable may be set at the node or profile level.
|
||||||
|
|
||||||
|
During boot, ``warewulfd`` will detect and dynamically serve an
|
||||||
|
initramfs from a node's container image in much the same way that it
|
||||||
|
can serve a kernel from a container image. This image is loaded by
|
||||||
|
iPXE (or GRUB) which directs dracut to fetch the node's container image
|
||||||
|
during boot.
|
||||||
|
|
||||||
|
The wwinit module provisions to tmpfs. By default, tmpfs is permitted
|
||||||
|
to use up to 50% of physical memory. This size limit may be adjustd
|
||||||
|
using the kernel argument `wwinit.tmpfs.size`. (This parameter is
|
||||||
|
passed to the `size` option during tmpfs mount. See ``tmpfs(5)`` for
|
||||||
|
more details.)
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Kernel overrides are not currently fully supported during dracut initramfs boot.
|
||||||
|
|
||||||
Booting with GRUB
|
Booting with GRUB
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,23 @@ BuildRequires: libassuan-devel gpgme-devel
|
|||||||
Warewulf is a stateless and diskless container operating system provisioning
|
Warewulf is a stateless and diskless container operating system provisioning
|
||||||
system for large clusters of bare metal and/or virtual systems.
|
system for large clusters of bare metal and/or virtual systems.
|
||||||
|
|
||||||
|
%package dracut
|
||||||
|
Summary: dracut module for loading a Warewulf container image
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
|
Requires: dracut
|
||||||
|
%if 0%{?suse_version}
|
||||||
|
%else
|
||||||
|
Requires: dracut-network
|
||||||
|
%endif
|
||||||
|
|
||||||
|
%description dracut
|
||||||
|
Warewulf is a stateless and diskless container operating system provisioning
|
||||||
|
system for large clusters of bare metal and/or virtual systems.
|
||||||
|
|
||||||
|
This subpackage contains a dracut module that can be used to generate
|
||||||
|
an initramfs that can fetch and boot a Warewulf container image from a
|
||||||
|
Warewulf server.
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%setup -q -n %{name}-%{version} -b0 %if %{?with_offline:-a2}
|
%setup -q -n %{name}-%{version} -b0 %if %{?with_offline:-a2}
|
||||||
@@ -105,7 +122,8 @@ make defaults \
|
|||||||
BASHCOMPDIR=/etc/bash_completion.d/ \
|
BASHCOMPDIR=/etc/bash_completion.d/ \
|
||||||
FIREWALLDDIR=/usr/lib/firewalld/services \
|
FIREWALLDDIR=/usr/lib/firewalld/services \
|
||||||
WWCLIENTDIR=/warewulf \
|
WWCLIENTDIR=/warewulf \
|
||||||
IPXESOURCE=/usr/share/ipxe
|
IPXESOURCE=/usr/share/ipxe \
|
||||||
|
DRACUTMODDIR=/usr/lib/dracut/modules.d
|
||||||
make
|
make
|
||||||
%if %{api}
|
%if %{api}
|
||||||
make api
|
make api
|
||||||
@@ -185,7 +203,16 @@ getent group %{wwgroup} >/dev/null || groupadd -r %{wwgroup}
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
|
||||||
|
%files dracut
|
||||||
|
%defattr(-, root, root)
|
||||||
|
%dir %{_prefix}/lib/dracut/modules.d/90wwinit
|
||||||
|
%{_prefix}/lib/dracut/modules.d/90wwinit/*.sh
|
||||||
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Thu Apr 18 2024 Jonathon Anderson <janderson@ciq.com>
|
||||||
|
- New warewulf-dracut subpackage
|
||||||
|
|
||||||
* Wed Apr 17 2024 Jonathon Anderson <janderson@ciq.com>
|
* Wed Apr 17 2024 Jonathon Anderson <janderson@ciq.com>
|
||||||
- Don't build the API on EL7
|
- Don't build the API on EL7
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user