Performance improvements for overlay build

Rendering overlay templates requires fetching and populating / merging all
other nodes, because overlay templates have access to all cluster node info.
This was previously being done for each template render, but is not being done
once for each render operation and re-used for each template render.

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2024-12-23 20:52:06 -07:00
parent a59403ab3b
commit c244adc7f6
10 changed files with 77 additions and 48 deletions

View File

@@ -85,6 +85,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Log cpio errors more prominently. #1615 - Log cpio errors more prominently. #1615
- Improved syncuser conflict help text. #1614 - Improved syncuser conflict help text. #1614
- Parallelized overlay build. #1018 - Parallelized overlay build. #1018
- Parallelized and optimized overlay build. #1018
### Removed ### Removed

View File

@@ -97,17 +97,17 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("could not open node configuration: %s", err) return fmt.Errorf("could not open node configuration: %s", err)
} }
nodes, err := nodeDB.FindAllNodes() allNodes, err := nodeDB.FindAllNodes()
if err != nil { if err != nil {
return fmt.Errorf("could not get node list: %s", err) return fmt.Errorf("could not get node list: %s", err)
} }
nodes = node.FilterNodeListByName(nodes, []string{nodename}) filteredNodes := node.FilterNodeListByName(allNodes, []string{nodename})
if len(nodes) != 1 { if len(filteredNodes) != 1 {
return fmt.Errorf("no single node idendified with %s", nodename) return fmt.Errorf("no single node idendified with %s", nodename)
} }
overlays := nodes[0].SystemOverlay overlays := filteredNodes[0].SystemOverlay
overlays = append(overlays, nodes[0].RuntimeOverlay...) overlays = append(overlays, filteredNodes[0].RuntimeOverlay...)
err = overlay.BuildOverlayIndir(nodes[0], overlays, path.Join(runDir, "nodeoverlay")) err = overlay.BuildOverlayIndir(filteredNodes[0], allNodes, overlays, path.Join(runDir, "nodeoverlay"))
if err != nil { if err != nil {
return fmt.Errorf("could not build overlay: %s", err) return fmt.Errorf("could not build overlay: %s", err)
} }

View File

@@ -18,18 +18,21 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
return fmt.Errorf("could not open node configuration: %s", err) return fmt.Errorf("could not open node configuration: %s", err)
} }
db, err := nodeDB.FindAllNodes() allNodes, err := nodeDB.FindAllNodes()
if err != nil { if err != nil {
return fmt.Errorf("could not get node list: %s", err) return fmt.Errorf("could not get node list: %s", err)
} }
var filteredNodes []node.Node
if len(args) > 0 { if len(args) > 0 {
args = hostlist.Expand(args) args = hostlist.Expand(args)
db = node.FilterNodeListByName(db, args) filteredNodes = node.FilterNodeListByName(allNodes, args)
if len(db) < len(args) { if len(filteredNodes) < len(args) {
return errors.New("failed to find nodes") return errors.New("failed to find nodes")
} }
} else {
filteredNodes = allNodes
} }
// NOTE: this is to keep backward compatible // NOTE: this is to keep backward compatible
@@ -49,26 +52,25 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
} }
if len(args) > 0 { if len(args) > 0 {
if len(db) != 1 { if len(filteredNodes) != 1 {
return errors.New("must specify one node to build overlay") return errors.New("must specify one node to build overlay")
} }
for _, node := range db { for _, node := range filteredNodes {
return overlay.BuildOverlayIndir(node, OverlayNames, OverlayDir) return overlay.BuildOverlayIndir(node, allNodes, OverlayNames, OverlayDir)
} }
} else { } else {
return errors.New("must specify a node to build overlay") return errors.New("must specify a node to build overlay")
} }
} }
oldMask := syscall.Umask(007) oldMask := syscall.Umask(007)
defer syscall.Umask(oldMask) defer syscall.Umask(oldMask)
if len(OverlayNames) > 0 { if len(OverlayNames) > 0 {
err = overlay.BuildSpecificOverlays(db, OverlayNames, Workers) err = overlay.BuildSpecificOverlays(filteredNodes, allNodes, OverlayNames, Workers)
} else { } else {
err = overlay.BuildAllOverlays(db, Workers) err = overlay.BuildAllOverlays(filteredNodes, allNodes, Workers)
} }
if err != nil { if err != nil {

View File

@@ -86,7 +86,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
} }
} }
return overlay.BuildSpecificOverlays(updateNodes, []string{overlayName}, Workers) return overlay.BuildSpecificOverlays(updateNodes, nodes, []string{overlayName}, Workers)
} }
return nil return nil

View File

