Refactor kernel support

- Remove kernel imports and kmods images
- Repurpose KernelOverride as container path
- Support Kernel.Version as version prefix for kernel selection
- Compare initramfs by version

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2024-11-16 01:23:07 -07:00
committed by Christian Goll
parent d87ed27d8d
commit 8f21d54243
40 changed files with 997 additions and 1004 deletions

View File

@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Better handling of InfiniBand udev net naming. #1227
- use templating mechanism for power commands. #1004
- Document "known issues."
- Add `wwctl <node|profile> <add|set> --kernelversion` to specify the desired kernel version. #1556
### Changed
@@ -72,6 +73,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Bump github.com/Masterminds/sprig/v3 from 3.2.3 to 3.3.0 #1553
- Bump github.com/golang/glog from 1.2.0 to 1.2.3 #1527
- Bump github.com/opencontainers/runc from 1.1.12 to 1.1.14
- Repurpose Kernel.Override to specify the path to the desired kernel within the container. #1556
- Repurpose `wwctl kernel list` to list discovered kernels from containers. #1556
### Removed
@@ -80,6 +83,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Remove `wwctl server <start,stop,status,restart,reload>` #508
- Remove `wwctl overlay build --host` #1419
- Remove `wwctl overlay build --nodes` #1419
- Remove `wwctl kernel <import|delete>` #1556
### Fixed

View File

@@ -3,7 +3,7 @@
info "Mounting tmpfs at $NEWROOT"
mount -t tmpfs -o mpol=interleave ${wwinit_tmpfs_size_option} tmpfs "$NEWROOT"
for archive in "${wwinit_container}" "${wwinit_kmods}" "${wwinit_system}" "${wwinit_runtime}"
for archive in "${wwinit_container}" "${wwinit_system}" "${wwinit_runtime}"
do
if [ -n "${archive}" ]
then

View File

@@ -12,10 +12,6 @@ then
export wwinit_container="${wwinit_uri}&stage=container&compress=gz"; info "wwinit_container=${wwinit_container}"
export wwinit_system="${wwinit_uri}&stage=system&compress=gz"; info "wwinit_system=${wwinit_system}"
export wwinit_runtime="${wwinit_uri}&stage=runtime&compress=gz"; info "wwinit_runtime=${wwinit_runtime}"
if [ -n "$(getarg wwinit.KernelOverride)" ]
then
export wwinit_kmods="${wwinit_uri}&stage=kmods&compress=gz"; info "wwinit_kmods=${wwinit_kmods}"
fi
wwinit_tmpfs_size=$(getarg wwinit.tmpfs.size=)
if [ -n "$wwinit_tmpfs_size" ]

View File

@@ -39,11 +39,6 @@ imgextract --name system ${uri_base}&stage=system&compress=gz || goto rebo
echo Downloading Runtime Overlay:
imgextract --name runtime ${uri_base}&stage=runtime&compress=gz && set runtime_initrd initrd=runtime || echo Failed downloading runtime overlay.
{{if ne .KernelOverride "" -}}
echo Downloading Kernel Modules:
imgextract --name kmods ${uri_base}&stage=kmods&compress=gz || goto reboot
{{- end}}
goto imoktogo
:nocompress
@@ -60,11 +55,6 @@ initrd --name system ${uri_base}&stage=system || goto reboot
echo Downloading Runtime Overlay:
initrd --name runtime ${uri_base}&stage=runtime && set runtime_initrd initrd=runtime || echo Failed downloading runtime overlay.
{{if ne .KernelOverride "" -}}
echo Downloading Kernel Modules:
initrd --name kmods ${uri_base}&stage=kmods || goto reboot
{{- end}}
goto imoktogo
:noefi
@@ -81,21 +71,11 @@ initrd --name system ${uri_base}&stage=system&compress=gz || goto reboot
echo Downloading Runtime Overlay:
initrd --name runtime ${uri_base}&stage=runtime&compress=gz && set runtime_initrd initrd=runtime || echo Failed downloading runtime overlay.
{{if ne .KernelOverride "" -}}
echo Downloading Kernel Modules:
initrd --name kmods ${uri_base}&stage=kmods&compress=gz || goto reboot
{{- end}}
:imoktogo
{{if ne .KernelOverride "" -}}
echo boot kernel initrd=container initrd=kmods initrd=system ${runtime_initrd} wwid={{.Hwaddr}} {{.KernelArgs}}
boot kernel initrd=container initrd=kmods initrd=system ${runtime_initrd} wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
{{- else -}}
echo boot kernel initrd=container initrd=system ${runtime_initrd} wwid={{.Hwaddr}} {{.KernelArgs}}
boot kernel initrd=container initrd=system ${runtime_initrd} wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
{{- end}}
:reboot
echo

View File

@@ -20,23 +20,14 @@ 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.uri=${baseuri} {{if ne .KernelOverride ""}}wwinit.KernelOverride={{ .KernelOverride }}{{end}} init=/init
set dracut_wwinit root=wwinit wwinit.uri=${baseuri} 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}}
boot kernel initrd=initramfs ${dracut_net} ${dracut_wwinit} wwid={{.Hwaddr}} {{.KernelArgs}}
:reboot

View File

@@ -0,0 +1,87 @@
package completions
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/hostlist"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
)
func NodeKernelOverride(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var kernelPaths []string
registry, err := node.New()
if err != nil {
return kernelPaths, cobra.ShellCompDirectiveNoFileComp
}
nodes := hostlist.Expand(args)
for _, id := range nodes {
if node_, err := registry.GetNode(id); err != nil {
continue
} else if node_.ContainerName != "" {
kernels := kernel.FindKernels(node_.ContainerName)
for _, kernel_ := range kernels {
kernelPaths = append(kernelPaths, kernel_.Path)
}
}
}
return kernelPaths, cobra.ShellCompDirectiveNoFileComp
}
func NodeKernelVersion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var kernelVersions []string
registry, err := node.New()
if err != nil {
return kernelVersions, cobra.ShellCompDirectiveNoFileComp
}
nodes := hostlist.Expand(args)
for _, id := range nodes {
if node_, err := registry.GetNode(id); err != nil {
continue
} else if node_.ContainerName != "" {
kernels := kernel.FindKernels(node_.ContainerName)
for _, kernel_ := range kernels {
kernelVersions = append(kernelVersions, kernel_.Version())
}
}
}
return kernelVersions, cobra.ShellCompDirectiveNoFileComp
}
func ProfileKernelOverride(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var kernelPaths []string
registry, err := node.New()
if err != nil {
return kernelPaths, cobra.ShellCompDirectiveNoFileComp
}
for _, id := range args {
if profile, err := registry.GetProfile(id); err != nil {
continue
} else if profile.ContainerName != "" {
kernels := kernel.FindKernels(profile.ContainerName)
for _, kernel_ := range kernels {
kernelPaths = append(kernelPaths, kernel_.Path)
}
}
}
return kernelPaths, cobra.ShellCompDirectiveNoFileComp
}
func ProfileKernelVersion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var kernelVersions []string
registry, err := node.New()
if err != nil {
return kernelVersions, cobra.ShellCompDirectiveNoFileComp
}
for _, id := range args {
if profile, err := registry.GetProfile(id); err != nil {
continue
} else if profile.ContainerName != "" {
kernels := kernel.FindKernels(profile.ContainerName)
for _, kernel_ := range kernels {
kernelVersions = append(kernelVersions, kernel_.Version())
}
}
}
return kernelVersions, cobra.ShellCompDirectiveNoFileComp
}

View File

