Merge pull request #1004 from mslacken/CallImpiTemplate
Use templates for power calls
This commit is contained in:
@@ -167,6 +167,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
### Changed
|
||||
|
||||
- `wwctl container list` only lists names by default. (`--long` shows all attributes.) #1117
|
||||
- use templating mechanism for power commands
|
||||
|
||||
## v4.5.5, 2024-07-05
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -116,10 +116,12 @@ install: build docs
|
||||
install -d -m 0755 $(DESTDIR)$(IPXESOURCE)
|
||||
install -d -m 0755 $(DESTDIR)$(DATADIR)/warewulf
|
||||
# wwctl genconfig to get the compiled in paths to warewulf.conf
|
||||
install -d -m 0755 $(DESTDIR)$(WWDATADIR)/bmc
|
||||
test -f $(DESTDIR)$(WWCONFIGDIR)/warewulf.conf || ./wwctl --warewulfconf etc/warewulf.conf genconfig warewulfconf print> $(DESTDIR)$(WWCONFIGDIR)/warewulf.conf
|
||||
test -f $(DESTDIR)$(WWCONFIGDIR)/nodes.conf || install -m 0644 etc/nodes.conf $(DESTDIR)$(WWCONFIGDIR)
|
||||
for f in etc/examples/*.ww; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/examples/; done
|
||||
for f in etc/ipxe/*.ipxe; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/ipxe/; done
|
||||
for f in lib/warewulf/bmc/*.tmpl; do install -m 0644 $$f $(DESTDIR)$(WWDATADIR)/bmc; done
|
||||
install -m 0644 etc/grub/grub.cfg.ww $(DESTDIR)$(WWCONFIGDIR)/grub/grub.cfg.ww
|
||||
install -m 0644 etc/grub/chainload.ww $(DESTDIR)$(WWOVERLAYDIR)/host/rootfs$(TFTPDIR)/warewulf/grub.cfg.ww
|
||||
install -m 0644 etc/logrotate.d/warewulfd.conf $(DESTDIR)$(LOGROTATEDIR)/warewulfd.conf
|
||||
|
||||
@@ -26,4 +26,6 @@ nodeprofiles:
|
||||
init: /sbin/init
|
||||
root: initramfs
|
||||
ipxe template: default
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
nodes: {}
|
||||
|
||||
@@ -44,20 +44,8 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: node.Ipmi.Port,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
AuthType: "MD5",
|
||||
Interface: node.Ipmi.Interface,
|
||||
EscapeChar: node.Ipmi.EscapeChar,
|
||||
}
|
||||
|
||||
ipmiCmd := power.IPMI{IpmiConf: *node.Ipmi}
|
||||
err := ipmiCmd.Console()
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s: Console problem", node.Id())
|
||||
returnErr = err
|
||||
|
||||
@@ -3,6 +3,7 @@ package sensors
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/batch"
|
||||
@@ -12,91 +13,81 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
args = hostlist.Expand(args)
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, args)
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
wwlog.Info("No nodes found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
var ipmiInterface = "lan"
|
||||
if node.Ipmi.Interface != "" {
|
||||
ipmiInterface = node.Ipmi.Interface
|
||||
}
|
||||
var ipmiPort = "623"
|
||||
if node.Ipmi.Port != "" {
|
||||
ipmiPort = node.Ipmi.Port
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: ipmiPort,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
Interface: ipmiInterface,
|
||||
AuthType: "MD5",
|
||||
}
|
||||
|
||||
fullFlag := full
|
||||
|
||||
batchpool.Submit(func() {
|
||||
if fullFlag {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.SensorList()
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.SDRList()
|
||||
}
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
out, err := result.Result()
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.NodeName, out)
|
||||
returnErr = err
|
||||
continue
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s:\n%s\n", result.NodeName, out)
|
||||
}
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
return returnErr
|
||||
args = hostlist.Expand(args)
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, args)
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
wwlog.Info("No nodes found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
IpmiConf: *node.Ipmi,
|
||||
ShowOnly: vars.Showcmd,
|
||||
}
|
||||
batchpool.Submit(func() {
|
||||
if vars.Full {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.SensorList()
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.SDRList()
|
||||
}
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
out, err := result.Result()
|
||||
|
||||
if err != nil {
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
wwlog.Error("%s: %s", result.Ipaddr, line)
|
||||
}
|
||||
returnErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
wwlog.Info("%s: %s", result.Ipaddr, line)
|
||||
}
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,22 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
|
||||
var (
|
||||
powerCmd = &cobra.Command{
|
||||
type variables struct {
|
||||
Showcmd bool
|
||||
Full bool
|
||||
Fanout int
|
||||
}
|
||||
|
||||
func GetCommand() *cobra.Command {
|
||||
vars := variables{}
|
||||
|
||||
powerCmd := &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "sensors [OPTIONS] PATTERN",
|
||||
Short: "Show node IPMI sensor information",
|
||||
Long: "Show IPMI sensor information for nodes matching PATTERN.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: CobraRunE,
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
@@ -27,14 +35,8 @@ var (
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
full bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
powerCmd.PersistentFlags().BoolVarP(&full, "full", "F", false, "show detailed output.")
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Full, "full", "F", false, "show detailed output.")
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
powerCmd.PersistentFlags().IntVar(&vars.Fanout, "fanout", 50, "how many command should be executed in parallel")
|
||||
return powerCmd
|
||||
}
|
||||
|
||||
56
internal/app/wwctl/node/sensors/root_test.go
Normal file
56
internal/app/wwctl/node/sensors/root_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package sensors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func Test_Sensors(t *testing.T) {
|
||||
warewulfd.SetNoDaemon()
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll(t)
|
||||
env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43
|
||||
nodeprofiles:
|
||||
default:
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
username: admin
|
||||
password: admin
|
||||
nodes:
|
||||
n01:
|
||||
profiles:
|
||||
- default
|
||||
ipmi:
|
||||
ipaddr: 10.10.10.10`)
|
||||
env.ImportFile(t, "share/warewulf/bmc/ipmitool.tmpl", "../../../../../lib/warewulf/bmc/ipmitool.tmpl")
|
||||
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
"sensors": {
|
||||
args: []string{"--show", "n01"},
|
||||
expected: "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ sdr list",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
baseCmd := GetCommand()
|
||||
buf := new(bytes.Buffer)
|
||||
baseCmd.SetOut(buf)
|
||||
baseCmd.SetErr(buf)
|
||||
wwlog.SetLogWriter(buf)
|
||||
baseCmd.SetArgs(tt.args)
|
||||
err := baseCmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,84 +12,70 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
var ipmiInterface = "lan"
|
||||
if node.Ipmi.Interface != "" {
|
||||
ipmiInterface = node.Ipmi.Interface
|
||||
}
|
||||
var ipmiPort = "623"
|
||||
if node.Ipmi.Port != "" {
|
||||
ipmiPort = node.Ipmi.Port
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: ipmiPort,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
Interface: ipmiInterface,
|
||||
AuthType: "MD5",
|
||||
}
|
||||
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerCycle()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.NodeName, out)
|
||||
returnErr = err
|
||||
continue
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s: %s\n", result.NodeName, out)
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(vars.Fanout)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
IpmiConf: *node.Ipmi,
|
||||
ShowOnly: vars.Showcmd,
|
||||
}
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerCycle()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.Ipaddr, out)
|
||||
returnErr = err
|
||||
continue
|
||||
}
|
||||
wwlog.Info("%s: %s\n", result.Ipaddr, out)
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
@@ -2,19 +2,38 @@ package cycle
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
|
||||
var (
|
||||
powerCmd = &cobra.Command{
|
||||
type variables struct {
|
||||
Showcmd bool
|
||||
Fanout int
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
vars := variables{}
|
||||
powerCmd := &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "cycle [OPTIONS] [PATTERN ...]",
|
||||
Short: "Power cycle the given node(s)",
|
||||
Long: "This command cycles power for a set of nodes specified by PATTERN.",
|
||||
RunE: CobraRunE,
|
||||
}
|
||||
)
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
nodeDB, _ := node.New()
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
var node_names []string
|
||||
for _, node := range nodes {
|
||||
node_names = append(node_names, node.Id())
|
||||
}
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
powerCmd.PersistentFlags().IntVar(&vars.Fanout, "fanout", 50, "how many command should be executed in parallel")
|
||||
return powerCmd
|
||||
}
|
||||
|
||||
56
internal/app/wwctl/power/cycle/root_test.go
Normal file
56
internal/app/wwctl/power/cycle/root_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package cycle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func Test_PowerCycle(t *testing.T) {
|
||||
warewulfd.SetNoDaemon()
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll(t)
|
||||
env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43
|
||||
nodeprofiles:
|
||||
default:
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
username: admin
|
||||
password: admin
|
||||
nodes:
|
||||
n01:
|
||||
profiles:
|
||||
- default
|
||||
ipmi:
|
||||
ipaddr: 10.10.10.10`)
|
||||
env.ImportFile(t, "share/warewulf/bmc/ipmitool.tmpl", "../../../../../lib/warewulf/bmc/ipmitool.tmpl")
|
||||
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
"power cycle": {
|
||||
args: []string{"--show", "n01"},
|
||||
expected: "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power cycle",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
baseCmd := GetCommand()
|
||||
buf := new(bytes.Buffer)
|
||||
baseCmd.SetOut(buf)
|
||||
baseCmd.SetErr(buf)
|
||||
wwlog.SetLogWriter(buf)
|
||||
baseCmd.SetArgs(tt.args)
|
||||
err := baseCmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,84 +12,72 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
var ipmiInterface = "lan"
|
||||
if node.Ipmi.Interface != "" {
|
||||
ipmiInterface = node.Ipmi.Interface
|
||||
}
|
||||
var ipmiPort = "623"
|
||||
if node.Ipmi.Port != "" {
|
||||
ipmiPort = node.Ipmi.Port
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: ipmiPort,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
Interface: ipmiInterface,
|
||||
AuthType: "MD5",
|
||||
}
|
||||
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerOff()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.NodeName, out)
|
||||
returnErr = err
|
||||
continue
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s: %s\n", result.NodeName, out)
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(vars.Fanout)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
IpmiConf: *node.Ipmi,
|
||||
ShowOnly: vars.Showcmd,
|
||||
}
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerOff()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.Ipaddr, out)
|
||||
returnErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
wwlog.Info("%s: %s\n", result.Ipaddr, out)
|
||||
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
@@ -5,13 +5,20 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
|
||||
var (
|
||||
powerCmd = &cobra.Command{
|
||||
type variables struct {
|
||||
Showcmd bool
|
||||
Fanout int
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
vars := variables{}
|
||||
powerCmd := &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "off [OPTIONS] [PATTERN ...]",
|
||||
Short: "Power off the given node(s)",
|
||||
Long: "This command will shutdown power to a set of nodes specified by PATTERN.",
|
||||
RunE: CobraRunE,
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
@@ -26,9 +33,8 @@ var (
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
)
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
powerCmd.PersistentFlags().IntVar(&vars.Fanout, "fanout", 50, "how many command should be executed in parallel")
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
return powerCmd
|
||||
}
|
||||
|
||||
56
internal/app/wwctl/power/off/root_test.go
Normal file
56
internal/app/wwctl/power/off/root_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package off
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func Test_Power_Status(t *testing.T) {
|
||||
warewulfd.SetNoDaemon()
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll(t)
|
||||
env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43
|
||||
nodeprofiles:
|
||||
default:
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
username: admin
|
||||
password: admin
|
||||
nodes:
|
||||
n01:
|
||||
profiles:
|
||||
- default
|
||||
ipmi:
|
||||
ipaddr: 10.10.10.10`)
|
||||
env.ImportFile(t, "share/warewulf/bmc/ipmitool.tmpl", "../../../../../lib/warewulf/bmc/ipmitool.tmpl")
|
||||
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
"power off": {
|
||||
args: []string{"--show", "n01"},
|
||||
expected: "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power off",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
baseCmd := GetCommand()
|
||||
buf := new(bytes.Buffer)
|
||||
baseCmd.SetOut(buf)
|
||||
baseCmd.SetErr(buf)
|
||||
wwlog.SetLogWriter(buf)
|
||||
baseCmd.SetArgs(tt.args)
|
||||
err := baseCmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,84 +12,73 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
var ipmiInterface = "lan"
|
||||
if node.Ipmi.Interface != "" {
|
||||
ipmiInterface = node.Ipmi.Interface
|
||||
}
|
||||
var ipmiPort = "623"
|
||||
if node.Ipmi.Port != "" {
|
||||
ipmiPort = node.Ipmi.Port
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: ipmiPort,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
Interface: ipmiInterface,
|
||||
AuthType: "MD5",
|
||||
}
|
||||
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerOn()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.NodeName, out)
|
||||
returnErr = err
|
||||
continue
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s: %s\n", result.NodeName, out)
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(vars.Fanout)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
IpmiConf: *node.Ipmi,
|
||||
ShowOnly: vars.Showcmd,
|
||||
}
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerOn()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.Ipaddr, out)
|
||||
returnErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
wwlog.Info("%s: %s\n", result.Ipaddr, out)
|
||||
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
@@ -5,12 +5,19 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
|
||||
var (
|
||||
powerCmd = &cobra.Command{
|
||||
type variables struct {
|
||||
Showcmd bool
|
||||
Fanout int
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
vars := variables{}
|
||||
powerCmd := &cobra.Command{
|
||||
Use: "on [OPTIONS] [PATTERN ...]",
|
||||
Short: "Power on the given node(s)",
|
||||
Long: "This command will power on a set of nodes specified by PATTERN.",
|
||||
RunE: CobraRunE,
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
@@ -25,9 +32,8 @@ var (
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
)
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
powerCmd.PersistentFlags().IntVar(&vars.Fanout, "fanout", 50, "how many command should be executed in parallel")
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
return powerCmd
|
||||
}
|
||||
|
||||
55
internal/app/wwctl/power/on/root_test.go
Normal file
55
internal/app/wwctl/power/on/root_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package on
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func Test_Power_Status(t *testing.T) {
|
||||
warewulfd.SetNoDaemon()
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll(t)
|
||||
env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43
|
||||
nodeprofiles:
|
||||
default:
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
username: admin
|
||||
password: admin
|
||||
nodes:
|
||||
n01:
|
||||
profiles:
|
||||
- default
|
||||
ipmi:
|
||||
ipaddr: 10.10.10.10`)
|
||||
env.ImportFile(t, "share/warewulf/bmc/ipmitool.tmpl", "../../../../../lib/warewulf/bmc/ipmitool.tmpl")
|
||||
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
"power on": {
|
||||
args: []string{"--show", "n01"},
|
||||
expected: "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power on",
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
baseCmd := GetCommand()
|
||||
buf := new(bytes.Buffer)
|
||||
baseCmd.SetOut(buf)
|
||||
baseCmd.SetErr(buf)
|
||||
wwlog.SetLogWriter(buf)
|
||||
baseCmd.SetArgs(tt.args)
|
||||
err := baseCmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,84 +12,73 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloud not get nodeList: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
var ipmiInterface = "lan"
|
||||
if node.Ipmi.Interface != "" {
|
||||
ipmiInterface = node.Ipmi.Interface
|
||||
}
|
||||
var ipmiPort = "623"
|
||||
if node.Ipmi.Port != "" {
|
||||
ipmiPort = node.Ipmi.Port
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: ipmiPort,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
Interface: ipmiInterface,
|
||||
AuthType: "MD5",
|
||||
}
|
||||
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerReset()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.NodeName, out)
|
||||
returnErr = err
|
||||
continue
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s: %s\n", result.NodeName, out)
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloud not get nodeList: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(vars.Fanout)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
IpmiConf: *node.Ipmi,
|
||||
ShowOnly: vars.Showcmd,
|
||||
}
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerReset()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.Ipaddr, out)
|
||||
returnErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
wwlog.Info("%s: %s\n", result.Ipaddr, out)
|
||||
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
@@ -5,13 +5,20 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
|
||||
var (
|
||||
powerCmd = &cobra.Command{
|
||||
type variables struct {
|
||||
Showcmd bool
|
||||
Fanout int
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
vars := variables{}
|
||||
powerCmd := &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "reset [OPTIONS] [PATTERN ...]",
|
||||
Short: "Issue a reset to node(s)",
|
||||
Long: "This command will issue a reset to a set of nodes specified by PATTERN.",
|
||||
RunE: CobraRunE,
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
@@ -26,9 +33,7 @@ var (
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
powerCmd.PersistentFlags().IntVar(&vars.Fanout, "fanout", 50, "how many command should be executed in parallel")
|
||||
return powerCmd
|
||||
}
|
||||
|
||||
55
internal/app/wwctl/power/reset/root_test.go
Normal file
55
internal/app/wwctl/power/reset/root_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package reset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func Test_Power_Status(t *testing.T) {
|
||||
warewulfd.SetNoDaemon()
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll(t)
|
||||
env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43
|
||||
nodeprofiles:
|
||||
default:
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
username: admin
|
||||
password: admin
|
||||
nodes:
|
||||
n01:
|
||||
profiles:
|
||||
- default
|
||||
ipmi:
|
||||
ipaddr: 10.10.10.10`)
|
||||
env.ImportFile(t, "share/warewulf/bmc/ipmitool.tmpl", "../../../../../lib/warewulf/bmc/ipmitool.tmpl")
|
||||
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
"power reset": {
|
||||
args: []string{"--show", "n01"},
|
||||
expected: "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power reset",
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
baseCmd := GetCommand()
|
||||
buf := new(bytes.Buffer)
|
||||
baseCmd.SetOut(buf)
|
||||
baseCmd.SetErr(buf)
|
||||
wwlog.SetLogWriter(buf)
|
||||
baseCmd.SetArgs(tt.args)
|
||||
err := baseCmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,84 +12,73 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get nodeList: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
var ipmiInterface = "lan"
|
||||
if node.Ipmi.Interface != "" {
|
||||
ipmiInterface = node.Ipmi.Interface
|
||||
}
|
||||
var ipmiPort = "623"
|
||||
if node.Ipmi.Port != "" {
|
||||
ipmiPort = node.Ipmi.Port
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: ipmiPort,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
Interface: ipmiInterface,
|
||||
AuthType: "MD5",
|
||||
}
|
||||
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerSoft()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.NodeName, out)
|
||||
returnErr = err
|
||||
continue
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s: %s\n", result.NodeName, out)
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get nodeList: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(vars.Fanout)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
IpmiConf: *node.Ipmi,
|
||||
ShowOnly: vars.Showcmd,
|
||||
}
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerSoft()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.Ipaddr, out)
|
||||
returnErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
wwlog.Info("%s: %s\n", result.Ipaddr, out)
|
||||
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
@@ -5,13 +5,20 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
|
||||
var (
|
||||
powerCmd = &cobra.Command{
|
||||
type variables struct {
|
||||
Showcmd bool
|
||||
Fanout int
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
vars := variables{}
|
||||
powerCmd := &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "soft",
|
||||
Short: "Gracefully shuts down the given node(s)",
|
||||
Long: "This command uses the operating system to shut down the set of nodes specified by PATTERN.",
|
||||
RunE: CobraRunE,
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
@@ -26,9 +33,7 @@ var (
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
powerCmd.PersistentFlags().IntVar(&vars.Fanout, "fanout", 50, "how many command should be executed in parallel")
|
||||
return powerCmd
|
||||
}
|
||||
|
||||
56
internal/app/wwctl/power/soft/root_test.go
Normal file
56
internal/app/wwctl/power/soft/root_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package soft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func Test_Power_Status(t *testing.T) {
|
||||
warewulfd.SetNoDaemon()
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll(t)
|
||||
env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43
|
||||
nodeprofiles:
|
||||
default:
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
username: admin
|
||||
password: admin
|
||||
nodes:
|
||||
n01:
|
||||
profiles:
|
||||
- default
|
||||
ipmi:
|
||||
ipaddr: 10.10.10.10`)
|
||||
env.ImportFile(t, "share/warewulf/bmc/ipmitool.tmpl", "../../../../../lib/warewulf/bmc/ipmitool.tmpl")
|
||||
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
"power soft": {
|
||||
args: []string{"--show", "n01"},
|
||||
expected: "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power soft",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
baseCmd := GetCommand()
|
||||
buf := new(bytes.Buffer)
|
||||
baseCmd.SetOut(buf)
|
||||
baseCmd.SetErr(buf)
|
||||
wwlog.SetLogWriter(buf)
|
||||
baseCmd.SetArgs(tt.args)
|
||||
err := baseCmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,84 +12,72 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open node configuration: %s", err)
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get node list: %s", err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
var ipmiInterface = "lan"
|
||||
if node.Ipmi.Interface != "" {
|
||||
ipmiInterface = node.Ipmi.Interface
|
||||
}
|
||||
var ipmiPort = "623"
|
||||
if node.Ipmi.Port != "" {
|
||||
ipmiPort = node.Ipmi.Port
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
NodeName: node.Id(),
|
||||
HostName: node.Ipmi.Ipaddr.String(),
|
||||
Port: ipmiPort,
|
||||
User: node.Ipmi.UserName,
|
||||
Password: node.Ipmi.Password,
|
||||
Interface: ipmiInterface,
|
||||
AuthType: "MD5",
|
||||
}
|
||||
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerStatus()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
var returnErr error = nil
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.NodeName, out)
|
||||
returnErr = err
|
||||
continue
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s: %s\n", result.NodeName, out)
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args))
|
||||
} else {
|
||||
//nolint:errcheck
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("no nodes found")
|
||||
}
|
||||
|
||||
batchpool := batch.New(50)
|
||||
jobcount := len(nodes)
|
||||
results := make(chan power.IPMI, jobcount)
|
||||
|
||||
for _, node := range nodes {
|
||||
|
||||
if node.Ipmi.Ipaddr.IsUnspecified() {
|
||||
wwlog.Error("%s: No IPMI IP address", node.Id())
|
||||
continue
|
||||
}
|
||||
ipmiCmd := power.IPMI{
|
||||
IpmiConf: *node.Ipmi,
|
||||
ShowOnly: vars.Showcmd,
|
||||
}
|
||||
batchpool.Submit(func() {
|
||||
//nolint:errcheck
|
||||
ipmiCmd.PowerStatus()
|
||||
results <- ipmiCmd
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
batchpool.Run()
|
||||
|
||||
close(results)
|
||||
|
||||
for result := range results {
|
||||
|
||||
out, err := result.Result()
|
||||
|
||||
if err != nil {
|
||||
wwlog.Error("%s: %s", result.Ipaddr, out)
|
||||
returnErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
wwlog.Info("%s: %s\n", result.Ipaddr, out)
|
||||
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
return returnErr
|
||||
}
|
||||
|
||||
@@ -5,13 +5,20 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
|
||||
var (
|
||||
powerCmd = &cobra.Command{
|
||||
type variables struct {
|
||||
Showcmd bool
|
||||
Fanout int
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
vars := variables{}
|
||||
powerCmd := &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "status [OPTIONS] [PATTERN ...]",
|
||||
Short: "Show power status for the given node(s)",
|
||||
Long: "This command displays the power status of a set of nodes specified by PATTERN.",
|
||||
RunE: CobraRunE,
|
||||
RunE: CobraRunE(&vars),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
@@ -26,9 +33,7 @@ var (
|
||||
return node_names, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed")
|
||||
powerCmd.PersistentFlags().IntVar(&vars.Fanout, "fanout", 50, "how many command should be executed in parallel")
|
||||
return powerCmd
|
||||
}
|
||||
|
||||
56
internal/app/wwctl/power/status/root_test.go
Normal file
56
internal/app/wwctl/power/status/root_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/warewulf/warewulf/internal/pkg/testenv"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func Test_Power_Status(t *testing.T) {
|
||||
warewulfd.SetNoDaemon()
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll(t)
|
||||
env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43
|
||||
nodeprofiles:
|
||||
default:
|
||||
ipmi:
|
||||
template: ipmitool.tmpl
|
||||
username: admin
|
||||
password: admin
|
||||
nodes:
|
||||
n01:
|
||||
profiles:
|
||||
- default
|
||||
ipmi:
|
||||
ipaddr: 10.10.10.10`)
|
||||
env.ImportFile(t, "share/warewulf/bmc/ipmitool.tmpl", "../../../../../lib/warewulf/bmc/ipmitool.tmpl")
|
||||
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
"sensors": {
|
||||
args: []string{"--show", "n01"},
|
||||
expected: "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power status",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
baseCmd := GetCommand()
|
||||
buf := new(bytes.Buffer)
|
||||
baseCmd.SetOut(buf)
|
||||
baseCmd.SetErr(buf)
|
||||
wwlog.SetLogWriter(buf)
|
||||
baseCmd.SetArgs(tt.args)
|
||||
err := baseCmd.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ type IpmiConf struct {
|
||||
Interface string `yaml:"interface,omitempty" lopt:"ipmiinterface" comment:"Set the node's IPMI interface (defaults: 'lan')"`
|
||||
EscapeChar string `yaml:"escapechar,omitempty" lopt:"ipmiescapechar" comment:"Set the IPMI escape character (defaults: '~')"`
|
||||
Write wwtype.WWbool `yaml:"write,omitempty" lopt:"ipmiwrite" comment:"Enable the write of impi configuration (true/false)"`
|
||||
Template string `yaml:"template,omitempty" lopt:"ipmitemplate" comment:"template used for ipmi command"`
|
||||
Tags map[string]string `yaml:"tags,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
package power
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
type IPMIResult struct {
|
||||
@@ -12,117 +21,118 @@ type IPMIResult struct {
|
||||
}
|
||||
|
||||
type IPMI struct {
|
||||
NodeName string
|
||||
HostName string
|
||||
Port string
|
||||
User string
|
||||
Password string
|
||||
AuthType string
|
||||
Interface string
|
||||
EscapeChar string
|
||||
result IPMIResult
|
||||
node.IpmiConf
|
||||
ShowOnly bool
|
||||
Cmd string
|
||||
result IPMIResult
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) Result() (string, error) {
|
||||
return ipmi.result.out, ipmi.result.err
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) Command(ipmiArgs []string) ([]byte, error) {
|
||||
func (ipmi *IPMI) getStr() (cmdStr string, err error) {
|
||||
if ipmi.Template == "" {
|
||||
return "", fmt.Errorf("no ipmi/bmc template specified")
|
||||
}
|
||||
if !strings.HasPrefix(ipmi.Template, "/") {
|
||||
conf := warewulfconf.Get()
|
||||
ipmi.Template = path.Join(conf.Warewulf.DataStore, "warewulf/bmc", ipmi.Template)
|
||||
}
|
||||
fbuf, err := os.ReadFile(ipmi.Template)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("couldn't find the template which defines the ipmi/bmc command: %s", err)
|
||||
}
|
||||
cmdTmpl, err := template.New("bmc command").Parse(string(fbuf))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var tbuffer bytes.Buffer
|
||||
err = cmdTmpl.Execute(&tbuffer, *ipmi)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rg := regexp.MustCompile(`(\r\n?|\n){2,}`)
|
||||
cmdStr = rg.ReplaceAllString(tbuffer.String(), " ")
|
||||
wwlog.Debug("bmc string is: %s", strings.TrimSpace(cmdStr))
|
||||
return strings.TrimSpace(cmdStr), nil
|
||||
|
||||
var args []string
|
||||
}
|
||||
|
||||
if ipmi.Interface == "" {
|
||||
ipmi.Interface = "lan"
|
||||
func (ipmi *IPMI) Command() ([]byte, error) {
|
||||
cmdStr, err := ipmi.getStr()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
if ipmi.Port == "" {
|
||||
ipmi.Port = "623"
|
||||
if ipmi.ShowOnly {
|
||||
return []byte(cmdStr), nil
|
||||
}
|
||||
if ipmi.EscapeChar == "" {
|
||||
ipmi.EscapeChar = "~"
|
||||
}
|
||||
args = append(args, "-I", ipmi.Interface, "-H", ipmi.HostName, "-p", ipmi.Port, "-U", ipmi.User, "-P", ipmi.Password, "-e", ipmi.EscapeChar)
|
||||
args = append(args, ipmiArgs...)
|
||||
ipmiCmd := exec.Command("ipmitool", args...)
|
||||
ipmiCmd := exec.Command(cmdStr)
|
||||
return ipmiCmd.CombinedOutput()
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) InteractiveCommand(ipmiArgs []string) error {
|
||||
|
||||
var args []string
|
||||
|
||||
if ipmi.Interface == "" {
|
||||
ipmi.Interface = "lan"
|
||||
func (ipmi *IPMI) InteractiveCommand() (err error) {
|
||||
cmdStr, err := ipmi.getStr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ipmi.Port == "" {
|
||||
ipmi.Port = "623"
|
||||
}
|
||||
if ipmi.EscapeChar == "" {
|
||||
ipmi.EscapeChar = "~"
|
||||
}
|
||||
args = append(args, "-I", ipmi.Interface, "-H", ipmi.HostName, "-p", ipmi.Port, "-U", ipmi.User, "-P", ipmi.Password, "-e", ipmi.EscapeChar)
|
||||
args = append(args, ipmiArgs...)
|
||||
ipmiCmd := exec.Command("ipmitool", args...)
|
||||
ipmiCmd := exec.Command(cmdStr)
|
||||
ipmiCmd.Stdout = os.Stdout
|
||||
ipmiCmd.Stdin = os.Stdin
|
||||
ipmiCmd.Stderr = os.Stderr
|
||||
return ipmiCmd.Run()
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) IPMIInteractiveCommand(args ...string) error {
|
||||
return ipmi.InteractiveCommand(args)
|
||||
func (ipmi *IPMI) IPMIInteractiveCommand(cmd string) error {
|
||||
ipmi.Cmd = cmd
|
||||
return ipmi.InteractiveCommand()
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) IPMICommand(args ...string) (string, error) {
|
||||
ipmiOut, err := ipmi.Command(args)
|
||||
func (ipmi *IPMI) IPMICommand(cmd string) (string, error) {
|
||||
ipmi.Cmd = cmd
|
||||
ipmiOut, err := ipmi.Command()
|
||||
ipmi.result.out = strings.TrimSpace(string(ipmiOut))
|
||||
ipmi.result.err = err
|
||||
return ipmi.result.out, ipmi.result.err
|
||||
|
||||
}
|
||||
|
||||
//func (ipmi IPMI) PowerOn() (string, error) {
|
||||
//
|
||||
// var args []string
|
||||
//
|
||||
// args = append(args, "chassis", "power", "on")
|
||||
// ipmiOut, err := ipmi.Command(args)
|
||||
// ipmi.result.out = string(ipmiOut)
|
||||
// ipmi.result.err = err
|
||||
// return ipmi.result.out, ipmi.result.err
|
||||
//}
|
||||
/*
|
||||
Just define meta commands here, implementation is in the template
|
||||
*/
|
||||
|
||||
func (ipmi *IPMI) PowerOn() (string, error) {
|
||||
return ipmi.IPMICommand("chassis", "power", "on")
|
||||
return ipmi.IPMICommand("PowerOn")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) PowerOff() (string, error) {
|
||||
return ipmi.IPMICommand("chassis", "power", "off")
|
||||
return ipmi.IPMICommand("PowerOff")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) PowerCycle() (string, error) {
|
||||
return ipmi.IPMICommand("chassis", "power", "cycle")
|
||||
return ipmi.IPMICommand("PowerCycle")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) PowerReset() (string, error) {
|
||||
return ipmi.IPMICommand("chassis", "power", "reset")
|
||||
return ipmi.IPMICommand("PowerReset")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) PowerSoft() (string, error) {
|
||||
return ipmi.IPMICommand("chassis", "power", "soft")
|
||||
return ipmi.IPMICommand("PowerSoft")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) PowerStatus() (string, error) {
|
||||
return ipmi.IPMICommand("chassis", "power", "status")
|
||||
return ipmi.IPMICommand("PowerStatus")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) SDRList() (string, error) {
|
||||
return ipmi.IPMICommand("sdr", "list")
|
||||
return ipmi.IPMICommand("SDRList")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) SensorList() (string, error) {
|
||||
return ipmi.IPMICommand("sensor", "list")
|
||||
return ipmi.IPMICommand("SensorList")
|
||||
}
|
||||
|
||||
func (ipmi *IPMI) Console() error {
|
||||
return ipmi.IPMIInteractiveCommand("sol", "activate")
|
||||
return ipmi.IPMIInteractiveCommand("Console")
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package power
|
||||
|
||||
//type PowerControl interface {
|
||||
//PowerOn() (result string, err error)
|
||||
//PowerOff() (result string, err error)
|
||||
//PowerStatus() (result string, err error)
|
||||
//}
|
||||
|
||||
type PowerOnInterface interface {
|
||||
PowerOn() (result string, err error)
|
||||
}
|
||||
|
||||
type PowerOffInterface interface {
|
||||
PowerOff() (result string, err error)
|
||||
}
|
||||
|
||||
type PowerResetInterface interface {
|
||||
PowerReset() (result string, err error)
|
||||
}
|
||||
|
||||
type PowerSoftInterface interface {
|
||||
PowerSoft() (result string, err error)
|
||||
}
|
||||
|
||||
type PowerCycleInterface interface {
|
||||
PowerCycle() (result string, err error)
|
||||
}
|
||||
|
||||
type PowerStatusInterface interface {
|
||||
PowerStatus() (result string, err error)
|
||||
}
|
||||
15
lib/warewulf/bmc/ipmitool.tmpl
Normal file
15
lib/warewulf/bmc/ipmitool.tmpl
Normal file
@@ -0,0 +1,15 @@
|
||||
{{/* used command to access the ipmi interface of the nodes */}}
|
||||
{{- $escapechar := "~" }}{{ if .EscapeChar }}{{ $escapechar = .EscapeChar }}{{ end }}
|
||||
{{- $port := "623" }}{{ if .Port }}{{ $port = .Port }}{{ end }}
|
||||
{{- $interface := "lan" }}{{- if .Interface }}{{ $interface = .Interface }}{{ end }}
|
||||
{{- $args := "" }}
|
||||
{{- if eq .Cmd "PowerOn" }}{{ $args = "chassis power on" }}
|
||||
{{- else if eq .Cmd "PowerOff" }}{{ $args = "chassis power off" }}
|
||||
{{- else if eq .Cmd "PowerCycle" }}{{ $args = "chassis power cycle" }}
|
||||
{{- else if eq .Cmd "PowerReset" }}{{ $args = "chassis power reset" }}
|
||||
{{- else if eq .Cmd "PowerSoft" }}{{ $args = "chassis power soft" }}
|
||||
{{- else if eq .Cmd "PowerStatus" }}{{ $args = "chassis power status" }}
|
||||
{{- else if eq .Cmd "SDRList" }}{{ $args = "sdr list" }}
|
||||
{{- else if eq .Cmd "SensorList" }}{{ $args = "sensor list" }}
|
||||
{{- else if eq .Cmd "Console" }}{{ $args = "sol activate" }}{{ end }}
|
||||
ipmitool -I {{ $interface }} -H {{ .Ipaddr }} -p {{ $port }} -U {{ .UserName }} -P {{ .Password }} -e {{ $escapechar }} {{ $args }}
|
||||
11
lib/warewulf/bmc/nobmc.tmpl
Normal file
11
lib/warewulf/bmc/nobmc.tmpl
Normal file
@@ -0,0 +1,11 @@
|
||||
{{/* used command to access nodes without bmc*/}}
|
||||
{{- if eq .Cmd "PowerOn" }}wol {{ .Interface }}
|
||||
{{- else if eq .Cmd "PowerOff" }}ssh {{ .Ipaddr }} echo o > /proc/sysrq-trigger
|
||||
{{- else if eq .Cmd "PowerCycle" }}ssh {{ .Ipaddr }} echo r > /proc/sysrq-trigger
|
||||
{{- else if eq .Cmd "PowerReset" }}ssh {{ .Ipaddr }} echo r > /proc/sysrq-trigger
|
||||
{{- else if eq .Cmd "PowerSoft" }}ssh {{ .Ipaddr }} reboot
|
||||
{{- else if eq .Cmd "PowerStatus" }}ping -c 1 {{ .Ipaddr }} &> /dev/null && echo ON || echo OFF
|
||||
{{- else if eq .Cmd "SDRList" }}ssh {{ .Ipaddr }} sensors
|
||||
{{- else if eq .Cmd "SensorList" }}ssh {{ .Ipaddr }} sensors
|
||||
{{- else if eq .Cmd "Console" }}echo node sol
|
||||
{{- else }}echo "command not found"{{ end }}
|
||||
@@ -4,7 +4,9 @@ IPMI
|
||||
|
||||
It is possible to control the power or connect a console to your nodes
|
||||
being managed by Warewulf by connecting to the BMC through the use of
|
||||
IPMI. We will discuss how to set this up below.
|
||||
`ipmitool`. Other methods can also configured, but require additional
|
||||
configuration.
|
||||
We will discuss how to set this up below.
|
||||
|
||||
IPMI Settings
|
||||
=============
|
||||
@@ -15,8 +17,8 @@ individual node would be the IP address.
|
||||
|
||||
The settings are only written to the IPMI interface if ``--ipmiwrite``
|
||||
is set to `true`. The write process happens at every boot of the node
|
||||
through the script ``/warewulf/init.d/50-ipmi`` in the wwinit
|
||||
overlay.
|
||||
through the script ``/warewulf/init.d/50-ipmi`` in the **system**
|
||||
overlay and are done with `ipmitool`.
|
||||
|
||||
If an individual node has different settings, you can set the IPMI
|
||||
settings for that specific node, overriding the default settings.
|
||||
@@ -25,27 +27,29 @@ Here is a table outlining the fields on a Profile and Node which is
|
||||
the same as the parameter that can be used when running ``wwctl
|
||||
profile set`` or ``wwctl node set``.
|
||||
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| Parameter | Profile | Node | Valid Values | Default Value |
|
||||
+======================+=========+======+====================+===============+
|
||||
| ``--ipmiaddr`` | false | true | | |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipminetmask`` | true | true | | |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiport`` | true | true | | 623 |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmigateway`` | true | true | | |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiuser`` | true | true | | |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmipass`` | true | true | | |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiinterface`` | true | true | 'lan' or 'lanplus' | lan |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiwrite`` | true | true | true or false | false |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiescapechar`` | true | true | single character | ~ |
|
||||
+----------------------+---------+------+--------------------+---------------+
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| Parameter | Profile | Node | Valid Values | Default Value |
|
||||
+=====================+=========+======+====================+===============+
|
||||
| ``--ipmiaddr`` | false | true | | |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipminetmask`` | true | true | | |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiport`` | true | true | | 623 |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmigateway`` | true | true | | |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiuser`` | true | true | | |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmipass`` | true | true | | |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiinterface`` | true | true | 'lan' or 'lanplus' | lan |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiwrite`` | true | true | true or false | false |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmiescapechar``| true | true | single character | ~ |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
| ``--ipmitemplate`` | true | true | path to template | |
|
||||
+---------------------+---------+------+--------------------+---------------+
|
||||
|
||||
|
||||
Reviewing Settings
|
||||
@@ -181,3 +185,76 @@ connect a console to the node.
|
||||
.. code-block:: console
|
||||
|
||||
# wwctl node console n001
|
||||
|
||||
|
||||
Ipmi template
|
||||
=============
|
||||
|
||||
As warewulf doesn't manage the ipmi/bmc interfaces directly, but calls ``ipmitool``
|
||||
this managed with a template which defines the behavior. For ``ipmitool`` following
|
||||
template is used
|
||||
|
||||
.. code-block:: golang
|
||||
{{/* used command to access the ipmi interface of the nodes */}}
|
||||
{{- $escapechar := "~" }}
|
||||
{{- $port := "623" }}
|
||||
{{- $interface := "lan" }}
|
||||
{{- $args := "" }}
|
||||
{{- if .EscapeChar }} $escapechar = .EscapeChar {{ end }}
|
||||
{{- if .Port }} {{ $port = .Port }} {{ end }}
|
||||
{{- if .Interface }} {{ $interface = .Interface }} {{ end }}
|
||||
{{- if eq .Cmd "PowerOn" }} {{ $args = "chassis power on" }} {{ end }}
|
||||
{{- if eq .Cmd "PowerOff" }} {{ $args = "chassis power off" }} {{ end }}
|
||||
{{- if eq .Cmd "PowerCycle" }} {{ $args = "chassis power cycle" }} {{ end }}
|
||||
{{- if eq .Cmd "PowerReset" }} {{ $args = "chassis power reset" }} {{ end }}
|
||||
{{- if eq .Cmd "PowerSoft" }} {{ $args = "chassis power soft" }} {{ end }}
|
||||
{{- if eq .Cmd "PowerStatus" }} {{ $args = "chassis power status" }} {{ end }}
|
||||
{{- if eq .Cmd "SDRList" }} {{ $args = "sdr list" }} {{ end }}
|
||||
{{- if eq .Cmd "SensorList" }} {{ $args = "sensor list" }} {{ end }}
|
||||
{{- if eq .Cmd "Console" }} {{ $args = "sol activate" }} {{ end }}
|
||||
{{- $cmd := printf "ipmitool -I %s -H %s -p %s -U %s -P %s -e %s %s" $interface .Ipaddr $port .UserName .Password $escapechar $args }}
|
||||
{{ $cmd }}
|
||||
|
||||
In order to use another template, its filename must be specified for a node or profile via the
|
||||
``--ipmitemplate`` switch and the template must placed under ``/usr/lib/warewulf/bmc`` or to the
|
||||
path which is was defined as ``datadir`` in ``warwulf.conf`` or during compile time.
|
||||
All IPMI specific variables are accessible in the template which are the following
|
||||
|
||||
| Parameter | Template variable |
|
||||
+=====================+====================+
|
||||
| ``--ipmiaddr`` | ``.Ipaddr`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipminetmask`` | ``.Netmask`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmiport`` | ``.Port`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmigateway`` | ``.Gateway`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmiuser`` | ``.UserName`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmipass`` | ``.Password`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmiinterface`` | ``.Interface`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmiwrite`` | ``.Write`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmiescapechar``| ``.EscapeChar`` |
|
||||
+---------------------+--------------------+
|
||||
| ``--ipmitemplate`` | ``.Template`` |
|
||||
+---------------------+--------------------+
|
||||
|
||||
Additional the ``.Args`` variable is accessible which can have following
|
||||
values:
|
||||
* `PowerOn`
|
||||
* `PowerOff`
|
||||
* `PowerCycle`
|
||||
* `PowerReset`
|
||||
* `PowerSoft`
|
||||
* `PowerStatus`
|
||||
* `SDRList`
|
||||
* `SensorList`
|
||||
* `Console`
|
||||
which are the calls done by `wwctl power` commands.
|
||||
|
||||
Also the script ``/warewulf/init.d/50-ipmi`` in the **system**
|
||||
overlay may need an update. There the variables must have the prefix ``.Ipmi``
|
||||
|
||||
Reference in New Issue
Block a user