Merge pull request #1004 from mslacken/CallImpiTemplate

Use templates for power calls
This commit is contained in:
Jonathon Anderson
2024-11-08 09:41:46 -07:00
committed by GitHub
31 changed files with 1136 additions and 702 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -26,4 +26,6 @@ nodeprofiles:
init: /sbin/init
root: initramfs
ipxe template: default
ipmi:
template: ipmitool.tmpl
nodes: {}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}

View 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()))
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View 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()))
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View 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()))
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View 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()))
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View 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()))
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View 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()))
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View 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()))
})
}
}

View File

@@ -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"`
}

View File

@@ -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")
}

View File

@@ -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)
}

View 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 }}

View 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 }}

View File

@@ -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``