From 7798b6b1e74b3b395cb4452ddb171b81ed635d59 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Mon, 7 Dec 2020 19:44:46 -0800 Subject: [PATCH] Add the ability to do node discovery for unconfigured nodes. --- etc/ipxe/unconfigured.ipxe | 12 +++ internal/app/warewulfd/response/ipxe.go | 103 ++++++++++++++++++------ internal/pkg/node/constructors.go | 31 +++++++ 3 files changed, 121 insertions(+), 25 deletions(-) create mode 100644 etc/ipxe/unconfigured.ipxe diff --git a/etc/ipxe/unconfigured.ipxe b/etc/ipxe/unconfigured.ipxe new file mode 100644 index 00000000..e12de477 --- /dev/null +++ b/etc/ipxe/unconfigured.ipxe @@ -0,0 +1,12 @@ +#!ipxe + +echo +echo ================================================================================ +echo Warewulf v4 +echo +echo MESSAGE: This node is unconfigured. Please have your system administrator add a +echo configuration for this node with HW address: {{$.Hwaddr}} +echo +echo Rebooting in 1 minute... +sleep 60 +reboot \ No newline at end of file diff --git a/internal/app/warewulfd/response/ipxe.go b/internal/app/warewulfd/response/ipxe.go index 794159b5..0b838ecd 100644 --- a/internal/app/warewulfd/response/ipxe.go +++ b/internal/app/warewulfd/response/ipxe.go @@ -3,6 +3,7 @@ package response import ( "fmt" "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/overlay" "github.com/hpcng/warewulf/internal/pkg/warewulfconf" "github.com/hpcng/warewulf/internal/pkg/wwlog" "log" @@ -13,6 +14,8 @@ import ( ) type iPxeTemplate struct { + Message string + WaitTime string Hostname string Fqdn string ContainerName string @@ -25,8 +28,9 @@ type iPxeTemplate struct { func IpxeSend(w http.ResponseWriter, req *http.Request) { url := strings.Split(req.URL.Path, "/") + var unconfiguredNode bool - nodes, err := node.New() + nodeDB, err := node.New() if err != nil { log.Printf("Could not read node configuration file: %s\n", err) w.WriteHeader(503) @@ -35,28 +39,56 @@ func IpxeSend(w http.ResponseWriter, req *http.Request) { if url[2] == "" { log.Printf("ERROR: Bad iPXE request from %s\n", req.RemoteAddr) - return - } - - hwaddr := strings.ReplaceAll(url[2], "-", ":") - node, err := nodes.FindByHwaddr(hwaddr) - if err != nil { - log.Printf("Could not find HW Addr: %s: %s\n", hwaddr, err) w.WriteHeader(404) return } - if node.Id.Defined() == true { - conf, err := warewulfconf.New() + conf, err := warewulfconf.New() + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + w.WriteHeader(503) + return + } + + hwaddr := strings.ReplaceAll(url[2], "-", ":") + + n, err := nodeDB.FindByHwaddr(hwaddr) + if err != nil { + // If we failed to find a node, let's see if we can add one... + var netdev string + + wwlog.Printf(wwlog.INFO, "Node was not found, looking for discoverable nodes...\n") + + n, netdev, err = nodeDB.FindUnconfiguredNode() if err != nil { - wwlog.Printf(wwlog.ERROR, "%s\n", err) - return + wwlog.Printf(wwlog.WARN, "Node was not found, no nodes are discoverable...\n") + unconfiguredNode = true + + } else { + wwlog.Printf(wwlog.INFO, "Adding new configuration to discoverable node: %s\n", n.Id.Get()) + + n.NetDevs[netdev].Hwaddr.Set(hwaddr) + err := nodeDB.NodeUpdate(n) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not add discovered configuration for node: %s\n", n.Id.Get()) + unconfiguredNode = true + } else { + err := nodeDB.Persist() + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not persist new node configuration while adding node: %s\n", n.Id.Get()) + unconfiguredNode = true + } else { + _ = overlay.BuildSystemOverlay([]node.NodeInfo{n}) + _ = overlay.BuildRuntimeOverlay([]node.NodeInfo{n}) + } + } } + } - log.Printf("IPXE: %15s: %s\n", node.Id.Get(), req.URL.Path) + if unconfiguredNode == true { + log.Printf("UNCONFIGURED NODE: %15s\n", hwaddr) - // TODO: Fix template path to use config package - ipxeTemplate := fmt.Sprintf("/etc/warewulf/ipxe/%s.ipxe", node.Ipxe.Get()) + ipxeTemplate := fmt.Sprintf("/etc/warewulf/ipxe/unconfigured.ipxe", n.Ipxe.Get()) tmpl, err := template.ParseFiles(ipxeTemplate) if err != nil { @@ -66,14 +98,7 @@ func IpxeSend(w http.ResponseWriter, req *http.Request) { var replace iPxeTemplate - replace.Fqdn = node.Id.Get() - replace.Ipaddr = conf.Ipaddr - replace.Port = strconv.Itoa(conf.Warewulf.Port) - replace.Hostname = node.Id.Get() - replace.Hwaddr = url[2] - replace.ContainerName = node.ContainerName.Get() - replace.KernelArgs = node.KernelArgs.Get() - replace.KernelVersion = node.KernelVersion.Get() + replace.Hwaddr = hwaddr err = tmpl.Execute(w, replace) if err != nil { @@ -81,10 +106,38 @@ func IpxeSend(w http.ResponseWriter, req *http.Request) { return } - log.Printf("SEND: %15s: %s\n", node.Id.Get(), ipxeTemplate) + return } else { - log.Printf("ERROR: iPXE request from unknown Node (hwaddr=%s)\n", url[2]) + log.Printf("IPXE: %15s: %s\n", n.Id.Get(), req.URL.Path) + + ipxeTemplate := fmt.Sprintf("/etc/warewulf/ipxe/%s.ipxe", n.Ipxe.Get()) + + tmpl, err := template.ParseFiles(ipxeTemplate) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return + } + + var replace iPxeTemplate + + replace.Fqdn = n.Id.Get() + replace.Ipaddr = conf.Ipaddr + replace.Port = strconv.Itoa(conf.Warewulf.Port) + replace.Hostname = n.Id.Get() + replace.Hwaddr = url[2] + replace.ContainerName = n.ContainerName.Get() + replace.KernelArgs = n.KernelArgs.Get() + replace.KernelVersion = n.KernelVersion.Get() + + err = tmpl.Execute(w, replace) + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + return + } + + log.Printf("SEND: %15s: %s\n", n.Id.Get(), ipxeTemplate) + } return } diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 1211b384..35895d1b 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -7,6 +7,7 @@ import ( "gopkg.in/yaml.v2" "io/ioutil" "regexp" + "sort" "strings" ) @@ -174,9 +175,39 @@ func (self *nodeYaml) FindAllProfiles() ([]NodeInfo, error) { ret = append(ret, p) } + + sort.Slice(ret, func(i, j int) bool { + if ret[i].ClusterName.Get() < ret[j].ClusterName.Get() { + return true + } else if ret[i].ClusterName.Get() == ret[j].ClusterName.Get() { + if ret[i].Id.Get() < ret[j].Id.Get() { + return true + } + } + return false + }) + return ret, nil } +func (self *nodeYaml) FindUnconfiguredNode() (NodeInfo, string, error) { + var ret NodeInfo + + nodes, _ := self.FindAllNodes() + + for _, node := range nodes { + for netdev, dev := range node.NetDevs { + if dev.Hwaddr.Defined() == false && dev.Ipaddr.Defined() == true { + if dev.Type.Defined() == false || dev.Type.Get() == "ethernet" { + return node, netdev, nil + } + } + } + } + + return ret, "", errors.New("No unconfigured nodes found") +} + func (self *nodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { var ret NodeInfo