@@ -56,6 +56,7 @@ nodes:
warewulfd.SetNoDaemon()
for _, tt := range tests {
env := testenv.New(t)
defer env.RemoveAll(t)
env.WriteFile(t, "etc/warewulf/nodes.conf", tt.inDb)
t.Logf("Running test: %s\n", tt.name)

View File

@@ -1,39 +0,0 @@
package delete
import (
"fmt"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
nodeDB, err := node.New()
if err != nil {
return fmt.Errorf("could not open nodeDB: %s", err)
}
nodes, _ := nodeDB.FindAllNodes()
ARG_LOOP:
for _, arg := range args {
for _, n := range nodes {
if n.Kernel.Override == arg {
wwlog.Error("Kernel is configured for nodes, skipping: %s", arg)
continue ARG_LOOP
}
}
err := kernel.DeleteKernel(arg)
if err != nil {
wwlog.Error("Could not delete kernel: %s", arg)
} else {
fmt.Printf("Kernel has been deleted: %s\n", arg)
}
}
return nil
}

View File

@@ -1,34 +0,0 @@
package delete
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/kernel"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "delete [OPTIONS] KERNEL [...]",
Short: "Delete imported kernels",
Long: "This command will delete KERNEL versions that have been imported into Warewulf.",
Aliases: []string{"rm", "del", "remove"},
RunE: CobraRunE,
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := kernel.ListKernels()
return list, cobra.ShellCompDirectiveNoFileComp
},
}
)
func init() {
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -1,80 +0,0 @@
package imprt
import (
"fmt"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
if len(args) == 0 && !OptDetect {
return fmt.Errorf("the '--detect' flag is needed, if no kernel version is suppiled")
}
if OptDetect && (OptRoot == "" || OptContainer == "") {
return fmt.Errorf("the '--detect flag needs the '--container' or '--root' flag")
}
// Checking if container flag was set, then overwriting OptRoot
if OptContainer != "" {
if container.ValidSource(OptContainer) {
OptRoot = container.RootFsDir(OptContainer)
} else {
return fmt.Errorf(" %s is not a valid container", OptContainer)
}
}
var kernelVersion string
var err error
if len(args) > 0 {
kernelVersion = args[0]
} else {
_, kernelVersion, err = kernel.FindKernel(OptRoot)
if err != nil {
return err
}
}
kernelName := kernelVersion
if len(args) > 1 {
kernelName = args[1]
} else if OptDetect && (OptContainer != "") {
kernelName = OptContainer
}
err = kernel.Build(kernelVersion, kernelName, OptRoot)
if err != nil {
return fmt.Errorf("failed building kernel: %s", err)
} else {
fmt.Printf("%s: %s\n", kernelName, "Finished kernel build")
}
if SetDefault {
nodeDB, err := node.New()
if err != nil {
return fmt.Errorf("could not open node configuration: %s", err)
}
//TODO: Don't loop through profiles, instead have a nodeDB function that goes directly to the map
profiles, _ := nodeDB.FindAllProfiles()
for _, profile := range profiles {
wwlog.Debug("Looking for profile default: %s", profile.Id())
if profile.Id() == "default" {
wwlog.Debug("Found profile default, setting kernel version to: %s", args[0])
profile.Kernel.Override = args[0]
}
}
err = nodeDB.Persist()
if err != nil {
return fmt.Errorf("failed to persist nodedb: %w", err)
}
fmt.Printf("Set default kernel version to: %s\n", args[0])
err = warewulfd.DaemonReload()
if err != nil {
return fmt.Errorf("failed to reload warewulf daemon: %w", err)
}
}
return nil
}

View File

@@ -1,47 +0,0 @@
package imprt
import (
"log"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "import [OPTIONS] KERNEL",
Short: "Import Kernel version into Warewulf",
Long: "This will import a boot KERNEL version from the control node into Warewulf",
Aliases: []string{"pull"},
RunE: CobraRunE,
Args: cobra.MinimumNArgs(0),
}
BuildAll bool
ByNode bool
SetDefault bool
OptRoot string
OptContainer string
OptDetect bool
)
func init() {
baseCmd.PersistentFlags().BoolVarP(&BuildAll, "all", "a", false, "Build all overlays (runtime and system)")
baseCmd.PersistentFlags().BoolVarP(&ByNode, "node", "n", false, "Build overlay for a particular node(s)")
baseCmd.PersistentFlags().BoolVar(&SetDefault, "setdefault", false, "Set this kernel for the default profile")
baseCmd.PersistentFlags().StringVarP(&OptRoot, "root", "r", "/", "Import kernel from root (chroot) directory")
baseCmd.PersistentFlags().StringVarP(&OptContainer, "container", "C", "", "Import kernel from container")
err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Println(err)
}
baseCmd.PersistentFlags().BoolVarP(&OptDetect, "detect", "D", false, "Try to detect the kernel version in an automated way, needs the -C or -r option")
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -1,32 +1,54 @@
package list
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/table"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
nodesYaml, err := node.New()
if err != nil {
return err
}
nodes, err := nodesYaml.FindAllNodes()
if err != nil {
return err
}
kernelNodes := make(map[kernel.Kernel]int)
for _, node := range nodes {
kernel_ := kernel.FromNode(&node)
if kernel_ != nil {
kernelNodes[*kernel_]++
}
}
kernels, err := kernel.ListKernels()
sources, err := container.ListSources()
if err != nil {
return err
}
nconfig, _ := node.New()
nodes, _ := nconfig.FindAllNodes()
nodemap := make(map[string]int)
for _, n := range nodes {
nodemap[n.Kernel.Override]++
}
fmt.Printf("%-35s %-25s %-6s\n", "KERNEL NAME", "KERNEL VERSION", "NODES")
for _, k := range kernels {
fmt.Printf("%-35s %-25s %6d\n", k, kernel.GetKernelVersion(k), nodemap[k])
t := table.New(cmd.OutOrStdout())
t.AddHeader("Container", "Kernel", "Version", "Preferred", "Nodes")
for _, source := range sources {
containerKernels := kernel.FindKernels(source)
preferredKernel := containerKernels.Preferred()
for _, kernel_ := range containerKernels {
preferred := preferredKernel != nil && preferredKernel == kernel_
preferredStr := strconv.FormatBool(preferred)
nodeCount := kernelNodes[*kernel_]
if preferred {
nodeCount = nodeCount + kernelNodes[kernel.Kernel{ContainerName: source, Path: ""}]
}
t.AddLine(table.Prep([]string{source, kernel_.Path, kernel_.Version(), preferredStr, strconv.Itoa(nodeCount)})...)
}
}
t.Print()
return nil
}

View File

@@ -0,0 +1,80 @@
package list
import (
"bytes"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func Test_List(t *testing.T) {
tests := map[string]struct {
files map[string][]string
args []string
stdout string
}{
"default": {
files: map[string][]string{},
args: []string{},
stdout: `
Container Kernel Version Preferred Nodes
--------- ------ ------- --------- -----
`,
},
"list": {
files: map[string][]string{
"container1": []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
"container2": []string{
"/boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0",
"/boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug",
"/boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64",
},
},
args: []string{},
stdout: `
Container Kernel Version Preferred Nodes
--------- ------ ------- --------- -----
container1 /boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64 4.14.0-427.18.1 false 0
container1 /boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64 5.14.0-427.18.1 false 0
container1 /boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64 5.14.0-427.24.1 true 0
container2 /boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0 -- false 0
container2 /boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64 5.14.0-284.30.1 false 0
container2 /boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64 5.14.0-362.24.1 false 0
container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64 5.14.0-427.31.1 true 0
container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug 5.14.0-427.31.1 false 0
`,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
for container, files := range tt.files {
rootfs := filepath.Join(filepath.Join("/var/lib/warewulf/chroots", container), "rootfs")
for _, file := range files {
env.CreateFile(t, filepath.Join(rootfs, file))
}
}
buf := new(bytes.Buffer)
baseCmd := GetCommand()
baseCmd.SetArgs(tt.args)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
wwlog.SetLogWriter(buf)
err := baseCmd.Execute()
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(tt.stdout), strings.TrimSpace(buf.String()))
})
}
}

View File

@@ -6,8 +6,8 @@ var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "list [OPTIONS]",
Short: "List imported Kernel images",
Long: "This command will list the kernels that have been imported into Warewulf.",
Short: "List available kernels",
Long: "This command lists the kernels that are available in the imported containers.",
RunE: CobraRunE,
Args: cobra.ExactArgs(0),
Aliases: []string{"ls"},

View File

