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