Merge pull request #1958 from middelkoopt/tm-ipv6-pxe
Add IPv6 support for iPXE booting
This commit is contained in:
@@ -14,12 +14,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Added `DELETE /api/overlays/{name}/file?path={path}`
|
- Added `DELETE /api/overlays/{name}/file?path={path}`
|
||||||
- Added `wwctl configure warewulfd`
|
- Added `wwctl configure warewulfd`
|
||||||
- Added troubleshooting documentation regarding NUMA and tmpfs
|
- Added troubleshooting documentation regarding NUMA and tmpfs
|
||||||
|
- Added support for IPv6 when booting with iPXE. #1852
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Restore default idempotency of `PUT /api/nodes/{id}`
|
- Restore default idempotency of `PUT /api/nodes/{id}`
|
||||||
- `DELETE /api/overlays/{name}?force=true` can delete overlays that are in use
|
- `DELETE /api/overlays/{name}?force=true` can delete overlays that are in use
|
||||||
- `warewulfd` overlay autobuild rebuilds overlays after node discovery. #1468
|
- `warewulfd` overlay autobuild rebuilds overlays after node discovery. #1468
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
- Improved netplan support. #1873
|
- Improved netplan support. #1873
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -47,3 +47,4 @@
|
|||||||
* Stephen Simpson [@ssimpson89](https://github.com/ssimpson89)
|
* Stephen Simpson [@ssimpson89](https://github.com/ssimpson89)
|
||||||
* Rafael Lopez <raflopez1@gmail.com> @rafalop
|
* Rafael Lopez <raflopez1@gmail.com> @rafalop
|
||||||
* Arian Cabrera [@acabrera86](https://github.com/acabrera86)
|
* Arian Cabrera [@acabrera86](https://github.com/acabrera86)
|
||||||
|
* Dacian Reece-Stremtan <dacianstremtan@gmail.com> [@dacianstremtan](https://github.com/dacianstremtan)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ echo
|
|||||||
echo "Warewulf Server:"
|
echo "Warewulf Server:"
|
||||||
echo "* Ipaddr: {{.Ipaddr}}"
|
echo "* Ipaddr: {{.Ipaddr}}"
|
||||||
echo "* Port: {{.Port}}"
|
echo "* Port: {{.Port}}"
|
||||||
|
echo "* Authority: {{.Authority}}"
|
||||||
echo
|
echo
|
||||||
echo "This node:"
|
echo "This node:"
|
||||||
echo "* Fqdn: {{.Fqdn}}"
|
echo "* Fqdn: {{.Fqdn}}"
|
||||||
@@ -28,7 +29,7 @@ reboot
|
|||||||
echo "Reading asset key..."
|
echo "Reading asset key..."
|
||||||
smbios --type 3 --get-string 8 --set assetkey
|
smbios --type 3 --get-string 8 --set assetkey
|
||||||
|
|
||||||
uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}"
|
uri="(http,{{.Authority}})/provision/${net_default_mac}?assetkey=${assetkey}"
|
||||||
kernel="${uri}&stage=kernel"
|
kernel="${uri}&stage=kernel"
|
||||||
|
|
||||||
set default={{ or .Tags.GrubMenuEntry "single-stage" }}
|
set default={{ or .Tags.GrubMenuEntry "single-stage" }}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ sleep 30
|
|||||||
reboot
|
reboot
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
set baseuri http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}}
|
set baseuri http://{{.Authority}}/provision/{{.Hwaddr}}
|
||||||
set uri ${baseuri}?assetkey=${asset}&uuid=${uuid}
|
set uri ${baseuri}?assetkey=${asset}&uuid=${uuid}
|
||||||
|
|
||||||
echo Downloading kernel image...
|
echo Downloading kernel image...
|
||||||
@@ -115,6 +115,7 @@ goto menu
|
|||||||
echo Warewulf Server:
|
echo Warewulf Server:
|
||||||
echo * Ipaddr: {{.Ipaddr}}
|
echo * Ipaddr: {{.Ipaddr}}
|
||||||
echo * Port: {{.Port}}
|
echo * Port: {{.Port}}
|
||||||
|
echo * Authority: {{.Authority}}
|
||||||
echo
|
echo
|
||||||
echo This node:
|
echo This node:
|
||||||
echo * Fqdn: {{.Fqdn}}
|
echo * Fqdn: {{.Fqdn}}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package warewulfd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -44,9 +44,12 @@ func parseReq(req *http.Request) (parserInfo, error) {
|
|||||||
ret.efifile = path_parts[2]
|
ret.efifile = path_parts[2]
|
||||||
}
|
}
|
||||||
ret.hwaddr = hwaddr
|
ret.hwaddr = hwaddr
|
||||||
ret.ipaddr = strings.Split(req.RemoteAddr, ":")[0]
|
remoteAddrPort, err := netip.ParseAddrPort(req.RemoteAddr)
|
||||||
ret.remoteport, _ = strconv.Atoi(strings.Split(req.RemoteAddr, ":")[1])
|
if err != nil {
|
||||||
|
return ret, errors.New("could not parse remote address")
|
||||||
|
}
|
||||||
|
ret.ipaddr = remoteAddrPort.Addr().String()
|
||||||
|
ret.remoteport = int(remoteAddrPort.Port())
|
||||||
if len(req.URL.Query()["assetkey"]) > 0 {
|
if len(req.URL.Query()["assetkey"]) > 0 {
|
||||||
ret.assetkey = req.URL.Query()["assetkey"][0]
|
ret.assetkey = req.URL.Query()["assetkey"][0]
|
||||||
}
|
}
|
||||||
|
|||||||
53
internal/pkg/warewulfd/parser_test.go
Normal file
53
internal/pkg/warewulfd/parser_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package warewulfd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var parseReqTests = []struct {
|
||||||
|
description string
|
||||||
|
url string
|
||||||
|
remoteAddr string
|
||||||
|
result parserInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "basic ipv4 request",
|
||||||
|
url: "/provision/00:00:00:ff:ff:ff",
|
||||||
|
remoteAddr: "10.5.1.1:9873",
|
||||||
|
result: parserInfo{
|
||||||
|
hwaddr: "00:00:00:ff:ff:ff",
|
||||||
|
ipaddr: "10.5.1.1",
|
||||||
|
remoteport: 9873,
|
||||||
|
stage: "ipxe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "basic ipv6 request",
|
||||||
|
url: "/provision/00:00:00:ff:ff:ff",
|
||||||
|
remoteAddr: "[fd00:5::1:1]:9873",
|
||||||
|
result: parserInfo{
|
||||||
|
hwaddr: "00:00:00:ff:ff:ff",
|
||||||
|
ipaddr: "fd00:5::1:1",
|
||||||
|
remoteport: 9873,
|
||||||
|
stage: "ipxe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ParseReq(t *testing.T) {
|
||||||
|
for _, tt := range parseReqTests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
req := &http.Request{
|
||||||
|
URL: &url.URL{Path: tt.url},
|
||||||
|
RemoteAddr: tt.remoteAddr,
|
||||||
|
}
|
||||||
|
result, err := parseReq(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.result, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -32,7 +33,9 @@ type templateVars struct {
|
|||||||
Ipxe string
|
Ipxe string
|
||||||
Hwaddr string
|
Hwaddr string
|
||||||
Ipaddr string
|
Ipaddr string
|
||||||
|
Ipaddr6 string
|
||||||
Port string
|
Port string
|
||||||
|
Authority string
|
||||||
KernelArgs string
|
KernelArgs string
|
||||||
KernelVersion string
|
KernelVersion string
|
||||||
Root string
|
Root string
|
||||||
@@ -115,12 +118,30 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
kernelVersion = kernel_.Version()
|
kernelVersion = kernel_.Version()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port)
|
||||||
|
ipaddr6 := ""
|
||||||
|
if confIpaddr6, err := netip.ParsePrefix(conf.Ipaddr6); err == nil {
|
||||||
|
ipaddr6 = confIpaddr6.Addr().String()
|
||||||
|
}
|
||||||
|
if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil {
|
||||||
|
if rinfoIpaddr.Is6() {
|
||||||
|
if ipaddr6 != "" {
|
||||||
|
authority = fmt.Sprintf("[%s]:%d", ipaddr6, conf.Warewulf.Port)
|
||||||
|
} else {
|
||||||
|
wwlog.Error("No valid IPv6 address configured, but request is IPv6")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wwlog.Error("Could not parse request IP address: %s", rinfo.ipaddr)
|
||||||
|
}
|
||||||
tmpl_data = &templateVars{
|
tmpl_data = &templateVars{
|
||||||
Id: remoteNode.Id(),
|
Id: remoteNode.Id(),
|
||||||
Cluster: remoteNode.ClusterName,
|
Cluster: remoteNode.ClusterName,
|
||||||
Fqdn: remoteNode.Id(),
|
Fqdn: remoteNode.Id(),
|
||||||
Ipaddr: conf.Ipaddr,
|
Ipaddr: conf.Ipaddr,
|
||||||
|
Ipaddr6: ipaddr6,
|
||||||
Port: strconv.Itoa(conf.Warewulf.Port),
|
Port: strconv.Itoa(conf.Warewulf.Port),
|
||||||
|
Authority: authority,
|
||||||
Hostname: remoteNode.Id(),
|
Hostname: remoteNode.Id(),
|
||||||
Hwaddr: rinfo.hwaddr,
|
Hwaddr: rinfo.hwaddr,
|
||||||
ImageName: remoteNode.ImageName,
|
ImageName: remoteNode.ImageName,
|
||||||
@@ -203,12 +224,30 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
kernelVersion = kernel_.Version()
|
kernelVersion = kernel_.Version()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port)
|
||||||
|
ipaddr6 := ""
|
||||||
|
if confIpaddr6, err := netip.ParsePrefix(conf.Ipaddr6); err == nil {
|
||||||
|
ipaddr6 = confIpaddr6.Addr().String()
|
||||||
|
}
|
||||||
|
if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil {
|
||||||
|
if rinfoIpaddr.Is6() {
|
||||||
|
if ipaddr6 != "" {
|
||||||
|
authority = fmt.Sprintf("[%s]:%d", ipaddr6, conf.Warewulf.Port)
|
||||||
|
} else {
|
||||||
|
wwlog.Error("No valid IPv6 address configured, but request is IPv6")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wwlog.Error("Could not parse request IP address: %s", rinfo.ipaddr)
|
||||||
|
}
|
||||||
tmpl_data = &templateVars{
|
tmpl_data = &templateVars{
|
||||||
Id: remoteNode.Id(),
|
Id: remoteNode.Id(),
|
||||||
Cluster: remoteNode.ClusterName,
|
Cluster: remoteNode.ClusterName,
|
||||||
Fqdn: remoteNode.Id(),
|
Fqdn: remoteNode.Id(),
|
||||||
Ipaddr: conf.Ipaddr,
|
Ipaddr: conf.Ipaddr,
|
||||||
|
Ipaddr6: ipaddr6,
|
||||||
Port: strconv.Itoa(conf.Warewulf.Port),
|
Port: strconv.Itoa(conf.Warewulf.Port),
|
||||||
|
Authority: authority,
|
||||||
Hostname: remoteNode.Id(),
|
Hostname: remoteNode.Id(),
|
||||||
Hwaddr: rinfo.hwaddr,
|
Hwaddr: rinfo.hwaddr,
|
||||||
ImageName: remoteNode.ImageName,
|
ImageName: remoteNode.ImageName,
|
||||||
|
|||||||
@@ -23,22 +23,23 @@ var provisionSendTests = []struct {
|
|||||||
}{
|
}{
|
||||||
{"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200, "10.10.10.10:9873"},
|
{"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200, "10.10.10.10:9873"},
|
||||||
{"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200, "10.10.10.10:9873"},
|
{"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200, "10.10.10.10:9873"},
|
||||||
{"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404, "10.10.10.10:9873:9873"},
|
{"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404, "10.10.10.10:9873"},
|
||||||
{"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200, "10.10.10.10:9873"},
|
{"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200, "10.10.10.10:9873"},
|
||||||
{"find shim", "/efiboot/shim.efi", "", 200, "10.10.10.10:9873"},
|
{"find shim", "/efiboot/shim.efi", "", 200, "10.10.10.10:9873"},
|
||||||
{"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"},
|
{"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"},
|
||||||
{"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"},
|
{"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"},
|
||||||
{"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"},
|
{"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"},
|
||||||
{"find initramfs", "/provision/00:00:00:ff:ff:ff?stage=initramfs", "", 200, "10.10.10.10:9873"},
|
{"find initramfs", "/provision/00:00:00:ff:ff:ff?stage=initramfs", "", 200, "10.10.10.10:9873"},
|
||||||
{"ipxe test with NetDevs and KernelVersion", "/provision/00:00:00:00:00:ff?stage=ipxe", "1.1.1 ifname=net:00:00:00:00:00:ff ", 200, "10.10.10.12:9873"},
|
{"ipxe test with NetDevs, KernelVersion, and Authority", "/provision/00:00:00:00:00:ff?stage=ipxe", "1.1.1 ifname=net:00:00:00:00:00:ff 10.10.0.1 fd00:10::1 10.10.0.1:9873", 200, "10.10.10.12:9873"},
|
||||||
{"find grub.cfg", "/efiboot/grub.cfg", "dracut", 200, "10.10.10.11:9873"},
|
{"ipxe ipv6", "/provision/00:00:00:00:00:ff?stage=ipxe", "1.1.1 ifname=net:00:00:00:00:00:ff 10.10.0.1 fd00:10::1 [fd00:10::1]:9873", 200, "[fd00:10::10:12]:9873"},
|
||||||
|
{"find grub.cfg", "/efiboot/grub.cfg", "dracut 10.10.0.1:9873", 200, "10.10.10.11:9873"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ProvisionSend(t *testing.T) {
|
func Test_ProvisionSend(t *testing.T) {
|
||||||
env := testenv.New(t)
|
env := testenv.New(t)
|
||||||
defer env.RemoveAll()
|
defer env.RemoveAll()
|
||||||
|
|
||||||
env.WriteFile("etc/warewulf/nodes.conf", `nodeprofiles:
|
env.WriteFile("/etc/warewulf/nodes.conf", `nodeprofiles:
|
||||||
default:
|
default:
|
||||||
image name: suse
|
image name: suse
|
||||||
nodes:
|
nodes:
|
||||||
@@ -78,8 +79,8 @@ nodes:
|
|||||||
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/usr/lib64/efi/shim.efi")
|
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/usr/lib64/efi/shim.efi")
|
||||||
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/usr/share/efi/x86_64/grub.efi")
|
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/usr/share/efi/x86_64/grub.efi")
|
||||||
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/boot/initramfs-1.1.0.img")
|
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/boot/initramfs-1.1.0.img")
|
||||||
env.WriteFile("/etc/warewulf/ipxe/test.ipxe", "{{.KernelVersion}}{{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}")
|
env.WriteFile("/etc/warewulf/ipxe/test.ipxe", "{{.KernelVersion}}{{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}} {{.Ipaddr}} {{.Ipaddr6}} {{.Authority}}")
|
||||||
env.WriteFile("/etc/warewulf/grub/grub.cfg.ww", "{{ .Tags.GrubMenuEntry }}")
|
env.WriteFile("/etc/warewulf/grub/grub.cfg.ww", "{{ .Tags.GrubMenuEntry }} {{ .Authority }}")
|
||||||
|
|
||||||
dbErr := LoadNodeDB()
|
dbErr := LoadNodeDB()
|
||||||
assert.NoError(t, dbErr)
|
assert.NoError(t, dbErr)
|
||||||
@@ -87,6 +88,8 @@ nodes:
|
|||||||
conf := warewulfconf.Get()
|
conf := warewulfconf.Get()
|
||||||
secureFalse := false
|
secureFalse := false
|
||||||
conf.Warewulf.SecureP = &secureFalse
|
conf.Warewulf.SecureP = &secureFalse
|
||||||
|
conf.Ipaddr = "10.10.0.1"
|
||||||
|
conf.Ipaddr6 = "fd00:10::1/64"
|
||||||
assert.NoError(t, os.MkdirAll(path.Join(conf.Paths.OverlayProvisiondir(), "n1"), 0700))
|
assert.NoError(t, os.MkdirAll(path.Join(conf.Paths.OverlayProvisiondir(), "n1"), 0700))
|
||||||
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__SYSTEM__.img"), []byte("system overlay"), 0600))
|
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__SYSTEM__.img"), []byte("system overlay"), 0600))
|
||||||
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600))
|
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600))
|
||||||
|
|||||||
Reference in New Issue
Block a user