diff --git a/internal/app/wwctl/kernel/delete/main.go b/internal/app/wwctl/kernel/delete/main.go new file mode 100644 index 00000000..c289a27d --- /dev/null +++ b/internal/app/wwctl/kernel/delete/main.go @@ -0,0 +1,41 @@ +package delete + +import ( + "fmt" + "os" + + "github.com/hpcng/warewulf/internal/pkg/kernel" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + nodeDB, err := node.New() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not open nodeDB: %s\n", err) + os.Exit(1) + } + + nodes, _ := nodeDB.FindAllNodes() + +ARG_LOOP: + for _, arg := range args { + for _, n := range nodes { + if n.KernelVersion.Get() == arg { + wwlog.Printf(wwlog.ERROR, "Kernel is configured for nodes, skipping: %s\n", arg) + continue ARG_LOOP + } + } + + err := kernel.DeleteKernel(arg) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not delete kernel: %s\n", 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 new file mode 100644 index 00000000..5fc68bea --- /dev/null +++ b/internal/app/wwctl/kernel/delete/root.go @@ -0,0 +1,33 @@ +package delete + +import ( + "github.com/hpcng/warewulf/internal/pkg/kernel" + "github.com/spf13/cobra" +) + +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.", + 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 new file mode 100644 index 00000000..97e3e219 --- /dev/null +++ b/internal/app/wwctl/kernel/imprt/main.go @@ -0,0 +1,92 @@ +package imprt + +import ( + "fmt" + "os" + + "github.com/hpcng/warewulf/internal/pkg/container" + "github.com/hpcng/warewulf/internal/pkg/kernel" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + if len(args) == 0 && !OptDetect { + wwlog.Printf(wwlog.ERROR, "the '--detect' flag is needed, if no kernel version is suppiled") + os.Exit(1) + } + if OptDetect && (OptRoot == "" || OptContainer == "") { + wwlog.Printf(wwlog.ERROR, "the '--detect flag needs the '--container' or '--root' flag") + os.Exit(1) + } + // Checking if container flag was set, then overwriting OptRoot + if OptContainer != "" { + if container.ValidSource(OptContainer) { + OptRoot = container.RootFsDir(OptContainer) + } else { + wwlog.Printf(wwlog.ERROR, " %s is not a valid container", OptContainer) + os.Exit(1) + } + } + + var kernelVersion string + var err error + if len(args) > 0 { + kernelVersion = args[0] + } else { + kernelVersion, err = kernel.FindKernelVersion(OptRoot) + if err != nil { + wwlog.Printf(wwlog.ERROR, "could not detect kernel under %s\n", OptRoot) + os.Exit(1) + } + } + kernelName := kernelVersion + if len(args) > 1 { + kernelName = args[1] + } else if OptDetect && (OptContainer != "") { + kernelName = OptContainer + } + output, err := kernel.Build(kernelVersion, kernelName, OptRoot) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Failed building kernel: %s\n", err) + os.Exit(1) + } else { + fmt.Printf("%s: %s\n", kernelName, output) + } + + if SetDefault { + + nodeDB, err := node.New() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not open node configuration: %s\n", err) + os.Exit(1) + } + //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.Printf(wwlog.DEBUG, "Looking for profile default: %s\n", profile.Id.Get()) + if profile.Id.Get() == "default" { + wwlog.Printf(wwlog.DEBUG, "Found profile default, setting kernel version to: %s\n", args[0]) + profile.KernelVersion.Set(args[0]) + err := nodeDB.ProfileUpdate(profile) + if err != nil { + return errors.Wrap(err, "failed to update node profile") + } + } + } + err = nodeDB.Persist() + if err != nil { + return errors.Wrap(err, "failed to persist nodedb") + } + fmt.Printf("Set default kernel version to: %s\n", args[0]) + err = warewulfd.DaemonReload() + if err != nil { + return errors.Wrap(err, "failed to reload warewulf daemon") + } + } + + return nil +} diff --git a/internal/app/wwctl/kernel/imprt/root.go b/internal/app/wwctl/kernel/imprt/root.go new file mode 100644 index 00000000..c8521a7b --- /dev/null +++ b/internal/app/wwctl/kernel/imprt/root.go @@ -0,0 +1,46 @@ +package imprt + +import ( + "log" + + "github.com/hpcng/warewulf/internal/pkg/container" + "github.com/spf13/cobra" +) + +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", + 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 new file mode 100644 index 00000000..a3a8d4ad --- /dev/null +++ b/internal/app/wwctl/kernel/list/main.go @@ -0,0 +1,35 @@ +package list + +import ( + "fmt" + "os" + + "github.com/hpcng/warewulf/internal/pkg/kernel" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + kernels, err := kernel.ListKernels() + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + os.Exit(1) + } + + nconfig, _ := node.New() + nodes, _ := nconfig.FindAllNodes() + nodemap := make(map[string]int) + + for _, n := range nodes { + nodemap[n.KernelVersion.Get()]++ + } + + 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]) + } + + return nil +} diff --git a/internal/app/wwctl/kernel/list/root.go b/internal/app/wwctl/kernel/list/root.go new file mode 100644 index 00000000..fba84101 --- /dev/null +++ b/internal/app/wwctl/kernel/list/root.go @@ -0,0 +1,23 @@ +package list + +import "github.com/spf13/cobra" + +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.", + RunE: CobraRunE, + Args: cobra.ExactArgs(0), + Aliases: []string{"ls"}, + } +) + +func init() { +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/kernel/root.go b/internal/app/wwctl/kernel/root.go new file mode 100644 index 00000000..bb0cdb8d --- /dev/null +++ b/internal/app/wwctl/kernel/root.go @@ -0,0 +1,28 @@ +package kernel + +import ( + "github.com/hpcng/warewulf/internal/app/wwctl/kernel/delete" + "github.com/hpcng/warewulf/internal/app/wwctl/kernel/imprt" + "github.com/hpcng/warewulf/internal/app/wwctl/kernel/list" + "github.com/spf13/cobra" +) + +var ( + baseCmd = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "kernel COMMAND [OPTIONS]", + Short: "Kernel Image Management", + Long: "This command manages Warewulf Kernels used for bootstrapping nodes", + } +) + +func init() { + baseCmd.AddCommand(imprt.GetCommand()) + baseCmd.AddCommand(list.GetCommand()) + baseCmd.AddCommand(delete.GetCommand()) +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/node/list/main.go b/internal/app/wwctl/node/list/main.go index 76db8201..0e6845c8 100644 --- a/internal/app/wwctl/node/list/main.go +++ b/internal/app/wwctl/node/list/main.go @@ -41,6 +41,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { fmt.Printf("%-20s %-18s %-12s %t\n", node.Id.Get(), "Discoverable", node.Discoverable.Source(), node.Discoverable.PrintB()) fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Container", node.ContainerName.Source(), node.ContainerName.Print()) + fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Kernel", node.KernelVersion.Source(), node.KernelVersion.Print()) fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "KernelArgs", node.KernelArgs.Source(), node.KernelArgs.Print()) fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "SystemOverlay", node.SystemOverlay.Source(), node.SystemOverlay.Print()) fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "RuntimeOverlay", node.RuntimeOverlay.Source(), node.RuntimeOverlay.Print()) @@ -96,11 +97,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } } else if ShowLong { - fmt.Printf("%-22s %-35s %s\n", "NODE NAME", "CONTAINER", "OVERLAYS (S/R)") + fmt.Printf("%-22s %-26s %-35s %s\n", "NODE NAME", "KERNEL", "CONTAINER", "OVERLAYS (S/R)") fmt.Println(strings.Repeat("=", 120)) for _, node := range node.FilterByName(nodes, args) { - fmt.Printf("%-22s %-35s %s\n", node.Id.Get(), node.ContainerName.Print(), node.SystemOverlay.Print()+"/"+node.RuntimeOverlay.Print()) + fmt.Printf("%-22s %-26s %-35s %s\n", node.Id.Get(), node.KernelVersion.Print(), node.ContainerName.Print(), node.SystemOverlay.Print()+"/"+node.RuntimeOverlay.Print()) } } else { diff --git a/internal/app/wwctl/node/set/main.go b/internal/app/wwctl/node/set/main.go index 2e2c4861..59f3e7c5 100644 --- a/internal/app/wwctl/node/set/main.go +++ b/internal/app/wwctl/node/set/main.go @@ -70,6 +70,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error { n.AssetKey.Set(SetAssetKey) } + if SetKernel != "" { + wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting kernel to: %s\n", n.Id.Get(), SetKernel) + n.KernelVersion.Set(SetKernel) + } + if SetKernelArgs != "" { wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting kernel args to: %s\n", n.Id.Get(), SetKernelArgs) n.KernelArgs.Set(SetKernelArgs) diff --git a/internal/app/wwctl/node/set/root.go b/internal/app/wwctl/node/set/root.go index 0dcbc865..a3feec62 100644 --- a/internal/app/wwctl/node/set/root.go +++ b/internal/app/wwctl/node/set/root.go @@ -4,6 +4,7 @@ import ( "log" "github.com/hpcng/warewulf/internal/pkg/container" + "github.com/hpcng/warewulf/internal/pkg/kernel" "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/overlay" "github.com/spf13/cobra" @@ -33,6 +34,7 @@ var ( } SetComment string SetContainer string + SetKernel string SetKernelArgs string SetNetName string SetNetDev string @@ -80,6 +82,13 @@ func init() { }); err != nil { log.Println(err) } + baseCmd.PersistentFlags().StringVarP(&SetKernel, "kernel", "K", "", "Set Kernel version for nodes") + if err := baseCmd.RegisterFlagCompletionFunc("kernel", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + list, _ := kernel.ListKernels() + return list, cobra.ShellCompDirectiveNoFileComp + }); err != nil { + log.Println(err) + } baseCmd.PersistentFlags().StringVarP(&SetKernelArgs, "kernelargs", "A", "", "Set Kernel argument for nodes") baseCmd.PersistentFlags().StringVarP(&SetClusterName, "cluster", "c", "", "Set the node's cluster group") baseCmd.PersistentFlags().StringVar(&SetIpxe, "ipxe", "", "Set the node's iPXE template name") diff --git a/internal/app/wwctl/profile/list/main.go b/internal/app/wwctl/profile/list/main.go index d1120cbc..a419952e 100644 --- a/internal/app/wwctl/profile/list/main.go +++ b/internal/app/wwctl/profile/list/main.go @@ -33,6 +33,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Cluster", profile.ClusterName.Print()) fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Container", profile.ContainerName.Print()) + fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Kernel", profile.KernelVersion.Print()) fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "KernelArgs", profile.KernelArgs.Print()) fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Init", profile.Init.Print()) fmt.Printf("%-20s %-18s %s\n", profile.Id.Get(), "Root", profile.Root.Print()) diff --git a/internal/app/wwctl/profile/set/main.go b/internal/app/wwctl/profile/set/main.go index e4957be3..09d4c288 100644 --- a/internal/app/wwctl/profile/set/main.go +++ b/internal/app/wwctl/profile/set/main.go @@ -75,8 +75,13 @@ func CobraRunE(cmd *cobra.Command, args []string) error { p.AssetKey.Set(SetAssetKey) } + if SetKernel != "" { + wwlog.Printf(wwlog.VERBOSE, "Profile: %s, Setting Kernel to: %s\n", p.Id.Get(), SetKernel) + p.KernelVersion.Set(SetKernel) + } + if SetKernelArgs != "" { - wwlog.Printf(wwlog.VERBOSE, "Profile: %s, Setting kernel args to: %s\n", p.Id.Get(), SetKernelArgs) + wwlog.Printf(wwlog.VERBOSE, "Profile: %s, Setting Kernel args to: %s\n", p.Id.Get(), SetKernelArgs) p.KernelArgs.Set(SetKernelArgs) } diff --git a/internal/app/wwctl/profile/set/root.go b/internal/app/wwctl/profile/set/root.go index e7da4c20..d8b08dc6 100644 --- a/internal/app/wwctl/profile/set/root.go +++ b/internal/app/wwctl/profile/set/root.go @@ -4,6 +4,7 @@ import ( "log" "github.com/hpcng/warewulf/internal/pkg/container" + "github.com/hpcng/warewulf/internal/pkg/kernel" "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/overlay" "github.com/spf13/cobra" @@ -36,6 +37,7 @@ var ( SetForce bool SetComment string SetContainer string + SetKernel string SetKernelArgs string SetClusterName string SetIpxe string @@ -77,6 +79,13 @@ func init() { }); err != nil { log.Println(err) } + baseCmd.PersistentFlags().StringVarP(&SetKernel, "kernel", "K", "", "Set Kernel version for nodes") + if err := baseCmd.RegisterFlagCompletionFunc("kernel", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + list, _ := kernel.ListKernels() + return list, cobra.ShellCompDirectiveNoFileComp + }); err != nil { + log.Println(err) + } baseCmd.PersistentFlags().StringVarP(&SetKernelArgs, "kernelargs", "A", "", "Set Kernel argument for nodes") baseCmd.PersistentFlags().StringVarP(&SetClusterName, "cluster", "c", "", "Set the node's cluster group") baseCmd.PersistentFlags().StringVarP(&SetIpxe, "ipxe", "P", "", "Set the node's iPXE template name") diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go index f6e7137d..059b38db 100644 --- a/internal/app/wwctl/root.go +++ b/internal/app/wwctl/root.go @@ -3,14 +3,15 @@ package wwctl import ( "github.com/hpcng/warewulf/internal/app/wwctl/configure" "github.com/hpcng/warewulf/internal/app/wwctl/container" + "github.com/hpcng/warewulf/internal/app/wwctl/kernel" "github.com/hpcng/warewulf/internal/app/wwctl/node" "github.com/hpcng/warewulf/internal/app/wwctl/overlay" "github.com/hpcng/warewulf/internal/app/wwctl/power" "github.com/hpcng/warewulf/internal/app/wwctl/profile" "github.com/hpcng/warewulf/internal/app/wwctl/server" "github.com/hpcng/warewulf/internal/app/wwctl/version" - "github.com/hpcng/warewulf/internal/pkg/help" "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/hpcng/warewulf/internal/pkg/help" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" @@ -20,12 +21,12 @@ import ( var ( rootCmd = &cobra.Command{ DisableFlagsInUseLine: true, - Use: "wwctl COMMAND [OPTIONS]", - Short: "Warewulf Control", - Long: "Control interface to the Warewulf Cluster Provisioning System.", - PersistentPreRunE: rootPersistentPreRunE, - SilenceUsage: true, - SilenceErrors: true, + Use: "wwctl COMMAND [OPTIONS]", + Short: "Warewulf Control", + Long: "Control interface to the Warewulf Cluster Provisioning System.", + PersistentPreRunE: rootPersistentPreRunE, + SilenceUsage: true, + SilenceErrors: true, } verboseArg bool DebugFlag bool @@ -35,12 +36,13 @@ func init() { rootCmd.PersistentFlags().BoolVarP(&verboseArg, "verbose", "v", false, "Run with increased verbosity.") rootCmd.PersistentFlags().BoolVarP(&DebugFlag, "debug", "d", false, "Run with debugging messages enabled.") - rootCmd.SetUsageTemplate(help.UsageTemplate) - rootCmd.SetHelpTemplate(help.HelpTemplate) + rootCmd.SetUsageTemplate(help.UsageTemplate) + rootCmd.SetHelpTemplate(help.HelpTemplate) rootCmd.AddCommand(overlay.GetCommand()) rootCmd.AddCommand(container.GetCommand()) rootCmd.AddCommand(node.GetCommand()) + rootCmd.AddCommand(kernel.GetCommand()) rootCmd.AddCommand(power.GetCommand()) rootCmd.AddCommand(profile.GetCommand()) rootCmd.AddCommand(configure.GetCommand()) diff --git a/internal/pkg/kernel/kernel.go b/internal/pkg/kernel/kernel.go new file mode 100644 index 00000000..37941032 --- /dev/null +++ b/internal/pkg/kernel/kernel.go @@ -0,0 +1,257 @@ +package kernel + +import ( + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + + "github.com/pkg/errors" + + "github.com/hpcng/warewulf/internal/pkg/buildconfig" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" +) + +var ( + kernelSearchPaths = []string{ + // This is a printf format where the %s will be the kernel version + "/boot/vmlinuz-%s", + "/boot/vmlinuz-%s.gz", + "/lib/mmodules/%s/vmlinuz", + "/lib/mmodules/%s/vmlinuz.gz", + } +) + +func KernelImageTopDir() string { + return path.Join(buildconfig.WWPROVISIONDIR(), "kernel") +} + +func KernelImage(kernelName string) string { + if kernelName == "" { + wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n") + return "" + } + + if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") { + wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", kernelName) + return "" + } + + return path.Join(KernelImageTopDir(), kernelName, "vmlinuz") +} + +func GetKernelVersion(kernelName string) string { + if kernelName == "" { + wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n") + return "" + } + kernelVersion, err := ioutil.ReadFile(KernelVersionFile(kernelName)) + if err != nil { + return "" + } + return string(kernelVersion) +} + +func KmodsImage(kernelName string) string { + if kernelName == "" { + wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n") + return "" + } + + if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") { + wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", kernelName) + return "" + } + + return path.Join(KernelImageTopDir(), kernelName, "kmods.img") +} + +func KernelVersionFile(kernelName string) string { + if kernelName == "" { + wwlog.Printf(wwlog.ERROR, "Kernel Name is not defined\n") + return "" + } + + if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") { + wwlog.Printf(wwlog.ERROR, "Runtime overlay name contains illegal characters: %s\n", 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.Printf(wwlog.DEBUG, "Searching for Kernel image directories: %s\n", KernelImageTopDir()) + + kernels, err := ioutil.ReadDir(KernelImageTopDir()) + if err != nil { + return ret, err + } + + for _, kernel := range kernels { + wwlog.Printf(wwlog.VERBOSE, "Found Kernel: %s\n", kernel.Name()) + + ret = append(ret, kernel.Name()) + + } + + return ret, nil +} + +func Build(kernelVersion, kernelName, root string) (string, error) { + kernelDrivers := path.Join(root, "/lib/modules/", kernelVersion) + kernelDriversRelative := path.Join("/lib/modules/", kernelVersion) + kernelDestination := KernelImage(kernelName) + driversDestination := KmodsImage(kernelName) + versionDestination := KernelVersionFile(kernelName) + var kernelSource string + + // Create the destination paths just in case it doesn't exist + err := os.MkdirAll(path.Dir(kernelDestination), 0755) + if err != nil { + return "", errors.Wrap(err, "failed to create kernel dest") + } + + err = os.MkdirAll(path.Dir(driversDestination), 0755) + if err != nil { + return "", errors.Wrap(err, "failed to create driver dest") + } + + err = os.MkdirAll(path.Dir(versionDestination), 0755) + if err != nil { + return "", fmt.Errorf("failed to create version dest: %s", err) + } + + for _, searchPath := range kernelSearchPaths { + testPath := fmt.Sprintf(path.Join(root, searchPath), kernelVersion) + wwlog.Printf(wwlog.VERBOSE, "Looking for kernel at: %s\n", testPath) + if util.IsFile(testPath) { + kernelSource = testPath + break + } + } + + if kernelSource == "" { + wwlog.Printf(wwlog.ERROR, "Could not locate kernel image\n") + return "", errors.New("could not locate kernel image") + } else { + wwlog.Printf(wwlog.INFO, "Found kernel at: %s\n", kernelSource) + } + + if !util.IsDir(kernelDrivers) { + return "", errors.New("Could not locate kernel drivers") + } + + wwlog.Printf(wwlog.VERBOSE, "Setting up Kernel\n") + if _, err := os.Stat(kernelSource); err == nil { + kernel, err := os.Open(kernelSource) + if err != nil { + return "", errors.Wrap(err, "could not open kernel") + } + defer kernel.Close() + + gzipreader, err := gzip.NewReader(kernel) + if err == nil { + defer gzipreader.Close() + + writer, err := os.Create(kernelDestination) + if err != nil { + return "", errors.Wrap(err, "could not decompress kernel") + } + defer writer.Close() + + _, err = io.Copy(writer, gzipreader) + if err != nil { + return "", errors.Wrap(err, "could not write decompressed kernel") + } + + } else { + + err := util.CopyFile(kernelSource, kernelDestination) + if err != nil { + return "", errors.Wrap(err, "could not copy kernel") + } + } + + } + + wwlog.Printf(wwlog.VERBOSE, "Building Kernel driver image\n") + if _, err := os.Stat(kernelDrivers); err == nil { + compressor, err := exec.LookPath("pigz") + if err != nil { + wwlog.Printf(wwlog.VERBOSE, "Could not locate PIGZ, using GZIP\n") + compressor = "gzip" + } else { + wwlog.Printf(wwlog.VERBOSE, "Using PIGZ to compress the container: %s\n", compressor) + } + + cmd := fmt.Sprintf("cd %s; find .%s | cpio --quiet -o -L -H newc | %s -c > \"%s\"", root, kernelDriversRelative, compressor, driversDestination) + + wwlog.Printf(wwlog.DEBUG, "RUNNING: %s\n", cmd) + err = exec.Command("/bin/sh", "-c", cmd).Run() + if err != nil { + return "", err + } + } + + wwlog.Printf(wwlog.VERBOSE, "Creating version file\n") + file, err := os.Create(versionDestination) + if err != nil { + return "", errors.Wrap(err, "Failed to create version file") + } + defer file.Close() + _, err = io.WriteString(file, kernelVersion) + if err != nil { + return "", errors.Wrap(err, "Could not write kernel version") + } + err = file.Sync() + if err != nil { + return "", errors.Wrap(err, "Could not sync kernel version") + } + return "Done", nil +} + +func DeleteKernel(name string) error { + fullPath := path.Join(KernelImageTopDir(), name) + + wwlog.Printf(wwlog.VERBOSE, "Removing path: %s\n", fullPath) + return os.RemoveAll(fullPath) +} + +func FindKernelVersion(root string) (string, error) { + for _, searchPath := range kernelSearchPaths { + testPattern := fmt.Sprintf(path.Join(root, searchPath), `*`) + wwlog.Printf(wwlog.VERBOSE, "Looking for kernel version with pattern at: %s\n", testPattern) + potentialKernel, _ := filepath.Glob(testPattern) + if len(potentialKernel) == 0 { + continue + } + for _, foundKernel := range potentialKernel { + wwlog.Printf(wwlog.VERBOSE, "Parsing out kernel version for %s\n", foundKernel) + re := regexp.MustCompile(fmt.Sprintf(path.Join(root, searchPath), `([\w\d-\.]*)`)) + version := re.FindAllStringSubmatch(foundKernel, -1) + if version == nil { + return "", fmt.Errorf("could not parse kernel version") + } + wwlog.Printf(wwlog.VERBOSE, "found kernel version %s\n", version) + return version[0][1], nil + + } + + } + return "", fmt.Errorf("could not find kernel version") + +} diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 1f73bd98..ebf35397 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -72,6 +72,7 @@ func (config *nodeYaml) FindAllNodes() ([]NodeInfo, error) { n.Id.Set(nodename) n.Comment.Set(node.Comment) n.ContainerName.Set(node.ContainerName) + n.KernelVersion.Set(node.KernelVersion) n.KernelArgs.Set(node.KernelArgs) n.ClusterName.Set(node.ClusterName) n.Ipxe.Set(node.Ipxe) @@ -133,6 +134,7 @@ func (config *nodeYaml) FindAllNodes() ([]NodeInfo, error) { n.Comment.SetAlt(config.NodeProfiles[p].Comment, p) n.ClusterName.SetAlt(config.NodeProfiles[p].ClusterName, p) n.ContainerName.SetAlt(config.NodeProfiles[p].ContainerName, p) + n.KernelVersion.SetAlt(config.NodeProfiles[p].KernelVersion, p) n.KernelArgs.SetAlt(config.NodeProfiles[p].KernelArgs, p) n.Ipxe.SetAlt(config.NodeProfiles[p].Ipxe, p) n.Init.SetAlt(config.NodeProfiles[p].Init, p) @@ -216,6 +218,7 @@ func (config *nodeYaml) FindAllProfiles() ([]NodeInfo, error) { p.ContainerName.Set(profile.ContainerName) p.Ipxe.Set(profile.Ipxe) p.Init.Set(profile.Init) + p.KernelVersion.Set(profile.KernelVersion) p.KernelArgs.Set(profile.KernelArgs) p.IpmiNetmask.Set(profile.IpmiNetmask) p.IpmiPort.Set(profile.IpmiPort) diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index a55b71f1..7b273f82 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -19,6 +19,7 @@ type NodeConf struct { ClusterName string `yaml:"cluster name,omitempty"` ContainerName string `yaml:"container name,omitempty"` Ipxe string `yaml:"ipxe template,omitempty"` + KernelVersion string `yaml:"kernel version,omitempty"` KernelArgs string `yaml:"kernel args,omitempty"` IpmiUserName string `yaml:"ipmi username,omitempty"` IpmiPassword string `yaml:"ipmi password,omitempty"` @@ -70,6 +71,7 @@ type NodeInfo struct { ClusterName Entry ContainerName Entry Ipxe Entry + KernelVersion Entry KernelArgs Entry IpmiIpaddr Entry IpmiNetmask Entry diff --git a/internal/pkg/node/modifiers.go b/internal/pkg/node/modifiers.go index 2a147a5d..6502be84 100644 --- a/internal/pkg/node/modifiers.go +++ b/internal/pkg/node/modifiers.go @@ -60,6 +60,7 @@ func (config *nodeYaml) NodeUpdate(node NodeInfo) error { config.Nodes[nodeID].ClusterName = node.ClusterName.GetReal() config.Nodes[nodeID].Ipxe = node.Ipxe.GetReal() config.Nodes[nodeID].Init = node.Init.GetReal() + config.Nodes[nodeID].KernelVersion = node.KernelVersion.GetReal() config.Nodes[nodeID].KernelArgs = node.KernelArgs.GetReal() config.Nodes[nodeID].IpmiIpaddr = node.IpmiIpaddr.GetReal() config.Nodes[nodeID].IpmiNetmask = node.IpmiNetmask.GetReal() @@ -147,6 +148,7 @@ func (config *nodeYaml) ProfileUpdate(profile NodeInfo) error { config.NodeProfiles[profileID].Ipxe = profile.Ipxe.GetReal() config.NodeProfiles[profileID].Init = profile.Init.GetReal() config.NodeProfiles[profileID].ClusterName = profile.ClusterName.GetReal() + config.NodeProfiles[profileID].KernelVersion = profile.KernelVersion.GetReal() config.NodeProfiles[profileID].KernelArgs = profile.KernelArgs.GetReal() config.NodeProfiles[profileID].IpmiIpaddr = profile.IpmiIpaddr.GetReal() config.NodeProfiles[profileID].IpmiNetmask = profile.IpmiNetmask.GetReal() diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 08a72065..36083519 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -174,6 +174,7 @@ func BuildOverlay(nodeInfo node.NodeInfo, overlayName string) error { tstruct.Hostname = nodeInfo.Id.Get() tstruct.ClusterName = nodeInfo.ClusterName.Get() tstruct.Container = nodeInfo.ContainerName.Get() + tstruct.KernelVersion = nodeInfo.KernelVersion.Get() tstruct.KernelArgs = nodeInfo.KernelArgs.Get() tstruct.Init = nodeInfo.Init.Get() tstruct.Root = nodeInfo.Root.Get() diff --git a/internal/pkg/warewulfd/ipxe.go b/internal/pkg/warewulfd/ipxe.go index 1cb3d605..84c11808 100644 --- a/internal/pkg/warewulfd/ipxe.go +++ b/internal/pkg/warewulfd/ipxe.go @@ -140,6 +140,7 @@ func IpxeSend(w http.ResponseWriter, req *http.Request) { replace.Hwaddr = rinfo.hwaddr replace.ContainerName = node.ContainerName.Get() replace.KernelArgs = node.KernelArgs.Get() + replace.KernelVersion = node.KernelVersion.Get() err = tmpl.Execute(w, replace) if err != nil { diff --git a/internal/pkg/warewulfd/kmods.go b/internal/pkg/warewulfd/kmods.go new file mode 100644 index 00000000..13ace42b --- /dev/null +++ b/internal/pkg/warewulfd/kmods.go @@ -0,0 +1,45 @@ +package warewulfd + +import ( + "net/http" + "strings" + + "github.com/hpcng/warewulf/internal/pkg/kernel" +) + +func KmodsSend(w http.ResponseWriter, req *http.Request) { + rinfo, err := parseReq(req) + if err != nil { + w.WriteHeader(404) + daemonLogf("ERROR: %s\n", err) + return + } + node, err := GetNode(rinfo.hwaddr) + if err != nil { + w.WriteHeader(403) + daemonLogf("ERROR(%s): %s\n", rinfo.hwaddr, err) + return + } + + if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey { + w.WriteHeader(404) + daemonLogf("ERROR: Incorrect asset key for node: %s\n", node.Id.Get()) + updateStatus(node.Id.Get(), "KMODS_OVERLAY", "BAD_ASSET", rinfo.ipaddr) + return + } + + if node.KernelVersion.Defined() { + fileName := kernel.KmodsImage(node.KernelVersion.Get()) + + updateStatus(node.Id.Get(), "KMODS_OVERLAY", node.KernelVersion.Get()+".img", strings.Split(req.RemoteAddr, ":")[0]) + + err := sendFile(w, fileName, node.Id.Get()) + if err != nil { + daemonLogf("ERROR: %s\n", err) + } + + } else { + w.WriteHeader(503) + daemonLogf("WARNING: No 'kernel version' set for node %s\n", node.Id.Get()) + } +} diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go index 7406d782..1600a637 100644 --- a/internal/pkg/warewulfd/warewulfd.go +++ b/internal/pkg/warewulfd/warewulfd.go @@ -48,6 +48,7 @@ func RunServer() error { http.HandleFunc("/ipxe/", IpxeSend) http.HandleFunc("/kernel/", KernelSend) + http.HandleFunc("/kmods/", KmodsSend) http.HandleFunc("/container/", ContainerSend) http.HandleFunc("/overlay-system/", SystemOverlaySend) http.HandleFunc("/overlay-runtime/", RuntimeOverlaySend)