From fa03b40f4affa891cab94061014999881747483f Mon Sep 17 00:00:00 2001 From: Kostiantyn Kalynovskyi Date: Tue, 10 Nov 2020 16:40:22 -0600 Subject: [PATCH] Implement RunAction method of the baremetal interface Change-Id: I86b2cd7824c68ccd4fac875a5ad1d3ff9cd69072 Relates-To: #397 Relates-To: #362 Relates-To: #359 --- pkg/inventory/baremetal/baremetal.go | 52 ++++++++++- pkg/inventory/baremetal/baremetal_test.go | 96 +++++++++++++++++++++ pkg/inventory/baremetal/errors.go | 9 ++ pkg/inventory/baremetal/testdata/hosts.yaml | 2 +- 4 files changed, 156 insertions(+), 3 deletions(-) diff --git a/pkg/inventory/baremetal/baremetal.go b/pkg/inventory/baremetal/baremetal.go index 62bf30815..9b2a49c57 100644 --- a/pkg/inventory/baremetal/baremetal.go +++ b/pkg/inventory/baremetal/baremetal.go @@ -19,7 +19,6 @@ import ( "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" - "opendev.org/airship/airshipctl/pkg/errors" "opendev.org/airship/airshipctl/pkg/inventory/ifc" "opendev.org/airship/airshipctl/pkg/log" remoteifc "opendev.org/airship/airshipctl/pkg/remote/ifc" @@ -89,7 +88,33 @@ func (i Inventory) RunOperation( op ifc.BaremetalOperation, selector ifc.BaremetalHostSelector, _ ifc.BaremetalBatchRunOptions) error { - return errors.ErrNotImplemented{What: "RunOperation of the baremetal inventory interface"} + log.Debugf("Running operation '%s' against hosts selected by selector '%v'", op, selector) + + hostAction, err := action(ctx, op) + if err != nil { + return err + } + + hosts, err := i.Select(selector) + if err != nil { + return err + } + + if len(hosts) == 0 { + log.Printf("Filtering using selector %v' didn't return any hosts to perform operation '%s'", selector, op) + return ErrNoBaremetalHostsFound{Selector: selector} + } + + // TODO add concurent action execution + // TODO consider adding FailFast flag to BaremetalBatchRunOptions that would allow + // not fail on first error, but accumulate errors and return them at the end. + for _, host := range hosts { + if hostErr := hostAction(host); hostErr != nil { + return hostErr + } + } + + return nil } // Host implements baremetal host interface @@ -138,6 +163,29 @@ func (i Inventory) newHost(doc document.Document) (Host, error) { return Host{Client: client}, nil } +func action(ctx context.Context, op ifc.BaremetalOperation) (func(remoteifc.Client) error, error) { + switch op { + case ifc.BaremetalOperationReboot: + return func(host remoteifc.Client) error { + return host.RebootSystem(ctx) + }, nil + case ifc.BaremetalOperationPowerOff: + return func(host remoteifc.Client) error { + return host.SystemPowerOff(ctx) + }, nil + case ifc.BaremetalOperationPowerOn: + return func(host remoteifc.Client) error { + return host.SystemPowerOn(ctx) + }, nil + case ifc.BaremetalOperationEjectVirtualMedia: + return func(host remoteifc.Client) error { + return host.EjectVirtualMedia(ctx) + }, nil + default: + return nil, ErrBaremetalOperationNotSupported{Operation: op} + } +} + func toDocumentSelector(selector ifc.BaremetalHostSelector) document.Selector { return document.NewSelector(). ByKind(document.BareMetalHostKind). diff --git a/pkg/inventory/baremetal/baremetal_test.go b/pkg/inventory/baremetal/baremetal_test.go index 454c64e17..c38ff6fce 100644 --- a/pkg/inventory/baremetal/baremetal_test.go +++ b/pkg/inventory/baremetal/baremetal_test.go @@ -15,6 +15,7 @@ package baremetal import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -119,6 +120,101 @@ func TestSelectOne(t *testing.T) { } } +func TestRunAction(t *testing.T) { + tests := []struct { + name, remoteDriver, expectedErr string + operation ifc.BaremetalOperation + + selector ifc.BaremetalHostSelector + }{ + { + name: "success return one host", + remoteDriver: "redfish", + operation: ifc.BaremetalOperation("not supported"), + selector: (ifc.BaremetalHostSelector{}).ByName("master-0"), + expectedErr: "Baremetal operation not supported", + }, + { + name: "success return one host", + remoteDriver: "redfish", + operation: ifc.BaremetalOperationPowerOn, + selector: (ifc.BaremetalHostSelector{}).ByName("does not exist"), + expectedErr: "No baremetal hosts matched selector", + }, + { + name: "success return one host", + remoteDriver: "redfish", + operation: ifc.BaremetalOperationPowerOn, + selector: (ifc.BaremetalHostSelector{}).ByName("master-0"), + expectedErr: "HTTP request failed", + }, + } + + bundle := testBundle(t) + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + mgmCfg := config.ManagementConfiguration{Type: tt.remoteDriver} + inventory := NewInventory(mgmCfg, bundle) + err := inventory.RunOperation( + context.Background(), + tt.operation, + tt.selector, + ifc.BaremetalBatchRunOptions{}) + if tt.expectedErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestAction(t *testing.T) { + tests := []struct { + name string + action ifc.BaremetalOperation + expectErr bool + }{ + { + name: "poweron", + action: ifc.BaremetalOperationPowerOn, + }, + { + name: "poweroff", + action: ifc.BaremetalOperationPowerOff, + }, + { + name: "ejectvirtualmedia", + action: ifc.BaremetalOperationEjectVirtualMedia, + }, + { + name: "reboot", + action: ifc.BaremetalOperationReboot, + }, + { + name: "reboot", + action: ifc.BaremetalOperation("not supported"), + expectErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + actionFunc, err := action(context.Background(), tt.action) + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + // TODO inject fake host interface here to validate + // that correct actions were selected + assert.NotNil(t, actionFunc) + } + }) + } +} + func testBundle(t *testing.T) document.Bundle { t.Helper() bundle, err := document.NewBundleByPath("testdata") diff --git a/pkg/inventory/baremetal/errors.go b/pkg/inventory/baremetal/errors.go index 746bbae18..407626ed6 100644 --- a/pkg/inventory/baremetal/errors.go +++ b/pkg/inventory/baremetal/errors.go @@ -40,3 +40,12 @@ type ErrNoBaremetalHostsFound struct { func (e ErrNoBaremetalHostsFound) Error() string { return fmt.Sprintf("No baremetal hosts matched selector %v", e.Selector) } + +// ErrBaremetalOperationNotSupported is returned when baremetal operation is not supported +type ErrBaremetalOperationNotSupported struct { + Operation ifc.BaremetalOperation +} + +func (e ErrBaremetalOperationNotSupported) Error() string { + return fmt.Sprintf("Baremetal operation not supported: '%s'", e.Operation) +} diff --git a/pkg/inventory/baremetal/testdata/hosts.yaml b/pkg/inventory/baremetal/testdata/hosts.yaml index d8a67f9a9..3f01ddbdf 100644 --- a/pkg/inventory/baremetal/testdata/hosts.yaml +++ b/pkg/inventory/baremetal/testdata/hosts.yaml @@ -9,7 +9,7 @@ spec: online: true bootMACAddress: 00:3b:8b:0c:ec:8b bmc: - address: redfish+http://nolocalhost:8888/redfish/v1/Systems/ephemeral + address: redfish+http://nolocalhost:32201/redfish/v1/Systems/ephemeral credentialsName: master-0-bmc-secret --- apiVersion: v1