Files
warewulf/internal/pkg/node/constructors.go
Jonathon Anderson feeb6211af Remove device: eth0 from dummy interface
With eth0 specified as the default interface device, all interfaces by default
were attempted to be renamed to eth0. This didn't break before because device
renaming wasn't working for multiple interfaces in general; but now that it's
fixed, we should no longer be setting a default interface name.

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
2023-12-21 22:11:44 -07:00

337 lines
9.0 KiB
Go

package node
import (
"errors"
"fmt"
"os"
"path"
"sort"
"strings"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"gopkg.in/yaml.v2"
)
var (
ConfigFile string
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:
type: ethernet
netmask: 255.255.255.0
onboot: true`
/*
Creates a new nodeDb object from the on-disk configuration
*/
func New() (NodeYaml, error) {
conf := warewulfconf.Get()
if ConfigFile == "" {
ConfigFile = path.Join(conf.Paths.Sysconfdir, "warewulf/nodes.conf")
}
if DefaultConfig == "" {
DefaultConfig = path.Join(conf.Paths.Datadir, "warewulf/defaults.conf")
}
wwlog.Verbose("Opening node configuration file: %s", ConfigFile)
data, err := os.ReadFile(ConfigFile)
if err != nil {
return NodeYaml{}, err
}
return Parse(data)
}
// Parse constructs a new nodeDb object from an input YAML
// document. Passes any errors return from yaml.Unmarshal. Returns an
// error if any parsed value is not of a valid type for the given
// parameter.
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 {
SetDefFrom(defConfNet, netdev)
}
// 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 && !n.PrimaryNetDev.Defined() {
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
}
/*
FindDiscoverableNode returns the first discoverable node and an
interface to associate with the discovered interface. If the node has
a primary interface, it is returned; otherwise, the first interface
without a hardware address is returned.
If no unconfigured nodes are found, an error is returned.
*/
func (config *NodeYaml) FindDiscoverableNode() (NodeInfo, string, error) {
var ret NodeInfo
nodes, _ := config.FindAllNodes()
for _, node := range nodes {
if !node.Discoverable.GetB() {
continue
}
if _, ok := node.NetDevs[node.PrimaryNetDev.Get()]; ok {
return node, node.PrimaryNetDev.Get(), nil
}
for netdev, dev := range node.NetDevs {
if !dev.Hwaddr.Defined() {
return node, netdev, nil
}
}
}
return ret, "", errors.New("no unconfigured nodes found")
}