Merge "Add ability to perform baremetal operation against all nodes"

This commit is contained in:
Zuul 2020-10-07 14:35:02 +00:00 committed by Gerrit Code Review
commit 857b51612c
9 changed files with 157 additions and 195 deletions

View File

@ -15,12 +15,18 @@
package baremetal
import (
"fmt"
"io"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/remote"
)
// Action type is used to perform specific baremetal action
type Action int
const (
flagLabel = "labels"
flagLabelShort = "l"
@ -32,25 +38,111 @@ const (
flagPhase = "phase"
flagPhaseDescription = "airshipctl phase that contains the desired baremetal host document(s)"
ejectAction Action = iota
powerOffAction
powerOnAction
powerStatusAction
rebootAction
remoteDirectAction
)
// CommonOptions is used to store common variables from cmd flags for baremetal command group
type CommonOptions struct {
labels string
name string
phase string
}
// NewBaremetalCommand creates a new command for interacting with baremetal using airshipctl.
func NewBaremetalCommand(cfgFactory config.Factory) *cobra.Command {
options := &CommonOptions{}
baremetalRootCmd := &cobra.Command{
Use: "baremetal",
Short: "Perform actions on baremetal hosts",
}
baremetalRootCmd.AddCommand(NewEjectMediaCommand(cfgFactory))
baremetalRootCmd.AddCommand(NewPowerOffCommand(cfgFactory))
baremetalRootCmd.AddCommand(NewPowerOnCommand(cfgFactory))
baremetalRootCmd.AddCommand(NewPowerStatusCommand(cfgFactory))
baremetalRootCmd.AddCommand(NewRebootCommand(cfgFactory))
baremetalRootCmd.AddCommand(NewRemoteDirectCommand(cfgFactory))
baremetalRootCmd.AddCommand(NewEjectMediaCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewPowerOffCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewPowerOnCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewPowerStatusCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewRebootCommand(cfgFactory, options))
baremetalRootCmd.AddCommand(NewRemoteDirectCommand(cfgFactory, options))
return baremetalRootCmd
}
func initFlags(options *CommonOptions, cmd *cobra.Command) {
flags := cmd.Flags()
flags.StringVarP(&options.labels, flagLabel, flagLabelShort, "", flagLabelDescription)
flags.StringVarP(&options.name, flagName, flagNameShort, "", flagNameDescription)
flags.StringVar(&options.phase, flagPhase, config.BootstrapPhase, flagPhaseDescription)
}
func performAction(cfgFactory config.Factory, options *CommonOptions, action Action, writer io.Writer) error {
cfg, err := cfgFactory()
if err != nil {
return err
}
selectors := GetHostSelections(options.name, options.labels)
m, err := remote.NewManager(cfg, options.phase, selectors...)
if err != nil {
return err
}
return selectAction(m, cfg, action, writer)
}
func selectAction(m *remote.Manager, cfg *config.Config, action Action, writer io.Writer) error {
if action == remoteDirectAction {
if len(m.Hosts) != 1 {
return remote.NewRemoteDirectErrorf("more than one node defined as the ephemeral node")
}
ephemeralHost := m.Hosts[0]
return ephemeralHost.DoRemoteDirect(cfg)
}
for _, host := range m.Hosts {
switch action {
case ejectAction:
if err := host.EjectVirtualMedia(host.Context); err != nil {
return err
}
fmt.Fprintf(writer, "All media ejected from host '%s'.\n", host.HostName)
case powerOffAction:
if err := host.SystemPowerOff(host.Context); err != nil {
return err
}
fmt.Fprintf(writer, "Powered off host '%s'.\n", host.HostName)
case powerOnAction:
if err := host.SystemPowerOn(host.Context); err != nil {
return err
}
fmt.Fprintf(writer, "Powered on host '%s'.\n", host.HostName)
case powerStatusAction:
powerStatus, err := host.SystemPowerStatus(host.Context)
if err != nil {
return err
}
fmt.Fprintf(writer, "Host '%s' has power status: '%s'\n",
host.HostName, powerStatus)
case rebootAction:
if err := host.RebootSystem(host.Context); err != nil {
return err
}
fmt.Fprintf(writer, "Rebooted host '%s'.\n", host.HostName)
}
}
return nil
}
// GetHostSelections builds a list of selectors that can be passed to a manager
// using the name and label flags passed to airshipctl.
func GetHostSelections(name string, labels string) []remote.HostSelector {
@ -63,5 +155,9 @@ func GetHostSelections(name string, labels string) []remote.HostSelector {
selectors = append(selectors, remote.ByLabel(labels))
}
if len(selectors) == 0 {
selectors = append(selectors, remote.All())
}
return selectors
}

View File

@ -33,32 +33,32 @@ func TestBaremetal(t *testing.T) {
{
Name: "baremetal-ejectmedia-with-help",
CmdLine: "-h",
Cmd: baremetal.NewEjectMediaCommand(nil),
Cmd: baremetal.NewEjectMediaCommand(nil, &baremetal.CommonOptions{}),
},
{
Name: "baremetal-poweroff-with-help",
CmdLine: "-h",
Cmd: baremetal.NewPowerOffCommand(nil),
Cmd: baremetal.NewPowerOffCommand(nil, &baremetal.CommonOptions{}),
},
{
Name: "baremetal-poweron-with-help",
CmdLine: "-h",
Cmd: baremetal.NewPowerOnCommand(nil),
Cmd: baremetal.NewPowerOnCommand(nil, &baremetal.CommonOptions{}),
},
{
Name: "baremetal-powerstatus-with-help",
CmdLine: "-h",
Cmd: baremetal.NewPowerStatusCommand(nil),
Cmd: baremetal.NewPowerStatusCommand(nil, &baremetal.CommonOptions{}),
},
{
Name: "baremetal-reboot-with-help",
CmdLine: "-h",
Cmd: baremetal.NewRebootCommand(nil),
Cmd: baremetal.NewRebootCommand(nil, &baremetal.CommonOptions{}),
},
{
Name: "baremetal-remotedirect-with-help",
CmdLine: "-h",
Cmd: baremetal.NewRemoteDirectCommand(nil),
Cmd: baremetal.NewRemoteDirectCommand(nil, &baremetal.CommonOptions{}),
},
}
@ -79,5 +79,5 @@ func TestGetHostSelectionsBothSelectors(t *testing.T) {
func TestGetHostSelectionsNone(t *testing.T) {
selectors := baremetal.GetHostSelections("", "")
assert.Len(t, selectors, 0)
assert.Len(t, selectors, 1)
}

View File

@ -15,52 +15,23 @@
package baremetal
import (
"fmt"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/remote"
)
// NewEjectMediaCommand provides a command to eject media attached to a baremetal host.
func NewEjectMediaCommand(cfgFactory config.Factory) *cobra.Command {
var labels string
var name string
var phase string
func NewEjectMediaCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "ejectmedia",
Short: "Eject media attached to a baremetal host",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := cfgFactory()
if err != nil {
return err
}
selectors := GetHostSelections(name, labels)
m, err := remote.NewManager(cfg, phase, selectors...)
if err != nil {
return err
}
for _, host := range m.Hosts {
if err := host.EjectVirtualMedia(host.Context); err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "All media ejected from host '%s'.\n", host.HostName)
}
return nil
return performAction(cfgFactory, options, ejectAction, cmd.OutOrStdout())
},
}
flags := cmd.Flags()
flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription)
flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription)
flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription)
initFlags(options, cmd)
return cmd
}