@@ -57,7 +57,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
nodeConf = node.NewNode(hostName) nodeConf = node.NewNode(hostName)
nodeConf.ClusterName = hostName nodeConf.ClusterName = hostName
} }
tstruct, err := overlay.InitStruct(overlayName, nodeConf) var allNodes []node.Node
allNodes, err = nodeDB.FindAllNodes()
if err != nil {
return err
}
tstruct, err := overlay.InitStruct(overlayName, nodeConf, allNodes)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -20,8 +20,18 @@ func Hostfile() (err error) {
return fmt.Errorf("'the overlay template '/etc/hosts.ww' does not exists in 'host' overlay") return fmt.Errorf("'the overlay template '/etc/hosts.ww' does not exists in 'host' overlay")
} }
var allNodes []node.Node
if nodeDB, err := node.New(); err != nil {
return err
} else {
allNodes, err = nodeDB.FindAllNodes()
if err != nil {
return err
}
}
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
tstruct, err := overlay.InitStruct(overlay_.Name(), node.NewNode(hostname)) tstruct, err := overlay.InitStruct(overlay_.Name(), node.NewNode(hostname), allNodes)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -46,16 +46,12 @@ type TemplateStruct struct {
/* /*
Initialize an TemplateStruct with the given node.NodeInfo Initialize an TemplateStruct with the given node.NodeInfo
*/ */
func InitStruct(overlayName string, nodeData node.Node) (TemplateStruct, error) { func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (TemplateStruct, error) {
var tstruct TemplateStruct var tstruct TemplateStruct
tstruct.Overlay = overlayName tstruct.Overlay = overlayName
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
tstruct.BuildHost = hostname tstruct.BuildHost = hostname
controller := warewulfconf.Get() controller := warewulfconf.Get()
nodeDB, err := node.New()
if err != nil {
return tstruct, err
}
tstruct.ThisNode = &nodeData tstruct.ThisNode = &nodeData
if tstruct.ThisNode.Kernel == nil { if tstruct.ThisNode.Kernel == nil {
tstruct.ThisNode.Kernel = new(node.KernelConf) tstruct.ThisNode.Kernel = new(node.KernelConf)
@@ -75,10 +71,6 @@ func InitStruct(overlayName string, nodeData node.Node) (TemplateStruct, error)
tstruct.Ipaddr6 = controller.Ipaddr6 tstruct.Ipaddr6 = controller.Ipaddr6
tstruct.Netmask = controller.Netmask tstruct.Netmask = controller.Netmask
tstruct.Network = controller.Network tstruct.Network = controller.Network
allNodes, err := nodeDB.FindAllNodes()
if err != nil {
return tstruct, err
}
// init some convenience vars // init some convenience vars
tstruct.Id = nodeData.Id() tstruct.Id = nodeData.Id()
tstruct.Hostname = nodeData.Id() tstruct.Hostname = nodeData.Id()
@@ -92,14 +84,11 @@ func InitStruct(overlayName string, nodeData node.Node) (TemplateStruct, error)
var buf bytes.Buffer var buf bytes.Buffer
enc := gob.NewEncoder(&buf) enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf) dec := gob.NewDecoder(&buf)
err = enc.Encode(nodeData) if err := enc.Encode(nodeData); err != nil {
if err != nil {
return tstruct, err return tstruct, err
} }
err = dec.Decode(&tstruct) if err := dec.Decode(&tstruct); err != nil {
if err != nil {
return tstruct, err return tstruct, err
} }
return tstruct, nil return tstruct, nil
} }

View File

@@ -101,7 +101,7 @@ func (this Overlay) IsDistributionOverlay() bool {
return path.Dir(this.Path()) == config.Get().Paths.DistributionOverlaydir() return path.Dir(this.Path()) == config.Get().Paths.DistributionOverlaydir()
} }
func BuildAllOverlays(nodes []node.Node, workerCount int) error { func BuildAllOverlays(nodes []node.Node, allNodes []node.Node, workerCount int) error {
nodeChan := make(chan node.Node, len(nodes)) nodeChan := make(chan node.Node, len(nodes))
errChan := make(chan error, len(nodes)*2) errChan := make(chan error, len(nodes)*2)
@@ -109,12 +109,12 @@ func BuildAllOverlays(nodes []node.Node, workerCount int) error {
worker := func() { worker := func() {
for n := range nodeChan { for n := range nodeChan {
wwlog.Info("Building system overlays for %s: [%s]", n.Id(), strings.Join(n.SystemOverlay, ", ")) wwlog.Info("Building system overlays for %s: [%s]", n.Id(), strings.Join(n.SystemOverlay, ", "))
if err := BuildOverlay(n, "system", n.SystemOverlay); err != nil { if err := BuildOverlay(n, allNodes, "system", n.SystemOverlay); err != nil {
errChan <- fmt.Errorf("could not build system overlays %v for node %s: %w", n.SystemOverlay, n.Id(), err) errChan <- fmt.Errorf("could not build system overlays %v for node %s: %w", n.SystemOverlay, n.Id(), err)
} }
wwlog.Info("Building runtime overlays for %s: [%s]", n.Id(), strings.Join(n.RuntimeOverlay, ", ")) wwlog.Info("Building runtime overlays for %s: [%s]", n.Id(), strings.Join(n.RuntimeOverlay, ", "))
if err := BuildOverlay(n, "runtime", n.RuntimeOverlay); err != nil { if err := BuildOverlay(n, allNodes, "runtime", n.RuntimeOverlay); err != nil {
errChan <- fmt.Errorf("could not build runtime overlays %v for node %s: %w", n.RuntimeOverlay, n.Id(), err) errChan <- fmt.Errorf("could not build runtime overlays %v for node %s: %w", n.RuntimeOverlay, n.Id(), err)
} }
} }
@@ -139,7 +139,7 @@ func BuildAllOverlays(nodes []node.Node, workerCount int) error {
return nil return nil
} }
func BuildSpecificOverlays(nodes []node.Node, overlayNames []string, workerCount int) error { func BuildSpecificOverlays(nodes []node.Node, allNodes []node.Node, overlayNames []string, workerCount int) error {
nodeChan := make(chan node.Node, len(nodes)) nodeChan := make(chan node.Node, len(nodes))
errChan := make(chan error, len(nodes)) errChan := make(chan error, len(nodes))
@@ -148,7 +148,7 @@ func BuildSpecificOverlays(nodes []node.Node, overlayNames []string, workerCount
for n := range nodeChan { for n := range nodeChan {
wwlog.Info("Building overlay for %s: %v", n, overlayNames) wwlog.Info("Building overlay for %s: %v", n, overlayNames)
for _, overlayName := range overlayNames { for _, overlayName := range overlayNames {
err := BuildOverlay(n, "", []string{overlayName}) err := BuildOverlay(n, allNodes, "", []string{overlayName})
if err != nil { if err != nil {
errChan <- fmt.Errorf("could not build overlay %s for node %s: %w", overlayName, n.Id(), err) errChan <- fmt.Errorf("could not build overlay %s for node %s: %w", overlayName, n.Id(), err)
} }
@@ -190,7 +190,16 @@ func BuildHostOverlay() error {
if !(stats.Mode() == os.FileMode(0750|os.ModeDir) || stats.Mode() == os.FileMode(0700|os.ModeDir)) { if !(stats.Mode() == os.FileMode(0750|os.ModeDir) || stats.Mode() == os.FileMode(0700|os.ModeDir)) {
wwlog.SecWarn("Permissions of host overlay dir %s are %s (750 is considered as secure)", hostdir, stats.Mode()) wwlog.SecWarn("Permissions of host overlay dir %s are %s (750 is considered as secure)", hostdir, stats.Mode())
} }
return BuildOverlayIndir(hostData, []string{"host"}, "/") registry, err := node.New()
if err != nil {
return err
}
var allNodes []node.Node
allNodes, err = registry.FindAllNodes()
if err != nil {
return err
}
return BuildOverlayIndir(hostData, allNodes, []string{"host"}, "/")
} }
/* /*
@@ -220,7 +229,7 @@ func FindOverlays() (overlayList []string, err error) {
/* /*
Build the given overlays for a node and create a Image for them Build the given overlays for a node and create a Image for them
*/ */
func BuildOverlay(nodeConf node.Node, context string, overlayNames []string) error { func BuildOverlay(nodeConf node.Node, allNodes []node.Node, context string, overlayNames []string) error {
if len(overlayNames) == 0 && context == "" { if len(overlayNames) == 0 && context == "" {
return nil return nil
} }
@@ -245,7 +254,7 @@ func BuildOverlay(nodeConf node.Node, context string, overlayNames []string) err
wwlog.Debug("Created temporary directory for %s: %s", name, buildDir) wwlog.Debug("Created temporary directory for %s: %s", name, buildDir)
err = BuildOverlayIndir(nodeConf, overlayNames, buildDir) err = BuildOverlayIndir(nodeConf, allNodes, overlayNames, buildDir)
if err != nil { if err != nil {
return fmt.Errorf("failed to generate files for %s: %w", name, err) return fmt.Errorf("failed to generate files for %s: %w", name, err)
} }
@@ -274,7 +283,7 @@ func init() {
} }
// Build the given overlays for a node in the given directory. // Build the given overlays for a node in the given directory.
func BuildOverlayIndir(nodeData node.Node, overlayNames []string, outputDir string) error { func BuildOverlayIndir(nodeData node.Node, allNodes []node.Node, overlayNames []string, outputDir string) error {
if len(overlayNames) == 0 { if len(overlayNames) == 0 {
return nil return nil
} }
@@ -323,7 +332,7 @@ func BuildOverlayIndir(nodeData node.Node, overlayNames []string, outputDir stri
} else if filepath.Ext(walkPath) == ".ww" { } else if filepath.Ext(walkPath) == ".ww" {
originalOutputPath := outputPath originalOutputPath := outputPath
outputPath := strings.TrimSuffix(outputPath, ".ww") outputPath := strings.TrimSuffix(outputPath, ".ww")
tstruct, err := InitStruct(overlayName, nodeData) tstruct, err := InitStruct(overlayName, nodeData, allNodes)
if err != nil { if err != nil {
return fmt.Errorf("failed to initial data for %s: %w", nodeData.Id(), err) return fmt.Errorf("failed to initial data for %s: %w", nodeData.Id(), err)
} }

View File

@@ -229,7 +229,7 @@ T3
env.MkdirAll(t, tt.outputDir) env.MkdirAll(t, tt.outputDir)
assert.NoError(t, BuildOverlayIndir(tt.node, tt.overlays, env.GetPath(tt.outputDir))) assert.NoError(t, BuildOverlayIndir(tt.node, []node.Node{tt.node}, tt.overlays, env.GetPath(tt.outputDir)))
dirFiles := tt.outputDirs dirFiles := tt.outputDirs
for outputFile, _ := range tt.outputFiles { for outputFile, _ := range tt.outputFiles {
dirFiles = append(dirFiles, outputFile) dirFiles = append(dirFiles, outputFile)
@@ -400,7 +400,7 @@ func Test_BuildOverlay(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
nodeInfo := node.NewNode(tt.nodeName) nodeInfo := node.NewNode(tt.nodeName)
t.Run(tt.description, func(t *testing.T) { t.Run(tt.description, func(t *testing.T) {
err := BuildOverlay(nodeInfo, tt.context, tt.overlays) err := BuildOverlay(nodeInfo, []node.Node{nodeInfo}, tt.context, tt.overlays)
assert.NoError(t, err) assert.NoError(t, err)
if tt.image != "" { if tt.image != "" {
image := env.GetPath(path.Join("srv/warewulf/overlays", tt.image)) image := env.GetPath(path.Join("srv/warewulf/overlays", tt.image))
@@ -523,7 +523,7 @@ func Test_BuildAllOverlays(t *testing.T) {
} }
nodes = append(nodes, nodeInfo) nodes = append(nodes, nodeInfo)
} }
err := BuildAllOverlays(nodes, runtime.NumCPU()) err := BuildAllOverlays(nodes, nodes, runtime.NumCPU())
assert.NoError(t, err) assert.NoError(t, err)
if tt.createdOverlays == nil { if tt.createdOverlays == nil {
dirName := path.Join(provisionDir, "overlays") dirName := path.Join(provisionDir, "overlays")
@@ -615,7 +615,7 @@ func Test_BuildSpecificOverlays(t *testing.T) {
nodeInfo := node.NewNode(nodeName) nodeInfo := node.NewNode(nodeName)
nodes = append(nodes, nodeInfo) nodes = append(nodes, nodeInfo)
} }
err := BuildSpecificOverlays(nodes, tt.overlays, runtime.NumCPU()) err := BuildSpecificOverlays(nodes, nodes, tt.overlays, runtime.NumCPU())
if !tt.succeed { if !tt.succeed {
assert.Error(t, err) assert.Error(t, err)
} else { } else {

View File

@@ -58,10 +58,23 @@ func getOverlayFile(n node.Node, context string, stage_overlays []string, autobu
} }
if build { if build {
registry, err := node.New()
if err != nil {
wwlog.Error("Failed to build overlay: %s, %s, %s\n%s",
n.Id(), stage_overlays, stage_file, err)
return "", err
}
var allNodes []node.Node
allNodes, err = registry.FindAllNodes()
if err != nil {
wwlog.Error("Failed to build overlay: %s, %s, %s\n%s",
n.Id(), stage_overlays, stage_file, err)
return "", err
}
if len(stage_overlays) > 0 { if len(stage_overlays) > 0 {
err = overlay.BuildSpecificOverlays([]node.Node{n}, stage_overlays, 1) err = overlay.BuildSpecificOverlays([]node.Node{n}, allNodes, stage_overlays, 1)
} else { } else {
err = overlay.BuildAllOverlays([]node.Node{n}, 1) err = overlay.BuildAllOverlays([]node.Node{n}, allNodes, 1)
} }
if err != nil { if err != nil {
wwlog.Error("Failed to build overlay: %s, %s, %s\n%s", wwlog.Error("Failed to build overlay: %s, %s, %s\n%s",