@@ -2,8 +2,6 @@ package kernel
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/kernel/delete"
"github.com/warewulf/warewulf/internal/app/wwctl/kernel/imprt"
"github.com/warewulf/warewulf/internal/app/wwctl/kernel/list"
)
@@ -11,15 +9,13 @@ var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "kernel COMMAND [OPTIONS]",
Short: "Kernel Image Management",
Long: "This command manages Warewulf Kernels used for bootstrapping nodes",
Short: "Kernel Management",
Long: "This command manages kernels available to Warewulf",
}
)
func init() {
baseCmd.AddCommand(imprt.GetCommand())
baseCmd.AddCommand(list.GetCommand())
baseCmd.AddCommand(delete.GetCommand())
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -4,8 +4,8 @@ import (
"log"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -38,10 +38,10 @@ func GetCommand() *cobra.Command {
}); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := kernel.ListKernels()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", completions.NodeKernelOverride); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.NodeKernelVersion); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@@ -4,8 +4,8 @@ import (
"log"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -54,10 +54,10 @@ func GetCommand() *cobra.Command {
}); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := kernel.ListKernels()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", completions.NodeKernelOverride); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.NodeKernelVersion); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@@ -4,8 +4,8 @@ import (
"log"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -37,10 +37,10 @@ func GetCommand() *cobra.Command {
}); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := kernel.ListKernels()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", completions.ProfileKernelOverride); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.ProfileKernelVersion); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@@ -4,8 +4,8 @@ import (
"log"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -59,10 +59,10 @@ func GetCommand() *cobra.Command {
}); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := kernel.ListKernels()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
if err := baseCmd.RegisterFlagCompletionFunc("kerneloverride", completions.ProfileKernelOverride); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("kernelversion", completions.ProfileKernelVersion); err != nil {
log.Println(err)
}
if err := baseCmd.RegisterFlagCompletionFunc("runtime", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@@ -305,7 +305,11 @@ func ContainerList() (containerInfo []*wwapiv1.ContainerInfo, err error) {
}
wwlog.Debug("Finding kernel version for: %s", source)
_, kernelVersion, _ := kernel.FindKernel(container.RootFsDir(source))
kernel := kernel.FindKernels(source).Preferred()
kernelVersion := ""
if kernel != nil {
kernelVersion = kernel.Version()
}
var creationTime uint64
sourceStat, err := os.Stat(container.SourceDir(source))
wwlog.Debug("Checking creation time for: %s,%v", container.SourceDir(source), sourceStat.ModTime())
@@ -359,7 +363,11 @@ func ContainerShow(csp *wwapiv1.ContainerShowParameter) (response *wwapiv1.Conta
err = fmt.Errorf("%s is not a valid container", containerName)
return
}
_, kernelVersion, _ := kernel.FindKernel(container.RootFsDir(containerName))
kernel := kernel.FindKernels(containerName).Preferred()
kernelVersion := ""
if kernel != nil {
kernelVersion = kernel.Version()
}
nodeDB, err := node.New()
if err != nil {

View File

@@ -1,25 +1,11 @@
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
@@ -39,22 +25,9 @@ func RunDir(name string) string {
func ImageParentDir() string {
conf := warewulfconf.Get()
return path.Join(conf.Paths.WWProvisiondir, "container/")
return path.Join(conf.Paths.WWProvisiondir, "container")
}
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

@@ -1,88 +0,0 @@
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

@@ -0,0 +1,87 @@
package container
import (
"path/filepath"
"regexp"
"strings"
"github.com/hashicorp/go-version"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
var (
initramfsSearchPaths = []string{
"/boot/initramfs-*",
"/boot/initrd-*",
}
versionPattern *regexp.Regexp
)
func init() {
versionPattern = regexp.MustCompile(`\d+\.\d+\.\d+(-[\d\.]+|)`)
}
type Initramfs struct {
Path string
ContainerName string
}
func (this *Initramfs) version() *version.Version {
matches := versionPattern.FindAllString(this.Path, -1)
for i := len(matches) - 1; i >= 0; i-- {
if version_, err := version.NewVersion(strings.TrimSuffix(matches[i], ".")); err == nil {
return version_
}
}
return nil
}
func (this *Initramfs) Version() string {
version := this.version()
if version == nil {
return ""
} else {
return version.String()
}
}
func (this *Initramfs) FullPath() string {
root := RootFsDir(this.ContainerName)
return filepath.Join(root, this.Path)
}
func FindInitramfsFromPattern(containerName string, version string, pattern string) (initramfs *Initramfs) {
wwlog.Debug("FindInitramfsFromPattern(%v, %v, %v)", containerName, version, pattern)
root := RootFsDir(containerName)
fullPaths, err := filepath.Glob(filepath.Join(root, pattern))
wwlog.Debug("%v: fullPaths: %v", filepath.Join(root, pattern), fullPaths)
if err != nil {
panic(err)
}
for _, fullPath := range fullPaths {
path, err := filepath.Rel(root, fullPath)
if err != nil {
continue
} else {
initramfs := &Initramfs{Path: filepath.Join("/", path), ContainerName: containerName}
wwlog.Info("%v", initramfs)
if strings.HasPrefix(initramfs.Version(), version) {
return initramfs
}
}
}
return nil
}
// FindInitramfs returns the Initramfs for a given container and (kernel) version
func FindInitramfs(containerName string, version string) *Initramfs {
for _, pattern := range initramfsSearchPaths {
initramfs := FindInitramfsFromPattern(containerName, version, pattern)
if initramfs != nil {
return initramfs
}
}
return nil
}

View File

@@ -0,0 +1,82 @@
package container
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/testenv"
)
func TestFindInitramfs(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 := map[string]struct {
name string
initramfs []string
ver string
path string
}{
"ok case 1": {
initramfs: []string{"/boot/initramfs-1.1.1.aarch64.img"},
ver: "1.1.1",
path: "/boot/initramfs-1.1.1.aarch64.img",
},
"ok case 2": {
initramfs: []string{"/boot/initrd-1.1.1.aarch64"},
ver: "1.1.1",
path: "/boot/initrd-1.1.1.aarch64",
},
"ok case 3": {
initramfs: []string{"/boot/initramfs-1.1.1.aarch64"},
ver: "1.1.1",
path: "/boot/initramfs-1.1.1.aarch64",
},
"ok case 4": {
initramfs: []string{"/boot/initrd-1.1.1.aarch64.img"},
ver: "1.1.1",
path: "/boot/initrd-1.1.1.aarch64.img",
},
"prefix match": {
initramfs: []string{"/boot/initrd-1.1.1.aarch64.img"},
ver: "1.1",
path: "/boot/initrd-1.1.1.aarch64.img",
},
"error case, wrong init name": {
initramfs: []string{"/boot/initrr-1.1.1.aarch64.img"},
ver: "1.1.1",
path: "",
},
"error case, wrong ver": {
initramfs: []string{"/boot/initrd-1.1.1.aarch64.img"},
ver: "1.1.2",
path: "",
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
for _, init := range tt.initramfs {
env.CreateFile(t, filepath.Join("/var/lib/warewulf/chroots/image/rootfs", init))
}
initramfs := FindInitramfs("image", tt.ver)
if tt.path == "" {
assert.Nil(t, initramfs)
} else {
assert.NotNil(t, initramfs)
assert.Equal(t, tt.path, initramfs.Path)
}
})
}
}

View File

@@ -1,333 +1,150 @@
package kernel
import (
"compress/gzip"
"fmt"
"io"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/pkg/errors"
"github.com/hashicorp/go-version"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
var (
kernelSearchPaths = []string{
// This is a printf format where the %s will be the kernel version
"/boot/Image-%s", // this is the aarch64 for SUSE, vmlinux which is also present won't boot
"/boot/vmlinuz-linux%s",
"/boot/vmlinuz-%s",
"/boot/vmlinuz-%s.gz",
"/lib/modules/%s/vmlinuz",
"/lib/modules/%s/vmlinuz.gz",
"/boot/Image-*", // this is the aarch64 for SUSE, vmlinux which is also present won't boot
"/boot/vmlinuz-linux*",
"/boot/vmlinuz-*",
"/lib/modules/*/vmlinuz",
"/lib/modules/*/vmlinuz.gz",
}
kernelDrivers = []string{
"lib/modules/%s/*",
"lib/firmware/*",
"lib/modprobe.d",
"lib/modules-load.d"}
// kenrel naming convention <base kernel version>-<ABI number>.<upload number>-<flavour>
kernelVersionRegex = `(\d+\.\d+\.\d+)-((\d+\.*){1,})`
)
func KernelImageTopDir() string {
conf := warewulfconf.Get()
return path.Join(conf.Paths.WWProvisiondir, "kernel")
type collection []*Kernel
func (k collection) Len() int {
return len(k)
}
func KernelImage(kernelName string) string {
if kernelName == "" {
wwlog.Error("Kernel Name is not defined")
return ""
}
if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") {
wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName)
return ""
}
return path.Join(KernelImageTopDir(), kernelName, "vmlinuz")
func (k collection) Less(i, j int) bool {
iv := k[i].version()
jv := k[j].version()
return (iv == nil && jv != nil) ||
(iv != nil && jv != nil && iv.LessThan(jv))
}
func GetKernelVersion(kernelName string) string {
if kernelName == "" {
wwlog.Error("Kernel Name is not defined")
return ""
}
kernelVersion, err := os.ReadFile(KernelVersionFile(kernelName))
if err != nil {
return ""
}
return string(kernelVersion)
func (k collection) Swap(i, j int) {
k[i], k[j] = k[j], k[i]
}
func KmodsImage(kernelName string) string {
if kernelName == "" {
wwlog.Error("Kernel Name is not defined")
return ""
}
if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") {
wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName)
return ""
}
return path.Join(KernelImageTopDir(), kernelName, "kmods.img")
func (k collection) Sort() {
sort.Sort(k)
}
func KernelVersionFile(kernelName string) string {
if kernelName == "" {
wwlog.Error("Kernel Name is not defined")
return ""
}
if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") {
wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName)
return ""
}
return path.Join(KernelImageTopDir(), kernelName, "version")
}
func ListKernels() ([]string, error) {
var ret []string
err := os.MkdirAll(KernelImageTopDir(), 0755)
if err != nil {
return ret, errors.New("Could not create Kernel parent directory: " + KernelImageTopDir())
}
wwlog.Debug("Searching for Kernel image directories: %s", KernelImageTopDir())
kernels, err := os.ReadDir(KernelImageTopDir())
if err != nil {
return ret, err
}
for _, kernel := range kernels {
wwlog.Verbose("Found Kernel: %s", kernel.Name())
ret = append(ret, kernel.Name())
}
return ret, nil
}
/*
Triggers the kernel extraction and build of the modules for the given
kernel version. A name for this kernel and were to find has also to be
supplied
*/
func Build(kernelVersion, kernelName, root string) error {
kernelDestination := KernelImage(kernelName)
driversDestination := KmodsImage(kernelName)
versionDestination := KernelVersionFile(kernelName)
// Create the destination paths just in case it doesn't exist
err := os.MkdirAll(path.Dir(kernelDestination), 0755)
if err != nil {
return fmt.Errorf("failed to create kernel dest: %w", err)
}
err = os.MkdirAll(path.Dir(driversDestination), 0755)
if err != nil {
return fmt.Errorf("failed to create driver dest: %w", err)
}
err = os.MkdirAll(path.Dir(versionDestination), 0755)
if err != nil {
return fmt.Errorf("failed to create version dest: %s", err)
}
kernelSource, kernelVersFound, err := FindKernel(root)
if err != nil {
return err
} else if kernelVersFound != kernelVersion {
return fmt.Errorf("requested %s and found kernel version %s differ", kernelVersion, kernelVersFound)
} else {
wwlog.Info("Found kernel at: %s", kernelSource)
}
wwlog.Verbose("Setting up Kernel")
if _, err := os.Stat(kernelSource); err == nil {
kernel, err := os.Open(kernelSource)
if err != nil {
return fmt.Errorf("could not open kernel: %w", err)
func (k collection) Preferred() *Kernel {
nk := append(collection{}, k...)
sort.Sort(sort.Reverse(nk))
for _, kernel := range nk {
if !(kernel.IsDebug() || kernel.IsRescue()) {
return kernel
}
defer kernel.Close()
gzipreader, err := gzip.NewReader(kernel)
if err == nil {
defer gzipreader.Close()
writer, err := os.Create(kernelDestination)
if err != nil {
return fmt.Errorf("could not decompress kernel: %w", err)
}
defer writer.Close()
_, err = io.Copy(writer, gzipreader)
if err != nil {
return fmt.Errorf("could not write decompressed kernel: %w", err)
}
} else {
err := util.CopyFile(kernelSource, kernelDestination)
if err != nil {
return fmt.Errorf("could not copy kernel: %w", err)
}
}
}
name := kernelName + " drivers"
var kernelDriversSpecific []string
for _, kPath := range kernelDrivers {
if strings.Contains(kPath, "%s") {
kernelDriversSpecific = append(kernelDriversSpecific, fmt.Sprintf(kPath, kernelVersion))
} else {
kernelDriversSpecific = append(kernelDriversSpecific, kPath)
}
}
wwlog.Debug("kernelDriversSpecific: %v", kernelDriversSpecific)
wwlog.Verbose("Creating image for %s: %s", name, root)
err = util.BuildFsImage(
name,
root,
driversDestination,
kernelDriversSpecific,
[]string{},
// ignore cross-device files
true,
"newc",
// dereference symbolic links
"-L")
if err != nil {
return err
}
wwlog.Verbose("Creating version file")
file, err := os.Create(versionDestination)
if err != nil {
return fmt.Errorf("Failed to create version file: %w", err)
}
defer file.Close()
_, err = io.WriteString(file, kernelVersion)
if err != nil {
return fmt.Errorf("Could not write kernel version: %w", err)
}
err = file.Sync()
if err != nil {
return fmt.Errorf("Could not sync kernel version: %w", err)
}
return nil
}
func DeleteKernel(name string) error {
fullPath := path.Join(KernelImageTopDir(), name)
wwlog.Verbose("Removing path: %s", fullPath)
return os.RemoveAll(fullPath)
}
/*
Searches for kernel under a given path. First return result is the
full path, second the version and an error if the kernel couldn't be found.
*/
type kernel struct {
version string
path string
}
func filter(val string, filters []func(string) (string, error)) (string, error) {
for _, ft := range filters {
newVal, err := ft(val)
if err != nil {
return val, err
}
val = newVal
}
return val, nil
}
func nonDebugKernel(val string) (string, error) {
if strings.HasSuffix(val, "+debug") {
return val, fmt.Errorf("%s is debug kernel, skipped", val)
}
return val, nil
}
func nonSemaVer(val string) (string, error) {
// need to extract version info
verRegx := regexp.MustCompile(kernelVersionRegex)
verRe := verRegx.FindAllStringSubmatch(val, -1)
// only if at the least the following pattern is matched <xx.xx.xx>-<xx[.xx.xx]>
if len(verRe) > 0 && len(verRe[0]) > 2 {
// verRe[0][1] -> <xx.xx.xx>
// verRe[0][2] -> <xx[.xx.xx]>
verStr := strings.TrimSuffix(fmt.Sprintf("%s-%s", verRe[0][1], verRe[0][2]), ".")
_, err := version.NewVersion(verStr)
if err != nil {
return val, fmt.Errorf("semantic incompatible version detected, version string: %s, err: %s", verStr, err)
}
return verStr, nil
}
return val, fmt.Errorf("unable to extract version info from %s", val)
}
func FindKernel(root string) (string, string, error) {
wwlog.Debug("root: %s", root)
for _, searchPath := range kernelSearchPaths {
testPattern := fmt.Sprintf(path.Join(root, searchPath), `*`)
wwlog.Debug("Looking for kernel version with pattern at: %s", testPattern)
potentialKernel, _ := filepath.Glob(testPattern)
if len(potentialKernel) == 0 {
func (k collection) Version(version string) *Kernel {
for _, kernel := range k {
if kernel.IsDebug() || kernel.IsRescue() {
continue
}
verMap := make(map[*version.Version]*kernel, len(potentialKernel))
for _, foundKernel := range potentialKernel {
wwlog.Debug("Parsing out kernel version for %s", foundKernel)
re := regexp.MustCompile(fmt.Sprintf(path.Join(root, searchPath), `([\w\d-\.+]*)`))
kernelVer := re.FindAllStringSubmatch(foundKernel, -1)
if kernelVer == nil {
break
}
// kernelVerStr is like 5.14.0-427.18.1.el9_4.x86_64
kernelVerStr := strings.TrimSuffix(kernelVer[0][1], ".gz")
newVal, err := filter(kernelVerStr, []func(string) (string, error){nonDebugKernel, nonSemaVer})
if err != nil {
wwlog.Verbose("While filtering kernel version for %s, having error: %s", kernelVerStr, err)
continue
}
ver, _ := version.NewVersion(newVal)
verMap[ver] = &kernel{
version: kernelVerStr,
path: foundKernel,
}
}
if len(verMap) > 0 {
var keys []*version.Version
for k := range verMap {
keys = append(keys, k)
}
sort.Sort(sort.Reverse(version.Collection(keys)))
return verMap[keys[0]].path, verMap[keys[0]].version, nil
} else if strings.HasPrefix(kernel.Version(), version) {
return kernel
}
}
return "", "", fmt.Errorf("could not find kernel version")
return nil
}
type Kernel struct {
Path string
ContainerName string
}
func FromNode(node *node.Node) *Kernel {
wwlog.Debug("FromNode(%v)", node)
if node.ContainerName == "" {
return nil
} else if node.Kernel.Override != "" {
return &Kernel{ContainerName: node.ContainerName, Path: filepath.Join("/", node.Kernel.Override)}
} else if node.Kernel.Version != "" {
return FindKernels(node.ContainerName).Version(node.Kernel.Version)
} else {
return FindKernels(node.ContainerName).Preferred()
}
}
func FindKernelsFromPattern(containerName string, pattern string) (kernels collection) {
wwlog.Debug("FindKernelsFromPattern(%v, %v)", containerName, pattern)
root := container.RootFsDir(containerName)
fullPaths, err := filepath.Glob(filepath.Join(root, pattern))
wwlog.Debug("%v: fullPaths: %v", filepath.Join(root, pattern), fullPaths)
if err != nil {
panic(err)
}
for _, fullPath := range fullPaths {
path, err := filepath.Rel(root, fullPath)
if err != nil {
continue
} else {
kernels = append(kernels, &Kernel{ContainerName: containerName, Path: filepath.Join("/", path)})
}
}
return kernels
}
func FindKernels(containerName string) (kernels collection) {
wwlog.Debug("FindKernels(%v)", containerName)
for _, pattern := range kernelSearchPaths {
kernels = append(kernels, FindKernelsFromPattern(containerName, pattern)...)
}
return kernels
}
func FindAllKernels() (kernels collection) {
wwlog.Debug("FindAllKernels()")
if sources, err := container.ListSources(); err == nil {
for _, source := range sources {
kernels = append(kernels, FindKernels(source)...)
}
} else {
wwlog.Error("%s", err)
}
return kernels
}
func (this *Kernel) version() *version.Version {
return util.ParseVersion(this.Path)
}
func (this *Kernel) Version() string {
version := this.version()
if version == nil {
return ""
} else {
return version.String()
}
}
func (this *Kernel) IsDebug() bool {
return strings.Contains(this.Path, "+debug")
}
func (this *Kernel) IsRescue() bool {
return strings.Contains(this.Path, "-rescue")
}
func (this *Kernel) FullPath() string {
root := container.RootFsDir(this.ContainerName)
return filepath.Join(root, this.Path)
}

View File

@@ -1,168 +1,236 @@
package kernel
import (
"fmt"
"os"
"path"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/testenv"
)
var kernelBuildTests = []struct {
kernelVersion string
kernelName string
kernelFileName string
succeed bool
}{
// kernel naming convention is <base kernel version>-<ABI number>.<upload number>-<flavour>
{"4.3.2-1", "kernel1", "vmlinuz-1.2.3-4.gz", false},
{"1.2.3-4", "kernel1", "vmlinuz-1.2.3-4.gz", true},
{"1.2.3-4.3.1-generic", "kernel1", "vmlinuz-1.2.3-4.3.1-generic.gz", true},
}
func Test_BuildKernel(t *testing.T) {
wwlog.SetLogLevel(wwlog.DEBUG)
for _, tt := range kernelBuildTests {
srvDir, err := os.MkdirTemp(os.TempDir(), "ww-test-srv-*")
assert.NoError(t, err)
conf := warewulfconf.Get()
conf.Paths.WWProvisiondir = srvDir
kernelDir, err := os.MkdirTemp(os.TempDir(), "ww-test-kernel-*")
assert.NoError(t, err)
{
err = os.MkdirAll(path.Join(kernelDir, "boot"), 0755)
assert.NoError(t, err)
err = os.MkdirAll(path.Join(kernelDir, "lib/modules/old-kernel"), 0755)
assert.NoError(t, err)
_, err = os.Create(path.Join(kernelDir, "lib/modules/old-kernel/old-module"))
assert.NoError(t, err)
err = os.MkdirAll(path.Join(kernelDir, "lib/firmware"), 0755)
assert.NoError(t, err)
_, err = os.Create(path.Join(kernelDir, "lib/firmware/test-firmware"))
assert.NoError(t, err)
_, err = os.Create(path.Join(kernelDir, "boot", tt.kernelFileName))
assert.NoError(t, err)
err = os.MkdirAll(path.Join(kernelDir, "lib/modules", tt.kernelVersion, "/nested"), 0755)
assert.NoError(t, err)
_, err = os.Create(path.Join(kernelDir, "lib/modules", tt.kernelVersion, "test-module"))
assert.NoError(t, err)
err = os.Symlink(path.Join(kernelDir, "lib/modules/old-kernel/old-module"), path.Join(kernelDir, "lib/modules", tt.kernelVersion, "symlink-module"))
assert.NoError(t, err)
}
t.Run(tt.kernelName, func(t *testing.T) {
err = Build(tt.kernelVersion, tt.kernelName, kernelDir)
if tt.succeed {
assert.NoError(t, err)
assert.FileExists(t, path.Join(srvDir, "kernel", tt.kernelName, "vmlinuz"))
assert.FileExists(t, path.Join(srvDir, "kernel", tt.kernelName, "kmods.img.gz"))
assert.FileExists(t, path.Join(srvDir, "kernel", tt.kernelName, "kmods.img"))
files, err := util.CpioFiles(path.Join(srvDir, "kernel", tt.kernelName, "kmods.img"))
assert.NoError(t, err)
assert.ElementsMatch(t, files, []string{
"lib/firmware/test-firmware",
"lib/modules/" + tt.kernelVersion + "/symlink-module",
"lib/modules/" + tt.kernelVersion + "/test-module",
"lib/modules/" + tt.kernelVersion + "/nested"})
} else {
assert.Error(t, err)
}
})
os.RemoveAll(srvDir)
os.RemoveAll(kernelDir)
}
}
var kernelFindTests = []struct {
name string
prefix string // kernel name prefix
kernelNames []string
expVer string
expPath string
}{
{
name: "vmlinuz under boot directory ok case",
prefix: "/boot/vmlinuz-%s",
kernelNames: []string{"5.14.0-427.18.1.el9_4.x86_64", "5.14.0-427.24.1.el9_4.x86_64", "4.14.0-427.18.1.el8_4.x86_64"},
expVer: "5.14.0-427.24.1.el9_4.x86_64",
expPath: "/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
},
{
name: "vmlinuz under boot directory ok case 2",
prefix: "/boot/vmlinuz-%s",
kernelNames: []string{"5.15.0-119-generic", "5.14.0-427.24.1.el9_4.x86_64", "6.15.0-119-generic"},
expVer: "6.15.0-119-generic",
expPath: "/boot/vmlinuz-6.15.0-119-generic",
},
{
name: "vmlinuz under boot directory ok case 3",
prefix: "/boot/vmlinuz-%s",
kernelNames: []string{"5.15.0-0-vanilla", "5.14.0-427.24.1.el9_4.x86_64"},
expVer: "5.15.0-0-vanilla",
expPath: "/boot/vmlinuz-5.15.0-0-vanilla",
},
{
// <base kernel version>-<ABI number>.<upload number>-<flavour>
name: "vmlinuz under boot directory ok case (becuase the first version naming is incorrect)",
prefix: "/boot/vmlinuz-%s",
kernelNames: []string{"5.15.0-generic", "5.14.0-427.24.1.el9_4.x86_64", "5.13.0-427.24.1.el9_4.x86_64"},
expVer: "5.14.0-427.24.1.el9_4.x86_64",
expPath: "/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
},
{
name: "vmlinuz under lib modules ok case",
prefix: "/lib/modules/%s/vmlinuz",
kernelNames: []string{"5.14.0-427.18.1.el9_4.x86_64", "5.14.0-427.24.1.el9_4.x86_64"},
expVer: "5.14.0-427.24.1.el9_4.x86_64",
expPath: "/lib/modules/5.14.0-427.24.1.el9_4.x86_64/vmlinuz",
},
{
name: "vmlinuz.gz under boot directory ok case",
prefix: "/boot/vmlinuz-%s.gz",
kernelNames: []string{"5.14.0-427.18.1.el9_4.x86_64", "5.14.0-427.24.1.el9_4.x86_64"},
expVer: "5.14.0-427.24.1.el9_4.x86_64",
expPath: "/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64.gz",
},
{
name: "mixed rescue / debug kernel testing",
prefix: "/boot/vmlinuz-%s",
kernelNames: []string{"0-rescue-eb46964329b146e39518c625feab3ea0", "5.14.0-362.24.1.el9_3.aarch64", "5.14.0-427.31.1.el9_4.aarch64+debug", "5.14.0-284.30.1.el9_2.aarch64", "5.14.0-427.31.1.el9_4.aarch64"},
expVer: "5.14.0-427.31.1.el9_4.aarch64",
expPath: "/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64",
},
}
func Test_FindKernel(t *testing.T) {
wwlog.SetLogLevel(wwlog.DEBUG)
for _, tt := range kernelFindTests {
srvDir, err := os.MkdirTemp(os.TempDir(), "ww-test-srv-*")
assert.NoError(t, err)
conf := warewulfconf.Get()
conf.Paths.WWProvisiondir = srvDir
kernelDir, err := os.MkdirTemp(os.TempDir(), "ww-test-kernel-*")
assert.NoError(t, err)
{
for _, version := range tt.kernelNames {
kernel := fmt.Sprintf(tt.prefix, version)
parent := filepath.Dir(kernel)
err = os.MkdirAll(path.Join(kernelDir, parent), 0755)
assert.NoError(t, err)
_, err := os.Create(path.Join(kernelDir, kernel))
assert.NoError(t, err)
}
}
var tests = map[string]struct {
files []string
version string
path string
}{
"/boot/vmlinuz-* (1)": {
files: []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
version: "5.14.0-427.24.1",
path: "/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
},
"/boot/vmlinuz-* (2)": {
files: []string{
"/boot/vmlinuz-5.15.0-119-generic",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-6.15.0-119-generic",
},
version: "6.15.0-119",
path: "/boot/vmlinuz-6.15.0-119-generic",
},
"/boot/vmlinuz-* (3)": {
files: []string{
"/boot/vmlinuz-5.15.0-0-vanilla",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
},
version: "5.15.0-0",
path: "/boot/vmlinuz-5.15.0-0-vanilla",
},
"/boot/vmlinuz-* (4)": {
files: []string{
"/boot/vmlinuz-5.15.0-generic",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-5.13.0-427.24.1.el9_4.x86_64",
},
version: "5.15.0",
path: "/boot/vmlinuz-5.15.0-generic",
},
"/lib/modules/*/vmlinuz": {
files: []string{
"/lib/modules/5.14.0-427.18.1.el9_4.x86_64/vmlinuz",
"/lib/modules/5.14.0-427.24.1.el9_4.x86_64/vmlinuz",
},
version: "5.14.0-427.24.1",
path: "/lib/modules/5.14.0-427.24.1.el9_4.x86_64/vmlinuz",
},
"/boot/vmlinuz-*.gz": {
files: []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64.gz",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64.gz",
},
version: "5.14.0-427.24.1",
path: "/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64.gz",
},
"ignore rescue and debug kernels": {
files: []string{
"/boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0",
"/boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug",
"/boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64",
},
version: "5.14.0-427.31.1",
path: "/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64",
},
"no kernels": {
files: []string{},
version: "",
path: "",
},
}
t.Run(tt.name, func(t *testing.T) {
kpath, kver, err := FindKernel(kernelDir)
assert.NoError(t, err)
assert.Equal(t, tt.expVer, kver)
assert.Equal(t, filepath.Join(kernelDir, tt.expPath), kpath)
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
rootfs := "/var/lib/warewulf/chroots/testcontainer/rootfs"
for _, file := range tt.files {
env.CreateFile(t, filepath.Join(rootfs, file))
}
kernels := FindKernels("testcontainer")
assert.Equal(t, len(tt.files), len(kernels))
kernel := kernels.Preferred()
if tt.version == "" && tt.path == "" {
assert.Nil(t, kernel)
} else {
assert.Equal(t, "testcontainer", kernel.ContainerName)
assert.Equal(t, tt.version, kernel.Version())
assert.Equal(t, tt.path, kernel.Path)
assert.Equal(t, env.GetPath(filepath.Join(rootfs, tt.path)), kernel.FullPath())
}
})
}
}
func Test_FromNode(t *testing.T) {
tests := map[string]struct {
files []string
override string
path string
}{
"default": {
files: []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
override: "",
path: "/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
},
"override": {
files: []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
override: "/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
path: "/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
"none": {
files: []string{},
override: "",
path: "",
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
rootfs := "/var/lib/warewulf/chroots/testcontainer/rootfs"
for _, file := range tt.files {
env.CreateFile(t, filepath.Join(rootfs, file))
}
node := node.EmptyNode()
node.ContainerName = "testcontainer"
node.Kernel.Override = tt.override
kernel := FromNode(&node)
if tt.path == "" {
assert.Nil(t, kernel)
} else {
assert.Equal(t, "testcontainer", kernel.ContainerName)
assert.Equal(t, tt.path, kernel.Path)
}
})
}
}
func Test_FindAllKernels(t *testing.T) {
tests := map[string]struct {
files map[string][]string
count int
}{
"two containers": {
files: map[string][]string{
"container1": []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
"container2": []string{
"/boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0",
"/boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug",
"/boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64",
},
},
count: 8,
},
"empty": {
files: map[string][]string{},
count: 0,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
for container, files := range tt.files {
rootfs := filepath.Join(filepath.Join("/var/lib/warewulf/chroots", container), "rootfs")
for _, file := range files {
env.CreateFile(t, filepath.Join(rootfs, file))
}
}
kernels := FindAllKernels()
assert.Equal(t, tt.count, len(kernels))
})
}
}
func Test_IsDebugOrRescue(t *testing.T) {
tests := map[string]struct {
path string
debug bool
rescue bool
}{
"default": {
path: "/boot/vmlinuz-1.0.0",
debug: false,
rescue: false,
},
"debug": {
path: "/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug",
debug: true,
rescue: false,
},
"rescue": {
path: "/boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0",
debug: false,
rescue: true,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
kernel := &Kernel{ContainerName: "", Path: tt.path}
assert.Equal(t, tt.debug, kernel.IsDebug())
assert.Equal(t, tt.rescue, kernel.IsRescue())
})
os.RemoveAll(srvDir)
os.RemoveAll(kernelDir)
}
}

View File

@@ -70,9 +70,9 @@ type IpmiConf struct {
}
type KernelConf struct {
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Override string `yaml:"override,omitempty" lopt:"kerneloverride" sopt:"K" comment:"Set kernel override version" json:"override,omitempty"`
Args string `yaml:"args,omitempty" lopt:"kernelargs" sopt:"A" comment:"Set Kernel argument" json:"args,omitempty"`
Version string `yaml:"version,omitempty" lopt:"kernelversion" comment:"Set kernel version" json:"version,omitempty"`
Override string `yaml:"override,omitempty" lopt:"kerneloverride" sopt:"K" comment:"Set kernel override path" json:"override,omitempty"`
Args string `yaml:"args,omitempty" lopt:"kernelargs" sopt:"A" comment:"Set kernel arguments" json:"args,omitempty"`
}
type NetDev struct {

View File

@@ -144,6 +144,12 @@ func (env *TestEnv) ImportFile(t *testing.T, fileName string, inputFileName stri
env.WriteFile(t, fileName, string(buffer))
}
// CreateFile creates an empty file to fileName, creating any necessary intermediate directories
// relative to the test environment.
func (env *TestEnv) CreateFile(t *testing.T, fileName string) {
env.WriteFile(t, fileName, "")
}
// ReadFile returns the content of fileName as converted to a
// string.
//

View File

@@ -7,6 +7,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
@@ -162,7 +163,7 @@ func (this *Node) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *nod
}
upgraded.Ipxe = this.Ipxe
if this.Kernel != nil {
upgraded.Kernel = this.Kernel.Upgrade()
upgraded.Kernel = this.Kernel.Upgrade(this.ContainerName)
} else {
upgraded.Kernel = new(node.KernelConf)
}
@@ -355,7 +356,7 @@ func (this *Profile) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *
}
upgraded.Ipxe = this.Ipxe
if this.Kernel != nil {
upgraded.Kernel = this.Kernel.Upgrade()
upgraded.Kernel = this.Kernel.Upgrade(this.ContainerName)
} else {
upgraded.Kernel = new(node.KernelConf)
}
@@ -489,10 +490,31 @@ type KernelConf struct {
Version string `yaml:"version,omitempty"`
}
func (this *KernelConf) Upgrade() (upgraded *node.KernelConf) {
func (this *KernelConf) Upgrade(containerName string) (upgraded *node.KernelConf) {
upgraded = new(node.KernelConf)
upgraded.Args = this.Args
upgraded.Override = this.Override
kernels := kernel.FindKernels(containerName)
wwlog.Debug("referencing kernels: %v (containerName: %v)", kernels, containerName)
if this.Override != "" {
if version := util.ParseVersion(legacyKernelVersion(this.Override)); version != nil {
for _, kernel_ := range kernels {
wwlog.Debug("checking if kernel '%v' version '%v' from container '%v' matches override '%v'", kernel_, kernel_.Version(), containerName, this.Override)
if kernel_.Version() == version.String() {
upgraded.Override = kernel_.Path
wwlog.Info("kernel override %v -> %v (container %v)", this.Override, upgraded.Override, containerName)
}
}
} else if util.IsFile((&kernel.Kernel{ContainerName: containerName, Path: this.Override}).FullPath()) {
upgraded.Override = this.Override
}
if upgraded.Override == "" {
containerDisplay := "unknown"
if containerName != "" {
containerDisplay = containerName
}
wwlog.Warn("unable to resolve kernel override %v (container %v)", this.Override, containerDisplay)
}
}
upgraded.Version = this.Version
return
}

View File

@@ -5,12 +5,16 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
var nodesYamlUpgradeTests = []struct {
name string
addDefaults bool
replaceOverlays bool
files map[string]string
legacyYaml string
upgradedYaml string
}{
@@ -115,8 +119,6 @@ nodeprofiles:
- generic
leap:
comment: openSUSE leap
kernel:
override: 5.14.21
ipmi:
netmask: 255.255.255.0
network devices:
@@ -682,13 +684,87 @@ nodes:
- debian.interfaces
- wicked
- ignition
`,
},
{
name: "Kernel.Override (legacy)",
addDefaults: false,
replaceOverlays: false,
files: map[string]string{
"/srv/warewulf/kernel/mykernel/version": "1.2.3",
"/var/lib/warewulf/chroots/mycontainer/rootfs/boot/vmlinuz-1.2.3": "",
},
legacyYaml: `
nodeprofiles:
default:
container name: mycontainer
kernel:
override: mykernel
nodes:
n1:
container name: mycontainer
kernel:
override: mykernel
`,
upgradedYaml: `
nodeprofiles:
default:
container name: mycontainer
kernel:
override: /boot/vmlinuz-1.2.3
nodes:
n1:
container name: mycontainer
kernel:
override: /boot/vmlinuz-1.2.3
`,
},
{
name: "Kernel.Override (upgraded)",
addDefaults: false,
replaceOverlays: false,
files: map[string]string{
"/srv/warewulf/kernel/mykernel/version": "1.2.3",
"/var/lib/warewulf/chroots/mycontainer/rootfs/boot/vmlinuz-1.2.3": "",
},
legacyYaml: `
nodeprofiles:
default:
container name: mycontainer
kernel:
override: /boot/vmlinuz-1.2.3
nodes:
n1:
container name: mycontainer
kernel:
override: /boot/vmlinuz-1.2.3
`,
upgradedYaml: `
nodeprofiles:
default:
container name: mycontainer
kernel:
override: /boot/vmlinuz-1.2.3
nodes:
n1:
container name: mycontainer
kernel:
override: /boot/vmlinuz-1.2.3
`,
},
}
func Test_UpgradeNodesYaml(t *testing.T) {
wwlog.SetLogLevel(wwlog.DEBUG)
for _, tt := range nodesYamlUpgradeTests {
t.Run(tt.name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
if tt.files != nil {
for fileName, content := range tt.files {
env.WriteFile(t, fileName, content)
}
}
legacy, err := ParseNodes([]byte(tt.legacyYaml))
assert.NoError(t, err)
upgraded := legacy.Upgrade(tt.addDefaults, tt.replaceOverlays)

View File

@@ -0,0 +1,43 @@
package upgrade
import (
"os"
"path/filepath"
"github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func legacyKernelVersion(kernelName string) string {
wwlog.Debug("legacyKernelVersion(%v)", kernelName)
if kernelName == "" {
return ""
}
kernelVersion, err := os.ReadFile(legacyKernelVersionFile(kernelName))
if err != nil {
return ""
}
wwlog.Debug("legacyKernelVersion(%v) -> %v", kernelName, string(kernelVersion))
return string(kernelVersion)
}
func legacyKernelVersionFile(kernelName string) string {
if kernelName == "" {
return ""
}
if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") {
return ""
}
return filepath.Join(legacyKernelImageDir(kernelName), "version")
}
func legacyKernelImageDir(name string) string {
return filepath.Join(legacyKernelImageTopDir(), name)
}
func legacyKernelImageTopDir() string {
return filepath.Join(config.Get().Paths.WWProvisiondir, "kernel")
}

View File

@@ -0,0 +1,26 @@
package util
import (
"regexp"
"strings"
"github.com/hashicorp/go-version"
)
var (
versionPattern *regexp.Regexp
)
func init() {
versionPattern = regexp.MustCompile(`\d+\.\d+\.\d+(-[\d\.]+|)`)
}
func ParseVersion(versionString string) *version.Version {
matches := versionPattern.FindAllString(versionString, -1)
for i := len(matches) - 1; i >= 0; i-- {
if version_, err := version.NewVersion(strings.TrimSuffix(matches[i], ".")); err == nil {
return version_
}
}
return nil
}

View File

@@ -63,8 +63,6 @@ func parseReq(req *http.Request) (parserInfo, error) {
ret.stage = "ipxe"
} else if stage == "kernel" {
ret.stage = "kernel"
} else if stage == "kmods" {
ret.stage = "kmods"
} else if stage == "container" {
ret.stage = "container"
} else if stage == "overlay-system" {

View File

@@ -48,6 +48,8 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
return
}
wwlog.Debug("stage: %s", rinfo.stage)
wwlog.Info("request from hwaddr:%s ipaddr:%s | stage:%s", rinfo.hwaddr, req.RemoteAddr, rinfo.stage)
if (rinfo.stage == "runtime" || len(rinfo.overlay) > 0) && conf.Warewulf.Secure() {
@@ -62,7 +64,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
"efiboot": "EFI",
"ipxe": "IPXE",
"kernel": "KERNEL",
"kmods": "KMODS_OVERLAY",
"system": "SYSTEM_OVERLAY",
"runtime": "RUNTIME_OVERLAY",
"initramfs": "INITRAMFS"}
@@ -111,22 +112,14 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
NetDevs: remoteNode.NetDevs,
Tags: remoteNode.Tags}
} else if rinfo.stage == "kernel" {
if remoteNode.Kernel.Override != "" {
stage_file = kernel.KernelImage(remoteNode.Kernel.Override)
} else if remoteNode.ContainerName != "" {
stage_file, _, err = kernel.FindKernel(container.RootFsDir(remoteNode.ContainerName))
if err != nil {
wwlog.Error("No kernel found for container %s: %s", remoteNode.ContainerName, err)
kernel_ := kernel.FromNode(&remoteNode)
if kernel_ == nil {
wwlog.Error("No kernel found for node %s", remoteNode.Id())
} else {
stage_file = kernel_.FullPath()
if stage_file == "" {
wwlog.Error("No kernel path found for node %s", remoteNode.Id())
}
} else {
wwlog.Warn("No kernel version set for node %s", remoteNode.Id())
}
} else if rinfo.stage == "kmods" {
if remoteNode.Kernel.Override != "" {
stage_file = kernel.KmodsImage(remoteNode.Kernel.Override)
} else {
wwlog.Warn("No kernel override modules set for node %s", remoteNode.Id())
}
} else if rinfo.stage == "container" {
@@ -168,14 +161,14 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
case "shim.efi":
stage_file = container.ShimFind(containerName)
if stage_file == "" {
wwlog.ErrorExc(fmt.Errorf("could't find shim.efi"), containerName)
wwlog.Error("couldn't find shim.efi for %s", containerName)
w.WriteHeader(http.StatusNotFound)
return
}
case "grub.efi", "grub-tpm.efi", "grubx64.efi", "grubia32.efi", "grubaa64.efi", "grubarm.efi":
stage_file = container.GrubFind(containerName)
if stage_file == "" {
wwlog.ErrorExc(fmt.Errorf("could't find grub.efi"), containerName)
wwlog.Error("could't find grub*.efi for %s", containerName)
w.WriteHeader(http.StatusNotFound)
return
}
@@ -195,7 +188,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
NetDevs: remoteNode.NetDevs,
Tags: remoteNode.Tags}
if stage_file == "" {
wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName)
wwlog.Error("could't find grub.cfg template for %s", containerName)
w.WriteHeader(http.StatusNotFound)
return
}
@@ -222,17 +215,22 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
wwlog.Warn("No conainer set for node %s", remoteNode.Id())
}
} else if rinfo.stage == "initramfs" {
if remoteNode.ContainerName != "" {
_, kver, err := kernel.FindKernel(container.RootFsDir(remoteNode.ContainerName))
if err != nil {
wwlog.Error("No kernel found for initramfs for container %s: %s", remoteNode.ContainerName, err)
kver := remoteNode.Kernel.Version
if kver == "" || remoteNode.Kernel.Override != "" {
kernel_ := kernel.FromNode(&remoteNode)
if kernel_ != nil {
kver = kernel_.Version()
}
stage_file, err = container.InitramfsBootPath(remoteNode.ContainerName, kver)
if err != nil {
wwlog.Error("No initramfs found for container %s: %s", remoteNode.ContainerName, err)
}
if kver != "" {
initramfs := container.FindInitramfs(remoteNode.ContainerName, kver)
if initramfs == nil {
wwlog.Error("No initramfs found for kernel %s in container %s", kver, remoteNode.ContainerName)
} else {
stage_file = initramfs.FullPath()
}
} else {
wwlog.Warn("No container set for node %s", remoteNode.Id())
wwlog.Error("No initramfs found: unable to determine kernel version for node %s", remoteNode.Id())
}
}

View File

@@ -37,6 +37,7 @@ var provisionSendTests = []struct {
func Test_ProvisionSend(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
env.WriteFile(t, "etc/warewulf/nodes.conf", `nodeprofiles:
default:
@@ -63,51 +64,28 @@ nodes:
ipxe template: test
kernel:
override: 1.1.1`)
// create a arp file as for grub we look up the ip address through the arp cache
env.WriteFile(t, "arpcache", `IP address HW type Flags HW address Mask Device
// create a arp file as for grub we look up the ip address through the arp cache
env.WriteFile(t, "/var/tmp/arpcache", `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.11 0x1 0x2 00:00:00:00:ff:ff * dummy
10.10.10.12 0x1 0x2 00:00:00:00:00:ff * dummy`)
SetArpFile(path.Join(env.BaseDir, "arpcache"))
conf := warewulfconf.Get()
containerDir, imageDirErr := os.MkdirTemp(os.TempDir(), "ww-test-container-*")
assert.NoError(t, imageDirErr)
defer os.RemoveAll(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))
{
_, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi"))
assert.NoError(t, err)
}
assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/"), 0700))
{
_, 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)
}
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))
}
assert.NoError(t, os.MkdirAll(path.Join(conf.Paths.Sysconfdir, "warewulf/grub"), 0700))
{
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.Sysconfdir, "warewulf/grub", "grub.cfg.ww"), []byte("{{ .Tags.GrubMenuEntry }}"), 0600))
}
prevArpFile := arpFile
arpFile = env.GetPath("/var/tmp/arpcache")
defer func() {
arpFile = prevArpFile
}()
env.CreateFile(t, "/var/lib/warewulf/chroots/suse/rootfs/boot/vmlinuz-1.1.0")
env.CreateFile(t, "/var/lib/warewulf/chroots/suse/rootfs/usr/lib64/efi/shim.efi")
env.CreateFile(t, "/var/lib/warewulf/chroots/suse/rootfs/usr/share/efi/x86_64/grub.efi")
env.CreateFile(t, "/var/lib/warewulf/chroots/suse/rootfs/boot/initramfs-1.1.0.img")
env.WriteFile(t, "/etc/warewulf/ipxe/test.ipxe", "{{.KernelOverride}}{{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}")
env.WriteFile(t, "/etc/warewulf/grub/grub.cfg.ww", "{{ .Tags.GrubMenuEntry }}")
dbErr := LoadNodeDB()
assert.NoError(t, dbErr)
conf := warewulfconf.Get()
secureFalse := false
conf.Warewulf.SecureP = &secureFalse
assert.NoError(t, os.MkdirAll(path.Join(conf.Paths.OverlayProvisiondir(), "n1"), 0700))

