From 1cc2be693d6713c111369921eb8cc2ad28a78608 Mon Sep 17 00:00:00 2001 From: "Shannon V. Davidson" Date: Thu, 19 Nov 2020 10:25:12 -0600 Subject: [PATCH 1/2] added creation of chroot directory --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 81fcb1d8..8dc3acba 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ all: vendor warewulfd wwctl wwclient files: all install -d -m 0755 /var/warewulf/ + install -d -m 0755 /var/warewulf/chroot install -d -m 0755 /etc/warewulf/ install -d -m 0755 /etc/warewulf/ipxe install -d -m 0755 /var/lib/tftpboot/warewulf/ipxe/ From 6b9fc7dc925f02338e873cef298eec8cd3272044 Mon Sep 17 00:00:00 2001 From: "Shannon V. Davidson" Date: Thu, 19 Nov 2020 17:51:57 -0600 Subject: [PATCH 2/2] initial checkin of node IPMI power control --- README-KVM.md | 6 ++ internal/app/wwctl/node/power/root.go | 31 ----------- internal/app/wwctl/node/poweroff/power.go | 58 ++++++++++++++++++++ internal/app/wwctl/node/poweroff/root.go | 19 +++++++ internal/app/wwctl/node/poweron/power.go | 58 ++++++++++++++++++++ internal/app/wwctl/node/poweron/root.go | 19 +++++++ internal/app/wwctl/node/powerstatus/power.go | 58 ++++++++++++++++++++ internal/app/wwctl/node/powerstatus/root.go | 19 +++++++ internal/app/wwctl/node/root.go | 12 ++-- internal/pkg/assets/assets.go | 28 +++++++++- internal/pkg/power/ipmitool.go | 49 +++++++++++++++++ 11 files changed, 317 insertions(+), 40 deletions(-) delete mode 100644 internal/app/wwctl/node/power/root.go create mode 100644 internal/app/wwctl/node/poweroff/power.go create mode 100644 internal/app/wwctl/node/poweroff/root.go create mode 100644 internal/app/wwctl/node/poweron/power.go create mode 100644 internal/app/wwctl/node/poweron/root.go create mode 100644 internal/app/wwctl/node/powerstatus/power.go create mode 100644 internal/app/wwctl/node/powerstatus/root.go create mode 100644 internal/pkg/power/ipmitool.go diff --git a/README-KVM.md b/README-KVM.md index db5cec38..7ca45a73 100644 --- a/README-KVM.md +++ b/README-KVM.md @@ -68,13 +68,19 @@ # Fedora prerequisites sudo dnf -y install tftp-server tftp sudo dnf -y install dhcp + sudo dnf -y install ipmitool sudo dnf install singularity + sudo dnf install gpgme-devel + sudo dnf install libassuan.x86_64 libassuan-devel.x86_64 # Centos prerequisites sudo yum -y install tftp-server tftp sudo yum -y install dhcp + sudo yum -y install ipmitool sudo yum install http://repo.ctrliq.com/packages/rhel7/ctrl-release.rpm sudo yum install singularityplus + sudo yum install gpgme-devel + sudo yum install libassuan.x86_64 libassuan-devel.x86_64 # follow README.md instructions diff --git a/internal/app/wwctl/node/power/root.go b/internal/app/wwctl/node/power/root.go deleted file mode 100644 index 7e0e7ee5..00000000 --- a/internal/app/wwctl/node/power/root.go +++ /dev/null @@ -1,31 +0,0 @@ -package power - -import ( - "fmt" - "github.com/spf13/cobra" -) - -var ( - baseCmd = &cobra.Command{ - Use: "power", - Short: "Node power management", - Long: "Node Power management commands", - RunE: CobraRunE, - } - test bool -) - -func init() { - baseCmd.PersistentFlags().BoolVarP(&test, "test", "t", false, "Testing.") - -} - -// GetRootCommand returns the root cobra.Command for the application. -func GetCommand() *cobra.Command { - return baseCmd -} - -func CobraRunE(cmd *cobra.Command, args []string) error { - fmt.Printf("Power: Hello World\n") - return nil -} \ No newline at end of file diff --git a/internal/app/wwctl/node/poweroff/power.go b/internal/app/wwctl/node/poweroff/power.go new file mode 100644 index 00000000..b6f05a7d --- /dev/null +++ b/internal/app/wwctl/node/poweroff/power.go @@ -0,0 +1,58 @@ +package poweroff + +import ( + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/power" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "os" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + var returnErr error = nil + + var nodeList []assets.NodeInfo + + if len(args) >= 1 { + //nodeList, _ = assets.SearchByName(args[0]) + nodeList, _ = assets.SearchByNameList(args) + } else { + wwlog.Printf(wwlog.ERROR, "No requested nodes\n") + os.Exit(255) + } + + if len(nodeList) == 0 { + wwlog.Printf(wwlog.ERROR, "No nodes found matching: '%s'\n", args[0]) + os.Exit(255) + } else { + wwlog.Printf(wwlog.VERBOSE, "Found %d matching nodes for power command\n", len(nodeList)) + } + + for _, node := range nodeList { + + if node.IpmiIpaddr == "" { + wwlog.Printf(wwlog.ERROR, "%s: No IPMI IP address\n", node.HostName) + continue + } + + ipmiCmd := power.IPMI{ + HostName: node.IpmiIpaddr, + User: "ADMIN", + Password: "ADMIN", + AuthType: "MD5", + } + + out, err := ipmiCmd.PowerOff() + + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s: %s\n", node.HostName, out) + returnErr = err + continue + } + + wwlog.Printf(wwlog.INFO, "%s: %s\n", node.HostName, out) + } + + return returnErr +} diff --git a/internal/app/wwctl/node/poweroff/root.go b/internal/app/wwctl/node/poweroff/root.go new file mode 100644 index 00000000..3d6a0de1 --- /dev/null +++ b/internal/app/wwctl/node/poweroff/root.go @@ -0,0 +1,19 @@ +package poweroff + +import ( + "github.com/spf13/cobra" +) + +var ( + powerCmd = &cobra.Command{ + Use: "poweroff", + Short: "power off node(s)", + Long: "turn power off for one or more nodes", + RunE: CobraRunE, + } +) + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return powerCmd +} diff --git a/internal/app/wwctl/node/poweron/power.go b/internal/app/wwctl/node/poweron/power.go new file mode 100644 index 00000000..6b5821ab --- /dev/null +++ b/internal/app/wwctl/node/poweron/power.go @@ -0,0 +1,58 @@ +package poweron + +import ( + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/power" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "os" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + var returnErr error = nil + + var nodeList []assets.NodeInfo + + if len(args) >= 1 { + //nodeList, _ = assets.SearchByName(args[0]) + nodeList, _ = assets.SearchByNameList(args) + } else { + wwlog.Printf(wwlog.ERROR, "No requested nodes\n") + os.Exit(255) + } + + if len(nodeList) == 0 { + wwlog.Printf(wwlog.ERROR, "No nodes found matching: '%s'\n", args[0]) + os.Exit(255) + } else { + wwlog.Printf(wwlog.VERBOSE, "Found %d matching nodes for power command\n", len(nodeList)) + } + + for _, node := range nodeList { + + if node.IpmiIpaddr == "" { + wwlog.Printf(wwlog.ERROR, "%s: No IPMI IP address\n", node.HostName) + continue + } + + ipmiCmd := power.IPMI{ + HostName: node.IpmiIpaddr, + User: "ADMIN", + Password: "ADMIN", + AuthType: "MD5", + } + + out, err := ipmiCmd.PowerOn() + + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s: %s\n", node.HostName, out) + returnErr = err + continue + } + + wwlog.Printf(wwlog.INFO, "%s: %s\n", node.HostName, out) + } + + return returnErr +} diff --git a/internal/app/wwctl/node/poweron/root.go b/internal/app/wwctl/node/poweron/root.go new file mode 100644 index 00000000..3ef9041e --- /dev/null +++ b/internal/app/wwctl/node/poweron/root.go @@ -0,0 +1,19 @@ +package poweron + +import ( + "github.com/spf13/cobra" +) + +var ( + powerCmd = &cobra.Command{ + Use: "poweron", + Short: "power on node(s)", + Long: "turn power on for one or more nodes", + RunE: CobraRunE, + } +) + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return powerCmd +} diff --git a/internal/app/wwctl/node/powerstatus/power.go b/internal/app/wwctl/node/powerstatus/power.go new file mode 100644 index 00000000..f503389b --- /dev/null +++ b/internal/app/wwctl/node/powerstatus/power.go @@ -0,0 +1,58 @@ +package powerstatus + +import ( + "github.com/hpcng/warewulf/internal/pkg/assets" + "github.com/hpcng/warewulf/internal/pkg/power" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "os" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + + var returnErr error = nil + + var nodeList []assets.NodeInfo + + if len(args) >= 1 { + //nodeList, _ = assets.SearchByName(args[0]) + nodeList, _ = assets.SearchByNameList(args) + } else { + wwlog.Printf(wwlog.ERROR, "No requested nodes\n") + os.Exit(255) + } + + if len(nodeList) == 0 { + wwlog.Printf(wwlog.ERROR, "No nodes found matching: '%s'\n", args[0]) + os.Exit(255) + } else { + wwlog.Printf(wwlog.VERBOSE, "Found %d matching nodes for power command\n", len(nodeList)) + } + + for _, node := range nodeList { + + if node.IpmiIpaddr == "" { + wwlog.Printf(wwlog.ERROR, "%s: No IPMI IP address\n", node.HostName) + continue + } + + ipmiCmd := power.IPMI{ + HostName: node.IpmiIpaddr, + User: "ADMIN", + Password: "ADMIN", + AuthType: "MD5", + } + + out, err := ipmiCmd.PowerStatus() + + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s: %s\n", node.HostName, out) + returnErr = err + continue + } + + wwlog.Printf(wwlog.INFO, "%s: %s\n", node.HostName, out) + } + + return returnErr +} diff --git a/internal/app/wwctl/node/powerstatus/root.go b/internal/app/wwctl/node/powerstatus/root.go new file mode 100644 index 00000000..4c4ade66 --- /dev/null +++ b/internal/app/wwctl/node/powerstatus/root.go @@ -0,0 +1,19 @@ +package powerstatus + +import ( + "github.com/spf13/cobra" +) + +var ( + powerCmd = &cobra.Command{ + Use: "powerstatus", + Short: "node power status", + Long: "Get Node Power Status", + RunE: CobraRunE, + } +) + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return powerCmd +} diff --git a/internal/app/wwctl/node/root.go b/internal/app/wwctl/node/root.go index e06a5af1..150ae6bc 100644 --- a/internal/app/wwctl/node/root.go +++ b/internal/app/wwctl/node/root.go @@ -1,7 +1,9 @@ package node import ( - "github.com/hpcng/warewulf/internal/app/wwctl/node/power" + "github.com/hpcng/warewulf/internal/app/wwctl/node/poweron" + "github.com/hpcng/warewulf/internal/app/wwctl/node/poweroff" + "github.com/hpcng/warewulf/internal/app/wwctl/node/powerstatus" "github.com/spf13/cobra" ) @@ -11,14 +13,12 @@ var ( Short: "Node management", Long: "Management of node settings and power management", } - test bool ) func init() { - baseCmd.PersistentFlags().BoolVarP(&test, "test", "t", false, "Testing.") - - baseCmd.AddCommand(power.GetCommand()) - + baseCmd.AddCommand(poweron.GetCommand()) + baseCmd.AddCommand(poweroff.GetCommand()) + baseCmd.AddCommand(powerstatus.GetCommand()) } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/pkg/assets/assets.go b/internal/pkg/assets/assets.go index 1312465f..e027ad53 100644 --- a/internal/pkg/assets/assets.go +++ b/internal/pkg/assets/assets.go @@ -36,7 +36,7 @@ type nodeGroup struct { RuntimeOverlay string `yaml:"runtime overlay""` DomainSuffix string `yaml:"domain suffix"` KernelVersion string `yaml:"kernel version"` - KernelArgs string `yaml:"kernel args"` + KernelArgs string `yaml:"kernel args"` Nodes map[string]nodeEntry } @@ -48,7 +48,7 @@ type nodeEntry struct { RuntimeOverlay string `yaml:"runtime overlay"` DomainSuffix string `yaml:"domain suffix"` KernelVersion string `yaml:"kernel version"` - KernelArgs string `yaml:"kernel args"` + KernelArgs string `yaml:"kernel args"` IpmiIpaddr string `yaml:"ipmi ipaddr"` NetDevs map[string]netDevs } @@ -72,7 +72,8 @@ type NodeInfo struct { SystemOverlay string RuntimeOverlay string KernelVersion string - KernelArgs string + KernelArgs string + IpmiIpaddr string NetDevs map[string]netDevs } @@ -100,6 +101,7 @@ func FindAllNodes() ([]NodeInfo, error) { n.GroupName = groupname n.HostName = node.Hostname + n.IpmiIpaddr = node.IpmiIpaddr n.Vnfs = group.Vnfs n.SystemOverlay = group.SystemOverlay @@ -223,6 +225,26 @@ func SearchByName(search string) ([]NodeInfo, error) { return ret, nil } +func SearchByNameList(searchList []string) ([]NodeInfo, error) { + var ret []NodeInfo + + nodeList, err := FindAllNodes() + if err != nil { + return ret, err + } + + for _, search := range searchList { + for _, node := range nodeList { + b, _ := regexp.MatchString(search, node.Fqdn) + if b == true { + ret = append(ret, node) + } + } + } + + return ret, nil +} + /* func FindAllVnfs() ([]string, error) { var ret []string diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go new file mode 100644 index 00000000..e33c0f99 --- /dev/null +++ b/internal/pkg/power/ipmitool.go @@ -0,0 +1,49 @@ +package power + +import ( + "os/exec" +) + +type IPMI struct { + HostName string + User string + Password string + AuthType string +} + +func (ipmi IPMI) Command(ipmiArgs []string) ([]byte, error) { + + var args []string + + args = append(args, "-I", "lan", "-H", ipmi.HostName, "-U", ipmi.User, "-P", ipmi.Password) + args = append(args, ipmiArgs...) + ipmiCmd := exec.Command("/usr/bin/ipmitool", args...) + return ipmiCmd.CombinedOutput() +} + +func (ipmi IPMI) PowerOn() (string, error) { + + var args []string + + args = append(args, "chassis", "power", "on") + ipmiOut, err := ipmi.Command(args) + return string(ipmiOut), err +} + +func (ipmi IPMI) PowerOff() (string, error) { + + var args []string + + args = append(args, "chassis", "power", "off") + ipmiOut, err := ipmi.Command(args) + return string(ipmiOut), err +} + +func (ipmi IPMI) PowerStatus() (string, error) { + + var args []string + + args = append(args, "chassis", "power", "status") + ipmiOut, err := ipmi.Command(args) + return string(ipmiOut), err +}