From 60dc8f62510baaae40d19b104df0188b89ced676 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Tue, 25 Jul 2023 15:19:16 +0200 Subject: [PATCH] enable grub boot for all nodes Signed-off-by: Christian Goll --- Makefile | 2 + internal/pkg/container/shim.go | 2 +- internal/pkg/node/util.go | 6 ++ internal/pkg/overlay/datastructure.go | 15 ++-- internal/pkg/warewulfd/provision.go | 114 ++++++++++++++++++++++++++ internal/pkg/warewulfd/util.go | 24 ++++++ internal/pkg/warewulfd/warewulfd.go | 1 + overlays/host/etc/dhcp/dhcpd.conf.ww | 66 ++++++++------- 8 files changed, 191 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index 4ef5a5bf..d6ef7fec 100644 --- a/Makefile +++ b/Makefile @@ -96,6 +96,7 @@ install: build docs install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR) install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe + install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/grub install -d -m 0755 $(DESTDIR)$(BASHCOMPDIR) install -d -m 0755 $(DESTDIR)$(MANDIR)/man1 install -d -m 0755 $(DESTDIR)$(MANDIR)/man5 @@ -112,6 +113,7 @@ install: build docs test -f $(DESTDIR)$(DATADIR)/warewulf/defaults.conf || install -m 0644 etc/defaults.conf $(DESTDIR)$(DATADIR)/warewulf/defaults.conf for f in etc/examples/*.ww; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/examples/; done for f in etc/ipxe/*.ipxe; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/ipxe/; done + for f in etc/grub/*; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/grub/; done (cd overlays && find * -type f -exec install -D -m 0644 {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) (cd overlays && find * -type d -exec mkdir -pv $(DESTDIR)$(WWOVERLAYDIR)/{} \;) (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) diff --git a/internal/pkg/container/shim.go b/internal/pkg/container/shim.go index aa704751..9517d2b9 100644 --- a/internal/pkg/container/shim.go +++ b/internal/pkg/container/shim.go @@ -28,7 +28,7 @@ func shimNames() []string { find the path of the shim binary */ func ShimFind(container string) string { - wwlog.Debug("Finding shim") + wwlog.Debug("Finding shim for container %s", container) container_path := RootFsDir(container) if container_path == "" { return "" diff --git a/internal/pkg/node/util.go b/internal/pkg/node/util.go index e90efee6..8e1273c3 100644 --- a/internal/pkg/node/util.go +++ b/internal/pkg/node/util.go @@ -6,6 +6,9 @@ import ( "strings" ) +/* +get node by its hardware/MAC address, return error otherwise +*/ func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { if _, err := net.ParseMAC(hwa); err != nil { return NodeInfo{}, errors.New("invalid hardware address: " + hwa) @@ -26,6 +29,9 @@ func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { return ret, errors.New("No nodes found with HW Addr: " + hwa) } +/* +get node by its ip address, return error otherwise +*/ func (config *NodeYaml) FindByIpaddr(ipaddr string) (NodeInfo, error) { if addr := net.ParseIP(ipaddr); addr == nil { return NodeInfo{}, errors.New("invalid IP:" + ipaddr) diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go index 15c9d3d5..04bddc52 100644 --- a/internal/pkg/overlay/datastructure.go +++ b/internal/pkg/overlay/datastructure.go @@ -34,6 +34,7 @@ type TemplateStruct struct { Tftp warewulfconf.TFTPConf Paths warewulfconf.BuildConfig AllNodes []node.NodeInfo + ProfileMap map[string]*node.NodeInfo node.NodeConf // backward compatiblity Container string @@ -49,19 +50,19 @@ func InitStruct(nodeInfo *node.NodeInfo) TemplateStruct { controller := warewulfconf.Get() nodeDB, err := node.New() if err != nil { - wwlog.Error("%s", err) - os.Exit(1) + wwlog.Warn("Problems opening nodes.conf: %s", err) } - allNodes, err := nodeDB.FindAllNodes() + tstruct.AllNodes, err = nodeDB.FindAllNodes() if err != nil { - wwlog.Error("%s", err) - os.Exit(1) + wwlog.Warn("couldn't get all nodes: %s", err) + } + tstruct.ProfileMap, err = nodeDB.MapAllProfiles() + if err != nil { + wwlog.Warn("couldn't get all profiles: %s", err) } // init some convenience vars tstruct.Id = nodeInfo.Id.Get() tstruct.Hostname = nodeInfo.Id.Get() - // Backwards compatibility for templates using "Keys" - tstruct.AllNodes = allNodes tstruct.Nfs = *controller.NFS tstruct.Dhcp = *controller.DHCP tstruct.Tftp = *controller.TFTP diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index 0cfc2aa7..e301ac77 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -3,20 +3,28 @@ package warewulfd import ( "bytes" "errors" + "fmt" "net/http" "path" "strconv" "strings" + "sync" "text/template" warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" "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/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" ) +var sentMap = make(map[string]string) + +// define a mutex lock +var mu sync.Mutex + type iPxeTemplate struct { Message string WaitTime string @@ -262,3 +270,109 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } } + +/* +simple handler for sending only out grub to the nodes, parses the +GET request, so that for "/grub/shim.efi" the "shim.efi" from the +container of the default profile will be sent and for "/grub/shim.efi?$container" the +shim of $container will be sent +*/ +func GrubSend(w http.ResponseWriter, req *http.Request) { + req.Close = true + wwlog.Debug("Grub send called with url: %s host: %s", req.URL, req.Host) + url := strings.Split(req.URL.Path, "?")[0] + path_parts := strings.Split(path.Join(url), "/") + remoteIP := strings.Split(req.RemoteAddr, ":")[0] + mu.Lock() + if last_sent, ok := sentMap[remoteIP]; ok { + wwlog.Debug("last sent is: %s", last_sent) + } + mu.Unlock() + if len(path_parts) != 3 { + w.WriteHeader(http.StatusBadRequest) + wwlog.ErrorExc(fmt.Errorf("unknown path components in GET: %s", req.URL.Path), remoteIP) + return + } + requested_file := path_parts[2] + nodeDB, err := node.New() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + profiles, _ := nodeDB.MapAllProfiles() + containerName := "" + if profile, ok := profiles["default"]; ok { + containerName = profile.ContainerName.Get() + } + remoteNode, err := nodeDB.FindByIpaddr(remoteIP) + if err == nil { + containerName = remoteNode.ContainerName.Get() + } else { + if mac := ArpFind(remoteIP); mac != "" { + remoteNode, err := nodeDB.FindByHwaddr(mac) + if err != nil { + containerName = remoteNode.ContainerName.Get() + } else if remoteNode, err = GetNodeOrSetDiscoverable(mac); err == nil { + containerName = remoteNode.ContainerName.Get() + } + } + } + + if len(url) == 2 { + containerName = strings.Split(req.URL.Path, "?")[1] + } + mu.Lock() + if last_sent, ok := sentMap[remoteIP]; ok && last_sent == "shim.efi" { + requested_file = "grub.efi" + } + mu.Unlock() + wwlog.Recv("remoteIP: %s, container: %s, filename: %s", remoteIP, containerName, requested_file) + var stage_file string + switch requested_file { + case "shim.efi": + stage_file = container.ShimFind(containerName) + if stage_file == "" { + wwlog.ErrorExc(fmt.Errorf("could't find shim.efi"), containerName) + w.WriteHeader(http.StatusNotFound) + return + } + mu.Lock() + sentMap[remoteIP] = "shim.efi" + mu.Unlock() + case "grub.efi", "grubx86.efi": + stage_file = container.GrubFind(containerName) + if stage_file == "" { + wwlog.ErrorExc(fmt.Errorf("could't find grub.efi"), containerName) + w.WriteHeader(http.StatusNotFound) + return + } + mu.Lock() + sentMap[remoteIP] = "grub.efi" + mu.Unlock() + } + wwlog.Serv("stage_file '%s'", stage_file) + + if util.IsFile(stage_file) { + /* + fd, err := os.Open(stage_file) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + defer fd.Close() + */ + /* + stat, err := fd.Stat() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + http.ServeContent(w, req, stage_file, stat.ModTime(), fd) + io.Copy(w, fd) + req.Close = true + */ + http.ServeFile(w, req, stage_file) + wwlog.Send("%15s: %s", remoteIP, stage_file) + } +} diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go index 3290a678..8094def9 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go @@ -1,8 +1,10 @@ package warewulfd import ( + "bufio" "net/http" "os" + "strings" "github.com/hpcng/warewulf/internal/pkg/node" nodepkg "github.com/hpcng/warewulf/internal/pkg/node" @@ -70,3 +72,25 @@ func getOverlayFile( return } + +/* +returns the mac address if it has an entry in the arp cache +*/ + +func ArpFind(ip string) (mac string) { + arpCache, err := os.Open("/proc/net/arp") + if err != nil { + return + } + defer arpCache.Close() + + scanner := bufio.NewScanner(arpCache) + scanner.Scan() + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if strings.EqualFold(fields[0], ip) { + return fields[3] + } + } + return +} diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go index ce599aad..e76826aa 100644 --- a/internal/pkg/warewulfd/warewulfd.go +++ b/internal/pkg/warewulfd/warewulfd.go @@ -56,6 +56,7 @@ func RunServer() error { http.HandleFunc("/provision/", ProvisionSend) http.HandleFunc("/ipxe/", ProvisionSend) + http.HandleFunc("/grub/", GrubSend) http.HandleFunc("/kernel/", ProvisionSend) http.HandleFunc("/kmods/", ProvisionSend) http.HandleFunc("/container/", ProvisionSend) diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww index 2582c03c..b7b35e3f 100644 --- a/overlays/host/etc/dhcp/dhcpd.conf.ww +++ b/overlays/host/etc/dhcp/dhcpd.conf.ww @@ -1,4 +1,4 @@ -{{if .Dhcp.Enabled -}} +{{if $.Dhcp.Enabled -}} # This file is autogenerated by warewulf # Host: {{.BuildHost}} # Time: {{.BuildTime}} @@ -15,52 +15,56 @@ option space ipxe; option ipxe.no-pxedhcp code 176 = unsigned integer 8; option ipxe.no-pxedhcp 1; +option space PXE; +option PXE.mtftp-ip code 1 = ip-address; +option PXE.mtftp-cport code 2 = unsigned integer 16; +option PXE.mtftp-sport code 3 = unsigned integer 16; +option PXE.mtftp-tmout code 4 = unsigned integer 8; +option PXE.mtftp-delay code 5 = unsigned integer 8; + option architecture-type code 93 = unsigned integer 16; +{{- if eq $.ProfileMap.default.BootMethod.Get "grub" }} +if substring (option vendor-class-identifier, 0, 9) = "PXEClient" { + next-server {{ $.Ipaddr }}; + filename "warewulf/shim.efi"; +} elsif substring (option vendor-class-identifier, 0, 10) = "HTTPClient" { + filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/grub/shim.efi"; +} +{{- else }} if exists user-class and option user-class = "iPXE" { filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; } else { -{{- if .BoothMethod == "ipxe" }} {{range $type,$name := $.Tftp.IpxeBinaries }} if option architecture-type = {{ $type }} { filename "/warewulf/{{ basename $name }}"; } {{- end }}{{/* range IpxeBinaries */}} -{{ else }} - filename "warewulf/shim.efi" +} {{- end }}{{/* BootMethod */}} -} -{{if eq .Dhcp.Template "static" -}} -subnet {{$.Network}} netmask {{$.Netmask}} { - next-server {{$.Ipaddr}}; -} -{{range $nodes := .AllNodes}} -{{- range $netname, $netdevs := $nodes.NetDevs}} -host {{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}} { - {{- if $netdevs.Hwaddr.Defined}} - hardware ethernet {{$netdevs.Hwaddr.Get}}; - {{- end}} - {{- if $netdevs.Ipaddr.Defined}} - fixed-address {{$netdevs.Ipaddr.Get}}; - {{- end}} - {{- if $netdevs.Primary.GetB}} - option host-name "{{$nodes.Id.Get}}"; - {{else}} - option host-name "{{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}}"; - {{- end}} -} -{{end -}} -{{end -}} - -{{else -}} subnet {{$.Network}} netmask {{$.Netmask}} { max-lease-time 120; range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}}; - next-server {{$.Ipaddr}}; + next-server {{.Ipaddr}}; } -{{end}} +{{- range $nodes := $.AllNodes}} +{{- range $netname, $netdevs := $nodes.NetDevs}} +host {{$nodes.Id.Get}}-{{$netdevs.Device.Get}} +{ + {{- if $netdevs.Primary.GetB}} + {{- if $netdevs.Hwaddr.Defined}} + hardware ethernet {{$netdevs.Hwaddr.Get}}; + {{- end }} + {{- if $netdevs.Ipaddr.Defined}} + fixed-address {{$netdevs.Ipaddr.Get}}; + {{- end }} + option host-name "{{$nodes.Id.Get}}"; + {{- end }} +} +{{end -}}{{/* range NetDevs */}} +{{end -}}{{/* range AllNodes */}} {{- else}} {{abort}} -{{- end}} +{{- end}}{{/* dhcp enabled */}}