Files
warewulf/internal/pkg/node/constructors.go
Jonathon Anderson 2319906d97 Fix writing of nodes.conf
The caching functionality of nodes.conf, and the optional persistence
functionality that came with it, appears to have been preventing
actually writing the configuration to disk in all cases. This change
fixes the ability to write to nodes.conf.

Somewhat opinionatedly, this change removes the caching functionality of
nodes.conf. The tests have also been updated to use real files for i/o
rather than tying into the caching system, making them more accurately
test the ability of Warewulf to actually read and write its
configuration.

Fixes #779

Signed-off-by: Jonathon Anderson <janderson@ciq.co>
2023-04-14 10:53:31 -06:00

325 lines
8.4 KiB
Go

package node
import (
"errors"
"fmt"
"os"
"path"
"sort"
"strings"
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"gopkg.in/yaml.v2"
)
var ConfigFile string
var DefaultConfig string
// used as fallback if DefaultConfig can't be read
var FallBackConf = `---
defaultnode:
runtime overlay:
- generic
system overlay:
- wwinit
kernel:
args: quiet crashkernel=no vga=791 net.naming-scheme=v238
init: /sbin/init
root: initramfs
ipxe template: default
profiles:
- default
network devices:
dummy:
device: eth0
type: ethernet
netmask: 255.255.255.0`
func init() {
conf := warewulfconf.New()
if ConfigFile == "" {
ConfigFile = path.Join(conf.Paths.Sysconfdir, "warewulf/nodes.conf")
}
if DefaultConfig == "" {
DefaultConfig = path.Join(conf.Paths.Datadir, "warewulf/defaults.conf")
}
}
/*
Creates a new nodeDb object from the actual configuration
*/
func New() (NodeYaml, error) {
wwlog.Verbose("Opening node configuration file: %s", ConfigFile)
data, err := os.ReadFile(ConfigFile)
if err != nil {
return NodeYaml{}, err
}
return Parse(data)
}
func Parse(data []byte) (NodeYaml, error) {
var ret NodeYaml
var err error
wwlog.Debug("Unmarshaling the node configuration")
err = yaml.Unmarshal(data, &ret)
if err != nil {
return ret, err
}
wwlog.Debug("Checking nodes for types")
if ret.Nodes == nil {
ret.Nodes = map[string]*NodeConf{}
}
for nodeName, node := range ret.Nodes {
err = node.Check()
if err != nil {
wwlog.Warn("node: %s parsing error: %s", nodeName, err)
return ret, err
}
}
if ret.NodeProfiles == nil {
ret.NodeProfiles = map[string]*NodeConf{}
}
for profileName, profile := range ret.NodeProfiles {
err = profile.Check()
if err != nil {
wwlog.Warn("node: %s parsing error: %s", profileName, err)
return ret, err
}
}
wwlog.Debug("Returning node object")
return ret, nil
}
/*
Get all the nodes of a configuration. This function also merges
the nodes with the given profiles and set the default values
for every node
*/
func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) {
var ret []NodeInfo
var defConf map[string]*NodeConf
wwlog.Verbose("Opening defaults from file failed %s\n", DefaultConfig)
defData, err := os.ReadFile(DefaultConfig)
if err != nil {
wwlog.Verbose("Couldn't read DefaultConfig :%s\n", err)
wwlog.Verbose("Using building defaults")
defData = []byte(FallBackConf)
}
wwlog.Debug("Unmarshalling default config\n")
err = yaml.Unmarshal(defData, &defConf)
if err != nil {
wwlog.Verbose("Couldn't unmarshall defaults from file :%s\n", err)
err = yaml.Unmarshal([]byte(FallBackConf), &defConf)
if err != nil {
wwlog.Warn("Could not get any defaults")
}
}
var defConfNet *NetDevs
if _, ok := defConf["defaultnode"]; ok {
if _, ok := defConf["defaultnode"].NetDevs["dummy"]; ok {
defConfNet = defConf["defaultnode"].NetDevs["dummy"]
}
defConf["defaultnode"].NetDevs = nil
}
wwlog.Debug("Finding all nodes...")
for nodename, node := range config.Nodes {
var n NodeInfo
wwlog.Debug("In node loop: %s", nodename)
n.NetDevs = make(map[string]*NetDevEntry)
n.Tags = make(map[string]*Entry)
n.Kernel = new(KernelEntry)
n.Ipmi = new(IpmiEntry)
n.SetDefFrom(defConf["defaultnode"])
fullname := strings.SplitN(nodename, ".", 2)
if len(fullname) > 1 {
n.ClusterName.SetDefault(fullname[1])
}
// special handling for profile to get the default one
if len(node.Profiles) == 0 {
n.Profiles.SetSlice([]string{"default"})
} else {
n.Profiles.SetSlice(node.Profiles)
}
// node explicitly nodename field in NodeConf
n.Id.Set(nodename)
// backward compatibility
for keyname, key := range node.Keys {
node.Tags[keyname] = key
delete(node.Keys, keyname)
}
err = node.Check()
if err != nil {
return nil, fmt.Errorf("node: %s check error: %s", nodename, err)
}
n.SetFrom(node)
// only now the netdevs start to exist so that default values can be set
for _, netdev := range n.NetDevs {
netdev.SetDefFrom(defConfNet)
}
// backward compatibility
n.Ipmi.Ipaddr.Set(node.IpmiIpaddr)
n.Ipmi.Netmask.Set(node.IpmiNetmask)
n.Ipmi.Port.Set(node.IpmiPort)
n.Ipmi.Gateway.Set(node.IpmiGateway)
n.Ipmi.UserName.Set(node.IpmiUserName)
n.Ipmi.Password.Set(node.IpmiPassword)
n.Ipmi.Interface.Set(node.IpmiInterface)
n.Ipmi.Write.Set(node.IpmiWrite)
n.Kernel.Args.Set(node.KernelArgs)
n.Kernel.Override.Set(node.KernelOverride)
n.Kernel.Override.Set(node.KernelVersion)
// delete deprecated structures so that they do not get unmarshalled
node.IpmiIpaddr = ""
node.IpmiNetmask = ""
node.IpmiGateway = ""
node.IpmiUserName = ""
node.IpmiPassword = ""
node.IpmiInterface = ""
node.IpmiWrite = ""
node.KernelArgs = ""
node.KernelOverride = ""
node.KernelVersion = ""
// Merge Keys into Tags for backwards compatibility
if len(node.Tags) == 0 {
node.Tags = make(map[string]string)
}
for _, profileName := range n.Profiles.GetSlice() {
if _, ok := config.NodeProfiles[profileName]; !ok {
wwlog.Warn("Profile not found for node '%s': %s", nodename, profileName)
continue
}
// can't call setFrom() as we have to use SetAlt instead of Set for an Entry
wwlog.Verbose("Merging profile into node: %s <- %s", nodename, profileName)
n.SetAltFrom(config.NodeProfiles[profileName], profileName)
}
// set default/primary network is just one network exist
if len(n.NetDevs) >= 1 {
tmpNets := make([]string, 0, len(n.NetDevs))
for key := range node.NetDevs {
tmpNets = append(tmpNets, key)
}
sort.Strings(tmpNets)
// if a value is present in profile or node, default is not visible
wwlog.Debug("%s setting primary network device: %s", n.Id.Get(), tmpNets[0])
n.PrimaryNetDev.SetDefault(tmpNets[0])
}
if dev, ok := n.NetDevs[n.PrimaryNetDev.Get()]; ok {
dev.Primary.SetDefaultB(true)
}
ret = append(ret, n)
}
sort.Slice(ret, func(i, j int) bool {
if ret[i].ClusterName.Get() < ret[j].ClusterName.Get() {
return true
} else if ret[i].ClusterName.Get() == ret[j].ClusterName.Get() {
if ret[i].Id.Get() < ret[j].Id.Get() {
return true
}
}
return false
})
return ret, nil
}
/*
Return all profiles as NodeInfo
*/
func (config *NodeYaml) FindAllProfiles() ([]NodeInfo, error) {
var ret []NodeInfo
for name, profile := range config.NodeProfiles {
var p NodeInfo
p.NetDevs = make(map[string]*NetDevEntry)
p.Tags = make(map[string]*Entry)
p.Kernel = new(KernelEntry)
p.Ipmi = new(IpmiEntry)
p.Id.Set(name)
for keyname, key := range profile.Keys {
profile.Tags[keyname] = key
delete(profile.Keys, keyname)
}
p.SetFrom(profile)
p.Ipmi.Ipaddr.Set(profile.IpmiIpaddr)
p.Ipmi.Netmask.Set(profile.IpmiNetmask)
p.Ipmi.Port.Set(profile.IpmiPort)
p.Ipmi.Gateway.Set(profile.IpmiGateway)
p.Ipmi.UserName.Set(profile.IpmiUserName)
p.Ipmi.Password.Set(profile.IpmiPassword)
p.Ipmi.Interface.Set(profile.IpmiInterface)
p.Ipmi.Write.Set(profile.IpmiWrite)
p.Kernel.Args.Set(profile.KernelArgs)
p.Kernel.Override.Set(profile.KernelOverride)
p.Kernel.Override.Set(profile.KernelVersion)
// delete deprecated stuff
profile.IpmiIpaddr = ""
profile.IpmiNetmask = ""
profile.IpmiGateway = ""
profile.IpmiUserName = ""
profile.IpmiPassword = ""
profile.IpmiInterface = ""
profile.IpmiWrite = ""
profile.KernelArgs = ""
profile.KernelOverride = ""
profile.KernelVersion = ""
// Merge Keys into Tags for backwards compatibility
if len(profile.Tags) == 0 {
profile.Tags = make(map[string]string)
}
ret = append(ret, p)
}
sort.Slice(ret, func(i, j int) bool {
if ret[i].ClusterName.Get() < ret[j].ClusterName.Get() {
return true
} else if ret[i].ClusterName.Get() == ret[j].ClusterName.Get() {
if ret[i].Id.Get() < ret[j].Id.Get() {
return true
}
}
return false
})
return ret, nil
}
/*
Return the names of all available profiles
*/
func (config *NodeYaml) ListAllProfiles() []string {
var ret []string
for name := range config.NodeProfiles {
ret = append(ret, name)
}
return ret
}
func (config *NodeYaml) FindDiscoverableNode() (NodeInfo, string, error) {
var ret NodeInfo
nodes, _ := config.FindAllNodes()
for _, node := range nodes {
if !node.Discoverable.GetB() {
continue
}
for netdev, dev := range node.NetDevs {
if !dev.Hwaddr.Defined() {
return node, netdev, nil
}
}
}
return ret, "", errors.New("no unconfigured nodes found")
}