View File

@ -15,52 +15,23 @@
package baremetal
import (
"fmt"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/remote"
)
// NewPowerOffCommand provides a command to shutdown a remote host.
func NewPowerOffCommand(cfgFactory config.Factory) *cobra.Command {
var labels string
var name string
var phase string
func NewPowerOffCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "poweroff",
Short: "Shutdown a baremetal host",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := cfgFactory()
if err != nil {
return err
}
selectors := GetHostSelections(name, labels)
m, err := remote.NewManager(cfg, phase, selectors...)
if err != nil {
return err
}
for _, host := range m.Hosts {
if err := host.SystemPowerOff(host.Context); err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "Powered off host '%s'.\n", host.HostName)
}
return nil
return performAction(cfgFactory, options, powerOffAction, cmd.OutOrStdout())
},
}
flags := cmd.Flags()
flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription)
flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription)
flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription)
initFlags(options, cmd)
return cmd
}

View File

@ -15,52 +15,23 @@ Licensed under the Apache License, Version 2.0 (the "License");
package baremetal
import (
"fmt"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/remote"
)
// NewPowerOnCommand provides a command with the capability to power on baremetal hosts.
func NewPowerOnCommand(cfgFactory config.Factory) *cobra.Command {
var labels string
var name string
var phase string
func NewPowerOnCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "poweron",
Short: "Power on a host",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := cfgFactory()
if err != nil {
return err
}
selectors := GetHostSelections(name, labels)
m, err := remote.NewManager(cfg, phase, selectors...)
if err != nil {
return err
}
for _, host := range m.Hosts {
if err := host.SystemPowerOn(host.Context); err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "Powered on host '%s'.\n", host.HostName)
}
return nil
return performAction(cfgFactory, options, powerOnAction, cmd.OutOrStdout())
},
}
flags := cmd.Flags()
flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription)
flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription)
flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription)
initFlags(options, cmd)
return cmd
}

