diff --git a/CHANGELOG.md b/CHANGELOG.md index df8bb4887..484d20b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 --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 ` #508 - Remove `wwctl overlay build --host` #1419 - Remove `wwctl overlay build --nodes` #1419 +- Remove `wwctl kernel ` #1556 ### Fixed diff --git a/dracut/modules.d/90wwinit/load-wwinit.sh b/dracut/modules.d/90wwinit/load-wwinit.sh index b115857cd..97d7e774c 100755 --- a/dracut/modules.d/90wwinit/load-wwinit.sh +++ b/dracut/modules.d/90wwinit/load-wwinit.sh @@ -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 diff --git a/dracut/modules.d/90wwinit/parse-wwinit.sh b/dracut/modules.d/90wwinit/parse-wwinit.sh index 8479796de..28d0df239 100755 --- a/dracut/modules.d/90wwinit/parse-wwinit.sh +++ b/dracut/modules.d/90wwinit/parse-wwinit.sh @@ -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" ] diff --git a/etc/ipxe/default.ipxe b/etc/ipxe/default.ipxe index fa4ed4592..8133ea7f5 100644 --- a/etc/ipxe/default.ipxe +++ b/etc/ipxe/default.ipxe @@ -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 diff --git a/etc/ipxe/dracut.ipxe b/etc/ipxe/dracut.ipxe index d002689ff..a0af07144 100644 --- a/etc/ipxe/dracut.ipxe +++ b/etc/ipxe/dracut.ipxe @@ -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 diff --git a/internal/app/wwctl/completions/completions.go b/internal/app/wwctl/completions/completions.go new file mode 100644 index 000000000..56f2d0925 --- /dev/null +++ b/internal/app/wwctl/completions/completions.go @@ -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 +} diff --git a/internal/app/wwctl/container/list/main_test.go b/internal/app/wwctl/container/list/main_test.go index 1baf769c9..27e8d7707 100644 --- a/internal/app/wwctl/container/list/main_test.go +++ b/internal/app/wwctl/container/list/main_test.go @@ -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) diff --git a/internal/app/wwctl/kernel/delete/main.go b/internal/app/wwctl/kernel/delete/main.go deleted file mode 100644 index aae8f75ac..000000000 --- a/internal/app/wwctl/kernel/delete/main.go +++ /dev/null @@ -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 -} diff --git a/internal/app/wwctl/kernel/delete/root.go b/internal/app/wwctl/kernel/delete/root.go deleted file mode 100644 index 187ea731b..000000000 --- a/internal/app/wwctl/kernel/delete/root.go +++ /dev/null @@ -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 -} diff --git a/internal/app/wwctl/kernel/imprt/main.go b/internal/app/wwctl/kernel/imprt/main.go deleted file mode 100644 index 41ef9c5d6..000000000 --- a/internal/app/wwctl/kernel/imprt/main.go +++ /dev/null @@ -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 -} diff --git a/internal/app/wwctl/kernel/imprt/root.go b/internal/app/wwctl/kernel/imprt/root.go deleted file mode 100644 index 8a071d311..000000000 --- a/internal/app/wwctl/kernel/imprt/root.go +++ /dev/null @@ -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 -} diff --git a/internal/app/wwctl/kernel/list/main.go b/internal/app/wwctl/kernel/list/main.go index d11aae14c..9262a431b 100644 --- a/internal/app/wwctl/kernel/list/main.go +++ b/internal/app/wwctl/kernel/list/main.go @@ -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 { - - kernels, err := kernel.ListKernels() + 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_]++ + } + } - nconfig, _ := node.New() - nodes, _ := nconfig.FindAllNodes() - nodemap := make(map[string]int) - - for _, n := range nodes { - nodemap[n.Kernel.Override]++ + sources, err := container.ListSources() + if err != nil { + return err } - 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 } diff --git a/internal/app/wwctl/kernel/list/main_test.go b/internal/app/wwctl/kernel/list/main_test.go new file mode 100644 index 000000000..bc6498ae2 --- /dev/null +++ b/internal/app/wwctl/kernel/list/main_test.go @@ -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())) + }) + } +} diff --git a/internal/app/wwctl/kernel/list/root.go b/internal/app/wwctl/kernel/list/root.go index 08fdbd095..20ef44c77 100644 --- a/internal/app/wwctl/kernel/list/root.go +++ b/internal/app/wwctl/kernel/list/root.go @@ -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"}, diff --git a/internal/app/wwctl/kernel/root.go b/internal/app/wwctl/kernel/root.go index 5c1cadfeb..1d151f9e3 100644 --- a/internal/app/wwctl/kernel/root.go +++ b/internal/app/wwctl/kernel/root.go @@ -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. diff --git a/internal/app/wwctl/node/add/root.go b/internal/app/wwctl/node/add/root.go index 4626e3255..154a0f0b5 100644 --- a/internal/app/wwctl/node/add/root.go +++ b/internal/app/wwctl/node/add/root.go @@ -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) { diff --git a/internal/app/wwctl/node/set/root.go b/internal/app/wwctl/node/set/root.go index 6821614d6..2c0fda76c 100644 --- a/internal/app/wwctl/node/set/root.go +++ b/internal/app/wwctl/node/set/root.go @@ -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) { diff --git a/internal/app/wwctl/profile/add/root.go b/internal/app/wwctl/profile/add/root.go index cb9d30af0..8fc9b85fb 100644 --- a/internal/app/wwctl/profile/add/root.go +++ b/internal/app/wwctl/profile/add/root.go @@ -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) { diff --git a/internal/app/wwctl/profile/set/root.go b/internal/app/wwctl/profile/set/root.go index b341bfd3c..5af46b890 100644 --- a/internal/app/wwctl/profile/set/root.go +++ b/internal/app/wwctl/profile/set/root.go @@ -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) { diff --git a/internal/pkg/api/container/container.go b/internal/pkg/api/container/container.go index 07b1e8271..221f47204 100644 --- a/internal/pkg/api/container/container.go +++ b/internal/pkg/api/container/container.go @@ -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 { diff --git a/internal/pkg/container/config.go b/internal/pkg/container/config.go index afbd3b40b..9647f82fc 100644 --- a/internal/pkg/container/config.go +++ b/internal/pkg/container/config.go @@ -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") -} diff --git a/internal/pkg/container/config_test.go b/internal/pkg/container/config_test.go deleted file mode 100644 index 904bc6b76..000000000 --- a/internal/pkg/container/config_test.go +++ /dev/null @@ -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))) - } - } -} diff --git a/internal/pkg/container/initramfs.go b/internal/pkg/container/initramfs.go new file mode 100644 index 000000000..dce238a73 --- /dev/null +++ b/internal/pkg/container/initramfs.go @@ -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 +} diff --git a/internal/pkg/container/initramfs_test.go b/internal/pkg/container/initramfs_test.go new file mode 100644 index 000000000..cef93790e --- /dev/null +++ b/internal/pkg/container/initramfs_test.go @@ -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) + } + }) + } +} diff --git a/internal/pkg/kernel/kernel.go b/internal/pkg/kernel/kernel.go index ad683eb88..0cff27bcb 100644 --- a/internal/pkg/kernel/kernel.go +++ b/internal/pkg/kernel/kernel.go @@ -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 -.- - kernelVersionRegex = `(\d+\.\d+\.\d+)-((\d+\.*){1,})` ) -func KernelImageTopDir() string { - conf := warewulfconf.Get() - return path.Join(conf.Paths.WWProvisiondir, "kernel") -} - -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 "" - } +type collection []*Kernel - return path.Join(KernelImageTopDir(), kernelName, "vmlinuz") +func (k collection) Len() int { + return len(k) } -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) 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 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) Swap(i, j int) { + k[i], k[j] = k[j], k[i] } -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 (k collection) Sort() { + sort.Sort(k) } -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()) - +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 + } } - - return ret, nil + return 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) +func (k collection) Version(version string) *Kernel { + for _, kernel := range k { + if kernel.IsDebug() || kernel.IsRescue() { + continue + } else if strings.HasPrefix(kernel.Version(), version) { + return kernel + } } + return nil +} - err = os.MkdirAll(path.Dir(driversDestination), 0755) - if err != nil { - return fmt.Errorf("failed to create driver dest: %w", err) - } +type Kernel struct { + Path string + ContainerName string +} - err = os.MkdirAll(path.Dir(versionDestination), 0755) - if err != nil { - return fmt.Errorf("failed to create version dest: %s", err) +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() } +} - kernelSource, kernelVersFound, err := FindKernel(root) +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 { - 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) + panic(err) } - - wwlog.Verbose("Setting up Kernel") - if _, err := os.Stat(kernelSource); err == nil { - kernel, err := os.Open(kernelSource) + for _, fullPath := range fullPaths { + path, err := filepath.Rel(root, fullPath) if err != nil { - return fmt.Errorf("could not open kernel: %w", err) - } - 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)) + continue } else { - kernelDriversSpecific = append(kernelDriversSpecific, kPath) + kernels = append(kernels, &Kernel{ContainerName: containerName, Path: filepath.Join("/", path)}) } } - 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) + return kernels } -/* -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 FindKernels(containerName string) (kernels collection) { + wwlog.Debug("FindKernels(%v)", containerName) + for _, pattern := range kernelSearchPaths { + kernels = append(kernels, FindKernelsFromPattern(containerName, pattern)...) + } + return kernels } -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 +func FindAllKernels() (kernels collection) { + wwlog.Debug("FindAllKernels()") + if sources, err := container.ListSources(); err == nil { + for _, source := range sources { + kernels = append(kernels, FindKernels(source)...) } - val = newVal + } else { + wwlog.Error("%s", err) } - return val, nil + return kernels } -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 (this *Kernel) version() *version.Version { + return util.ParseVersion(this.Path) } -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 - - if len(verRe) > 0 && len(verRe[0]) > 2 { - // verRe[0][1] -> - // verRe[0][2] -> - 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 +func (this *Kernel) Version() string { + version := this.version() + if version == nil { + return "" + } else { + return version.String() } - 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 { - 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, - } - } +func (this *Kernel) IsDebug() bool { + return strings.Contains(this.Path, "+debug") +} - 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 - } - } - return "", "", fmt.Errorf("could not find kernel version") +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) } diff --git a/internal/pkg/kernel/kernel_test.go b/internal/pkg/kernel/kernel_test.go index 7f96da736..5008df95d 100644 --- a/internal/pkg/kernel/kernel_test.go +++ b/internal/pkg/kernel/kernel_test.go @@ -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 -.- - {"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_FindKernel(t *testing.T) { + 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: "", + }, + } -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"}) + 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.Error(t, err) + 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()) } }) - 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", - }, - { - // -.- - 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_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_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) +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)) + }) + } +} - 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) +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) } } diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index cc48b5d35..e518ea042 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -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 { diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go index 24ab8e501..0b0588078 100644 --- a/internal/pkg/testenv/testenv.go +++ b/internal/pkg/testenv/testenv.go @@ -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. // diff --git a/internal/pkg/upgrade/node.go b/internal/pkg/upgrade/node.go index 2c60ad02a..aaea858a3 100644 --- a/internal/pkg/upgrade/node.go +++ b/internal/pkg/upgrade/node.go @@ -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 } diff --git a/internal/pkg/upgrade/node_test.go b/internal/pkg/upgrade/node_test.go index 1f28f5637..8630834d4 100644 --- a/internal/pkg/upgrade/node_test.go +++ b/internal/pkg/upgrade/node_test.go @@ -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) diff --git a/internal/pkg/upgrade/util.go b/internal/pkg/upgrade/util.go new file mode 100644 index 000000000..188db6e1b --- /dev/null +++ b/internal/pkg/upgrade/util.go @@ -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") +} diff --git a/internal/pkg/util/version.go b/internal/pkg/util/version.go new file mode 100644 index 000000000..4b9d1f915 --- /dev/null +++ b/internal/pkg/util/version.go @@ -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 +} diff --git a/internal/pkg/warewulfd/parser.go b/internal/pkg/warewulfd/parser.go index e542982f8..2d3e846da 100644 --- a/internal/pkg/warewulfd/parser.go +++ b/internal/pkg/warewulfd/parser.go @@ -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" { diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index f332b7cf1..db5ce823e 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -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 { - 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()) + stage_file = kernel_.FullPath() + if stage_file == "" { + wwlog.Error("No kernel path found 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()) } } diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go index c9840950b..9c679aa76 100644 --- a/internal/pkg/warewulfd/provision_test.go +++ b/internal/pkg/warewulfd/provision_test.go @@ -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)) diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go index 7938d3434..b37ff437b 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go @@ -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 */ diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go index 0ab8f4915..15dc42200 100644 --- a/internal/pkg/warewulfd/warewulfd.go +++ b/internal/pkg/warewulfd/warewulfd.go @@ -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) diff --git a/userdocs/contents/kernel.rst b/userdocs/contents/kernel.rst index a2eb1715b..46a4b47fd 100644 --- a/userdocs/contents/kernel.rst +++ b/userdocs/contents/kernel.rst @@ -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 - -Here you will notice the alpine contianer that was imported has no -kernel within it, and the rocky container includes a kernel. - -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.). - -Kernel Overrides -================ + # 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 -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. +Kernel Version and Override +=========================== -You can specify this option with the ``--kerneloverride`` option to -``wwctl node set`` or ``wwctl profile set`` commands. +If a container includes multiple kernels, the desired kernel may be selected by +specifying the desired version or an explicit override. -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 diff --git a/userdocs/contents/nodeconfig.rst b/userdocs/contents/nodeconfig.rst index a2d3fef3c..b25f94cb0 100644 --- a/userdocs/contents/nodeconfig.rst +++ b/userdocs/contents/nodeconfig.rst @@ -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 ------------------------------ diff --git a/userdocs/contents/troubleshooting.rst b/userdocs/contents/troubleshooting.rst index 4c3adb5c9..e582228de 100644 --- a/userdocs/contents/troubleshooting.rst +++ b/userdocs/contents/troubleshooting.rst @@ -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::