View File

@@ -79,10 +79,6 @@ func init() {
arpFile = "/proc/net/arp"
}
func SetArpFile(newName string) {
arpFile = newName
}
/*
returns the mac address if it has an entry in the arp cache
*/

View File

@@ -70,7 +70,6 @@ func RunServer() error {
wwHandler.HandleFunc("/ipxe/", ProvisionSend)
wwHandler.HandleFunc("/efiboot/", ProvisionSend)
wwHandler.HandleFunc("/kernel/", ProvisionSend)
wwHandler.HandleFunc("/kmods/", ProvisionSend)
wwHandler.HandleFunc("/container/", ProvisionSend)
wwHandler.HandleFunc("/overlay-system/", ProvisionSend)
wwHandler.HandleFunc("/overlay-runtime/", ProvisionSend)

View File

@@ -2,72 +2,40 @@
Kernel Management
=================
Node Kernels
============
Warewulf nodes require a Linux kernel to boot. There are multiple ways
to do this, but the default and easiest way is to install the kernel
you wish to use for a particular container into the container.
Warewulf will locate the kernel automatically within the container and
by default use that kernel for any node configured to use that
Warewulf nodes require a Linux kernel to boot. As of Warewulf v4.6, the kernel
you wish to use must be present in the relevant container. Warewulf locates and
provisions the kernel automatically for any node configured to use that
container image.
You can see what kernel is included in a container by using the
You can see what kernels are available in imported containers by using the
``wwctl container list`` command:
.. code-block:: console
# wwctl container list
CONTAINER NAME NODES KERNEL VERSION CREATION TIME MODIFICATION TIME SIZE
alpine 0 05 Jun 23 20:02 MDT 05 Jun 23 20:02 MDT 17.9 MiB
rocky-8 1 4.18.0-372.13.1.el8_6.x86_64 17 Jan 23 23:48 MST 06 Apr 23 09:40 MDT 2.4 GiB
# wwctl kernel list
Container Kernel Version Preferred Nodes
--------- ------ ------- --------- -----
newroot-test /boot/vmlinuz-5.14.0-427.37.1.el9_4.aarch64 5.14.0-427.37.1 true 0
newroot-test /lib/modules/5.14.0-427.37.1.el9_4.aarch64/vmlinuz 5.14.0-427.37.1 false 0
rocky-8 /boot/vmlinuz-4.18.0-372.13.1.el8_6.x86_64 4.18.0-372.13.1 true 2
rocky-8 /lib/modules/4.18.0-372.13.1.el8_6.x86_64/vmlinuz 4.18.0-372.13.1 false 0
rocky-9.3 /lib/modules/5.14.0-362.13.1.el9_3.aarch64/vmlinuz 5.14.0-362.13.1 true 0
rockylinux-9-custom /lib/modules/5.14.0-427.40.1.el9_4.aarch64/vmlinuz 5.14.0-427.40.1 true 0
Here you will notice the alpine contianer that was imported has no
kernel within it, and the rocky container includes a kernel.
Kernel Version and Override
===========================
This model was introduced in Warewulf v4.3. Previously, Warewulf
managed the kernel and the container separately, which made it hard to
build and distribute containers that have custom drivers and/or
configurations included (e.g. OFED, GPUs, etc.).
If a container includes multiple kernels, the desired kernel may be selected by
specifying the desired version or an explicit override.
Kernel Overrides
================
It is still possible to specify a kernel for a container if it doesn't
include it's own kernel, or if you wish to override the default kernel
by using the ``kernel override`` capability.
You can specify this option with the ``--kerneloverride`` option to
``wwctl node set`` or ``wwctl profile set`` commands.
In this case you will also need to import a kernel specifically into
Warewulf for this purpose using the ``wwctl kernel import`` command as
follows:
``--kernelversion`` specifies the desired kernel version.
.. code-block:: console
# wwctl kernel import $(uname -r)
4.18.0-305.3.1.el8_4.x86_64: Done
# wwctl node set n1 --kernelversion=4.18.0-372.13.1
This process will import not only the kernel image itself, but also
all of the kernel modules and firmware associated with this kernel.
Listing All Imported Kernels
----------------------------
Once the kernel has been imported, you can list them all with the
following command:
``--kerneloverride`` specifies the full path to the desired kernel.
.. code-block:: console
# wwctl kernel list
VNFS NAME NODES
4.18.0-305.3.1.el8_4.x86_64 0
# wwctl kernel list
KERNEL NAME KERNEL VERSION NODES
4.18.0-305.3.1.el8_4.x86_64 0
Once a kernel has been imported you can configure it to boot compute
nodes.
# wwctl node set n1 --kerneloverride=/boot/vmlinuz-4.18.0-372.13.1.el8_6.x86_64

View File

@@ -149,27 +149,6 @@ And you can check that the container name is set for ``n001``:
# wwctl node list -a n001 | grep Container
n0000 Container -- rocky-8
Configuring the Node's Kernel
-----------------------------
While the recommended method for assigning a kernel in v4.3 and beyond
is to include it in the container / node image, a kernel can still be
specified as an override at the node or profile. To illustrate this,
we import the most recent kernel from a openSUSE Tumbleweed release.
.. code-block:: console
# wwctl container import docker://registry.opensuse.org/science/warewulf/tumbleweed/containerfile/kernel:latest tw
# wwctl kernel import -DC tw
# wwctl kernel list
KERNEL NAME KERNEL VERSION NODES
tw 6.1.10-1-default 0
# wwctl node set --kerneloverride tw n001
Are you sure you want to modify 1 nodes(s): y
# wwctl node list -a n001 | grep kerneloverride
n001 kerneloverride -- tw
Configuring the Node's Network
------------------------------

View File

@@ -24,7 +24,7 @@ and substitute your cluster node's MAC addres in place of 00:00:00:00:00:00.)
imgextract --name container ${uri}?stage=container&compress=gz
imgextract --name system ${uri}?stage=system&compress=gz
imgextract --name runtime ${uri}?stage=runtime&compress=gz
boot kernel initrd=container initrd=kmods initrd=system initrd=runtime
boot kernel initrd=container initrd=system initrd=runtime
- The ``uri`` variable points to ``warewulfd`` for future reference.
This includes the cluster node's MAC address so that Warewulf knows what container and overlays to provide.
@@ -48,7 +48,7 @@ To do so, substitute the ``boot`` command above.
.. code-block::
boot kernel initrd=container initrd=kmods initrd=system initrd=runtime rdinit=/bin/sh
boot kernel initrd=container initrd=system initrd=runtime rdinit=/bin/sh
.. note::