Add GRUB config route, tests, and server route documentation
- Add /grub/{hwaddr} route for serving GRUB configuration files,
replacing the shim handler with a config-based approach
- Add short URL aliases: 'system' for overlay-system, 'runtime' for
overlay-runtime, and 'grub' stage in parser
- Add 'grub' to status stage tracking in request handling
- Add comprehensive test coverage for EFI, GRUB, initramfs, iPXE,
overlay, and provision handlers (efi_test.go, grub_test.go,
initramfs_test.go, ipxe_test.go, overlay_test.go)
- Expand existing parser and provision tests
- Remove shim.go (functionality folded into grub.go)
- Add userdocs/server/routes.rst documenting all warewulfd HTTP routes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
90
internal/pkg/warewulfd/efi_test.go
Normal file
90
internal/pkg/warewulfd/efi_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
)
|
||||
|
||||
var efiBootTests = []struct {
|
||||
description string
|
||||
url string
|
||||
body string
|
||||
status int
|
||||
ip string
|
||||
}{
|
||||
{"find shim", "/efiboot/shim.efi", "", 200, "10.10.10.10:9873"},
|
||||
{"find shim: node with missing image returns 404", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"},
|
||||
{"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"},
|
||||
{"find grub: node with missing image returns 404", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"},
|
||||
{"find grub.cfg", "/efiboot/grub.cfg", "dracut 10.10.0.1:9873", 200, "10.10.10.11:9873"},
|
||||
}
|
||||
|
||||
func Test_HandleEfiBoot(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll()
|
||||
|
||||
env.WriteFile("/etc/warewulf/nodes.conf", `nodeprofiles:
|
||||
default:
|
||||
image name: suse
|
||||
nodes:
|
||||
n1:
|
||||
network devices:
|
||||
default:
|
||||
hwaddr: 00:00:00:ff:ff:ff
|
||||
profiles:
|
||||
- default
|
||||
n2:
|
||||
network devices:
|
||||
default:
|
||||
hwaddr: 00:00:00:00:ff:ff
|
||||
image name: none
|
||||
tags:
|
||||
GrubMenuEntry: dracut`)
|
||||
|
||||
env.WriteFile("/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`)
|
||||
prevArpFile := arpFile
|
||||
arpFile = env.GetPath("/var/tmp/arpcache")
|
||||
defer func() {
|
||||
arpFile = prevArpFile
|
||||
}()
|
||||
|
||||
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.WriteFile("/etc/warewulf/grub/grub.cfg.ww", "{{ .Tags.GrubMenuEntry }} {{ .Authority }}")
|
||||
|
||||
dbErr := LoadNodeDB()
|
||||
assert.NoError(t, dbErr)
|
||||
|
||||
conf := warewulfconf.Get()
|
||||
secureFalse := false
|
||||
conf.Warewulf.SecureP = &secureFalse
|
||||
conf.Ipaddr = "10.10.0.1"
|
||||
conf.Ipaddr6 = "fd00:10::1"
|
||||
|
||||
for _, tt := range efiBootTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
req.RemoteAddr = tt.ip
|
||||
w := httptest.NewRecorder()
|
||||
HandleEfiBoot(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, readErr := io.ReadAll(res.Body)
|
||||
assert.NoError(t, readErr)
|
||||
if tt.body != "" {
|
||||
assert.Equal(t, tt.body, string(data))
|
||||
}
|
||||
assert.Equal(t, tt.status, res.StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,25 @@ package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleGrub handles direct GRUB binary requests
|
||||
// HandleGrub handles GRUB configuration requests
|
||||
func HandleGrub(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
} else {
|
||||
if ctx.remoteNode.ImageName != "" {
|
||||
stageFile = image.GrubFind(ctx.remoteNode.ImageName)
|
||||
if stageFile == "" {
|
||||
wwlog.Error("No grub found for image %s", ctx.remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Warn("No conainer set for node %s", ctx.remoteNode.Id())
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
stageFile := path.Join(ctx.conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww")
|
||||
tmplData := buildTemplateVars(ctx.conf, ctx.rinfo, ctx.remoteNode)
|
||||
sendResponse(w, req, stageFile, tmplData, ctx)
|
||||
}
|
||||
|
||||
76
internal/pkg/warewulfd/grub_test.go
Normal file
76
internal/pkg/warewulfd/grub_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
)
|
||||
|
||||
var grubHandlerTests = []struct {
|
||||
description string
|
||||
url string
|
||||
body string
|
||||
status int
|
||||
ip string
|
||||
}{
|
||||
{"grub config for node with image", "/grub/00:00:00:ff:ff:ff", "", 200, "10.10.10.10:9873"},
|
||||
{"grub config rendered with tag", "/grub/00:00:00:00:ff:ff", "dracut 10.10.0.1:9873", 200, "10.10.10.11:9873"},
|
||||
}
|
||||
|
||||
func Test_HandleGrub(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll()
|
||||
|
||||
env.WriteFile("/etc/warewulf/nodes.conf", `nodeprofiles:
|
||||
default:
|
||||
image name: suse
|
||||
nodes:
|
||||
n1:
|
||||
network devices:
|
||||
default:
|
||||
hwaddr: 00:00:00:ff:ff:ff
|
||||
profiles:
|
||||
- default
|
||||
n2:
|
||||
network devices:
|
||||
default:
|
||||
hwaddr: 00:00:00:00:ff:ff
|
||||
image name: none
|
||||
tags:
|
||||
GrubMenuEntry: dracut`)
|
||||
|
||||
env.WriteFile("/etc/warewulf/grub/grub.cfg.ww", "{{ .Tags.GrubMenuEntry }} {{ .Authority }}")
|
||||
|
||||
dbErr := LoadNodeDB()
|
||||
assert.NoError(t, dbErr)
|
||||
|
||||
conf := warewulfconf.Get()
|
||||
secureFalse := false
|
||||
conf.Warewulf.SecureP = &secureFalse
|
||||
conf.Ipaddr = "10.10.0.1"
|
||||
conf.Ipaddr6 = "fd00:10::1"
|
||||
|
||||
for _, tt := range grubHandlerTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
req.RemoteAddr = tt.ip
|
||||
w := httptest.NewRecorder()
|
||||
HandleGrub(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, readErr := io.ReadAll(res.Body)
|
||||
assert.NoError(t, readErr)
|
||||
if tt.body != "" {
|
||||
assert.Equal(t, tt.body, string(data))
|
||||
}
|
||||
assert.Equal(t, tt.status, res.StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
60
internal/pkg/warewulfd/initramfs_test.go
Normal file
60
internal/pkg/warewulfd/initramfs_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
)
|
||||
|
||||
var initramfsHandlerTests = []struct {
|
||||
description string
|
||||
url string
|
||||
status int
|
||||
ip string
|
||||
}{
|
||||
{"find initramfs", "/initramfs/00:00:00:ff:ff:ff", 200, "10.10.10.10:9873"},
|
||||
}
|
||||
|
||||
func Test_HandleInitramfs(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll()
|
||||
|
||||
env.WriteFile("/etc/warewulf/nodes.conf", `nodeprofiles:
|
||||
default:
|
||||
image name: suse
|
||||
nodes:
|
||||
n1:
|
||||
network devices:
|
||||
default:
|
||||
hwaddr: 00:00:00:ff:ff:ff
|
||||
profiles:
|
||||
- default`)
|
||||
|
||||
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/boot/vmlinuz-1.1.0")
|
||||
env.CreateFile("/var/lib/warewulf/chroots/suse/rootfs/boot/initramfs-1.1.0.img")
|
||||
|
||||
dbErr := LoadNodeDB()
|
||||
assert.NoError(t, dbErr)
|
||||
|
||||
conf := warewulfconf.Get()
|
||||
secureFalse := false
|
||||
conf.Warewulf.SecureP = &secureFalse
|
||||
|
||||
for _, tt := range initramfsHandlerTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
req.RemoteAddr = tt.ip
|
||||
w := httptest.NewRecorder()
|
||||
HandleInitramfs(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, tt.status, res.StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
80
internal/pkg/warewulfd/ipxe_test.go
Normal file
80
internal/pkg/warewulfd/ipxe_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
)
|
||||
|
||||
var ipxeHandlerTests = []struct {
|
||||
description string
|
||||
url string
|
||||
body string
|
||||
status int
|
||||
ip string
|
||||
}{
|
||||
{
|
||||
"ipxe with NetDevs, KernelVersion, and Authority",
|
||||
"/ipxe/00:00:00:00:00:ff",
|
||||
"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",
|
||||
},
|
||||
{
|
||||
"ipxe over ipv6",
|
||||
"/ipxe/00:00:00:00:00:ff",
|
||||
"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",
|
||||
},
|
||||
}
|
||||
|
||||
func Test_HandleIpxe(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll()
|
||||
|
||||
env.WriteFile("/etc/warewulf/nodes.conf", `nodes:
|
||||
n3:
|
||||
network devices:
|
||||
default:
|
||||
hwaddr: 00:00:00:00:00:ff
|
||||
device: net
|
||||
ipxe template: test
|
||||
kernel:
|
||||
version: 1.1.1`)
|
||||
|
||||
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}}")
|
||||
|
||||
dbErr := LoadNodeDB()
|
||||
assert.NoError(t, dbErr)
|
||||
|
||||
conf := warewulfconf.Get()
|
||||
secureFalse := false
|
||||
conf.Warewulf.SecureP = &secureFalse
|
||||
conf.Ipaddr = "10.10.0.1"
|
||||
conf.Ipaddr6 = "fd00:10::1"
|
||||
|
||||
for _, tt := range ipxeHandlerTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
req.RemoteAddr = tt.ip
|
||||
w := httptest.NewRecorder()
|
||||
HandleIpxe(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, readErr := io.ReadAll(res.Body)
|
||||
assert.NoError(t, readErr)
|
||||
if tt.body != "" {
|
||||
assert.Equal(t, tt.body, string(data))
|
||||
}
|
||||
assert.Equal(t, tt.status, res.StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
)
|
||||
|
||||
@@ -108,3 +111,89 @@ nodes:
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var systemOverlayTests = []struct {
|
||||
description string
|
||||
url string
|
||||
body string
|
||||
status int
|
||||
ip string
|
||||
}{
|
||||
{"system overlay", "/system/00:00:00:ff:ff:ff", "system overlay", 200, "10.10.10.10:9873"},
|
||||
{"fake overlay returns 404", "/system/00:00:00:ff:ff:ff?overlay=fake", "", 404, "10.10.10.10:9873"},
|
||||
{"specific overlay", "/system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200, "10.10.10.10:9873"},
|
||||
}
|
||||
|
||||
var runtimeOverlayTests = []struct {
|
||||
description string
|
||||
url string
|
||||
body string
|
||||
status int
|
||||
ip string
|
||||
}{
|
||||
{"runtime overlay", "/runtime/00:00:00:ff:ff:ff", "runtime overlay", 200, "10.10.10.10:9873"},
|
||||
}
|
||||
|
||||
func Test_HandleSystemRuntimeOverlay(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll()
|
||||
|
||||
env.WriteFile("/etc/warewulf/nodes.conf", `nodeprofiles:
|
||||
default:
|
||||
image name: suse
|
||||
nodes:
|
||||
n1:
|
||||
network devices:
|
||||
default:
|
||||
hwaddr: 00:00:00:ff:ff:ff
|
||||
profiles:
|
||||
- default`)
|
||||
|
||||
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))
|
||||
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", "o1.img"), []byte("specific overlay"), 0600))
|
||||
|
||||
for _, tt := range systemOverlayTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
req.RemoteAddr = tt.ip
|
||||
w := httptest.NewRecorder()
|
||||
HandleSystemOverlay(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, readErr := io.ReadAll(res.Body)
|
||||
assert.NoError(t, readErr)
|
||||
if tt.body != "" {
|
||||
assert.Equal(t, tt.body, string(data))
|
||||
}
|
||||
assert.Equal(t, tt.status, res.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range runtimeOverlayTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
req.RemoteAddr = tt.ip
|
||||
w := httptest.NewRecorder()
|
||||
HandleRuntimeOverlay(w, req)
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
|
||||
data, readErr := io.ReadAll(res.Body)
|
||||
assert.NoError(t, readErr)
|
||||
if tt.body != "" {
|
||||
assert.Equal(t, tt.body, string(data))
|
||||
}
|
||||
assert.Equal(t, tt.status, res.StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ func initHandleRequest(w http.ResponseWriter, req *http.Request) (*requestContex
|
||||
|
||||
status_stages := map[string]string{
|
||||
"efiboot": "EFI",
|
||||
"grub": "GRUB",
|
||||
"ipxe": "IPXE",
|
||||
"kernel": "KERNEL",
|
||||
"system": "SYSTEM_OVERLAY",
|
||||
@@ -102,14 +103,23 @@ func parseRequest(req *http.Request) (parsedRequest, error) {
|
||||
// handle when stage was passed in the url path /[stage]/hwaddr
|
||||
stage := path_parts[1]
|
||||
hwaddr := ""
|
||||
if stage != "efiboot" {
|
||||
switch stage {
|
||||
case "efiboot":
|
||||
// /efiboot/{file}: no hwaddr in path; identified via ARP
|
||||
if len(path_parts) > 3 {
|
||||
ret.efifile = strings.Join(path_parts[2:], "/")
|
||||
} else {
|
||||
ret.efifile = path_parts[2]
|
||||
}
|
||||
case "grub":
|
||||
// /grub/{hwaddr}: hwaddr explicit in path
|
||||
hwaddr = path_parts[2]
|
||||
hwaddr = strings.ReplaceAll(hwaddr, "-", ":")
|
||||
hwaddr = strings.ToLower(hwaddr)
|
||||
default:
|
||||
hwaddr = path_parts[2]
|
||||
hwaddr = strings.ReplaceAll(hwaddr, "-", ":")
|
||||
hwaddr = strings.ToLower(hwaddr)
|
||||
} else if len(path_parts) > 3 {
|
||||
ret.efifile = strings.Join(path_parts[2:], "/")
|
||||
} else {
|
||||
ret.efifile = path_parts[2]
|
||||
}
|
||||
ret.hwaddr = hwaddr
|
||||
remoteAddrPort, err := netip.ParseAddrPort(req.RemoteAddr)
|
||||
@@ -137,12 +147,14 @@ func parseRequest(req *http.Request) (parsedRequest, error) {
|
||||
ret.stage = "kernel"
|
||||
case "image", "container":
|
||||
ret.stage = "image"
|
||||
case "overlay-system":
|
||||
case "overlay-system", "system":
|
||||
ret.stage = "system"
|
||||
case "overlay-runtime":
|
||||
case "overlay-runtime", "runtime":
|
||||
ret.stage = "runtime"
|
||||
case "efiboot":
|
||||
ret.stage = "efiboot"
|
||||
case "grub":
|
||||
ret.stage = "grub"
|
||||
case "initramfs":
|
||||
ret.stage = "initramfs"
|
||||
}
|
||||
@@ -154,14 +166,23 @@ func parseRequest(req *http.Request) (parsedRequest, error) {
|
||||
if len(req.URL.Query()["compress"]) > 0 {
|
||||
ret.compress = req.URL.Query()["compress"][0]
|
||||
}
|
||||
if ret.efifile == "" && len(req.URL.Query()["file"]) > 0 {
|
||||
ret.efifile = req.URL.Query()["file"][0]
|
||||
}
|
||||
if ret.stage == "" {
|
||||
return ret, errors.New("no stage encoded in GET")
|
||||
}
|
||||
if ret.hwaddr == "" {
|
||||
ret.hwaddr = ArpFind(ret.ipaddr)
|
||||
wwlog.Verbose("node mac not encoded, arp cache got %s for %s", ret.hwaddr, ret.ipaddr)
|
||||
if ret.hwaddr == "" {
|
||||
return ret, errors.New("no hwaddr encoded in GET")
|
||||
if len(req.URL.Query()["wwid"]) > 0 {
|
||||
ret.hwaddr = req.URL.Query()["wwid"][0]
|
||||
ret.hwaddr = strings.ReplaceAll(ret.hwaddr, "-", ":")
|
||||
ret.hwaddr = strings.ToLower(ret.hwaddr)
|
||||
} else {
|
||||
ret.hwaddr = ArpFind(ret.ipaddr)
|
||||
wwlog.Verbose("node mac not encoded, arp cache got %s for %s", ret.hwaddr, ret.ipaddr)
|
||||
if ret.hwaddr == "" {
|
||||
return ret, errors.New("no hwaddr encoded in GET")
|
||||
}
|
||||
}
|
||||
}
|
||||
if ret.ipaddr == "" {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
var parseReqTests = []struct {
|
||||
description string
|
||||
url string
|
||||
rawQuery string
|
||||
remoteAddr string
|
||||
result parsedRequest
|
||||
}{
|
||||
@@ -36,13 +37,143 @@ var parseReqTests = []struct {
|
||||
stage: "ipxe",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "initramfs dedicated route",
|
||||
url: "/initramfs/00:00:00:ff:ff:ff",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "initramfs",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "grub route with hwaddr in path",
|
||||
url: "/grub/00:00:00:ff:ff:ff",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "grub",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "wwid query param on dedicated route",
|
||||
url: "/kernel/",
|
||||
rawQuery: "wwid=00%3A00%3A00%3Aff%3Aff%3Aff",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "kernel",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "wwid and stage query params on provision route",
|
||||
url: "/provision/",
|
||||
rawQuery: "wwid=00%3A00%3A00%3Aff%3Aff%3Aff&stage=kernel",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "kernel",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "efiboot with wwid and file query params",
|
||||
url: "/efiboot/",
|
||||
rawQuery: "wwid=00%3A00%3A00%3Aff%3Aff%3Aff&file=shim.efi",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "efiboot",
|
||||
efifile: "shim.efi",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "grub with wwid query param",
|
||||
url: "/grub/",
|
||||
rawQuery: "wwid=00%3A00%3A00%3Aff%3Aff%3Aff",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "grub",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "path hwaddr takes priority over wwid query param",
|
||||
url: "/kernel/00:00:00:ff:ff:ff",
|
||||
rawQuery: "wwid=11%3A11%3A11%3A11%3A11%3A11",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "kernel",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "system overlay dedicated route",
|
||||
url: "/system/00:00:00:ff:ff:ff",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "system",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "runtime overlay dedicated route",
|
||||
url: "/runtime/00:00:00:ff:ff:ff",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "runtime",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "stage=grub via provision route serves config (new semantics)",
|
||||
url: "/provision/00:00:00:ff:ff:ff",
|
||||
rawQuery: "stage=grub",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "grub",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "path efifile takes priority over file query param",
|
||||
url: "/efiboot/shim.efi",
|
||||
rawQuery: "wwid=00%3A00%3A00%3Aff%3Aff%3Aff&file=grub.cfg",
|
||||
remoteAddr: "10.5.1.1:9873",
|
||||
result: parsedRequest{
|
||||
hwaddr: "00:00:00:ff:ff:ff",
|
||||
ipaddr: "10.5.1.1",
|
||||
remoteport: 9873,
|
||||
stage: "efiboot",
|
||||
efifile: "shim.efi",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func Test_ParseRequest(t *testing.T) {
|
||||
for _, tt := range parseReqTests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
req := &http.Request{
|
||||
URL: &url.URL{Path: tt.url},
|
||||
URL: &url.URL{Path: tt.url, RawQuery: tt.rawQuery},
|
||||
RemoteAddr: tt.remoteAddr,
|
||||
}
|
||||
result, err := parseRequest(req)
|
||||
|
||||
@@ -51,10 +51,6 @@ func HandleProvision(w http.ResponseWriter, req *http.Request) {
|
||||
handler = HandleSystemOverlay
|
||||
case "runtime":
|
||||
handler = HandleRuntimeOverlay
|
||||
case "efiboot":
|
||||
handler = HandleEfiBoot
|
||||
case "shim":
|
||||
handler = HandleShim
|
||||
case "grub":
|
||||
handler = HandleGrub
|
||||
case "initramfs":
|
||||
|
||||
@@ -21,18 +21,15 @@ var provisionSendTests = []struct {
|
||||
status int
|
||||
ip string
|
||||
}{
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"find shim", "/efiboot/shim.efi", "", 200, "10.10.10.10: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", "", 404, "10.10.10.11:9873"},
|
||||
{"system overlay", "/provision/00:00:00:ff:ff:ff?stage=system", "system overlay", 200, "10.10.10.10:9873"},
|
||||
{"runtime overlay", "/provision/00:00:00:ff:ff:ff?stage=runtime", "runtime overlay", 200, "10.10.10.10:9873"},
|
||||
{"fake overlay", "/provision/00:00:00:ff:ff:ff?stage=system&overlay=fake", "", 404, "10.10.10.10:9873"},
|
||||
{"specific overlay", "/provision/00:00:00:ff:ff:ff?stage=system&overlay=o1", "specific overlay", 200, "10.10.10.10:9873"},
|
||||
{"grub config", "/provision/00:00:00:ff:ff:ff?stage=grub", "", 200, "10.10.10.10:9873"},
|
||||
{"grub config rendered", "/provision/00:00:00:00:ff:ff?stage=grub", "dracut 10.10.0.1:9873", 200, "10.10.10.11:9873"},
|
||||
{"find initramfs", "/provision/00:00:00:ff:ff:ff?stage=initramfs", "", 200, "10.10.10.10: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"},
|
||||
{"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) {
|
||||
|
||||
@@ -42,13 +42,19 @@ func configureRootHandler(apiHandler http.Handler) *slashFix {
|
||||
wwHandler.HandleFunc("/provision/", warewulfd.HandleProvision)
|
||||
wwHandler.HandleFunc("/ipxe/", warewulfd.HandleIpxe)
|
||||
wwHandler.HandleFunc("/efiboot/", warewulfd.HandleEfiBoot)
|
||||
wwHandler.HandleFunc("/grub/", warewulfd.HandleGrub)
|
||||
wwHandler.HandleFunc("/kernel/", warewulfd.HandleKernel)
|
||||
wwHandler.HandleFunc("/image/", warewulfd.HandleImage)
|
||||
wwHandler.HandleFunc("/initramfs/", warewulfd.HandleInitramfs)
|
||||
wwHandler.HandleFunc("/system/", warewulfd.HandleSystemOverlay)
|
||||
wwHandler.HandleFunc("/runtime/", warewulfd.HandleRuntimeOverlay)
|
||||
wwHandler.HandleFunc("/status", warewulfd.HandleStatus)
|
||||
|
||||
/* Deprecated */
|
||||
wwHandler.HandleFunc("/container/", warewulfd.HandleImage)
|
||||
wwHandler.HandleFunc("/overlay-system/", warewulfd.HandleSystemOverlay)
|
||||
wwHandler.HandleFunc("/overlay-runtime/", warewulfd.HandleRuntimeOverlay)
|
||||
wwHandler.HandleFunc("/overlay-file/", warewulfd.HandleOverlayFile)
|
||||
wwHandler.HandleFunc("/status", warewulfd.HandleStatus)
|
||||
|
||||
if apiHandler != nil {
|
||||
wwHandler.Handle("/api/", apiHandler)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package warewulfd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// HandleShim handles direct shim binary requests
|
||||
func HandleShim(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := initHandleRequest(w, req)
|
||||
if err != nil {
|
||||
return // response already written
|
||||
}
|
||||
|
||||
var stageFile string
|
||||
|
||||
if !ctx.remoteNode.Valid() {
|
||||
wwlog.Error("%s (unknown/unconfigured node)", ctx.rinfo.hwaddr)
|
||||
} else {
|
||||
if ctx.remoteNode.ImageName != "" {
|
||||
stageFile = image.ShimFind(ctx.remoteNode.ImageName)
|
||||
|
||||
if stageFile == "" {
|
||||
wwlog.Error("No kernel found for image %s", ctx.remoteNode.ImageName)
|
||||
}
|
||||
} else {
|
||||
wwlog.Warn("No image set for this %s", ctx.remoteNode.Id())
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(w, req, stageFile, nil, ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user