From 10ea0ac78d75a86ef6df32bede1c262f3ab3b0fb Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Tue, 5 Dec 2023 12:06:57 +0100 Subject: [PATCH 01/12] use ipmi interface from node.NodeConf Signed-off-by: Christian Goll --- internal/app/wwctl/node/console/main.go | 16 +++------------ internal/app/wwctl/node/sensors/main.go | 26 +++++------------------- internal/app/wwctl/power/cycle/main.go | 27 ++++++------------------- internal/app/wwctl/power/off/main.go | 25 +++++------------------ internal/app/wwctl/power/on/main.go | 25 +++++------------------ internal/app/wwctl/power/reset/main.go | 27 ++++++------------------- internal/app/wwctl/power/soft/main.go | 27 ++++++------------------- internal/app/wwctl/power/status/main.go | 27 ++++++------------------- internal/pkg/node/datastructure.go | 21 ++++++++++--------- internal/pkg/power/ipmitool.go | 17 ++++++---------- internal/pkg/power/power.go | 6 ------ 11 files changed, 59 insertions(+), 185 deletions(-) diff --git a/internal/app/wwctl/node/console/main.go b/internal/app/wwctl/node/console/main.go index bbc2d319..3361d4a5 100644 --- a/internal/app/wwctl/node/console/main.go +++ b/internal/app/wwctl/node/console/main.go @@ -44,20 +44,10 @@ 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, - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} err := ipmiCmd.Console() - if 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 00e382d4..436c4528 100644 --- a/internal/app/wwctl/node/sensors/main.go +++ b/internal/app/wwctl/node/sensors/main.go @@ -49,26 +49,10 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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", - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} fullFlag := full - batchpool.Submit(func() { if fullFlag { //nolint:errcheck @@ -90,12 +74,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.NodeName, out) + wwlog.Error("%s: %s", result.Ipaddr, out) returnErr = err continue } - fmt.Printf("%s:\n%s\n", result.NodeName, out) + fmt.Printf("%s:\n%s\n", result.Ipaddr, out) } return returnErr diff --git a/internal/app/wwctl/power/cycle/main.go b/internal/app/wwctl/power/cycle/main.go index 0018f857..38ac38e6 100644 --- a/internal/app/wwctl/power/cycle/main.go +++ b/internal/app/wwctl/power/cycle/main.go @@ -41,30 +41,15 @@ func CobraRunE(cmd *cobra.Command, args []string) error { jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, node := range nodes { + for _, n := 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", - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} batchpool.Submit(func() { //nolint:errcheck ipmiCmd.PowerCycle() @@ -82,12 +67,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.NodeName, out) + wwlog.Error("%s: %s", result.Ipaddr, out) returnErr = err continue } - fmt.Printf("%s: %s\n", result.NodeName, out) + fmt.Printf("%s: %s\n", result.Ipaddr, out) } diff --git a/internal/app/wwctl/power/off/main.go b/internal/app/wwctl/power/off/main.go index 278c08f2..939d9a66 100644 --- a/internal/app/wwctl/power/off/main.go +++ b/internal/app/wwctl/power/off/main.go @@ -47,24 +47,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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", - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} batchpool.Submit(func() { //nolint:errcheck ipmiCmd.PowerOff() @@ -82,12 +67,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.NodeName, out) + wwlog.Error("%s: %s", result.Ipaddr, out) returnErr = err continue } - fmt.Printf("%s: %s\n", result.NodeName, out) + fmt.Printf("%s: %s\n", result.Ipaddr, out) } diff --git a/internal/app/wwctl/power/on/main.go b/internal/app/wwctl/power/on/main.go index 9bf926d5..4e517ea7 100644 --- a/internal/app/wwctl/power/on/main.go +++ b/internal/app/wwctl/power/on/main.go @@ -47,24 +47,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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", - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} batchpool.Submit(func() { //nolint:errcheck ipmiCmd.PowerOn() @@ -82,12 +67,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.NodeName, out) + wwlog.Error("%s: %s", result.Ipaddr, out) returnErr = err continue } - fmt.Printf("%s: %s\n", result.NodeName, out) + fmt.Printf("%s: %s\n", result.Ipaddr, out) } diff --git a/internal/app/wwctl/power/reset/main.go b/internal/app/wwctl/power/reset/main.go index b3550944..89b0e5bb 100644 --- a/internal/app/wwctl/power/reset/main.go +++ b/internal/app/wwctl/power/reset/main.go @@ -41,30 +41,15 @@ func CobraRunE(cmd *cobra.Command, args []string) error { jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, node := range nodes { + for _, n := 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", - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} batchpool.Submit(func() { //nolint:errcheck ipmiCmd.PowerReset() @@ -82,12 +67,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.NodeName, out) + wwlog.Error("%s: %s", result.Ipaddr, out) returnErr = err continue } - fmt.Printf("%s: %s\n", result.NodeName, out) + fmt.Printf("%s: %s\n", result.Ipaddr, out) } diff --git a/internal/app/wwctl/power/soft/main.go b/internal/app/wwctl/power/soft/main.go index 9eede24a..955a9b63 100644 --- a/internal/app/wwctl/power/soft/main.go +++ b/internal/app/wwctl/power/soft/main.go @@ -41,30 +41,15 @@ func CobraRunE(cmd *cobra.Command, args []string) error { jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, node := range nodes { + for _, n := 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", - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} batchpool.Submit(func() { //nolint:errcheck ipmiCmd.PowerSoft() @@ -82,12 +67,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.NodeName, out) + wwlog.Error("%s: %s", result.Ipaddr, out) returnErr = err continue } - fmt.Printf("%s: %s\n", result.NodeName, out) + fmt.Printf("%s: %s\n", result.Ipaddr, out) } diff --git a/internal/app/wwctl/power/status/main.go b/internal/app/wwctl/power/status/main.go index 6bfe4d15..c5d79ca6 100644 --- a/internal/app/wwctl/power/status/main.go +++ b/internal/app/wwctl/power/status/main.go @@ -41,30 +41,15 @@ func CobraRunE(cmd *cobra.Command, args []string) error { jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, node := range nodes { + for _, n := 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", - } - + var conf node.NodeConf + conf.GetFrom(n) + ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} batchpool.Submit(func() { //nolint:errcheck ipmiCmd.PowerStatus() @@ -82,12 +67,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.NodeName, out) + wwlog.Error("%s: %s", result.Ipaddr, out) returnErr = err continue } - fmt.Printf("%s: %s\n", result.NodeName, out) + fmt.Printf("%s: %s\n", result.Ipaddr, out) } diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index 44597ad2..de593f95 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -59,16 +59,17 @@ type ProfileConf struct { } type IpmiConf struct { - UserName string `yaml:"username,omitempty" lopt:"ipmiuser" comment:"Set the IPMI username"` - Password string `yaml:"password,omitempty" lopt:"ipmipass" comment:"Set the IPMI password"` - Ipaddr net.IP `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address" type:"IP"` - Gateway net.IP `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway" type:"IP"` - Netmask net.IP `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask" type:"IP"` - Port string `yaml:"port,omitempty" lopt:"ipmiport" comment:"Set the IPMI port"` - 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)"` - Tags map[string]string `yaml:"tags,omitempty"` + UserName string `yaml:"username,omitempty" lopt:"ipmiuser" comment:"Set the IPMI username"` + Password string `yaml:"password,omitempty" lopt:"ipmipass" comment:"Set the IPMI password"` + Ipaddr net.IP `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address" type:"IP"` + Gateway net.IP `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway" type:"IP"` + Netmask net.IP `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask" type:"IP"` + Port string `yaml:"port,omitempty" lopt:"ipmiport" comment:"Set the IPMI port"` + 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)"` + BmcTemplate string `yaml:"template,omitempty" lopt:"ipmitemplate" comment:"template used for ipmi command"` + Tags map[string]string `yaml:"tags,omitempty"` } type KernelConf struct { diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go index 73a5b913..945d83a8 100644 --- a/internal/pkg/power/ipmitool.go +++ b/internal/pkg/power/ipmitool.go @@ -4,6 +4,8 @@ import ( "os" "os/exec" "strings" + + "github.com/hpcng/warewulf/internal/pkg/node" ) type IPMIResult struct { @@ -12,15 +14,8 @@ 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 + result IPMIResult } func (ipmi *IPMI) Result() (string, error) { @@ -40,7 +35,7 @@ func (ipmi *IPMI) Command(ipmiArgs []string) ([]byte, error) { 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, "-I", ipmi.Interface, "-H", ipmi.Ipaddr, "-p", ipmi.Port, "-U", ipmi.UserName, "-P", ipmi.Password, "-e", ipmi.EscapeChar) args = append(args, ipmiArgs...) ipmiCmd := exec.Command("ipmitool", args...) return ipmiCmd.CombinedOutput() @@ -59,7 +54,7 @@ func (ipmi *IPMI) InteractiveCommand(ipmiArgs []string) error { 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, "-I", ipmi.Interface, "-H", ipmi.Ipaddr, "-p", ipmi.Port, "-U", ipmi.UserName, "-P", ipmi.Password, "-e", ipmi.EscapeChar) args = append(args, ipmiArgs...) ipmiCmd := exec.Command("ipmitool", args...) ipmiCmd.Stdout = os.Stdout diff --git a/internal/pkg/power/power.go b/internal/pkg/power/power.go index 2faa3e67..5702e2d1 100644 --- a/internal/pkg/power/power.go +++ b/internal/pkg/power/power.go @@ -1,11 +1,5 @@ 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) } From 4597fdfaa968a249d0257defd4883bd14af8724d Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Tue, 5 Dec 2023 15:49:29 +0100 Subject: [PATCH 02/12] use ipmitool created from template file Signed-off-by: Christian Goll --- Makefile | 2 + internal/app/wwctl/power/status/main.go | 78 ++++++++-------- internal/app/wwctl/power/status/root.go | 17 ++-- internal/pkg/power/ipmitool.go | 115 +++++++++++++----------- internal/pkg/power/power.go | 25 ------ lib/warewulf/bmc/ipmitool.tmpl | 19 ++++ 6 files changed, 135 insertions(+), 121 deletions(-) delete mode 100644 internal/pkg/power/power.go create mode 100644 lib/warewulf/bmc/ipmitool.tmpl diff --git a/Makefile b/Makefile index 0f970b57..b567eb38 100644 --- a/Makefile +++ b/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 diff --git a/internal/app/wwctl/power/status/main.go b/internal/app/wwctl/power/status/main.go index c5d79ca6..5e91d7fe 100644 --- a/internal/app/wwctl/power/status/main.go +++ b/internal/app/wwctl/power/status/main.go @@ -12,36 +12,37 @@ 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 { + var returnErr error = nil - nodeDB, err := node.New() - if err != nil { - return fmt.Errorf("could not open node configuration: %s", err) - } + nodeDB, err := node.New() + if err != nil { + return err + } - nodes, err := nodeDB.FindAllNodes() - if err != nil { - return fmt.Errorf("could not get node list: %s", err) - } + 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(args) > 0 { + nodes = node.FilterByName(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) + batchpool := batch.New(50) + jobcount := len(nodes) + results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, n := range nodes { if node.Ipmi.Ipaddr.IsUnspecified() { wwlog.Error("%s: No IPMI IP address", node.Id()) @@ -56,25 +57,26 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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 } - fmt.Printf("%s: %s\n", result.Ipaddr, out) + 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 } diff --git a/internal/app/wwctl/power/status/root.go b/internal/app/wwctl/power/status/root.go index 4fda726c..116404a6 100644 --- a/internal/app/wwctl/power/status/root.go +++ b/internal/app/wwctl/power/status/root.go @@ -5,13 +5,19 @@ import ( "github.com/warewulf/warewulf/internal/pkg/node" ) -var ( - powerCmd = &cobra.Command{ +type variables struct { + Showcmd bool +} + +// 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 +32,6 @@ 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") return powerCmd } diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go index 945d83a8..02b3553c 100644 --- a/internal/pkg/power/ipmitool.go +++ b/internal/pkg/power/ipmitool.go @@ -1,10 +1,18 @@ package power import ( + "bytes" + "fmt" + "html/template" "os" "os/exec" + "path" + "regexp" "strings" + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/hpcng/warewulf/internal/pkg/node" ) @@ -15,109 +23,114 @@ type IPMIResult struct { type IPMI struct { node.IpmiConf - result IPMIResult + 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.BmcTemplate == "" { + ipmi.BmcTemplate = "ipmitool.tmpl" + } + conf := warewulfconf.Get() + fbuf, err := os.ReadFile(path.Join(conf.Paths.Datadir, "bmc", ipmi.BmcTemplate)) + if err != nil { + return "", fmt.Errorf("couldn't find the template which defines the ipmi/redfish 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.Ipaddr, "-p", ipmi.Port, "-U", ipmi.UserName, "-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.Ipaddr, "-p", ipmi.Port, "-U", ipmi.UserName, "-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") } diff --git a/internal/pkg/power/power.go b/internal/pkg/power/power.go deleted file mode 100644 index 5702e2d1..00000000 --- a/internal/pkg/power/power.go +++ /dev/null @@ -1,25 +0,0 @@ -package power - -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) -} diff --git a/lib/warewulf/bmc/ipmitool.tmpl b/lib/warewulf/bmc/ipmitool.tmpl new file mode 100644 index 00000000..318a35b6 --- /dev/null +++ b/lib/warewulf/bmc/ipmitool.tmpl @@ -0,0 +1,19 @@ +{{/* 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 }} \ No newline at end of file From f53ae48ae661e008ab2e7df345dcc28361b50dd8 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Tue, 5 Dec 2023 20:04:32 +0100 Subject: [PATCH 03/12] added test for power status Signed-off-by: Christian Goll --- internal/app/wwctl/power/cycle/main.go | 60 +++++++++---------- internal/app/wwctl/power/cycle/root.go | 31 +++++++--- internal/app/wwctl/power/cycle/root_test.go | 42 ++++++++++++++ internal/app/wwctl/power/off/main.go | 60 +++++++++---------- internal/app/wwctl/power/off/root.go | 16 +++-- internal/app/wwctl/power/off/root_test.go | 42 ++++++++++++++ internal/app/wwctl/power/on/main.go | 61 ++++++++++---------- internal/app/wwctl/power/on/root.go | 16 +++-- internal/app/wwctl/power/on/root_test.go | 42 ++++++++++++++ internal/app/wwctl/power/reset/main.go | 56 ++++++++++-------- internal/app/wwctl/power/reset/root.go | 16 +++-- internal/app/wwctl/power/reset/root_test.go | 42 ++++++++++++++ internal/app/wwctl/power/soft/main.go | 49 ++++++++-------- internal/app/wwctl/power/soft/root.go | 19 +++--- internal/app/wwctl/power/soft/root_test.go | 42 ++++++++++++++ internal/app/wwctl/power/status/root_test.go | 42 ++++++++++++++ internal/pkg/testenv/testenv.go | 20 ++++++- 17 files changed, 486 insertions(+), 170 deletions(-) create mode 100644 internal/app/wwctl/power/cycle/root_test.go create mode 100644 internal/app/wwctl/power/off/root_test.go create mode 100644 internal/app/wwctl/power/on/root_test.go create mode 100644 internal/app/wwctl/power/reset/root_test.go create mode 100644 internal/app/wwctl/power/soft/root_test.go create mode 100644 internal/app/wwctl/power/status/root_test.go diff --git a/internal/app/wwctl/power/cycle/main.go b/internal/app/wwctl/power/cycle/main.go index 38ac38e6..9ffd0aa6 100644 --- a/internal/app/wwctl/power/cycle/main.go +++ b/internal/app/wwctl/power/cycle/main.go @@ -12,8 +12,9 @@ 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 { + var returnErr error = nil nodeDB, err := node.New() if err != nil { @@ -25,23 +26,23 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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(args) > 0 { + nodes = node.FilterByName(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) + batchpool := batch.New(50) + jobcount := len(nodes) + results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, n := range nodes { if node.Ipmi.Ipaddr.IsUnspecified() { wwlog.Error("%s: No IPMI IP address", node.Id()) @@ -56,25 +57,24 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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 } - fmt.Printf("%s: %s\n", result.Ipaddr, out) + 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 } diff --git a/internal/app/wwctl/power/cycle/root.go b/internal/app/wwctl/power/cycle/root.go index 803ba0da..9054a5ea 100644 --- a/internal/app/wwctl/power/cycle/root.go +++ b/internal/app/wwctl/power/cycle/root.go @@ -2,19 +2,36 @@ package cycle import ( "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/node" ) -var ( - powerCmd = &cobra.Command{ +type variables struct { + Showcmd bool +} + +// 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.Get()) + } + return node_names, cobra.ShellCompDirectiveNoFileComp + }, + } + powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed") return powerCmd } diff --git a/internal/app/wwctl/power/cycle/root_test.go b/internal/app/wwctl/power/cycle/root_test.go new file mode 100644 index 00000000..37f4dca1 --- /dev/null +++ b/internal/app/wwctl/power/cycle/root_test.go @@ -0,0 +1,42 @@ +package powercycle + +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) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: + ipmi: + username: admin + password: admin +nodes: + n01: + profiles: + - default + ipmi: + ipaddr: 10.10.10.10`) + warewulfd.SetNoDaemon() + t.Run("ipmitool status test", func(t *testing.T) { + baseCmd := GetCommand() + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + baseCmd.SetArgs([]string{"--show", "n01"}) + err := baseCmd.Execute() + assert.NoError(t, err) + assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power cycle", strings.TrimSpace(buf.String())) + }) +} diff --git a/internal/app/wwctl/power/off/main.go b/internal/app/wwctl/power/off/main.go index 939d9a66..60f156e8 100644 --- a/internal/app/wwctl/power/off/main.go +++ b/internal/app/wwctl/power/off/main.go @@ -12,8 +12,9 @@ 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 { + var returnErr error = nil nodeDB, err := node.New() if err != nil { @@ -25,21 +26,21 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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(args) > 0 { + nodes = node.FilterByName(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) + batchpool := batch.New(50) + jobcount := len(nodes) + results := make(chan power.IPMI, jobcount) for _, node := range nodes { @@ -56,25 +57,26 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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 } - fmt.Printf("%s: %s\n", result.Ipaddr, out) + 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 } diff --git a/internal/app/wwctl/power/off/root.go b/internal/app/wwctl/power/off/root.go index 004ad427..bedd5c9e 100644 --- a/internal/app/wwctl/power/off/root.go +++ b/internal/app/wwctl/power/off/root.go @@ -5,13 +5,19 @@ import ( "github.com/warewulf/warewulf/internal/pkg/node" ) -var ( - powerCmd = &cobra.Command{ +type variables struct { + Showcmd bool +} + +// 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 +32,7 @@ var ( return node_names, cobra.ShellCompDirectiveNoFileComp }, } -) + powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed") -// GetRootCommand returns the root cobra.Command for the application. -func GetCommand() *cobra.Command { return powerCmd } diff --git a/internal/app/wwctl/power/off/root_test.go b/internal/app/wwctl/power/off/root_test.go new file mode 100644 index 00000000..7af6e297 --- /dev/null +++ b/internal/app/wwctl/power/off/root_test.go @@ -0,0 +1,42 @@ +package poweroff + +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) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: + ipmi: + username: admin + password: admin +nodes: + n01: + profiles: + - default + ipmi: + ipaddr: 10.10.10.10`) + warewulfd.SetNoDaemon() + t.Run("ipmitool status test", func(t *testing.T) { + baseCmd := GetCommand() + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + baseCmd.SetArgs([]string{"--show", "n01"}) + err := baseCmd.Execute() + assert.NoError(t, err) + assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power off", strings.TrimSpace(buf.String())) + }) +} diff --git a/internal/app/wwctl/power/on/main.go b/internal/app/wwctl/power/on/main.go index 4e517ea7..b19ba533 100644 --- a/internal/app/wwctl/power/on/main.go +++ b/internal/app/wwctl/power/on/main.go @@ -12,8 +12,10 @@ 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 { + + var returnErr error = nil nodeDB, err := node.New() if err != nil { @@ -25,21 +27,21 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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(args) > 0 { + nodes = node.FilterByName(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) + batchpool := batch.New(50) + jobcount := len(nodes) + results := make(chan power.IPMI, jobcount) for _, node := range nodes { @@ -56,25 +58,26 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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 } - fmt.Printf("%s: %s\n", result.Ipaddr, out) + 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 } diff --git a/internal/app/wwctl/power/on/root.go b/internal/app/wwctl/power/on/root.go index 42c0ebf4..8fb83ff2 100644 --- a/internal/app/wwctl/power/on/root.go +++ b/internal/app/wwctl/power/on/root.go @@ -5,12 +5,18 @@ import ( "github.com/warewulf/warewulf/internal/pkg/node" ) -var ( - powerCmd = &cobra.Command{ +type variables struct { + Showcmd bool +} + +// 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 +31,7 @@ var ( return node_names, cobra.ShellCompDirectiveNoFileComp }, } -) + powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed") -// GetRootCommand returns the root cobra.Command for the application. -func GetCommand() *cobra.Command { return powerCmd } diff --git a/internal/app/wwctl/power/on/root_test.go b/internal/app/wwctl/power/on/root_test.go new file mode 100644 index 00000000..0dd39ba8 --- /dev/null +++ b/internal/app/wwctl/power/on/root_test.go @@ -0,0 +1,42 @@ +package poweron + +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) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: + ipmi: + username: admin + password: admin +nodes: + n01: + profiles: + - default + ipmi: + ipaddr: 10.10.10.10`) + warewulfd.SetNoDaemon() + t.Run("ipmitool status test", func(t *testing.T) { + baseCmd := GetCommand() + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + baseCmd.SetArgs([]string{"--show", "n01"}) + err := baseCmd.Execute() + assert.NoError(t, err) + assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power on", strings.TrimSpace(buf.String())) + }) +} diff --git a/internal/app/wwctl/power/reset/main.go b/internal/app/wwctl/power/reset/main.go index 89b0e5bb..1abe3d3c 100644 --- a/internal/app/wwctl/power/reset/main.go +++ b/internal/app/wwctl/power/reset/main.go @@ -12,8 +12,10 @@ 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 { + + var returnErr error = nil nodeDB, err := node.New() if err != nil { @@ -25,6 +27,13 @@ func CobraRunE(cmd *cobra.Command, args []string) error { return fmt.Errorf("cloud not get nodeList: %s", err) } + if len(args) > 0 { + nodes = node.FilterByName(nodes, hostlist.Expand(args)) + } else { + //nolint:errcheck + cmd.Usage() + os.Exit(1) + } if len(args) > 0 { nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args)) } else { @@ -37,11 +46,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error { return fmt.Errorf("no nodes found") } - batchpool := batch.New(50) - jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + batchpool := batch.New(50) + jobcount := len(nodes) + results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, n := range nodes { if node.Ipmi.Ipaddr.IsUnspecified() { wwlog.Error("%s: No IPMI IP address", node.Id()) @@ -56,25 +65,26 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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 } - fmt.Printf("%s: %s\n", result.Ipaddr, out) + 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 } diff --git a/internal/app/wwctl/power/reset/root.go b/internal/app/wwctl/power/reset/root.go index 7be3abdf..bdab6b84 100644 --- a/internal/app/wwctl/power/reset/root.go +++ b/internal/app/wwctl/power/reset/root.go @@ -5,13 +5,19 @@ import ( "github.com/warewulf/warewulf/internal/pkg/node" ) -var ( - powerCmd = &cobra.Command{ +type variables struct { + Showcmd bool +} + +// 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 +32,7 @@ var ( return node_names, cobra.ShellCompDirectiveNoFileComp }, } -) + powerCmd.PersistentFlags().BoolVarP(&vars.Showcmd, "show", "s", false, "only show command which will be executed") -// GetRootCommand returns the root cobra.Command for the application. -func GetCommand() *cobra.Command { return powerCmd } diff --git a/internal/app/wwctl/power/reset/root_test.go b/internal/app/wwctl/power/reset/root_test.go new file mode 100644 index 00000000..d486d2d4 --- /dev/null +++ b/internal/app/wwctl/power/reset/root_test.go @@ -0,0 +1,42 @@ +package powerreset + +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) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: + ipmi: + username: admin + password: admin +nodes: + n01: + profiles: + - default + ipmi: + ipaddr: 10.10.10.10`) + warewulfd.SetNoDaemon() + t.Run("ipmitool status test", func(t *testing.T) { + baseCmd := GetCommand() + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + baseCmd.SetArgs([]string{"--show", "n01"}) + err := baseCmd.Execute() + assert.NoError(t, err) + assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power reset", strings.TrimSpace(buf.String())) + }) +} diff --git a/internal/app/wwctl/power/soft/main.go b/internal/app/wwctl/power/soft/main.go index 955a9b63..5027707b 100644 --- a/internal/app/wwctl/power/soft/main.go +++ b/internal/app/wwctl/power/soft/main.go @@ -12,8 +12,10 @@ 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 { + + var returnErr error = nil nodeDB, err := node.New() if err != nil { @@ -37,11 +39,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error { return fmt.Errorf("no nodes found") } - batchpool := batch.New(50) - jobcount := len(nodes) - results := make(chan power.IPMI, jobcount) + batchpool := batch.New(50) + jobcount := len(nodes) + results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, n := range nodes { if node.Ipmi.Ipaddr.IsUnspecified() { wwlog.Error("%s: No IPMI IP address", node.Id()) @@ -56,25 +58,26 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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 } - fmt.Printf("%s: %s\n", result.Ipaddr, out) + 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 } diff --git a/internal/app/wwctl/power/soft/root.go b/internal/app/wwctl/power/soft/root.go index 791aa0e0..6c7df0c3 100644 --- a/internal/app/wwctl/power/soft/root.go +++ b/internal/app/wwctl/power/soft/root.go @@ -5,13 +5,19 @@ import ( "github.com/warewulf/warewulf/internal/pkg/node" ) -var ( - powerCmd = &cobra.Command{ +type variables struct { + Showcmd bool +} + +// 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, + Long: "This command uses the operationg system to shut down the set of nodes specified by PATTERN.", + 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 +32,6 @@ 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") return powerCmd } diff --git a/internal/app/wwctl/power/soft/root_test.go b/internal/app/wwctl/power/soft/root_test.go new file mode 100644 index 00000000..87bb00b3 --- /dev/null +++ b/internal/app/wwctl/power/soft/root_test.go @@ -0,0 +1,42 @@ +package powersoft + +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) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: + ipmi: + username: admin + password: admin +nodes: + n01: + profiles: + - default + ipmi: + ipaddr: 10.10.10.10`) + warewulfd.SetNoDaemon() + t.Run("ipmitool status test", func(t *testing.T) { + baseCmd := GetCommand() + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + baseCmd.SetArgs([]string{"--show", "n01"}) + err := baseCmd.Execute() + assert.NoError(t, err) + assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power soft", strings.TrimSpace(buf.String())) + }) +} diff --git a/internal/app/wwctl/power/status/root_test.go b/internal/app/wwctl/power/status/root_test.go new file mode 100644 index 00000000..4446c37c --- /dev/null +++ b/internal/app/wwctl/power/status/root_test.go @@ -0,0 +1,42 @@ +package powerstatus + +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) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: + ipmi: + username: admin + password: admin +nodes: + n01: + profiles: + - default + ipmi: + ipaddr: 10.10.10.10`) + warewulfd.SetNoDaemon() + t.Run("ipmitool status test", func(t *testing.T) { + baseCmd := GetCommand() + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + baseCmd.SetArgs([]string{"--show", "n01"}) + err := baseCmd.Execute() + assert.NoError(t, err) + assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power status", strings.TrimSpace(buf.String())) + }) +} diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go index 94d88a83..1afc6c63 100644 --- a/internal/pkg/testenv/testenv.go +++ b/internal/pkg/testenv/testenv.go @@ -9,6 +9,7 @@ import ( "os" "path" "path/filepath" + "runtime" "testing" "time" @@ -16,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/util" ) const initWarewulfConf = `WW_INTERNAL: 0` @@ -93,9 +95,15 @@ func New(t *testing.T) (env *TestEnv) { conf.Paths.WWChrootdir, conf.Paths.WWProvisiondir, } { - env.MkdirAll(t, confPath) + env.MkdirAllAbs(t, confPath) } - + // copy templates + _, b, _, _ := runtime.Caller(0) + basepath := filepath.Dir(b) + env.MkdirAllAbs(t, path.Join(conf.Warewulf.DataStore, "warewulf/bmc")) + assert.DirExists(t, path.Join(conf.Warewulf.DataStore, "warewulf/bmc")) + err = util.CopyFile(path.Join(basepath, "../../../lib/warewulf/bmc/ipmitool.tmpl"), path.Join(conf.Warewulf.DataStore, "warewulf/bmc/ipmitool.tmpl")) + assert.NoError(t, err) // node.init() has already run, so set the config path again node.ConfigFile = env.GetPath(path.Join(Sysconfdir, "warewulf/nodes.conf")) @@ -110,13 +118,19 @@ func (env *TestEnv) GetPath(fileName string) string { // MkdirAll creates dirName and any intermediate directories relative // to the test environment. -// // Asserts no errors occur. func (env *TestEnv) MkdirAll(t *testing.T, dirName string) { err := os.MkdirAll(env.GetPath(dirName), 0755) assert.NoError(t, err) } +// MkdirAllAbs creates absolute dirName and any intermediate directories +// Asserts no errors occur. +func (env *TestEnv) MkdirAllAbs(t *testing.T, dirName string) { + err := os.MkdirAll(dirName, 0755) + assert.NoError(t, err) +} + // WriteFile writes content to fileName, creating any necessary // intermediate directories relative to the test environment. // From b422fd8de263ccb26ff3da47e56959ce47fa3ea7 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Wed, 6 Dec 2023 12:12:15 +0100 Subject: [PATCH 04/12] added template for nodes without bmc Signed-off-by: Christian Goll --- CHANGELOG.md | 1 + lib/warewulf/bmc/nobmc.tmpl | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 lib/warewulf/bmc/nobmc.tmpl diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2165e8..7d2868a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,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 diff --git a/lib/warewulf/bmc/nobmc.tmpl b/lib/warewulf/bmc/nobmc.tmpl new file mode 100644 index 00000000..75239ce6 --- /dev/null +++ b/lib/warewulf/bmc/nobmc.tmpl @@ -0,0 +1,12 @@ +{{/* used command to access nodes without bmc*/}} +{{- $cmd := "echo command not found" }} +{{- if eq .Cmd "PowerOn" }} {{ $cmd = printf "wol %s" .Interface }} {{ end }} +{{- if eq .Cmd "PowerOff" }} {{ $cmd = printf "ssh %s echo o > /proc/sysrq-trigger" .Ipaddr }} {{ end }} +{{- if eq .Cmd "PowerCycle" }} {{ $cmd = printf "ssh %s echo r > /proc/sysrq-trigger" .Ipaddr }} {{ end }} +{{- if eq .Cmd "PowerRest" }} {{ $cmd = printf "ssh %s echo r > /proc/sysrq-trigger" .Ipaddr }} {{ end }} +{{- if eq .Cmd "PowerSoft" }} {{ $cmd = print "ssh %s reboot" .Ipaddr }} {{ end }} +{{- if eq .Cmd "PowerStatus" }} {{ $args = "ping -c 1 %s &> /dev/null %s && echo ON || echo OFF" .Ipaddr }} {{ end }} +{{- if eq .Cmd "SDRList" }} {{ $cmd = print "ssh %s sensors" .Ipaddr }} {{ end }} +{{- if eq .Cmd "SensorList" }} {{ $cmd = print "ssh %s sensors" .Ipaddr }} {{ end }} +{{- if eq .Cmd "Console" }} {{ $args = "echo node sol" }} {{ end }} +{{ $cmd }} \ No newline at end of file From 0490f4c9a535b9fcc117a54df6ae76b38e145230 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 7 Dec 2023 16:39:45 +0100 Subject: [PATCH 05/12] added documentation for ipmi templates Signed-off-by: Christian Goll --- internal/pkg/power/ipmitool.go | 17 ++--- userdocs/contents/ipmi.rst | 125 ++++++++++++++++++++++++++------- 2 files changed, 110 insertions(+), 32 deletions(-) diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go index 02b3553c..ace25d0c 100644 --- a/internal/pkg/power/ipmitool.go +++ b/internal/pkg/power/ipmitool.go @@ -10,10 +10,9 @@ import ( "regexp" "strings" - warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - - "github.com/hpcng/warewulf/internal/pkg/node" + 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 { @@ -33,11 +32,13 @@ func (ipmi *IPMI) Result() (string, error) { } func (ipmi *IPMI) getStr() (cmdStr string, err error) { - if ipmi.BmcTemplate == "" { - ipmi.BmcTemplate = "ipmitool.tmpl" - } conf := warewulfconf.Get() - fbuf, err := os.ReadFile(path.Join(conf.Paths.Datadir, "bmc", ipmi.BmcTemplate)) + if ipmi.BmcTemplate == "" { + ipmi.BmcTemplate = path.Join(conf.Warewulf.DataStore, "warewulf/bmc/ipmitool.tmpl") + } else if !strings.HasPrefix(ipmi.BmcTemplate, "/") { + ipmi.BmcTemplate = path.Join(conf.Warewulf.DataStore, "warewulf/bmc", ipmi.BmcTemplate) + } + fbuf, err := os.ReadFile(ipmi.BmcTemplate) if err != nil { return "", fmt.Errorf("couldn't find the template which defines the ipmi/redfish command: %s", err) } diff --git a/userdocs/contents/ipmi.rst b/userdocs/contents/ipmi.rst index 627b1f09..c7cdc9a9 100644 --- a/userdocs/contents/ipmi.rst +++ b/userdocs/contents/ipmi.rst @@ -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`` | ``.BmcTemplate`` | ++---------------------+--------------------+ + +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`` \ No newline at end of file From 2e2a7d7de3c31ebec0717927b6efeea2532c7983 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 7 Nov 2024 17:45:02 -0700 Subject: [PATCH 06/12] rebased for the use without NodeInfo make fanout configureable via commanline Signed-off-by: Christian Goll --- internal/app/wwctl/node/console/main.go | 4 +- internal/app/wwctl/node/sensors/main.go | 134 ++++++++++--------- internal/app/wwctl/node/sensors/root.go | 26 ++-- internal/app/wwctl/node/sensors/root_test.go | 42 ++++++ internal/app/wwctl/power/cycle/main.go | 53 ++++---- internal/app/wwctl/power/cycle/root.go | 4 +- internal/app/wwctl/power/cycle/root_test.go | 2 +- internal/app/wwctl/power/off/main.go | 53 ++++---- internal/app/wwctl/power/off/root.go | 2 + internal/app/wwctl/power/off/root_test.go | 2 +- internal/app/wwctl/power/on/main.go | 53 ++++---- internal/app/wwctl/power/on/root.go | 2 + internal/app/wwctl/power/on/root_test.go | 2 +- internal/app/wwctl/power/reset/main.go | 60 ++++----- internal/app/wwctl/power/reset/root.go | 3 +- internal/app/wwctl/power/reset/root_test.go | 2 +- internal/app/wwctl/power/soft/main.go | 65 ++++----- internal/app/wwctl/power/soft/root.go | 4 +- internal/app/wwctl/power/soft/root_test.go | 2 +- internal/app/wwctl/power/status/main.go | 35 ++--- internal/app/wwctl/power/status/root.go | 2 + internal/app/wwctl/power/status/root_test.go | 2 +- 22 files changed, 304 insertions(+), 250 deletions(-) create mode 100644 internal/app/wwctl/node/sensors/root_test.go diff --git a/internal/app/wwctl/node/console/main.go b/internal/app/wwctl/node/console/main.go index 3361d4a5..0f150e31 100644 --- a/internal/app/wwctl/node/console/main.go +++ b/internal/app/wwctl/node/console/main.go @@ -44,9 +44,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Error("%s: No IPMI IP address", node.Id()) continue } - var conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} + ipmiCmd := power.IPMI{IpmiConf: *node.Ipmi} err := ipmiCmd.Console() if err != nil { wwlog.Error("%s: Console problem", node.Id()) diff --git a/internal/app/wwctl/node/sensors/main.go b/internal/app/wwctl/node/sensors/main.go index 436c4528..ea1a8db6 100644 --- a/internal/app/wwctl/node/sensors/main.go +++ b/internal/app/wwctl/node/sensors/main.go @@ -12,75 +12,77 @@ 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 conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} - 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.Ipaddr, out) - returnErr = err - continue + return fmt.Errorf("could not open node configuration: %s", err) } - fmt.Printf("%s:\n%s\n", result.Ipaddr, 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 { + wwlog.Error("%s: %s", result.Ipaddr, out) + returnErr = err + continue + } + + wwlog.Info("%s:\n%s\n", result.Ipaddr, out) + } + + return returnErr + } } diff --git a/internal/app/wwctl/node/sensors/root.go b/internal/app/wwctl/node/sensors/root.go index e67f48eb..f367caeb 100644 --- a/internal/app/wwctl/node/sensors/root.go +++ b/internal/app/wwctl/node/sensors/root.go @@ -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 } diff --git a/internal/app/wwctl/node/sensors/root_test.go b/internal/app/wwctl/node/sensors/root_test.go new file mode 100644 index 00000000..d2fcc970 --- /dev/null +++ b/internal/app/wwctl/node/sensors/root_test.go @@ -0,0 +1,42 @@ +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_Power_Status(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 43 +nodeprofiles: + default: + ipmi: + username: admin + password: admin +nodes: + n01: + profiles: + - default + ipmi: + ipaddr: 10.10.10.10`) + warewulfd.SetNoDaemon() + t.Run("ipmitool status test", func(t *testing.T) { + baseCmd := GetCommand() + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + baseCmd.SetArgs([]string{"--show", "n01"}) + err := baseCmd.Execute() + assert.NoError(t, err) + assert.Equal(t, "10.10.10.10:\nipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ sdr list", strings.TrimSpace(buf.String())) + }) +} diff --git a/internal/app/wwctl/power/cycle/main.go b/internal/app/wwctl/power/cycle/main.go index 9ffd0aa6..1ed8e57a 100644 --- a/internal/app/wwctl/power/cycle/main.go +++ b/internal/app/wwctl/power/cycle/main.go @@ -16,46 +16,47 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err return func(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) - } + 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) - } + nodes, err := nodeDB.FindAllNodes() + if err != nil { + return fmt.Errorf("could not get node list: %s", err) + } if len(args) > 0 { - nodes = node.FilterByName(nodes, hostlist.Expand(args)) + 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") - } + if len(nodes) == 0 { + return fmt.Errorf("no nodes found") + } - batchpool := batch.New(50) + batchpool := batch.New(vars.Fanout) jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, node := range nodes { - if node.Ipmi.Ipaddr.IsUnspecified() { - wwlog.Error("%s: No IPMI IP address", node.Id()) - continue - } - var conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} - batchpool.Submit(func() { - //nolint:errcheck - ipmiCmd.PowerCycle() - results <- ipmiCmd - }) + 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 + }) } diff --git a/internal/app/wwctl/power/cycle/root.go b/internal/app/wwctl/power/cycle/root.go index 9054a5ea..8b450b8c 100644 --- a/internal/app/wwctl/power/cycle/root.go +++ b/internal/app/wwctl/power/cycle/root.go @@ -7,6 +7,7 @@ import ( type variables struct { Showcmd bool + Fanout int } // GetRootCommand returns the root cobra.Command for the application. @@ -27,11 +28,12 @@ func GetCommand() *cobra.Command { nodes, _ := nodeDB.FindAllNodes() var node_names []string for _, node := range nodes { - node_names = append(node_names, node.Id.Get()) + 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 } diff --git a/internal/app/wwctl/power/cycle/root_test.go b/internal/app/wwctl/power/cycle/root_test.go index 37f4dca1..a3e72186 100644 --- a/internal/app/wwctl/power/cycle/root_test.go +++ b/internal/app/wwctl/power/cycle/root_test.go @@ -1,4 +1,4 @@ -package powercycle +package cycle import ( "bytes" diff --git a/internal/app/wwctl/power/off/main.go b/internal/app/wwctl/power/off/main.go index 60f156e8..9426ff0e 100644 --- a/internal/app/wwctl/power/off/main.go +++ b/internal/app/wwctl/power/off/main.go @@ -16,46 +16,47 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err return func(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) - } + 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) - } + nodes, err := nodeDB.FindAllNodes() + if err != nil { + return fmt.Errorf("could not get node list: %s", err) + } if len(args) > 0 { - nodes = node.FilterByName(nodes, hostlist.Expand(args)) + 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") - } + if len(nodes) == 0 { + return fmt.Errorf("no nodes found") + } - batchpool := batch.New(50) + batchpool := batch.New(vars.Fanout) jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, node := range nodes { + for _, node := range nodes { - if node.Ipmi.Ipaddr.IsUnspecified() { - wwlog.Error("%s: No IPMI IP address", node.Id()) - continue - } - var conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} - batchpool.Submit(func() { - //nolint:errcheck - ipmiCmd.PowerOff() - results <- ipmiCmd - }) + 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 + }) } diff --git a/internal/app/wwctl/power/off/root.go b/internal/app/wwctl/power/off/root.go index bedd5c9e..43670e29 100644 --- a/internal/app/wwctl/power/off/root.go +++ b/internal/app/wwctl/power/off/root.go @@ -7,6 +7,7 @@ import ( type variables struct { Showcmd bool + Fanout int } // GetRootCommand returns the root cobra.Command for the application. @@ -33,6 +34,7 @@ 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 } diff --git a/internal/app/wwctl/power/off/root_test.go b/internal/app/wwctl/power/off/root_test.go index 7af6e297..ce4adb09 100644 --- a/internal/app/wwctl/power/off/root_test.go +++ b/internal/app/wwctl/power/off/root_test.go @@ -1,4 +1,4 @@ -package poweroff +package off import ( "bytes" diff --git a/internal/app/wwctl/power/on/main.go b/internal/app/wwctl/power/on/main.go index b19ba533..eed53bb9 100644 --- a/internal/app/wwctl/power/on/main.go +++ b/internal/app/wwctl/power/on/main.go @@ -17,46 +17,47 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err var returnErr error = nil - nodeDB, err := node.New() - if err != nil { - return fmt.Errorf("could not open node configuration: %s", err) - } + 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) - } + nodes, err := nodeDB.FindAllNodes() + if err != nil { + return fmt.Errorf("could not get node list: %s", err) + } if len(args) > 0 { - nodes = node.FilterByName(nodes, hostlist.Expand(args)) + 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") - } + if len(nodes) == 0 { + return fmt.Errorf("no nodes found") + } - batchpool := batch.New(50) + batchpool := batch.New(vars.Fanout) jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, node := range nodes { + for _, node := range nodes { - if node.Ipmi.Ipaddr.IsUnspecified() { - wwlog.Error("%s: No IPMI IP address", node.Id()) - continue - } - var conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} - batchpool.Submit(func() { - //nolint:errcheck - ipmiCmd.PowerOn() - results <- ipmiCmd - }) + 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 + }) } diff --git a/internal/app/wwctl/power/on/root.go b/internal/app/wwctl/power/on/root.go index 8fb83ff2..f5841222 100644 --- a/internal/app/wwctl/power/on/root.go +++ b/internal/app/wwctl/power/on/root.go @@ -7,6 +7,7 @@ import ( type variables struct { Showcmd bool + Fanout int } // GetRootCommand returns the root cobra.Command for the application. @@ -32,6 +33,7 @@ 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 } diff --git a/internal/app/wwctl/power/on/root_test.go b/internal/app/wwctl/power/on/root_test.go index 0dd39ba8..b67f0968 100644 --- a/internal/app/wwctl/power/on/root_test.go +++ b/internal/app/wwctl/power/on/root_test.go @@ -1,4 +1,4 @@ -package poweron +package on import ( "bytes" diff --git a/internal/app/wwctl/power/reset/main.go b/internal/app/wwctl/power/reset/main.go index 1abe3d3c..aad82f42 100644 --- a/internal/app/wwctl/power/reset/main.go +++ b/internal/app/wwctl/power/reset/main.go @@ -17,53 +17,47 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err var returnErr error = nil - nodeDB, err := node.New() - if err != nil { - return fmt.Errorf("could not open node configuration: %s", err) - } + 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) - } + nodes, err := nodeDB.FindAllNodes() + if err != nil { + return fmt.Errorf("cloud not get nodeList: %s", err) + } if len(args) > 0 { - nodes = node.FilterByName(nodes, hostlist.Expand(args)) + nodes = node.FilterNodeListByName(nodes, hostlist.Expand(args)) } else { //nolint:errcheck cmd.Usage() os.Exit(1) } - 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") - } + if len(nodes) == 0 { + return fmt.Errorf("no nodes found") + } - batchpool := batch.New(50) + batchpool := batch.New(vars.Fanout) jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, node := range nodes { - if node.Ipmi.Ipaddr.IsUnspecified() { - wwlog.Error("%s: No IPMI IP address", node.Id()) - continue - } - var conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} - batchpool.Submit(func() { - //nolint:errcheck - ipmiCmd.PowerReset() - results <- ipmiCmd - }) + 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 + }) } diff --git a/internal/app/wwctl/power/reset/root.go b/internal/app/wwctl/power/reset/root.go index bdab6b84..c7346135 100644 --- a/internal/app/wwctl/power/reset/root.go +++ b/internal/app/wwctl/power/reset/root.go @@ -7,6 +7,7 @@ import ( type variables struct { Showcmd bool + Fanout int } // GetRootCommand returns the root cobra.Command for the application. @@ -33,6 +34,6 @@ 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 } diff --git a/internal/app/wwctl/power/reset/root_test.go b/internal/app/wwctl/power/reset/root_test.go index d486d2d4..013648e0 100644 --- a/internal/app/wwctl/power/reset/root_test.go +++ b/internal/app/wwctl/power/reset/root_test.go @@ -1,4 +1,4 @@ -package powerreset +package reset import ( "bytes" diff --git a/internal/app/wwctl/power/soft/main.go b/internal/app/wwctl/power/soft/main.go index 5027707b..4a9359dd 100644 --- a/internal/app/wwctl/power/soft/main.go +++ b/internal/app/wwctl/power/soft/main.go @@ -17,46 +17,47 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err var returnErr error = nil - nodeDB, err := node.New() - if err != nil { - return fmt.Errorf("could not open node configuration: %s", err) - } + 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) - } + 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(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") - } + if len(nodes) == 0 { + return fmt.Errorf("no nodes found") + } - batchpool := batch.New(50) + batchpool := batch.New(vars.Fanout) jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, node := range nodes { - if node.Ipmi.Ipaddr.IsUnspecified() { - wwlog.Error("%s: No IPMI IP address", node.Id()) - continue - } - var conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} - batchpool.Submit(func() { - //nolint:errcheck - ipmiCmd.PowerSoft() - results <- ipmiCmd - }) + 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 + }) } diff --git a/internal/app/wwctl/power/soft/root.go b/internal/app/wwctl/power/soft/root.go index 6c7df0c3..10093211 100644 --- a/internal/app/wwctl/power/soft/root.go +++ b/internal/app/wwctl/power/soft/root.go @@ -7,6 +7,7 @@ import ( type variables struct { Showcmd bool + Fanout int } // GetRootCommand returns the root cobra.Command for the application. @@ -16,7 +17,7 @@ func GetCommand() *cobra.Command { DisableFlagsInUseLine: true, Use: "soft", Short: "Gracefully shuts down the given node(s)", - Long: "This command uses the operationg system to shut down the set of nodes specified by PATTERN.", + Long: "This command uses the operating system to shut down the set of nodes specified by PATTERN.", RunE: CobraRunE(&vars), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { @@ -33,5 +34,6 @@ 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 } diff --git a/internal/app/wwctl/power/soft/root_test.go b/internal/app/wwctl/power/soft/root_test.go index 87bb00b3..9c9a28d5 100644 --- a/internal/app/wwctl/power/soft/root_test.go +++ b/internal/app/wwctl/power/soft/root_test.go @@ -1,4 +1,4 @@ -package powersoft +package soft import ( "bytes" diff --git a/internal/app/wwctl/power/status/main.go b/internal/app/wwctl/power/status/main.go index 5e91d7fe..d2d15fe9 100644 --- a/internal/app/wwctl/power/status/main.go +++ b/internal/app/wwctl/power/status/main.go @@ -27,35 +27,36 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err } if len(args) > 0 { - nodes = node.FilterByName(nodes, hostlist.Expand(args)) + 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") - } + if len(nodes) == 0 { + return fmt.Errorf("no nodes found") + } batchpool := batch.New(50) jobcount := len(nodes) results := make(chan power.IPMI, jobcount) - for _, n := range nodes { + for _, node := range nodes { - if node.Ipmi.Ipaddr.IsUnspecified() { - wwlog.Error("%s: No IPMI IP address", node.Id()) - continue - } - var conf node.NodeConf - conf.GetFrom(n) - ipmiCmd := power.IPMI{IpmiConf: *conf.Ipmi} - batchpool.Submit(func() { - //nolint:errcheck - ipmiCmd.PowerStatus() - results <- ipmiCmd - }) + 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 + }) } diff --git a/internal/app/wwctl/power/status/root.go b/internal/app/wwctl/power/status/root.go index 116404a6..1176792d 100644 --- a/internal/app/wwctl/power/status/root.go +++ b/internal/app/wwctl/power/status/root.go @@ -7,6 +7,7 @@ import ( type variables struct { Showcmd bool + Fanout int } // GetRootCommand returns the root cobra.Command for the application. @@ -33,5 +34,6 @@ 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 } diff --git a/internal/app/wwctl/power/status/root_test.go b/internal/app/wwctl/power/status/root_test.go index 4446c37c..1aa2f52a 100644 --- a/internal/app/wwctl/power/status/root_test.go +++ b/internal/app/wwctl/power/status/root_test.go @@ -1,4 +1,4 @@ -package powerstatus +package status import ( "bytes" From 141bcd8d4013d1930afbb722f4605806097bcc48 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 7 Nov 2024 21:41:54 -0700 Subject: [PATCH 07/12] Refactor ipmi tests as table-driven tests Also prefix each sensor line. Signed-off-by: Jonathon Anderson --- internal/app/wwctl/node/sensors/main.go | 9 ++++- internal/app/wwctl/node/sensors/root_test.go | 40 +++++++++++++------- internal/app/wwctl/power/cycle/root_test.go | 40 +++++++++++++------- internal/app/wwctl/power/off/root_test.go | 38 ++++++++++++------- internal/app/wwctl/power/on/root_test.go | 37 +++++++++++------- internal/app/wwctl/power/reset/root_test.go | 37 +++++++++++------- internal/app/wwctl/power/soft/root_test.go | 38 ++++++++++++------- internal/app/wwctl/power/status/root_test.go | 38 ++++++++++++------- 8 files changed, 182 insertions(+), 95 deletions(-) diff --git a/internal/app/wwctl/node/sensors/main.go b/internal/app/wwctl/node/sensors/main.go index ea1a8db6..c565f0aa 100644 --- a/internal/app/wwctl/node/sensors/main.go +++ b/internal/app/wwctl/node/sensors/main.go @@ -3,6 +3,7 @@ package sensors import ( "fmt" "os" + "strings" "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/pkg/batch" @@ -75,12 +76,16 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err out, err := result.Result() if err != nil { - wwlog.Error("%s: %s", result.Ipaddr, out) + for _, line := range strings.Split(out, "\n") { + wwlog.Error("%s: %s", result.Ipaddr, line) + } returnErr = err continue } - wwlog.Info("%s:\n%s\n", result.Ipaddr, out) + for _, line := range strings.Split(out, "\n") { + wwlog.Info("%s: %s", result.Ipaddr, line) + } } return returnErr diff --git a/internal/app/wwctl/node/sensors/root_test.go b/internal/app/wwctl/node/sensors/root_test.go index d2fcc970..d290563c 100644 --- a/internal/app/wwctl/node/sensors/root_test.go +++ b/internal/app/wwctl/node/sensors/root_test.go @@ -11,10 +11,10 @@ import ( "github.com/warewulf/warewulf/internal/pkg/wwlog" ) -func Test_Power_Status(t *testing.T) { +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: @@ -27,16 +27,28 @@ nodes: - default ipmi: ipaddr: 10.10.10.10`) - warewulfd.SetNoDaemon() - t.Run("ipmitool status test", func(t *testing.T) { - baseCmd := GetCommand() - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - wwlog.SetLogWriter(buf) - baseCmd.SetArgs([]string{"--show", "n01"}) - err := baseCmd.Execute() - assert.NoError(t, err) - assert.Equal(t, "10.10.10.10:\nipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ sdr list", strings.TrimSpace(buf.String())) - }) + + 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())) + }) + } } diff --git a/internal/app/wwctl/power/cycle/root_test.go b/internal/app/wwctl/power/cycle/root_test.go index a3e72186..1aec07fa 100644 --- a/internal/app/wwctl/power/cycle/root_test.go +++ b/internal/app/wwctl/power/cycle/root_test.go @@ -11,10 +11,10 @@ import ( "github.com/warewulf/warewulf/internal/pkg/wwlog" ) -func Test_Power_Status(t *testing.T) { +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: @@ -27,16 +27,28 @@ nodes: - default ipmi: ipaddr: 10.10.10.10`) - warewulfd.SetNoDaemon() - t.Run("ipmitool status test", func(t *testing.T) { - baseCmd := GetCommand() - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - wwlog.SetLogWriter(buf) - baseCmd.SetArgs([]string{"--show", "n01"}) - err := baseCmd.Execute() - assert.NoError(t, err) - assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power cycle", strings.TrimSpace(buf.String())) - }) + + 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())) + }) + } } diff --git a/internal/app/wwctl/power/off/root_test.go b/internal/app/wwctl/power/off/root_test.go index ce4adb09..18d5939a 100644 --- a/internal/app/wwctl/power/off/root_test.go +++ b/internal/app/wwctl/power/off/root_test.go @@ -12,9 +12,9 @@ import ( ) 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: @@ -27,16 +27,28 @@ nodes: - default ipmi: ipaddr: 10.10.10.10`) - warewulfd.SetNoDaemon() - t.Run("ipmitool status test", func(t *testing.T) { - baseCmd := GetCommand() - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - wwlog.SetLogWriter(buf) - baseCmd.SetArgs([]string{"--show", "n01"}) - err := baseCmd.Execute() - assert.NoError(t, err) - assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power off", strings.TrimSpace(buf.String())) - }) + + 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())) + }) + } } diff --git a/internal/app/wwctl/power/on/root_test.go b/internal/app/wwctl/power/on/root_test.go index b67f0968..b42920de 100644 --- a/internal/app/wwctl/power/on/root_test.go +++ b/internal/app/wwctl/power/on/root_test.go @@ -12,9 +12,9 @@ import ( ) 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: @@ -27,16 +27,27 @@ nodes: - default ipmi: ipaddr: 10.10.10.10`) - warewulfd.SetNoDaemon() - t.Run("ipmitool status test", func(t *testing.T) { - baseCmd := GetCommand() - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - wwlog.SetLogWriter(buf) - baseCmd.SetArgs([]string{"--show", "n01"}) - err := baseCmd.Execute() - assert.NoError(t, err) - assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power on", strings.TrimSpace(buf.String())) - }) + + 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())) + }) + } } diff --git a/internal/app/wwctl/power/reset/root_test.go b/internal/app/wwctl/power/reset/root_test.go index 013648e0..31c5420b 100644 --- a/internal/app/wwctl/power/reset/root_test.go +++ b/internal/app/wwctl/power/reset/root_test.go @@ -12,9 +12,9 @@ import ( ) 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: @@ -27,16 +27,27 @@ nodes: - default ipmi: ipaddr: 10.10.10.10`) - warewulfd.SetNoDaemon() - t.Run("ipmitool status test", func(t *testing.T) { - baseCmd := GetCommand() - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - wwlog.SetLogWriter(buf) - baseCmd.SetArgs([]string{"--show", "n01"}) - err := baseCmd.Execute() - assert.NoError(t, err) - assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power reset", strings.TrimSpace(buf.String())) - }) + + 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())) + }) + } } diff --git a/internal/app/wwctl/power/soft/root_test.go b/internal/app/wwctl/power/soft/root_test.go index 9c9a28d5..7f214f3f 100644 --- a/internal/app/wwctl/power/soft/root_test.go +++ b/internal/app/wwctl/power/soft/root_test.go @@ -12,9 +12,9 @@ import ( ) 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: @@ -27,16 +27,28 @@ nodes: - default ipmi: ipaddr: 10.10.10.10`) - warewulfd.SetNoDaemon() - t.Run("ipmitool status test", func(t *testing.T) { - baseCmd := GetCommand() - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - wwlog.SetLogWriter(buf) - baseCmd.SetArgs([]string{"--show", "n01"}) - err := baseCmd.Execute() - assert.NoError(t, err) - assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power soft", strings.TrimSpace(buf.String())) - }) + + 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())) + }) + } } diff --git a/internal/app/wwctl/power/status/root_test.go b/internal/app/wwctl/power/status/root_test.go index 1aa2f52a..d7c55b8b 100644 --- a/internal/app/wwctl/power/status/root_test.go +++ b/internal/app/wwctl/power/status/root_test.go @@ -12,9 +12,9 @@ import ( ) 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: @@ -27,16 +27,28 @@ nodes: - default ipmi: ipaddr: 10.10.10.10`) - warewulfd.SetNoDaemon() - t.Run("ipmitool status test", func(t *testing.T) { - baseCmd := GetCommand() - buf := new(bytes.Buffer) - baseCmd.SetOut(buf) - baseCmd.SetErr(buf) - wwlog.SetLogWriter(buf) - baseCmd.SetArgs([]string{"--show", "n01"}) - err := baseCmd.Execute() - assert.NoError(t, err) - assert.Equal(t, "10.10.10.10: ipmitool -I lan -H 10.10.10.10 -p 623 -U admin -P admin -e ~ chassis power status", strings.TrimSpace(buf.String())) - }) + + 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())) + }) + } } From 9ddb491ba39c0e125011336b32418afc9c96f190 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 7 Nov 2024 21:50:08 -0700 Subject: [PATCH 08/12] Rename BmcTemplate to Template Signed-off-by: Jonathon Anderson --- internal/pkg/node/datastructure.go | 22 +++++++++++----------- internal/pkg/power/ipmitool.go | 10 +++++----- userdocs/contents/ipmi.rst | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index de593f95..bf44236b 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -59,17 +59,17 @@ type ProfileConf struct { } type IpmiConf struct { - UserName string `yaml:"username,omitempty" lopt:"ipmiuser" comment:"Set the IPMI username"` - Password string `yaml:"password,omitempty" lopt:"ipmipass" comment:"Set the IPMI password"` - Ipaddr net.IP `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address" type:"IP"` - Gateway net.IP `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway" type:"IP"` - Netmask net.IP `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask" type:"IP"` - Port string `yaml:"port,omitempty" lopt:"ipmiport" comment:"Set the IPMI port"` - 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)"` - BmcTemplate string `yaml:"template,omitempty" lopt:"ipmitemplate" comment:"template used for ipmi command"` - Tags map[string]string `yaml:"tags,omitempty"` + UserName string `yaml:"username,omitempty" lopt:"ipmiuser" comment:"Set the IPMI username"` + Password string `yaml:"password,omitempty" lopt:"ipmipass" comment:"Set the IPMI password"` + Ipaddr net.IP `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address" type:"IP"` + Gateway net.IP `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway" type:"IP"` + Netmask net.IP `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask" type:"IP"` + Port string `yaml:"port,omitempty" lopt:"ipmiport" comment:"Set the IPMI port"` + 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"` } type KernelConf struct { diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go index ace25d0c..fd6efe73 100644 --- a/internal/pkg/power/ipmitool.go +++ b/internal/pkg/power/ipmitool.go @@ -33,12 +33,12 @@ func (ipmi *IPMI) Result() (string, error) { func (ipmi *IPMI) getStr() (cmdStr string, err error) { conf := warewulfconf.Get() - if ipmi.BmcTemplate == "" { - ipmi.BmcTemplate = path.Join(conf.Warewulf.DataStore, "warewulf/bmc/ipmitool.tmpl") - } else if !strings.HasPrefix(ipmi.BmcTemplate, "/") { - ipmi.BmcTemplate = path.Join(conf.Warewulf.DataStore, "warewulf/bmc", ipmi.BmcTemplate) + if ipmi.Template == "" { + ipmi.Template = path.Join(conf.Warewulf.DataStore, "warewulf/bmc/ipmitool.tmpl") + } else if !strings.HasPrefix(ipmi.Template, "/") { + ipmi.Template = path.Join(conf.Warewulf.DataStore, "warewulf/bmc", ipmi.Template) } - fbuf, err := os.ReadFile(ipmi.BmcTemplate) + fbuf, err := os.ReadFile(ipmi.Template) if err != nil { return "", fmt.Errorf("couldn't find the template which defines the ipmi/redfish command: %s", err) } diff --git a/userdocs/contents/ipmi.rst b/userdocs/contents/ipmi.rst index c7cdc9a9..6a4036cd 100644 --- a/userdocs/contents/ipmi.rst +++ b/userdocs/contents/ipmi.rst @@ -240,7 +240,7 @@ All IPMI specific variables are accessible in the template which are the followi +---------------------+--------------------+ | ``--ipmiescapechar``| ``.EscapeChar`` | +---------------------+--------------------+ -| ``--ipmitemplate`` | ``.BmcTemplate`` | +| ``--ipmitemplate`` | ``.Template`` | +---------------------+--------------------+ Additional the ``.Args`` variable is accessible which can have following @@ -257,4 +257,4 @@ values: 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`` \ No newline at end of file +overlay may need an update. There the variables must have the prefix ``.Ipmi`` From c25eaa8855fafbf450f94b4d5bd26502b5aab244 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 7 Nov 2024 21:59:13 -0700 Subject: [PATCH 09/12] Move default ipmi template to default profile Signed-off-by: Jonathon Anderson --- etc/nodes.conf | 2 ++ internal/app/wwctl/node/sensors/root_test.go | 1 + internal/app/wwctl/power/cycle/root_test.go | 1 + internal/app/wwctl/power/off/root_test.go | 1 + internal/app/wwctl/power/on/root_test.go | 1 + internal/app/wwctl/power/reset/root_test.go | 1 + internal/app/wwctl/power/soft/root_test.go | 1 + internal/app/wwctl/power/status/root_test.go | 1 + internal/pkg/power/ipmitool.go | 9 +++++---- 9 files changed, 14 insertions(+), 4 deletions(-) diff --git a/etc/nodes.conf b/etc/nodes.conf index 22ab4335..62d5b496 100644 --- a/etc/nodes.conf +++ b/etc/nodes.conf @@ -26,4 +26,6 @@ nodeprofiles: init: /sbin/init root: initramfs ipxe template: default + ipmi: + template: ipmitool.tmpl nodes: {} diff --git a/internal/app/wwctl/node/sensors/root_test.go b/internal/app/wwctl/node/sensors/root_test.go index d290563c..bd2b74bd 100644 --- a/internal/app/wwctl/node/sensors/root_test.go +++ b/internal/app/wwctl/node/sensors/root_test.go @@ -19,6 +19,7 @@ func Test_Sensors(t *testing.T) { nodeprofiles: default: ipmi: + template: ipmitool.tmpl username: admin password: admin nodes: diff --git a/internal/app/wwctl/power/cycle/root_test.go b/internal/app/wwctl/power/cycle/root_test.go index 1aec07fa..997a048f 100644 --- a/internal/app/wwctl/power/cycle/root_test.go +++ b/internal/app/wwctl/power/cycle/root_test.go @@ -19,6 +19,7 @@ func Test_PowerCycle(t *testing.T) { nodeprofiles: default: ipmi: + template: ipmitool.tmpl username: admin password: admin nodes: diff --git a/internal/app/wwctl/power/off/root_test.go b/internal/app/wwctl/power/off/root_test.go index 18d5939a..a0c6952f 100644 --- a/internal/app/wwctl/power/off/root_test.go +++ b/internal/app/wwctl/power/off/root_test.go @@ -19,6 +19,7 @@ func Test_Power_Status(t *testing.T) { nodeprofiles: default: ipmi: + template: ipmitool.tmpl username: admin password: admin nodes: diff --git a/internal/app/wwctl/power/on/root_test.go b/internal/app/wwctl/power/on/root_test.go index b42920de..26fab1ff 100644 --- a/internal/app/wwctl/power/on/root_test.go +++ b/internal/app/wwctl/power/on/root_test.go @@ -19,6 +19,7 @@ func Test_Power_Status(t *testing.T) { nodeprofiles: default: ipmi: + template: ipmitool.tmpl username: admin password: admin nodes: diff --git a/internal/app/wwctl/power/reset/root_test.go b/internal/app/wwctl/power/reset/root_test.go index 31c5420b..9f8c8552 100644 --- a/internal/app/wwctl/power/reset/root_test.go +++ b/internal/app/wwctl/power/reset/root_test.go @@ -19,6 +19,7 @@ func Test_Power_Status(t *testing.T) { nodeprofiles: default: ipmi: + template: ipmitool.tmpl username: admin password: admin nodes: diff --git a/internal/app/wwctl/power/soft/root_test.go b/internal/app/wwctl/power/soft/root_test.go index 7f214f3f..7eb0ff1a 100644 --- a/internal/app/wwctl/power/soft/root_test.go +++ b/internal/app/wwctl/power/soft/root_test.go @@ -19,6 +19,7 @@ func Test_Power_Status(t *testing.T) { nodeprofiles: default: ipmi: + template: ipmitool.tmpl username: admin password: admin nodes: diff --git a/internal/app/wwctl/power/status/root_test.go b/internal/app/wwctl/power/status/root_test.go index d7c55b8b..e6bbbe98 100644 --- a/internal/app/wwctl/power/status/root_test.go +++ b/internal/app/wwctl/power/status/root_test.go @@ -19,6 +19,7 @@ func Test_Power_Status(t *testing.T) { nodeprofiles: default: ipmi: + template: ipmitool.tmpl username: admin password: admin nodes: diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go index fd6efe73..268175cc 100644 --- a/internal/pkg/power/ipmitool.go +++ b/internal/pkg/power/ipmitool.go @@ -32,15 +32,16 @@ func (ipmi *IPMI) Result() (string, error) { } func (ipmi *IPMI) getStr() (cmdStr string, err error) { - conf := warewulfconf.Get() if ipmi.Template == "" { - ipmi.Template = path.Join(conf.Warewulf.DataStore, "warewulf/bmc/ipmitool.tmpl") - } else if !strings.HasPrefix(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/redfish command: %s", err) + 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 { From 8189390e0300d1a87ac241ab426223b71ca28ba2 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 7 Nov 2024 22:00:34 -0700 Subject: [PATCH 10/12] Switch bmc templates from html/template to text/template Signed-off-by: Jonathon Anderson --- internal/pkg/power/ipmitool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/power/ipmitool.go b/internal/pkg/power/ipmitool.go index 268175cc..dc543769 100644 --- a/internal/pkg/power/ipmitool.go +++ b/internal/pkg/power/ipmitool.go @@ -3,12 +3,12 @@ package power import ( "bytes" "fmt" - "html/template" "os" "os/exec" "path" "regexp" "strings" + "text/template" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/node" From d1697ef83d13dde4034c3d768d35807b9ca1be9c Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 7 Nov 2024 22:20:45 -0700 Subject: [PATCH 11/12] Simplify bmc templates Signed-off-by: Jonathon Anderson --- lib/warewulf/bmc/ipmitool.tmpl | 30 +++++++++++++----------------- lib/warewulf/bmc/nobmc.tmpl | 21 ++++++++++----------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/lib/warewulf/bmc/ipmitool.tmpl b/lib/warewulf/bmc/ipmitool.tmpl index 318a35b6..a1e8c604 100644 --- a/lib/warewulf/bmc/ipmitool.tmpl +++ b/lib/warewulf/bmc/ipmitool.tmpl @@ -1,19 +1,15 @@ {{/* used command to access the ipmi interface of the nodes */}} -{{- $escapechar := "~" }} -{{- $port := "623" }} -{{- $interface := "lan" }} +{{- $escapechar := "~" }}{{ if .EscapeChar }}{{ $escapechar = .EscapeChar }}{{ end }} +{{- $port := "623" }}{{ if .Port }}{{ $port = .Port }}{{ end }} +{{- $interface := "lan" }}{{- if .Interface }}{{ $interface = .Interface }}{{ end }} {{- $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 }} \ No newline at end of file +{{- 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 }} diff --git a/lib/warewulf/bmc/nobmc.tmpl b/lib/warewulf/bmc/nobmc.tmpl index 75239ce6..ab6ab127 100644 --- a/lib/warewulf/bmc/nobmc.tmpl +++ b/lib/warewulf/bmc/nobmc.tmpl @@ -1,12 +1,11 @@ {{/* used command to access nodes without bmc*/}} -{{- $cmd := "echo command not found" }} -{{- if eq .Cmd "PowerOn" }} {{ $cmd = printf "wol %s" .Interface }} {{ end }} -{{- if eq .Cmd "PowerOff" }} {{ $cmd = printf "ssh %s echo o > /proc/sysrq-trigger" .Ipaddr }} {{ end }} -{{- if eq .Cmd "PowerCycle" }} {{ $cmd = printf "ssh %s echo r > /proc/sysrq-trigger" .Ipaddr }} {{ end }} -{{- if eq .Cmd "PowerRest" }} {{ $cmd = printf "ssh %s echo r > /proc/sysrq-trigger" .Ipaddr }} {{ end }} -{{- if eq .Cmd "PowerSoft" }} {{ $cmd = print "ssh %s reboot" .Ipaddr }} {{ end }} -{{- if eq .Cmd "PowerStatus" }} {{ $args = "ping -c 1 %s &> /dev/null %s && echo ON || echo OFF" .Ipaddr }} {{ end }} -{{- if eq .Cmd "SDRList" }} {{ $cmd = print "ssh %s sensors" .Ipaddr }} {{ end }} -{{- if eq .Cmd "SensorList" }} {{ $cmd = print "ssh %s sensors" .Ipaddr }} {{ end }} -{{- if eq .Cmd "Console" }} {{ $args = "echo node sol" }} {{ end }} -{{ $cmd }} \ No newline at end of file +{{- 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 }} From 8efec499a257cdcdb094de48bdffc4636ebcadbd Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Thu, 7 Nov 2024 22:25:32 -0700 Subject: [PATCH 12/12] Revert changes to testenv and fix bmc tests Signed-off-by: Jonathon Anderson --- internal/app/wwctl/node/sensors/root_test.go | 1 + internal/app/wwctl/power/cycle/root_test.go | 1 + internal/app/wwctl/power/off/root_test.go | 1 + internal/app/wwctl/power/on/root_test.go | 1 + internal/app/wwctl/power/reset/root_test.go | 1 + internal/app/wwctl/power/soft/root_test.go | 1 + internal/app/wwctl/power/status/root_test.go | 1 + internal/pkg/testenv/testenv.go | 20 +++----------------- 8 files changed, 10 insertions(+), 17 deletions(-) diff --git a/internal/app/wwctl/node/sensors/root_test.go b/internal/app/wwctl/node/sensors/root_test.go index bd2b74bd..3fff5f6a 100644 --- a/internal/app/wwctl/node/sensors/root_test.go +++ b/internal/app/wwctl/node/sensors/root_test.go @@ -28,6 +28,7 @@ nodes: - 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 diff --git a/internal/app/wwctl/power/cycle/root_test.go b/internal/app/wwctl/power/cycle/root_test.go index 997a048f..89ba118c 100644 --- a/internal/app/wwctl/power/cycle/root_test.go +++ b/internal/app/wwctl/power/cycle/root_test.go @@ -28,6 +28,7 @@ nodes: - 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 diff --git a/internal/app/wwctl/power/off/root_test.go b/internal/app/wwctl/power/off/root_test.go index a0c6952f..4dd31141 100644 --- a/internal/app/wwctl/power/off/root_test.go +++ b/internal/app/wwctl/power/off/root_test.go @@ -28,6 +28,7 @@ nodes: - 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 diff --git a/internal/app/wwctl/power/on/root_test.go b/internal/app/wwctl/power/on/root_test.go index 26fab1ff..82485f10 100644 --- a/internal/app/wwctl/power/on/root_test.go +++ b/internal/app/wwctl/power/on/root_test.go @@ -28,6 +28,7 @@ nodes: - 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 diff --git a/internal/app/wwctl/power/reset/root_test.go b/internal/app/wwctl/power/reset/root_test.go index 9f8c8552..131f831a 100644 --- a/internal/app/wwctl/power/reset/root_test.go +++ b/internal/app/wwctl/power/reset/root_test.go @@ -28,6 +28,7 @@ nodes: - 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 diff --git a/internal/app/wwctl/power/soft/root_test.go b/internal/app/wwctl/power/soft/root_test.go index 7eb0ff1a..14728692 100644 --- a/internal/app/wwctl/power/soft/root_test.go +++ b/internal/app/wwctl/power/soft/root_test.go @@ -28,6 +28,7 @@ nodes: - 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 diff --git a/internal/app/wwctl/power/status/root_test.go b/internal/app/wwctl/power/status/root_test.go index e6bbbe98..e7160e3e 100644 --- a/internal/app/wwctl/power/status/root_test.go +++ b/internal/app/wwctl/power/status/root_test.go @@ -28,6 +28,7 @@ nodes: - 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 diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go index 1afc6c63..94d88a83 100644 --- a/internal/pkg/testenv/testenv.go +++ b/internal/pkg/testenv/testenv.go @@ -9,7 +9,6 @@ import ( "os" "path" "path/filepath" - "runtime" "testing" "time" @@ -17,7 +16,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/util" ) const initWarewulfConf = `WW_INTERNAL: 0` @@ -95,15 +93,9 @@ func New(t *testing.T) (env *TestEnv) { conf.Paths.WWChrootdir, conf.Paths.WWProvisiondir, } { - env.MkdirAllAbs(t, confPath) + env.MkdirAll(t, confPath) } - // copy templates - _, b, _, _ := runtime.Caller(0) - basepath := filepath.Dir(b) - env.MkdirAllAbs(t, path.Join(conf.Warewulf.DataStore, "warewulf/bmc")) - assert.DirExists(t, path.Join(conf.Warewulf.DataStore, "warewulf/bmc")) - err = util.CopyFile(path.Join(basepath, "../../../lib/warewulf/bmc/ipmitool.tmpl"), path.Join(conf.Warewulf.DataStore, "warewulf/bmc/ipmitool.tmpl")) - assert.NoError(t, err) + // node.init() has already run, so set the config path again node.ConfigFile = env.GetPath(path.Join(Sysconfdir, "warewulf/nodes.conf")) @@ -118,19 +110,13 @@ func (env *TestEnv) GetPath(fileName string) string { // MkdirAll creates dirName and any intermediate directories relative // to the test environment. +// // Asserts no errors occur. func (env *TestEnv) MkdirAll(t *testing.T, dirName string) { err := os.MkdirAll(env.GetPath(dirName), 0755) assert.NoError(t, err) } -// MkdirAllAbs creates absolute dirName and any intermediate directories -// Asserts no errors occur. -func (env *TestEnv) MkdirAllAbs(t *testing.T, dirName string) { - err := os.MkdirAll(dirName, 0755) - assert.NoError(t, err) -} - // WriteFile writes content to fileName, creating any necessary // intermediate directories relative to the test environment. //