View File

@ -15,54 +15,23 @@
package baremetal
import (
"fmt"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/remote"
)
// NewPowerStatusCommand provides a command to retrieve the power status of a baremetal host.
func NewPowerStatusCommand(cfgFactory config.Factory) *cobra.Command {
var labels string
var name string
var phase string
func NewPowerStatusCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "powerstatus",
Short: "Retrieve the power status of a baremetal host",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := cfgFactory()
if err != nil {
return err
}
selectors := GetHostSelections(name, labels)
m, err := remote.NewManager(cfg, phase, selectors...)
if err != nil {
return err
}
for _, host := range m.Hosts {
powerStatus, err := host.SystemPowerStatus(host.Context)
if err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "Host '%s' has power status: '%s'\n",
host.HostName, powerStatus)
}
return nil
return performAction(cfgFactory, options, powerStatusAction, cmd.OutOrStdout())
},
}
flags := cmd.Flags()
flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription)
flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription)
flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription)
initFlags(options, cmd)
return cmd
}

View File

@ -15,52 +15,23 @@ Licensed under the Apache License, Version 2.0 (the "License");
package baremetal
import (
"fmt"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/remote"
)
// NewRebootCommand provides a command with the capability to reboot baremetal hosts.
func NewRebootCommand(cfgFactory config.Factory) *cobra.Command {
var labels string
var name string
var phase string
func NewRebootCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "reboot",
Short: "Reboot a host",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := cfgFactory()
if err != nil {
return err
}
selectors := GetHostSelections(name, labels)
m, err := remote.NewManager(cfg, phase, selectors...)
if err != nil {
return err
}
for _, host := range m.Hosts {
if err := host.RebootSystem(host.Context); err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "Rebooted host '%s'.\n", host.HostName)
}
return nil
return performAction(cfgFactory, options, rebootAction, cmd.OutOrStdout())
},
}
flags := cmd.Flags()
flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription)
flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription)
flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription)
initFlags(options, cmd)
return cmd
}

View File

@ -19,33 +19,17 @@ import (
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/remote"
)
// NewRemoteDirectCommand provides a command with the capability to perform remote direct operations.
func NewRemoteDirectCommand(cfgFactory config.Factory) *cobra.Command {
func NewRemoteDirectCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "remotedirect",
Short: "Bootstrap the ephemeral host",
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := cfgFactory()
if err != nil {
return err
}
manager, err := remote.NewManager(cfg,
config.BootstrapPhase,
remote.ByLabel(document.EphemeralHostSelector))
if err != nil {
return err
}
if len(manager.Hosts) != 1 {
return remote.NewRemoteDirectErrorf("more than one node defined as the ephemeral node")
}
ephemeralHost := manager.Hosts[0]
return ephemeralHost.DoRemoteDirect(cfg)
options.phase = config.BootstrapPhase
options.labels = document.EphemeralHostSelector
return performAction(cfgFactory, options, remoteDirectAction, cmd.OutOrStdout())
},
}

View File

@ -115,6 +115,35 @@ func ByName(name string) HostSelector {
}
}
// All adds the host to a manager whose documents match the BareMetalHostKind.
func All() HostSelector {
return func(a *Manager, mgmtCfg config.ManagementConfiguration, docBundle document.Bundle) error {
selector := document.NewSelector().ByKind(document.BareMetalHostKind)
docs, err := docBundle.Select(selector)
if err != nil {
return err
}
if len(docs) == 0 {
return document.ErrDocNotFound{Selector: selector}
}
var matchingHosts []baremetalHost
for _, doc := range docs {
host, err := newBaremetalHost(mgmtCfg, doc, docBundle)
if err != nil {
return err
}
matchingHosts = append(matchingHosts, host)
}
a.Hosts = reconcileHosts(a.Hosts, matchingHosts...)
return nil
}
}
// NewManager provides a manager that exposes the capability to perform remote direct functionality and other
// out-of-band management on multiple hosts.
func NewManager(cfg *config.Config, phaseName string, hosts ...HostSelector) (*Manager, error) {