We need an API for Warewulf in order to automate around it. The initial version of the API will look a lot like wwctl. wwctl will call the API so that we do not need to maintain separate code paths. In this preview wwctl calls the API via direct function call, but we can change that to a gprc or REST call to a Warewulf server in the future.
This commit contains:
wwapid WareWulf API Daemon. A grpc server with mTLS auth.
wwapic WareWulf API Client. A grpc client with mTLS auth. It's just a sample that reads the version from the server.
wwapird WareWulf API Rest Daemon. A http REST reverse proxy to wwapid.
There are also sample insecure and secure curls to test with.
Implemented Functionality:
wwctl node add
wwctl node delete
wwctl node list
wwctl node set
wwctl node status
wwctl container build
wwctl container delete
wwctl container import
wwctl container list
wwctl container show
Some notes on the files:
Logic that was in wwctl has moved to warewulf/internal/pkg/api. wwctl just calls the API. wwctl functionality is unchanged.
Everything under the /google directory is copied google proto code to stand up wwapird. It's a bit strange to copy in the code, but that is currently how it's done.
Everything in warewulf/internal/pkg/api/routes/wwapiv1 is generated code.
There are some loose ends here, such as no service installers. I just ran off the command line for development.
The Makefile could use improvement. I'm building by make clean setup proto all build wwapid wwapic wwapird ; echo $?
I copied the configs from warewulf/etc to /usr/local/etc/warewulf manually.
462 lines
14 KiB
Go
462 lines
14 KiB
Go
package node
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hpcng/warewulf/internal/pkg/buildconfig"
|
|
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
|
|
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
var ConfigFile string
|
|
|
|
func init() {
|
|
if ConfigFile == "" {
|
|
ConfigFile = path.Join(buildconfig.SYSCONFDIR(), "warewulf/nodes.conf")
|
|
}
|
|
}
|
|
|
|
func New() (NodeYaml, error) {
|
|
var ret NodeYaml
|
|
|
|
wwlog.Printf(wwlog.VERBOSE, "Opening node configuration file: %s\n", ConfigFile)
|
|
data, err := ioutil.ReadFile(ConfigFile)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
|
|
wwlog.Printf(wwlog.DEBUG, "Unmarshaling the node configuration\n")
|
|
err = yaml.Unmarshal(data, &ret)
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
|
|
wwlog.Printf(wwlog.DEBUG, "Returning node object\n")
|
|
|
|
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
|
|
wwconfig, err := warewulfconf.New()
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
wwlog.Printf(wwlog.DEBUG, "Finding all nodes...\n")
|
|
for nodename, node := range config.Nodes {
|
|
var n NodeInfo
|
|
|
|
wwlog.Printf(wwlog.DEBUG, "In node loop: %s\n", nodename)
|
|
n.NetDevs = make(map[string]*NetDevEntry)
|
|
n.Tags = make(map[string]*Entry)
|
|
n.Kernel = new(KernelEntry)
|
|
n.Ipmi = new(IpmiEntry)
|
|
n.SystemOverlay.SetDefault("wwinit")
|
|
n.RuntimeOverlay.SetDefault("generic")
|
|
n.Ipxe.SetDefault("default")
|
|
n.Init.SetDefault("/sbin/init")
|
|
n.Root.SetDefault("initramfs")
|
|
n.Kernel.Args.SetDefault("quiet crashkernel=no vga=791")
|
|
|
|
fullname := strings.SplitN(nodename, ".", 2)
|
|
if len(fullname) > 1 {
|
|
n.ClusterName.SetDefault(fullname[1])
|
|
}
|
|
|
|
if len(node.Profiles) == 0 {
|
|
n.Profiles = []string{"default"}
|
|
} else {
|
|
n.Profiles = node.Profiles
|
|
}
|
|
|
|
n.Id.Set(nodename)
|
|
n.Comment.Set(node.Comment)
|
|
n.ContainerName.Set(node.ContainerName)
|
|
n.ClusterName.Set(node.ClusterName)
|
|
n.Ipxe.Set(node.Ipxe)
|
|
n.Init.Set(node.Init)
|
|
// backward compatibility for old Ipmi config
|
|
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)
|
|
// delete deprectated structures so that they do not get unmarshalled
|
|
node.IpmiIpaddr = ""
|
|
node.IpmiNetmask = ""
|
|
node.IpmiGateway = ""
|
|
node.IpmiUserName = ""
|
|
node.IpmiPassword = ""
|
|
node.IpmiInterface = ""
|
|
node.IpmiWrite = ""
|
|
|
|
if node.Ipmi != nil {
|
|
n.Ipmi.Ipaddr.Set(node.Ipmi.Ipaddr)
|
|
n.Ipmi.Netmask.Set(node.Ipmi.Netmask)
|
|
n.Ipmi.Port.Set(node.Ipmi.Port)
|
|
n.Ipmi.Gateway.Set(node.Ipmi.Gateway)
|
|
n.Ipmi.UserName.Set(node.Ipmi.UserName)
|
|
n.Ipmi.Password.Set(node.Ipmi.Password)
|
|
n.Ipmi.Interface.Set(node.Ipmi.Interface)
|
|
n.Ipmi.Write.Set(node.Ipmi.Write)
|
|
}
|
|
n.SystemOverlay.SetSlice(node.SystemOverlay)
|
|
n.RuntimeOverlay.SetSlice(node.RuntimeOverlay)
|
|
n.Root.Set(node.Root)
|
|
n.AssetKey.Set(node.AssetKey)
|
|
n.Discoverable.Set(node.Discoverable)
|
|
// backward compatibility
|
|
n.Kernel.Args.Set(node.KernelArgs)
|
|
n.Kernel.Override.Set(node.KernelOverride)
|
|
n.Kernel.Override.Set(node.KernelVersion)
|
|
node.KernelArgs = ""
|
|
node.KernelOverride = ""
|
|
node.KernelVersion = ""
|
|
|
|
if node.Kernel != nil {
|
|
n.Kernel.Args.Set(node.Kernel.Args)
|
|
if node.Kernel.Override != "" {
|
|
n.Kernel.Override.Set(node.Kernel.Override)
|
|
} else if node.Kernel.Version != "" {
|
|
n.Kernel.Override.Set(node.Kernel.Version)
|
|
}
|
|
}
|
|
|
|
for devname, netdev := range node.NetDevs {
|
|
if _, ok := n.NetDevs[devname]; !ok {
|
|
var netdev NetDevEntry
|
|
n.NetDevs[devname] = &netdev
|
|
}
|
|
n.NetDevs[devname].Device.Set(netdev.Device)
|
|
n.NetDevs[devname].Ipaddr.Set(netdev.Ipaddr)
|
|
n.NetDevs[devname].Ipaddr6.Set(netdev.Ipaddr6)
|
|
|
|
// Derive value of ipv6 address from ipv4 if not explicitly set
|
|
if wwconfig.Ipaddr6 != "" && netdev.Ipaddr != "" {
|
|
ipv4Arr := strings.Split(netdev.Ipaddr, ".")
|
|
// error can be ignored as check was done at init
|
|
_, ipv6Net, _ := net.ParseCIDR(wwconfig.Ipaddr6)
|
|
mSize, _ := ipv6Net.Mask.Size()
|
|
ipv6str := fmt.Sprintf("%s%s:%s:%s:%s/%v",
|
|
ipv6Net.IP.String(), ipv4Arr[0], ipv4Arr[1], ipv4Arr[2], ipv4Arr[3], mSize)
|
|
if strings.Count(ipv6Net.IP.String(), ":") == 5 {
|
|
ipv6str = strings.Replace(ipv6str, "::", ":", -1)
|
|
}
|
|
n.NetDevs[devname].Ipaddr6.SetDefault(ipv6str)
|
|
}
|
|
n.NetDevs[devname].Netmask.Set(netdev.Netmask)
|
|
n.NetDevs[devname].Netmask.SetDefault("255.255.255.0")
|
|
n.NetDevs[devname].Hwaddr.Set(strings.ToLower(netdev.Hwaddr))
|
|
n.NetDevs[devname].Gateway.Set(netdev.Gateway)
|
|
n.NetDevs[devname].Type.Set(netdev.Type)
|
|
n.NetDevs[devname].OnBoot.Set(netdev.OnBoot)
|
|
n.NetDevs[devname].Primary.Set(netdev.Primary)
|
|
n.NetDevs[devname].Primary.Set(netdev.Default) // backwards compatibility
|
|
// for just one netdev, it is always the primary
|
|
if len(node.NetDevs) == 1 {
|
|
n.NetDevs[devname].Primary.Set("true")
|
|
}
|
|
n.NetDevs[devname].Tags = make(map[string]*Entry)
|
|
for keyname, key := range netdev.Tags {
|
|
if _, ok := n.Tags[keyname]; !ok {
|
|
var keyVar Entry
|
|
n.NetDevs[devname].Tags[keyname] = &keyVar
|
|
}
|
|
n.NetDevs[devname].Tags[keyname].Set(key)
|
|
}
|
|
n.NetDevs[devname].Tags = make(map[string]*Entry)
|
|
for keyname, key := range netdev.Tags {
|
|
if _, ok := n.Tags[keyname]; !ok {
|
|
var keyVar Entry
|
|
n.NetDevs[devname].Tags[keyname] = &keyVar
|
|
}
|
|
n.NetDevs[devname].Tags[keyname].Set(key)
|
|
}
|
|
}
|
|
|
|
// Merge Keys into Tags for backwards compatibility
|
|
if len(node.Tags) == 0 {
|
|
node.Tags = make(map[string]string)
|
|
}
|
|
for keyname, key := range node.Keys {
|
|
node.Tags[keyname] = key
|
|
delete(node.Keys, keyname)
|
|
}
|
|
|
|
for keyname, key := range node.Tags {
|
|
if _, ok := n.Tags[keyname]; !ok {
|
|
var key Entry
|
|
n.Tags[keyname] = &key
|
|
}
|
|
n.Tags[keyname].Set(key)
|
|
}
|
|
|
|
for _, p := range n.Profiles {
|
|
if _, ok := config.NodeProfiles[p]; !ok {
|
|
wwlog.Printf(wwlog.WARN, "Profile not found for node '%s': %s\n", nodename, p)
|
|
continue
|
|
}
|
|
|
|
wwlog.Printf(wwlog.VERBOSE, "Merging profile into node: %s <- %s\n", nodename, p)
|
|
|
|
n.Comment.SetAlt(config.NodeProfiles[p].Comment, p)
|
|
n.ClusterName.SetAlt(config.NodeProfiles[p].ClusterName, p)
|
|
n.ContainerName.SetAlt(config.NodeProfiles[p].ContainerName, p)
|
|
if config.NodeProfiles[p].Kernel != nil {
|
|
n.Kernel.Args.SetAlt(config.NodeProfiles[p].Kernel.Args, p)
|
|
}
|
|
n.Ipxe.SetAlt(config.NodeProfiles[p].Ipxe, p)
|
|
n.Init.SetAlt(config.NodeProfiles[p].Init, p)
|
|
if config.NodeProfiles[p].Ipmi != nil {
|
|
n.Ipmi.Ipaddr.SetAlt(config.NodeProfiles[p].Ipmi.Ipaddr, p)
|
|
n.Ipmi.Netmask.SetAlt(config.NodeProfiles[p].Ipmi.Netmask, p)
|
|
n.Ipmi.Port.SetAlt(config.NodeProfiles[p].Ipmi.Port, p)
|
|
n.Ipmi.Gateway.SetAlt(config.NodeProfiles[p].Ipmi.Gateway, p)
|
|
n.Ipmi.UserName.SetAlt(config.NodeProfiles[p].Ipmi.UserName, p)
|
|
n.Ipmi.Password.SetAlt(config.NodeProfiles[p].Ipmi.Password, p)
|
|
n.Ipmi.Interface.SetAlt(config.NodeProfiles[p].Ipmi.Interface, p)
|
|
n.Ipmi.Write.SetAlt(config.NodeProfiles[p].Ipmi.Write, p)
|
|
}
|
|
n.SystemOverlay.SetAltSlice(config.NodeProfiles[p].SystemOverlay, p)
|
|
n.RuntimeOverlay.SetAltSlice(config.NodeProfiles[p].RuntimeOverlay, p)
|
|
n.Root.SetAlt(config.NodeProfiles[p].Root, p)
|
|
n.AssetKey.SetAlt(config.NodeProfiles[p].AssetKey, p)
|
|
n.Discoverable.SetAlt(config.NodeProfiles[p].Discoverable, p)
|
|
|
|
if config.NodeProfiles[p].Kernel != nil {
|
|
if config.NodeProfiles[p].Kernel.Override != "" {
|
|
n.Kernel.Override.SetAlt(config.NodeProfiles[p].Kernel.Override, p)
|
|
} else if config.NodeProfiles[p].Kernel.Version != "" {
|
|
n.Kernel.Override.SetAlt(config.NodeProfiles[p].Kernel.Version, p)
|
|
}
|
|
}
|
|
|
|
for devname, netdev := range config.NodeProfiles[p].NetDevs {
|
|
if _, ok := n.NetDevs[devname]; !ok {
|
|
var netdev NetDevEntry
|
|
n.NetDevs[devname] = &netdev
|
|
}
|
|
wwlog.Printf(wwlog.DEBUG, "Updating profile (%s) netdev: %s\n", p, devname)
|
|
|
|
n.NetDevs[devname].Device.SetAlt(netdev.Device, p)
|
|
n.NetDevs[devname].Ipaddr.SetAlt(netdev.Ipaddr, p) //FIXME? <- Ipaddr must be uniq
|
|
n.NetDevs[devname].Netmask.SetAlt(netdev.Netmask, p)
|
|
n.NetDevs[devname].Hwaddr.SetAlt(strings.ToLower(netdev.Hwaddr), p)
|
|
n.NetDevs[devname].Gateway.SetAlt(netdev.Gateway, p)
|
|
n.NetDevs[devname].Type.SetAlt(netdev.Type, p)
|
|
n.NetDevs[devname].OnBoot.SetAlt(netdev.OnBoot, p)
|
|
n.NetDevs[devname].Primary.SetAlt(netdev.Primary, p)
|
|
if len(netdev.Tags) != 0 {
|
|
for keyname, key := range netdev.Tags {
|
|
if _, ok := n.NetDevs[devname].Tags[keyname]; !ok {
|
|
var keyVar Entry
|
|
n.NetDevs[devname].Tags[keyname] = &keyVar
|
|
}
|
|
n.NetDevs[devname].Tags[keyname].SetAlt(key, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merge Keys into Tags for backwards compatibility
|
|
if len(config.NodeProfiles[p].Tags) == 0 {
|
|
config.NodeProfiles[p].Tags = make(map[string]string)
|
|
}
|
|
for keyname, key := range config.NodeProfiles[p].Keys {
|
|
config.NodeProfiles[p].Tags[keyname] = key
|
|
delete(config.NodeProfiles[p].Keys, keyname)
|
|
}
|
|
|
|
for keyname, key := range config.NodeProfiles[p].Tags {
|
|
if _, ok := n.Tags[keyname]; !ok {
|
|
var key Entry
|
|
n.Tags[keyname] = &key
|
|
}
|
|
n.Tags[keyname].SetAlt(key, p)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
p.Comment.Set(profile.Comment)
|
|
p.ClusterName.Set(profile.ClusterName)
|
|
p.ContainerName.Set(profile.ContainerName)
|
|
p.Ipxe.Set(profile.Ipxe)
|
|
p.Init.Set(profile.Init)
|
|
// backward compatibility
|
|
p.Kernel.Args.Set(profile.KernelArgs)
|
|
p.Kernel.Override.Set(profile.KernelOverride)
|
|
p.Kernel.Override.Set(profile.KernelVersion)
|
|
profile.KernelArgs = ""
|
|
profile.KernelOverride = ""
|
|
profile.KernelVersion = ""
|
|
if profile.Kernel != nil {
|
|
p.Kernel.Args.Set(profile.Kernel.Args)
|
|
if profile.Kernel.Override != "" {
|
|
p.Kernel.Override.Set(profile.Kernel.Override)
|
|
} else if profile.Kernel.Version != "" {
|
|
p.Kernel.Override.Set(profile.Kernel.Version)
|
|
}
|
|
}
|
|
// backward compatibility for old Ipmi config
|
|
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)
|
|
// delete deprectated structures so that they do not get unmarshalled
|
|
profile.IpmiIpaddr = ""
|
|
profile.IpmiNetmask = ""
|
|
profile.IpmiGateway = ""
|
|
profile.IpmiUserName = ""
|
|
profile.IpmiPassword = ""
|
|
profile.IpmiInterface = ""
|
|
profile.IpmiWrite = ""
|
|
if profile.Ipmi != nil {
|
|
p.Ipmi.Netmask.Set(profile.Ipmi.Netmask)
|
|
p.Ipmi.Port.Set(profile.Ipmi.Port)
|
|
p.Ipmi.Gateway.Set(profile.Ipmi.Gateway)
|
|
p.Ipmi.UserName.Set(profile.Ipmi.UserName)
|
|
p.Ipmi.Password.Set(profile.Ipmi.Password)
|
|
p.Ipmi.Interface.Set(profile.Ipmi.Interface)
|
|
p.Ipmi.Write.Set(profile.Ipmi.Write)
|
|
}
|
|
p.RuntimeOverlay.SetSlice(profile.RuntimeOverlay)
|
|
p.SystemOverlay.SetSlice(profile.SystemOverlay)
|
|
p.Root.Set(profile.Root)
|
|
p.AssetKey.Set(profile.AssetKey)
|
|
p.Discoverable.Set(profile.Discoverable)
|
|
|
|
for devname, netdev := range profile.NetDevs {
|
|
if _, ok := p.NetDevs[devname]; !ok {
|
|
var netdev NetDevEntry
|
|
p.NetDevs[devname] = &netdev
|
|
}
|
|
|
|
wwlog.Printf(wwlog.DEBUG, "Updating profile netdev: %s\n", devname)
|
|
|
|
p.NetDevs[devname].Device.Set(netdev.Device)
|
|
p.NetDevs[devname].Netmask.Set(netdev.Netmask)
|
|
p.NetDevs[devname].Gateway.Set(netdev.Gateway)
|
|
p.NetDevs[devname].Type.Set(netdev.Type)
|
|
p.NetDevs[devname].OnBoot.Set(netdev.OnBoot)
|
|
p.NetDevs[devname].Primary.Set(netdev.Primary)
|
|
p.NetDevs[devname].Primary.Set(netdev.Default) // backwards compatibility
|
|
|
|
// The following should not be set in a profile.
|
|
if netdev.Ipaddr != "" {
|
|
wwlog.Printf(wwlog.WARN, "Ignoring ip address %v in profile %v\n", netdev.Ipaddr, name)
|
|
}
|
|
if netdev.Hwaddr != "" {
|
|
wwlog.Printf(wwlog.WARN, "Ignoring hardware address %v in profile %v\n", netdev.Hwaddr, name)
|
|
}
|
|
p.NetDevs[devname].Tags = make(map[string]*Entry)
|
|
for keyname, key := range netdev.Tags {
|
|
if _, ok := p.Tags[keyname]; !ok {
|
|
var keyVar Entry
|
|
p.NetDevs[devname].Tags[keyname] = &keyVar
|
|
}
|
|
p.NetDevs[devname].Tags[keyname].Set(key)
|
|
}
|
|
|
|
}
|
|
|
|
// Merge Keys into Tags for backwards compatibility
|
|
if len(profile.Tags) == 0 {
|
|
profile.Tags = make(map[string]string)
|
|
}
|
|
for keyname, key := range profile.Keys {
|
|
profile.Tags[keyname] = key
|
|
delete(profile.Keys, keyname)
|
|
}
|
|
|
|
for keyname, key := range profile.Tags {
|
|
if _, ok := p.Tags[keyname]; !ok {
|
|
var key Entry
|
|
p.Tags[keyname] = &key
|
|
}
|
|
p.Tags[keyname].Set(key)
|
|
}
|
|
|
|
// TODO: Validate or die on all inputs
|
|
|
|
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
|
|
}
|
|
|
|
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")
|
|
}
|