diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1768e4..6e93e1f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fixed panic in warewulfd if node netdev is only defined in a profile. #1817 - Replaced unreadable >= character external sphinx module for pdf doc build. +- Handle missing ipmi fields during bmc commands. #1768 +- Properly configure a default IPMI template during `wwctl upgrade nodes --add-defaults`. ### Changed diff --git a/internal/app/wwctl/node/console/main.go b/internal/app/wwctl/node/console/main.go index 368eb0ce..4153d177 100644 --- a/internal/app/wwctl/node/console/main.go +++ b/internal/app/wwctl/node/console/main.go @@ -5,9 +5,9 @@ import ( "os" "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -43,7 +43,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{IpmiConf: *node.Ipmi} + ipmiCmd := bmc.TemplateStruct{IpmiConf: *node.Ipmi} if err := ipmiCmd.Console(); err != nil { wwlog.Error("%s: Console problem", node.Id()) returnErr = err diff --git a/internal/app/wwctl/node/sensors/main.go b/internal/app/wwctl/node/sensors/main.go index c7e71411..5819cd27 100644 --- a/internal/app/wwctl/node/sensors/main.go +++ b/internal/app/wwctl/node/sensors/main.go @@ -7,9 +7,9 @@ import ( "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -44,14 +44,14 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err batchpool := batch.New(50) jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + results := make(chan bmc.TemplateStruct, jobcount) for _, node := range nodes { if node.Ipmi == nil || node.Ipmi.Ipaddr == nil || node.Ipmi.Ipaddr.IsUnspecified() { wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{ + ipmiCmd := bmc.TemplateStruct{ IpmiConf: *node.Ipmi, ShowOnly: vars.Showcmd, } diff --git a/internal/app/wwctl/node/sensors/root_test.go b/internal/app/wwctl/node/sensors/root_test.go index 10a8ca84..63fdb601 100644 --- a/internal/app/wwctl/node/sensors/root_test.go +++ b/internal/app/wwctl/node/sensors/root_test.go @@ -36,7 +36,7 @@ nodes: }{ "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", + expected: `10.10.10.10: ipmitool -H 10.10.10.10 -U "admin" -P "admin" sdr list`, }, } diff --git a/internal/app/wwctl/power/cycle/main.go b/internal/app/wwctl/power/cycle/main.go index 1ed8e57a..b30c2297 100644 --- a/internal/app/wwctl/power/cycle/main.go +++ b/internal/app/wwctl/power/cycle/main.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -40,7 +40,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err batchpool := batch.New(vars.Fanout) jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + results := make(chan bmc.TemplateStruct, jobcount) for _, node := range nodes { @@ -48,7 +48,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{ + ipmiCmd := bmc.TemplateStruct{ IpmiConf: *node.Ipmi, ShowOnly: vars.Showcmd, } diff --git a/internal/app/wwctl/power/cycle/root_test.go b/internal/app/wwctl/power/cycle/root_test.go index 45667e09..d43ab20e 100644 --- a/internal/app/wwctl/power/cycle/root_test.go +++ b/internal/app/wwctl/power/cycle/root_test.go @@ -36,7 +36,7 @@ nodes: }{ "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", + expected: `10.10.10.10: ipmitool -H 10.10.10.10 -U "admin" -P "admin" chassis power cycle`, }, } diff --git a/internal/app/wwctl/power/off/main.go b/internal/app/wwctl/power/off/main.go index 9426ff0e..6fea7a8a 100644 --- a/internal/app/wwctl/power/off/main.go +++ b/internal/app/wwctl/power/off/main.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -40,7 +40,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err batchpool := batch.New(vars.Fanout) jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + results := make(chan bmc.TemplateStruct, jobcount) for _, node := range nodes { @@ -48,7 +48,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{ + ipmiCmd := bmc.TemplateStruct{ IpmiConf: *node.Ipmi, ShowOnly: vars.Showcmd, } diff --git a/internal/app/wwctl/power/off/root_test.go b/internal/app/wwctl/power/off/root_test.go index 805ffb83..81afba56 100644 --- a/internal/app/wwctl/power/off/root_test.go +++ b/internal/app/wwctl/power/off/root_test.go @@ -36,7 +36,7 @@ nodes: }{ "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", + expected: `10.10.10.10: ipmitool -H 10.10.10.10 -U "admin" -P "admin" chassis power off`, }, } diff --git a/internal/app/wwctl/power/on/main.go b/internal/app/wwctl/power/on/main.go index eed53bb9..5cff385e 100644 --- a/internal/app/wwctl/power/on/main.go +++ b/internal/app/wwctl/power/on/main.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -41,7 +41,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err batchpool := batch.New(vars.Fanout) jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + results := make(chan bmc.TemplateStruct, jobcount) for _, node := range nodes { @@ -49,7 +49,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{ + ipmiCmd := bmc.TemplateStruct{ IpmiConf: *node.Ipmi, ShowOnly: vars.Showcmd, } diff --git a/internal/app/wwctl/power/on/root_test.go b/internal/app/wwctl/power/on/root_test.go index aa932870..1803271e 100644 --- a/internal/app/wwctl/power/on/root_test.go +++ b/internal/app/wwctl/power/on/root_test.go @@ -36,7 +36,7 @@ nodes: }{ "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", + expected: `10.10.10.10: ipmitool -H 10.10.10.10 -U "admin" -P "admin" chassis power on`, }, } for name, tt := range tests { diff --git a/internal/app/wwctl/power/reset/main.go b/internal/app/wwctl/power/reset/main.go index aad82f42..425b72e6 100644 --- a/internal/app/wwctl/power/reset/main.go +++ b/internal/app/wwctl/power/reset/main.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -41,7 +41,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err batchpool := batch.New(vars.Fanout) jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + results := make(chan bmc.TemplateStruct, jobcount) for _, node := range nodes { @@ -49,7 +49,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{ + ipmiCmd := bmc.TemplateStruct{ IpmiConf: *node.Ipmi, ShowOnly: vars.Showcmd, } diff --git a/internal/app/wwctl/power/reset/root_test.go b/internal/app/wwctl/power/reset/root_test.go index 9bd4dbb0..0ddcf228 100644 --- a/internal/app/wwctl/power/reset/root_test.go +++ b/internal/app/wwctl/power/reset/root_test.go @@ -36,7 +36,7 @@ nodes: }{ "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", + expected: `10.10.10.10: ipmitool -H 10.10.10.10 -U "admin" -P "admin" chassis power reset`, }, } for name, tt := range tests { diff --git a/internal/app/wwctl/power/soft/main.go b/internal/app/wwctl/power/soft/main.go index 4a9359dd..9978ba4b 100644 --- a/internal/app/wwctl/power/soft/main.go +++ b/internal/app/wwctl/power/soft/main.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -41,7 +41,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err batchpool := batch.New(vars.Fanout) jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + results := make(chan bmc.TemplateStruct, jobcount) for _, node := range nodes { @@ -49,7 +49,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{ + ipmiCmd := bmc.TemplateStruct{ IpmiConf: *node.Ipmi, ShowOnly: vars.Showcmd, } diff --git a/internal/app/wwctl/power/soft/root_test.go b/internal/app/wwctl/power/soft/root_test.go index 09a31b48..9a9fc28a 100644 --- a/internal/app/wwctl/power/soft/root_test.go +++ b/internal/app/wwctl/power/soft/root_test.go @@ -36,7 +36,7 @@ nodes: }{ "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", + expected: `10.10.10.10: ipmitool -H 10.10.10.10 -U "admin" -P "admin" chassis power soft`, }, } diff --git a/internal/app/wwctl/power/status/main.go b/internal/app/wwctl/power/status/main.go index d2d15fe9..f2037d9b 100644 --- a/internal/app/wwctl/power/status/main.go +++ b/internal/app/wwctl/power/status/main.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" + "github.com/warewulf/warewulf/internal/pkg/bmc" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/power" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -40,7 +40,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err batchpool := batch.New(50) jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + results := make(chan bmc.TemplateStruct, jobcount) for _, node := range nodes { @@ -48,7 +48,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - ipmiCmd := power.IPMI{ + ipmiCmd := bmc.TemplateStruct{ IpmiConf: *node.Ipmi, ShowOnly: vars.Showcmd, } diff --git a/internal/app/wwctl/power/status/root_test.go b/internal/app/wwctl/power/status/root_test.go index 798da29b..259b9417 100644 --- a/internal/app/wwctl/power/status/root_test.go +++ b/internal/app/wwctl/power/status/root_test.go @@ -36,7 +36,7 @@ nodes: }{ "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", + expected: `10.10.10.10: ipmitool -H 10.10.10.10 -U "admin" -P "admin" chassis power status`, }, } diff --git a/internal/pkg/bmc/bmc.go b/internal/pkg/bmc/bmc.go new file mode 100644 index 00000000..3a61d735 --- /dev/null +++ b/internal/pkg/bmc/bmc.go @@ -0,0 +1,136 @@ +package bmc + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path" + "strings" + "text/template" + + "github.com/Masterminds/sprig/v3" + + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" + "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +type Result struct { + err error + out string +} + +type TemplateStruct struct { + node.IpmiConf + ShowOnly bool + Cmd string + result Result +} + +func (tstruct *TemplateStruct) Result() (string, error) { + return tstruct.result.out, tstruct.result.err +} + +func (tstruct *TemplateStruct) getCommand() (cmdStr string, err error) { + if tstruct.Template == "" { + return "", fmt.Errorf("no bmc template specified") + } + if !strings.HasPrefix(tstruct.Template, "/") { + conf := warewulfconf.Get() + tstruct.Template = path.Join(conf.Paths.Datadir, "warewulf/bmc", tstruct.Template) + } + fbuf, err := os.ReadFile(tstruct.Template) + if err != nil { + return "", fmt.Errorf("couldn't find the template which defines the bmc command: %s", err) + } + cmdTmpl, err := template.New("bmc command").Funcs(sprig.TxtFuncMap()).Parse(string(fbuf)) + if err != nil { + return "", err + } + var tbuffer bytes.Buffer + err = cmdTmpl.Execute(&tbuffer, *tstruct) + if err != nil { + return "", err + } + cmdStr = strings.TrimSpace(tbuffer.String()) + wwlog.Debug("bmc command: %s", cmdStr) + return cmdStr, nil + +} + +func (tstruct *TemplateStruct) runCommand() ([]byte, error) { + cmdStr, err := tstruct.getCommand() + if err != nil { + return []byte{}, err + } + if tstruct.ShowOnly { + return []byte(cmdStr), nil + } + return exec.Command("/bin/sh", "-c", cmdStr).CombinedOutput() +} + +func (tstruct *TemplateStruct) runInteractiveCommand() (err error) { + cmdStr, err := tstruct.getCommand() + if err != nil { + return err + } + cmd := exec.Command("/bin/sh", "-c", cmdStr) + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func (tstruct *TemplateStruct) InteractiveCommand(cmd string) error { + tstruct.Cmd = cmd + return tstruct.runInteractiveCommand() +} + +func (tstruct *TemplateStruct) Command(cmd string) (string, error) { + tstruct.Cmd = cmd + out, err := tstruct.runCommand() + tstruct.result.out = strings.TrimSpace(string(out)) + tstruct.result.err = err + return tstruct.result.out, tstruct.result.err +} + +/* +Just define meta commands here, implementation is in the template +*/ + +func (tstruct *TemplateStruct) PowerOn() (string, error) { + return tstruct.Command("PowerOn") +} + +func (tstruct *TemplateStruct) PowerOff() (string, error) { + return tstruct.Command("PowerOff") +} + +func (tstruct *TemplateStruct) PowerCycle() (string, error) { + return tstruct.Command("PowerCycle") +} + +func (tstruct *TemplateStruct) PowerReset() (string, error) { + return tstruct.Command("PowerReset") +} + +func (tstruct *TemplateStruct) PowerSoft() (string, error) { + return tstruct.Command("PowerSoft") +} + +func (tstruct *TemplateStruct) PowerStatus() (string, error) { + return tstruct.Command("PowerStatus") +} + +func (tstruct *TemplateStruct) SDRList() (string, error) { + return tstruct.Command("SDRList") +} + +func (tstruct *TemplateStruct) SensorList() (string, error) { + return tstruct.Command("SensorList") +} + +func (tstruct *TemplateStruct) Console() error { + return tstruct.InteractiveCommand("Console") +} diff --git a/internal/pkg/bmc/bmc_test.go b/internal/pkg/bmc/bmc_test.go new file mode 100644 index 00000000..fd6ae9e6 --- /dev/null +++ b/internal/pkg/bmc/bmc_test.go @@ -0,0 +1,81 @@ +package bmc + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/testenv" +) + +func Test_Ipmitool(t *testing.T) { + tests := map[string]struct { + bmc TemplateStruct + err bool + cmdStr string + }{ + "no template": { + bmc: TemplateStruct{}, + err: true, + }, + "ipmitool PowerStatus empty": { + bmc: TemplateStruct{ + Cmd: "PowerStatus", + IpmiConf: node.IpmiConf{ + Template: "ipmitool.tmpl", + }, + }, + cmdStr: `ipmitool chassis power status`, + }, + "ipmitool PowerStatus full": { + bmc: TemplateStruct{ + Cmd: "PowerStatus", + IpmiConf: node.IpmiConf{ + Template: "ipmitool.tmpl", + Interface: "lanplus", + EscapeChar: "~", + Port: "687", + Ipaddr: net.IP{192, 168, 1, 100}, + UserName: "root", + Password: "calvin", + }, + }, + cmdStr: `ipmitool -I lanplus -e ~ -p 687 -H 192.168.1.100 -U "root" -P "calvin" chassis power status`, + }, + "nobmc PowerStatus full": { + bmc: TemplateStruct{ + Cmd: "PowerStatus", + IpmiConf: node.IpmiConf{ + Template: "nobmc.tmpl", + Interface: "lanplus", + EscapeChar: "~", + Port: "687", + Ipaddr: net.IP{192, 168, 1, 100}, + UserName: "root", + Password: "calvin", + }, + }, + cmdStr: `ping -c 1 "192.168.1.100" &> /dev/null && echo ON || echo OFF`, + }, + } + + for name, test := range tests { + env := testenv.New(t) + defer env.RemoveAll() + env.ImportFile("usr/share/warewulf/bmc/ipmitool.tmpl", "../../../lib/warewulf/bmc/ipmitool.tmpl") + env.ImportFile("usr/share/warewulf/bmc/nobmc.tmpl", "../../../lib/warewulf/bmc/nobmc.tmpl") + + t.Run(name, func(t *testing.T) { + cmdStr, err := test.bmc.getCommand() + if test.err { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + if test.cmdStr != "" { + assert.Equal(t, test.cmdStr, cmdStr) + } + }) + } +} diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go deleted file mode 100644 index 1cc25f6c..00000000 --- a/internal/pkg/power/ipmitool.go +++ /dev/null @@ -1,138 +0,0 @@ -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 { - err error - out string -} - -type IPMI struct { - node.IpmiConf - ShowOnly bool - Cmd string - result IPMIResult -} - -func (ipmi *IPMI) Result() (string, error) { - return ipmi.result.out, ipmi.result.err -} - -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.Paths.Datadir, "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 - -} - -func (ipmi *IPMI) Command() ([]byte, error) { - cmdStr, err := ipmi.getStr() - if err != nil { - return []byte{}, err - } - if ipmi.ShowOnly { - return []byte(cmdStr), nil - } - ipmiCmd := exec.Command("/bin/sh", "-c", cmdStr) - return ipmiCmd.CombinedOutput() -} - -func (ipmi *IPMI) InteractiveCommand() (err error) { - cmdStr, err := ipmi.getStr() - if err != nil { - return err - } - ipmiCmd := exec.Command("/bin/sh", "-c", cmdStr) - ipmiCmd.Stdout = os.Stdout - ipmiCmd.Stdin = os.Stdin - ipmiCmd.Stderr = os.Stderr - return ipmiCmd.Run() -} - -func (ipmi *IPMI) IPMIInteractiveCommand(cmd string) error { - ipmi.Cmd = cmd - return ipmi.InteractiveCommand() -} - -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 - -} - -/* -Just define meta commands here, implementation is in the template -*/ - -func (ipmi *IPMI) PowerOn() (string, error) { - return ipmi.IPMICommand("PowerOn") -} - -func (ipmi *IPMI) PowerOff() (string, error) { - return ipmi.IPMICommand("PowerOff") -} - -func (ipmi *IPMI) PowerCycle() (string, error) { - return ipmi.IPMICommand("PowerCycle") -} - -func (ipmi *IPMI) PowerReset() (string, error) { - return ipmi.IPMICommand("PowerReset") -} - -func (ipmi *IPMI) PowerSoft() (string, error) { - return ipmi.IPMICommand("PowerSoft") -} - -func (ipmi *IPMI) PowerStatus() (string, error) { - return ipmi.IPMICommand("PowerStatus") -} - -func (ipmi *IPMI) SDRList() (string, error) { - return ipmi.IPMICommand("SDRList") -} - -func (ipmi *IPMI) SensorList() (string, error) { - return ipmi.IPMICommand("SensorList") -} - -func (ipmi *IPMI) Console() error { - return ipmi.IPMIInteractiveCommand("Console") -} diff --git a/internal/pkg/upgrade/node.go b/internal/pkg/upgrade/node.go index 3e8fa26f..353856c5 100644 --- a/internal/pkg/upgrade/node.go +++ b/internal/pkg/upgrade/node.go @@ -122,6 +122,9 @@ func (legacy *NodesYaml) Upgrade(addDefaults bool, replaceOverlays bool, warewul defaultProfile.RuntimeOverlay = append( defaultProfile.RuntimeOverlay, genericSplitOverlays...) } + if defaultProfile.Kernel == nil { + defaultProfile.Kernel = new(node.KernelConf) + } if len(defaultProfile.Kernel.Args) < 1 { defaultProfile.Kernel.Args = []string{"quiet", "crashkernel=no"} } @@ -153,6 +156,12 @@ func (legacy *NodesYaml) Upgrade(addDefaults bool, replaceOverlays bool, warewul }, } } + if defaultProfile.Ipmi == nil { + defaultProfile.Ipmi = new(node.IpmiConf) + } + if defaultProfile.Ipmi.Template == "" { + defaultProfile.Ipmi.Template = "ipmitool.tmpl" + } } return upgraded } diff --git a/internal/pkg/upgrade/node_test.go b/internal/pkg/upgrade/node_test.go index 132b109b..4db27cc4 100644 --- a/internal/pkg/upgrade/node_test.go +++ b/internal/pkg/upgrade/node_test.go @@ -553,6 +553,8 @@ nodes: nodeprofiles: default: ipxe template: default + ipmi: + template: ipmitool.tmpl runtime overlay: - hosts - ssh.authorized_keys @@ -634,6 +636,8 @@ nodes: nodeprofiles: custom: {} default: + ipmi: + template: ipmitool.tmpl ipxe template: default runtime overlay: - hosts @@ -891,6 +895,8 @@ nodeprofiles: upgradedYaml: ` nodeprofiles: default: + ipmi: + template: ipmitool.tmpl ipxe template: default runtime overlay: - hosts diff --git a/lib/warewulf/bmc/ipmitool.tmpl b/lib/warewulf/bmc/ipmitool.tmpl index a1e8c604..33412df9 100644 --- a/lib/warewulf/bmc/ipmitool.tmpl +++ b/lib/warewulf/bmc/ipmitool.tmpl @@ -1,15 +1,18 @@ -{{/* 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 }} +{{ $cmd := "ipmitool" }} +{{ if .Interface }}{{ $cmd = cat $cmd "-I" .Interface }}{{ end }} +{{ if .EscapeChar }}{{ $cmd = cat $cmd "-e" .EscapeChar }}{{ end }} +{{ if .Port }}{{ $cmd = cat $cmd "-p" .Port }}{{ end }} +{{ if .Ipaddr }}{{ $cmd = cat $cmd "-H" .Ipaddr }}{{ end }} +{{ if .UserName }}{{ $cmd = cat $cmd "-U" (printf "\"%s\"" .UserName) }}{{ end }} +{{ if .Password }}{{ $cmd = cat $cmd "-P" (printf "\"%s\"" .Password) }}{{ end }} +{{ if eq .Cmd "PowerOn" }}{{ $cmd = cat $cmd "chassis power on" }} +{{ else if eq .Cmd "PowerOff" }}{{ $cmd = cat $cmd "chassis power off" }} +{{ else if eq .Cmd "PowerCycle" }}{{ $cmd = cat $cmd "chassis power cycle" }} +{{ else if eq .Cmd "PowerReset" }}{{ $cmd = cat $cmd "chassis power reset" }} +{{ else if eq .Cmd "PowerSoft" }}{{ $cmd = cat $cmd "chassis power soft" }} +{{ else if eq .Cmd "PowerStatus" }}{{ $cmd = cat $cmd "chassis power status" }} +{{ else if eq .Cmd "SDRList" }}{{ $cmd = cat $cmd "sdr list" }} +{{ else if eq .Cmd "SensorList" }}{{ $cmd = cat $cmd "sensor list" }} +{{ else if eq .Cmd "Console" }}{{ $cmd = cat $cmd "sol activate" }} +{{ end }} +{{- $cmd -}} diff --git a/lib/warewulf/bmc/nobmc.tmpl b/lib/warewulf/bmc/nobmc.tmpl index ab6ab127..f90d4ac0 100644 --- a/lib/warewulf/bmc/nobmc.tmpl +++ b/lib/warewulf/bmc/nobmc.tmpl @@ -1,11 +1,10 @@ -{{/* 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 +{{- 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 "sol not available" {{- else }}echo "command not found"{{ end }} diff --git a/userdocs/nodes/ipmi.rst b/userdocs/nodes/ipmi.rst index 37aee4fe..4e198d54 100644 --- a/userdocs/nodes/ipmi.rst +++ b/userdocs/nodes/ipmi.rst @@ -75,25 +75,24 @@ configured with a template which defines Warewulf's IPMI behavior. .. code-block:: - {{/* 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 }} + {{ $cmd := "ipmitool" }} + {{ if .Interface }}{{ $cmd = cat $cmd "-I" .Interface }}{{ end }} + {{ if .EscapeChar }}{{ $cmd = cat $cmd "-e" .EscapeChar }}{{ end }} + {{ if .Port }}{{ $cmd = cat $cmd "-p" .Port }}{{ end }} + {{ if .Ipaddr }}{{ $cmd = cat $cmd "-H" .Ipaddr }}{{ end }} + {{ if .UserName }}{{ $cmd = cat $cmd "-U" (printf "\"%s\"" .UserName) }}{{ end }} + {{ if .Password }}{{ $cmd = cat $cmd "-P" (printf "\"%s\"" .Password) }}{{ end }} + {{ if eq .Cmd "PowerOn" }}{{ $cmd = cat $cmd "chassis power on" }} + {{ else if eq .Cmd "PowerOff" }}{{ $cmd = cat $cmd "chassis power off" }} + {{ else if eq .Cmd "PowerCycle" }}{{ $cmd = cat $cmd "chassis power cycle" }} + {{ else if eq .Cmd "PowerReset" }}{{ $cmd = cat $cmd "chassis power reset" }} + {{ else if eq .Cmd "PowerSoft" }}{{ $cmd = cat $cmd "chassis power soft" }} + {{ else if eq .Cmd "PowerStatus" }}{{ $cmd = cat $cmd "chassis power status" }} + {{ else if eq .Cmd "SDRList" }}{{ $cmd = cat $cmd "sdr list" }} + {{ else if eq .Cmd "SensorList" }}{{ $cmd = cat $cmd "sensor list" }} + {{ else if eq .Cmd "Console" }}{{ $cmd = cat $cmd "sol activate" }} + {{ end }} + {{- $cmd -}} A different template can be used to change the IPMI behavior using the ``--ipmitemplate`` field. Referenced templates must be located in