Rebuild overlays for discovered nodes

- Fixes: #1468

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2025-07-09 14:29:38 -06:00
parent 4f63790896
commit a2c7caa4ba
5 changed files with 69 additions and 11 deletions

View File

@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Changed
- `warewulfd` overlay autobuild rebuilds overlays after node discovery. #1468
## v4.6.2, 2025-07-09 ## v4.6.2, 2025-07-09
### Added ### Added

View File

@@ -105,3 +105,19 @@ func OverlayImage(nodeName string, context string, overlayNames []string) string
return path.Join(config.Get().Paths.OverlayProvisiondir(), nodeName, name) return path.Join(config.Get().Paths.OverlayProvisiondir(), nodeName, name)
} }
func ClearOverlayImage(nodeName string, context string, overlayNames []string) error {
imagePath := OverlayImage(nodeName, context, overlayNames)
if util.IsFile(imagePath) {
if err := os.Remove(imagePath); err != nil {
return fmt.Errorf("failed to remove overlay image: %w", err)
}
}
compressedImagePath := imagePath + ".gz"
if util.IsFile(compressedImagePath) {
if err := os.Remove(compressedImagePath); err != nil {
return fmt.Errorf("failed to remove compressed overlay image: %w", err)
}
}
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"sync" "sync"
"github.com/warewulf/warewulf/internal/pkg/node" "github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/wwlog" "github.com/warewulf/warewulf/internal/pkg/wwlog"
) )
@@ -53,7 +54,7 @@ func loadNodeDB() (err error) {
return nil return nil
} }
func GetNodeOrSetDiscoverable(hwaddr string) (node.Node, error) { func GetNodeOrSetDiscoverable(hwaddr string, autobuildOverlays bool) (node.Node, error) {
db.lock.RLock() db.lock.RLock()
defer db.lock.RUnlock() defer db.lock.RUnlock()
// NOTE: since discoverable nodes will write an updated DB to file and then // NOTE: since discoverable nodes will write an updated DB to file and then
@@ -97,9 +98,14 @@ func GetNodeOrSetDiscoverable(hwaddr string) (node.Node, error) {
if err != nil { if err != nil {
return nodeFound, fmt.Errorf("%s (failed to reload configuration) %w", hwaddr, err) return nodeFound, fmt.Errorf("%s (failed to reload configuration) %w", hwaddr, err)
} }
// NOTE: previously all overlays were built here, but that will also if autobuildOverlays {
// be done automatically when attempting to serve an overlay that if err := overlay.ClearOverlayImage(nodeFound.Id(), "system", []string{}); err != nil {
// hasn't been built (without blocking the database). wwlog.Warn("Failed to clear system overlay image: %s: %s", nodeFound.Id(), err)
}
if err := overlay.ClearOverlayImage(nodeFound.Id(), "runtime", []string{}); err != nil {
wwlog.Warn("Failed to clear runtime overlay image: %s: %s", nodeFound.Id(), err)
}
}
wwlog.Serv("%s (node %s automatically configured)", hwaddr, nodeFound.Id()) wwlog.Serv("%s (node %s automatically configured)", hwaddr, nodeFound.Id())

View File

@@ -10,10 +10,12 @@ import (
func Test_GetNodeOrSetDiscoverable(t *testing.T) { func Test_GetNodeOrSetDiscoverable(t *testing.T) {
var tests = map[string]struct { var tests = map[string]struct {
nodesConf string nodesConf string
hwaddr string hwaddr string
node string node string
err bool err bool
initFiles []string
removedFiles []string
}{ }{
"empty": { "empty": {
nodesConf: ` nodesConf: `
@@ -40,6 +42,29 @@ nodes:
discoverable: true discoverable: true
network devices: network devices:
default: {} default: {}
`,
hwaddr: "00:00:00:00:00:01",
node: "n1",
},
"discoverable autobuild": {
initFiles: []string{
"/srv/warewulf/overlays/n1/__SYSTEM__.img",
"/srv/warewulf/overlays/n1/__SYSTEM__.img.gz",
"/srv/warewulf/overlays/n1/__RUNTIME__.img",
"/srv/warewulf/overlays/n1/__RUNTIME__.img.gz",
},
removedFiles: []string{
"/srv/warewulf/overlays/n1/__SYSTEM__.img",
"/srv/warewulf/overlays/n1/__SYSTEM__.img.gz",
"/srv/warewulf/overlays/n1/__RUNTIME__.img",
"/srv/warewulf/overlays/n1/__RUNTIME__.img.gz",
},
nodesConf: `
nodes:
n1:
discoverable: true
network devices:
default: {}
`, `,
hwaddr: "00:00:00:00:00:01", hwaddr: "00:00:00:00:00:01",
node: "n1", node: "n1",
@@ -77,20 +102,25 @@ nodes:
for name, tt := range tests { for name, tt := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
env := testenv.New(t) env := testenv.New(t)
for _, file := range tt.initFiles {
env.CreateFile(file)
}
defer env.RemoveAll() defer env.RemoveAll()
env.WriteFile("/etc/warewulf/nodes.conf", tt.nodesConf) env.WriteFile("/etc/warewulf/nodes.conf", tt.nodesConf)
err := LoadNodeDB() err := LoadNodeDB()
assert.NoError(t, err) assert.NoError(t, err)
node, err := GetNodeOrSetDiscoverable(tt.hwaddr) node, err := GetNodeOrSetDiscoverable(tt.hwaddr, true)
if tt.err { if tt.err {
assert.Error(t, err) assert.Error(t, err)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.node, node.Id()) assert.Equal(t, tt.node, node.Id())
} }
for _, file := range tt.removedFiles {
assert.NoFileExists(t, env.GetPath(file), "File should not exist: %s", file)
}
}) })
} }
} }

View File

@@ -76,7 +76,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
// TODO: when module version is upgraded to go1.18, should be 'any' type // TODO: when module version is upgraded to go1.18, should be 'any' type
var tmpl_data *templateVars var tmpl_data *templateVars
remoteNode, err := GetNodeOrSetDiscoverable(rinfo.hwaddr) remoteNode, err := GetNodeOrSetDiscoverable(rinfo.hwaddr, conf.Warewulf.AutobuildOverlays())
if err != nil && err != node.ErrNoUnconfigured { if err != nil && err != node.ErrNoUnconfigured {
wwlog.ErrorExc(err, "") wwlog.ErrorExc(err, "")
w.WriteHeader(http.StatusServiceUnavailable) w.WriteHeader(http.StatusServiceUnavailable)