package node import ( "fmt" "github.com/hpcng/warewulf/internal/pkg/errors" "github.com/hpcng/warewulf/internal/pkg/util" "github.com/hpcng/warewulf/internal/pkg/wwlog" "gopkg.in/yaml.v2" "io/ioutil" "os" "regexp" "strings" ) const ConfigFile = "/etc/warewulf/nodes.conf" const LocalStateDir = "/var/warewulf" func init() { //TODO: Check to make sure nodes.conf is found } type nodeYaml struct { NodeGroups map[string]*nodeGroup //`yaml:"nodegroups"` } type nodeGroup struct { Comment string Vnfs string `yaml:"vnfs"` Ipxe string `yaml:"ipxe template,omitempty"` SystemOverlay string `yaml:"system overlay,omitempty""` RuntimeOverlay string `yaml:"runtime overlay""` DomainSuffix string `yaml:"domain suffix"` KernelVersion string `yaml:"kernel version"` KernelArgs string `yaml:"kernel args"` IpmiUserName string `yaml:"ipmi username,omitempty"` IpmiPassword string `yaml:"ipmi password,omitempty"` Nodes map[string]*nodeEntry } type nodeEntry struct { Hostname string `yaml:"hostname,omitempty"` Vnfs string `yaml:"vnfs,omitempty"` Ipxe string `yaml:"ipxe template,omitempty"` SystemOverlay string `yaml:"system overlay,omitempty"` RuntimeOverlay string `yaml:"runtime overlay,omitempty"` DomainSuffix string `yaml:"domain suffix,omitempty"` KernelVersion string `yaml:"kernel version,omitempty"` KernelArgs string `yaml:"kernel args,omitempty"` IpmiIpaddr string `yaml:"ipmi ipaddr,omitempty"` IpmiUserName string `yaml:"ipmi username,omitempty"` IpmiPassword string `yaml:"ipmi password,omitempty"` NetDevs map[string]*netDevs } type netDevs struct { Type string `yaml:"type,omitempty"` Hwaddr string Ipaddr string Netmask string Gateway string`yaml:"gateway,omitempty"` } type NodeInfo struct { Id string Gid string Uid string GroupName string HostName string DomainName string Fqdn string Vnfs string Ipxe string SystemOverlay string RuntimeOverlay string KernelVersion string KernelArgs string IpmiIpaddr string IpmiUserName string IpmiPassword string NetDevs map[string]*netDevs } type GroupInfo struct { Id string GroupName string DomainName string Vnfs string Ipxe string SystemOverlay string RuntimeOverlay string KernelVersion string KernelArgs string IpmiUserName string IpmiPassword string } func New() (nodeYaml, error) { var ret nodeYaml wwlog.Printf(wwlog.DEBUG, "Opening node configuration file: %s\n", ConfigFile) data, err := ioutil.ReadFile(ConfigFile) if err != nil { fmt.Printf("error reading node configuration file\n") return ret, err } err = yaml.Unmarshal(data, &ret) if err != nil { return ret, err } return ret, nil } func (self *nodeYaml) AddGroup(groupID string) error { var group nodeGroup if _, ok := self.NodeGroups[groupID]; ok { return errors.New("Group name already exists: " + groupID) } self.NodeGroups[groupID] = &group self.NodeGroups[groupID].DomainSuffix = groupID return nil } func (self *nodeYaml) AddNode(groupID string, nodeID string) error { var node nodeEntry wwlog.Printf(wwlog.VERBOSE, "Adding new node: %s/%s\n", groupID, nodeID) if _, ok := self.NodeGroups[groupID]; ok { if _, ok := self.NodeGroups[groupID].Nodes[nodeID]; ok { return errors.New("Nodename already exists in group: " + nodeID) } } else { return errors.New("Group does not exist: "+groupID) } self.NodeGroups[groupID].Nodes[nodeID] = &node self.NodeGroups[groupID].Nodes[nodeID].Hostname = nodeID return nil } func (self *nodeYaml) DelNode(groupID string, nodeID string) error { if _, ok := self.NodeGroups[groupID]; ok { if _, ok := self.NodeGroups[groupID].Nodes[nodeID]; ok { delete(self.NodeGroups[groupID].Nodes, nodeID) wwlog.Printf(wwlog.VERBOSE, "Deleting node: %s/%s\n", groupID, nodeID) } else { return errors.New("Node '"+nodeID+"' was not found in group '"+groupID+"'") } } else { return errors.New("Group '"+groupID+"' was not found") } return nil } func (self *nodeYaml) DelNodeNet(groupID string, nodeID string, netDev string) error { if _, ok := self.NodeGroups[groupID]; ok { if _, ok := self.NodeGroups[groupID].Nodes[nodeID]; ok { if _, ok := self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev]; ok { wwlog.Printf(wwlog.VERBOSE, "Deleting node network device: %s/%s:%s\n", groupID, nodeID, netDev) delete(self.NodeGroups[groupID].Nodes[nodeID].NetDevs, netDev) } else { return errors.New("Network device '"+netDev+"' was not found in node '"+groupID+"/"+nodeID+"'") } } else { return errors.New("Node '"+nodeID+"' was not found in group '"+groupID+"'") } } else { return errors.New("Group '"+groupID+"' was not found") } return nil } func (self *nodeYaml) DelGroup(groupname string) error { if _, ok := self.NodeGroups[groupname]; ok { delete(self.NodeGroups, groupname) } else { return errors.New("Node group undefined: " + groupname) } return nil } func (self *nodeYaml) SetGroupVal(groupID string, entry string, value string) error { if strings.ToUpper(value) == "UNDEF" || strings.ToUpper(value) == "NIL" || strings.ToUpper(value) == "DEL" { value = "" } if _, ok := self.NodeGroups[groupID]; ok { wwlog.Printf(wwlog.VERBOSE, "Setting group %s to: %s = '%s'\n", groupID, entry, value ) switch strings.ToUpper(entry) { case "VNFS": util.ValidateOrDie("VNFS", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Vnfs = value case "KERNEL": util.ValidateOrDie("Kernel Version", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].KernelVersion = value case "DOMAINSUFFIX": util.ValidateOrDie("Domain", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].DomainSuffix = value case "IPXE": util.ValidateOrDie("iPXE Template", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Ipxe = value case "SYSTEMOVERLAY": util.ValidateOrDie("System Overlay", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].SystemOverlay = value case "RUNTIMEOVERLAY": util.ValidateOrDie("Runtime Overlay", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].RuntimeOverlay = value case "IPMIUSERNAME": util.ValidateOrDie("IPMI Username", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].IpmiUserName = value case "IPMIPASSWORD": util.ValidateOrDie("IPMI Password", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].IpmiPassword = value } } else { return errors.New("Group does not exist: " +groupID) } return nil } func (self *nodeYaml) SetNodeVal(groupID string, nodeID string, entry string, value string) error { if strings.ToUpper(value) == "UNDEF" || strings.ToUpper(value) == "NIL" || strings.ToUpper(value) == "DEL" { value = "" } if _, ok := self.NodeGroups[groupID]; ok { if _, ok := self.NodeGroups[groupID].Nodes[nodeID]; ok { wwlog.Printf(wwlog.VERBOSE, "Setting node %s/%s to: %s = '%s'\n", groupID, nodeID, entry, value ) switch strings.ToUpper(entry) { case "VNFS": util.ValidateOrDie("VNFS", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].Vnfs = value case "KERNEL": util.ValidateOrDie("Kernel Version", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].KernelVersion = value case "DOMAINSUFFIX": util.ValidateOrDie("Domain", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].DomainSuffix = value case "IPXE": util.ValidateOrDie("iPXE Template", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].Ipxe = value case "SYSTEMOVERLAY": util.ValidateOrDie("System Overlay", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].SystemOverlay = value case "RUNTIMEOVERLAY": util.ValidateOrDie("Runtime Overlay", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].RuntimeOverlay = value case "HOSTNAME": util.ValidateOrDie("Hostname", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].Hostname = value case "IPMIIPADDR": util.ValidateOrDie("IPMI IP Address", entry, "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$") self.NodeGroups[groupID].Nodes[nodeID].IpmiIpaddr = value case "IPMIUSERNAME": util.ValidateOrDie("IPMI Username", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].IpmiUserName = value case "IPMIPASSWORD": util.ValidateOrDie("IPMI Password", entry, "^[a-zA-Z0-9-._]*$") self.NodeGroups[groupID].Nodes[nodeID].IpmiPassword = value } } else { return errors.New("Node does not exist: " +groupID+ "/" +nodeID) } } else { return errors.New("Group does not exist: " +groupID) } return nil } func (self *nodeYaml) SetNodeNet(groupID string, nodeID string, netDev string, entry string, value string) error { if strings.ToUpper(value) == "UNDEF" || strings.ToUpper(value) == "NIL" || strings.ToUpper(value) == "DEL" { value = "" } if _, ok := self.NodeGroups[groupID]; ok { if _, ok := self.NodeGroups[groupID].Nodes[nodeID]; ok { if _, ok := self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev]; ok { wwlog.Printf(wwlog.VERBOSE, "Editing existing node NetDev entry for node: %s/%s\n", groupID, nodeID) } else { var nd netDevs self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev] = &nd } } else { return errors.New("Node does not exist: "+groupID+"/"+nodeID) } } else { return errors.New("Group does not exist: "+groupID) } switch strings.ToUpper(entry) { case "IPADDR": util.ValidateOrDie("IP address", value, "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$") self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev].Ipaddr = value case "NETMASK": util.ValidateOrDie("Netmask", value, "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$") self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev].Netmask = value case "GATEWAY": util.ValidateOrDie("Gateway", value, "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$") self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev].Gateway = value case "TYPE": util.ValidateOrDie("Network device type", value, "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$") self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev].Type = value case "HWADDR": util.ValidateOrDie("HW address", value, "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$") self.NodeGroups[groupID].Nodes[nodeID].NetDevs[netDev].Hwaddr = value } return nil } func (self *nodeYaml) Persist() error { out, err := yaml.Marshal(self) if err != nil { return err } file, err := os.OpenFile(ConfigFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { wwlog.Printf(wwlog.ERROR, "%s\n", err) os.Exit(1) } defer file.Close() _, err = file.WriteString(string(out)) if err != nil { return err } return nil } func (self *nodeYaml) FindAllGroups() ([]GroupInfo, error) { var ret []GroupInfo for groupname, group := range self.NodeGroups { var g GroupInfo g.Id = groupname g.GroupName = groupname g.RuntimeOverlay = group.RuntimeOverlay g.SystemOverlay = group.SystemOverlay g.Ipxe = group.Ipxe g.KernelVersion = group.KernelVersion g.KernelArgs = group.KernelArgs g.Vnfs = group.Vnfs g.IpmiUserName = group.IpmiUserName g.IpmiPassword = group.IpmiPassword g.DomainName = group.DomainSuffix // TODO: Validate or die on all inputs ret = append(ret, g) } return ret, nil } func (self *nodeYaml) FindAllNodes() ([]NodeInfo, error) { var ret []NodeInfo for groupname, group := range self.NodeGroups { for nodename, node := range group.Nodes { var n NodeInfo n.Id = nodename n.Gid = groupname // n.Uid = groupname + ":" + nodename n.GroupName = groupname n.HostName = node.Hostname n.IpmiIpaddr = node.IpmiIpaddr n.Vnfs = group.Vnfs n.SystemOverlay = group.SystemOverlay n.RuntimeOverlay = group.RuntimeOverlay n.KernelVersion = group.KernelVersion n.KernelArgs = group.KernelArgs n.DomainName = group.DomainSuffix n.Ipxe = group.Ipxe n.IpmiUserName = group.IpmiUserName n.IpmiPassword = group.IpmiPassword n.NetDevs = node.NetDevs if node.IpmiUserName != "" { n.IpmiUserName = node.IpmiUserName } if node.IpmiPassword != "" { n.IpmiPassword = node.IpmiPassword } if node.KernelVersion != "" { n.KernelVersion = node.KernelVersion } if node.Vnfs != "" { n.Vnfs = node.Vnfs } if node.SystemOverlay != "" { n.SystemOverlay = node.SystemOverlay } if node.RuntimeOverlay != "" { n.RuntimeOverlay = node.RuntimeOverlay } if node.DomainSuffix != "" { n.DomainName = node.DomainSuffix } if node.Ipxe != "" { n.Ipxe = node.Ipxe } if node.KernelArgs != "" { n.KernelArgs = node.KernelArgs } if n.RuntimeOverlay == "" { n.RuntimeOverlay = "default" } if n.SystemOverlay == "" { n.SystemOverlay = "default" } if n.Ipxe == "" { n.Ipxe = "default" } if n.HostName == "" { n.HostName = nodename } if n.DomainName != "" { n.Fqdn = node.Hostname + "." + n.DomainName } else { n.Fqdn = node.Hostname } util.ValidateOrDie(n.Fqdn +":group name", n.GroupName, "^[a-zA-Z0-9-._]*$") util.ValidateOrDie(n.Fqdn +":vnfs", n.Vnfs, "^[a-zA-Z0-9-._:/]*$") util.ValidateOrDie(n.Fqdn +":system overlay", n.SystemOverlay, "^[a-zA-Z0-9-._]*$") util.ValidateOrDie(n.Fqdn +":runtime overlay", n.RuntimeOverlay, "^[a-zA-Z0-9-._]*$") util.ValidateOrDie(n.Fqdn +":domain suffix", n.DomainName, "^[a-zA-Z0-9-._]*$") util.ValidateOrDie(n.Fqdn +":hostname", n.HostName, "^[a-zA-Z0-9-_]*$") util.ValidateOrDie(n.Fqdn +":kernel version", n.KernelVersion, "^[a-zA-Z0-9-._]*$") ret = append(ret, n) } } return ret, nil } func (nodes *nodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { var ret NodeInfo n, _ := nodes.FindAllNodes() for _, node := range n { for _, dev := range node.NetDevs { if dev.Hwaddr == hwa { return node, nil } } } return ret, errors.New("No nodes found with HW Addr: " + hwa) } func (nodes *nodeYaml) FindByIpaddr(ipaddr string) (NodeInfo, error) { var ret NodeInfo n, _ := nodes.FindAllNodes() for _, node := range n { for _, dev := range node.NetDevs { if dev.Ipaddr == ipaddr { return node, nil } } } return ret, errors.New("No nodes found with IP Addr: " + ipaddr) } func (nodes *nodeYaml) SearchByName(search string) ([]NodeInfo, error) { var ret []NodeInfo n, _ := nodes.FindAllNodes() for _, node := range n { b, _ := regexp.MatchString(search, node.Fqdn) if b == true { ret = append(ret, node) } } return ret, nil } func (nodes *nodeYaml) SearchByNameList(searchList []string) ([]NodeInfo, error) { var ret []NodeInfo n, _ := nodes.FindAllNodes() for _, search := range searchList { for _, node := range n { b, _ := regexp.MatchString(search, node.Fqdn) if b == true { ret = append(ret, node) } } } return ret, nil }