removed NodeInfo and using NodeConf instead

This is a significant change in the undelying data model!

nodeDb, err := node.New()

will result in a structure which contains the on disk
values. Only

nodeDb.FindAllNodes() or nodeDb.GetNode(id) will give
the nodes with its merged in profiles.

Signed-off-by: Christian Goll <cgoll@suse.com>
This commit is contained in:
Christian Goll
2023-12-15 15:35:26 +01:00
committed by Jonathon Anderson
parent dc8fb2d6f2
commit 28a7b9fe84
27 changed files with 967 additions and 1801 deletions

View File

@@ -1,12 +1,11 @@
package node
import (
"errors"
"fmt"
"bytes"
"encoding/gob"
"os"
"path"
"sort"
"strings"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
@@ -15,45 +14,9 @@ import (
)
var (
ConfigFile string
DefaultConfig string
ConfigFile string
)
// used as fallback if DefaultConfig can't be read
var FallBackConf = `---
defaultnode:
runtime overlay:
- hosts
- ssh.authorized_keys
- syncuser
system overlay:
- wwinit
- wwclient
- fstab
- hostname
- ssh.host_keys
- issue
- resolv
- udev.netname
- systemd.netname
- ifcfg
- NetworkManager
- debian.interfaces
- wicked
- ignition
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
*/
@@ -62,9 +25,6 @@ func New() (NodeYaml, error) {
if ConfigFile == "" {
ConfigFile = path.Join(conf.Paths.Sysconfdir, "warewulf/nodes.conf")
}
if DefaultConfig == "" {
DefaultConfig = path.Join(conf.Warewulf.DataStore, "warewulf/defaults.conf")
}
wwlog.Verbose("Opening node configuration file: %s", ConfigFile)
data, err := os.ReadFile(ConfigFile)
if err != nil {
@@ -78,276 +38,204 @@ func New() (NodeYaml, error) {
// 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 nodeList NodeYaml
var err error
wwlog.Debug("Unmarshaling the node configuration")
err = yaml.Unmarshal(data, &ret)
err = yaml.Unmarshal(data, &nodeList)
if err != nil {
return ret, err
return nodeList, err
}
wwlog.Debug("Checking nodes for types")
if ret.Nodes == nil {
ret.Nodes = map[string]*NodeConf{}
wwlog.Debug("Checking profiles for types")
if nodeList.Nodes == nil {
nodeList.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
/*
for nodeName, node := range nodeList.Nodes {
err = node.Check()
if err != nil {
wwlog.Warn("node: %s parsing error: %s", nodeName, err)
return nodeList, err
}
}
*/
if nodeList.NodeProfiles == nil {
nodeList.NodeProfiles = map[string]*ProfileConf{}
}
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
/*
for profileName, profile := range nodeList.NodeProfiles {
err = profile.Check()
if err != nil {
wwlog.Warn("node: %s parsing error: %s", profileName, err)
return nodeList, err
}
}
}
*/
wwlog.Debug("Returning node object")
return ret, nil
return nodeList, 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
Get a node with its merged in profiles
*/
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)
func (config *NodeYaml) GetNode(id string) (node NodeConf, err error) {
if _, ok := config.Nodes[id]; !ok {
return node, ErrNotFound
}
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)
wwlog.Debug("constructing node: %s", id)
for _, p := range config.Nodes[id].Profiles {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
includedProfile, err := config.GetProfile(p)
if err != nil {
wwlog.Warn("Could not get any defaults")
return node, err
}
}
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()
err = enc.Encode(includedProfile)
if err != nil {
return nil, fmt.Errorf("node: %s check error: %s", nodename, err)
return node, 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)
err = dec.Decode(&node)
if err != nil {
return node, err
}
// 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 n.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)
}
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
err = enc.Encode(config.Nodes[id])
if err != nil {
return node, err
}
err = dec.Decode(&node)
if err != nil {
return node, err
}
// finally set no exported values
node.id = id
node.valid = true
if netdev, ok := node.NetDevs[node.PrimaryNetDev]; ok {
netdev.primary = true
} else {
keys := make([]string, 0)
for k := range node.NetDevs {
keys = append(keys, k)
}
sort.Strings(keys)
if len(keys) > 0 {
wwlog.Debug("%s: no primary defined, sanitizing to: %s", id, keys[0])
node.NetDevs[keys[0]].primary = true
node.PrimaryNetDev = keys[0]
}
}
return
}
sort.Slice(ret, func(i, j int) bool {
if ret[i].ClusterName.Get() < ret[j].ClusterName.Get() {
/*
Get the profile with all the profiles merged in
*/
func (config *NodeYaml) GetProfile(id string) (profile NodeConf, err error) {
if _, ok := config.NodeProfiles[id]; !ok {
return profile, ErrNotFound
}
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
// finally merge in the real profile
err = enc.Encode(config.NodeProfiles[id])
if err != nil {
return profile, err
}
err = dec.Decode(&profile)
if err != nil {
return profile, err
}
// finally set no exported values
profile.id = id
return
}
/*
Get the profiles from the loaded configuration. This function also merges
the profiles with the given profiles.
*/
func (config *NodeYaml) FindAllNodes(profiles ...string) (nodeList []NodeConf, err error) {
if len(profiles) == 0 {
for n := range config.Nodes {
profiles = append(profiles, n)
}
}
wwlog.Debug("Finding profiles: %s", profiles)
for _, profileId := range profiles {
node, err := config.GetNode(profileId)
if err != nil {
return nodeList, err
}
nodeList = append(nodeList, node)
}
sort.Slice(nodeList, func(i, j int) bool {
if nodeList[i].ClusterName < nodeList[j].ClusterName {
return true
} else if ret[i].ClusterName.Get() == ret[j].ClusterName.Get() {
if ret[i].Id.Get() < ret[j].Id.Get() {
} else if nodeList[i].ClusterName == nodeList[j].ClusterName {
if nodeList[i].id < nodeList[j].id {
return true
}
}
return false
})
return ret, nil
return nodeList, 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)
func (config *NodeYaml) FindAllProfiles(profiles ...string) (profileList []NodeConf, err error) {
if len(profiles) == 0 {
for n := range config.NodeProfiles {
profiles = append(profiles, n)
}
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() {
wwlog.Debug("Finding profiles: %s", profiles)
for _, profileId := range profiles {
node, err := config.GetProfile(profileId)
if err != nil {
return profileList, err
}
profileList = append(profileList, node)
}
sort.Slice(profileList, func(i, j int) bool {
if profileList[i].ClusterName < profileList[j].ClusterName {
return true
} else if ret[i].ClusterName.Get() == ret[j].ClusterName.Get() {
if ret[i].Id.Get() < ret[j].Id.Get() {
} else if profileList[i].ClusterName == profileList[j].ClusterName {
if profileList[i].id < profileList[j].id {
return true
}
}
return false
})
return ret, nil
return profileList, nil
}
/*
Return the names of all available nodes
*/
func (config *NodeYaml) ListAllNodes() []string {
nodeList := make([]string, len(config.Nodes))
for name := range config.Nodes {
nodeList = append(nodeList, name)
}
return nodeList
}
/*
Return the names of all available profiles
*/
func (config *NodeYaml) ListAllProfiles() []string {
var ret []string
var nodeList []string
for name := range config.NodeProfiles {
ret = append(ret, name)
nodeList = append(nodeList, name)
}
return ret
}
/*
return a map where the key is the profile id
*/
func (config *NodeYaml) MapAllProfiles() (retMap map[string]*NodeInfo, err error) {
retMap = make(map[string]*NodeInfo)
profileList, err := config.FindAllProfiles()
if err != nil {
return
}
for _, pr := range profileList {
retMap[pr.Id.Get()] = &pr
}
return
}
/*
return a map where the key is the node id
*/
func (config *NodeYaml) MapAllNodes() (retMap map[string]*NodeInfo, err error) {
retMap = make(map[string]*NodeInfo)
nodeList, err := config.FindAllNodes()
if err != nil {
return
}
for _, nd := range nodeList {
retMap[nd.Id.Get()] = &nd
}
return
return nodeList
}
/*
@@ -356,26 +244,25 @@ 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.
If no unconfigured node is found, an error is returned.
*/
func (config *NodeYaml) FindDiscoverableNode() (NodeInfo, string, error) {
var ret NodeInfo
func (config *NodeYaml) FindDiscoverableNode() (string, string, error) {
nodes, _ := config.FindAllNodes()
for _, node := range nodes {
if !node.Discoverable.GetB() {
if !node.Discoverable {
continue
}
if _, ok := node.NetDevs[node.PrimaryNetDev.Get()]; ok {
return node, node.PrimaryNetDev.Get(), nil
if _, ok := node.NetDevs[node.PrimaryNetDev]; ok {
return node.Id(), node.PrimaryNetDev, nil
}
for netdev, dev := range node.NetDevs {
if !dev.Hwaddr.Defined() {
return node, netdev, nil
if dev.Hwaddr != "" {
return node.Id(), netdev, nil
}
}
}
return ret, "", errors.New("no unconfigured nodes found")
return "", "", ErrNoUnconfigured
}