Add BaremetalManager executor
This will allow to peform remote BMH operations as a phase Change-Id: I8e99285e0407d1922312a08ad4f766363f8855d2
This commit is contained in:
parent
08681115d5
commit
f0e80cfdc5
@ -40,8 +40,10 @@ const (
|
||||
IsogenType
|
||||
// BootstrapType event emitted by Bootstrap executor
|
||||
BootstrapType
|
||||
//GenericContainerType event emitted by GenericContainer
|
||||
// GenericContainerType event emitted by GenericContainer
|
||||
GenericContainerType
|
||||
// BaremetalManagerEventType event emitted by BaremetalManager
|
||||
BaremetalManagerEventType
|
||||
)
|
||||
|
||||
// Event holds all possible events that can be produced by airship
|
||||
@ -55,6 +57,7 @@ type Event struct {
|
||||
IsogenEvent IsogenEvent
|
||||
BootstrapEvent BootstrapEvent
|
||||
GenericContainerEvent GenericContainerEvent
|
||||
BaremetalManagerEvent BaremetalManagerEvent
|
||||
}
|
||||
|
||||
//GenericEvent generalized type for custom events
|
||||
@ -260,3 +263,29 @@ func (e Event) WithGenericContainerEvent(concreteEvent GenericContainerEvent) Ev
|
||||
e.GenericContainerEvent = concreteEvent
|
||||
return e
|
||||
}
|
||||
|
||||
// BaremetalManagerStep indicates what operation baremetal manager is currently peforming
|
||||
// Note that this is not baremetal
|
||||
type BaremetalManagerStep int
|
||||
|
||||
const (
|
||||
// BaremetalManagerStart operation
|
||||
BaremetalManagerStart BaremetalManagerStep = iota
|
||||
// BaremetalManagerComplete operation
|
||||
BaremetalManagerComplete
|
||||
)
|
||||
|
||||
// BaremetalManagerEvent event emitted by BaremetalManager
|
||||
type BaremetalManagerEvent struct {
|
||||
Step BaremetalManagerStep
|
||||
// HostOperation indicates which operation is performed against BMH Host
|
||||
HostOperation string
|
||||
Message string
|
||||
}
|
||||
|
||||
// WithBaremetalManagerEvent sets type and actual bootstrap event
|
||||
func (e Event) WithBaremetalManagerEvent(concreteEvent BaremetalManagerEvent) Event {
|
||||
e.Type = BaremetalManagerEventType
|
||||
e.BaremetalManagerEvent = concreteEvent
|
||||
return e
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func DefaultExecutorRegistry() map[schema.GroupVersionKind]ifc.ExecutorFactory {
|
||||
execMap := make(map[schema.GroupVersionKind]ifc.ExecutorFactory)
|
||||
|
||||
for _, execName := range []string{executors.Clusterctl, executors.KubernetesApply,
|
||||
executors.Isogen, executors.GenericContainer, executors.Ephemeral} {
|
||||
executors.Isogen, executors.GenericContainer, executors.Ephemeral, executors.BMHManager} {
|
||||
if err := executors.RegisterExecutor(execName, execMap); err != nil {
|
||||
log.Fatal(ErrExecutorRegistration{ExecutorName: execName, Err: err})
|
||||
}
|
||||
|
142
pkg/phase/executors/baremetal_manager.go
Normal file
142
pkg/phase/executors/baremetal_manager.go
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package executors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/events"
|
||||
"opendev.org/airship/airshipctl/pkg/inventory"
|
||||
inventoryifc "opendev.org/airship/airshipctl/pkg/inventory/ifc"
|
||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||
)
|
||||
|
||||
// BaremetalManagerExectuor is abstraction built on top of baremetal commands of airshipctl
|
||||
type BaremetalManagerExectuor struct {
|
||||
inventory inventoryifc.Inventory
|
||||
options *airshipv1.BaremetalManager
|
||||
}
|
||||
|
||||
// NewBaremetalExecutor constructor for baremetal executor
|
||||
func NewBaremetalExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) {
|
||||
inv, err := cfg.Helper.Inventory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := airshipv1.DefaultBaremetalManager()
|
||||
if err := cfg.ExecutorDocument.ToAPIObject(options, airshipv1.Scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BaremetalManagerExectuor{
|
||||
inventory: inv,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run runs baremetal operations as exectuor
|
||||
func (e *BaremetalManagerExectuor) Run(evtCh chan events.Event, opts ifc.RunOptions) {
|
||||
defer close(evtCh)
|
||||
commandOptions := toCommandOptions(e.inventory, e.options.Spec, opts)
|
||||
|
||||
evtCh <- events.NewEvent().WithBaremetalManagerEvent(events.BaremetalManagerEvent{
|
||||
Step: events.BaremetalManagerStart,
|
||||
HostOperation: string(e.options.Spec.Operation),
|
||||
Message: fmt.Sprintf("Starting remote operation, selector to be to filter hosts %v",
|
||||
e.options.Spec.HostSelector),
|
||||
})
|
||||
|
||||
op, err := e.validate()
|
||||
if err != nil {
|
||||
handleError(evtCh, err)
|
||||
return
|
||||
}
|
||||
if !opts.DryRun {
|
||||
switch e.options.Spec.Operation {
|
||||
case airshipv1.BaremetalOperationPowerOn, airshipv1.BaremetalOperationPowerOff,
|
||||
airshipv1.BaremetalOperationReboot, airshipv1.BaremetalOperationEjectVirtualMedia:
|
||||
err = commandOptions.BMHAction(op)
|
||||
case airshipv1.BaremetalOperationRemoteDirect:
|
||||
err = commandOptions.RemoteDirect()
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
handleError(evtCh, err)
|
||||
return
|
||||
}
|
||||
|
||||
evtCh <- events.NewEvent().WithBaremetalManagerEvent(events.BaremetalManagerEvent{
|
||||
Step: events.BaremetalManagerComplete,
|
||||
HostOperation: string(e.options.Spec.Operation),
|
||||
Message: fmt.Sprintf("Successfully completed operation against host selected by selector %v",
|
||||
e.options.Spec.HostSelector),
|
||||
})
|
||||
}
|
||||
|
||||
// Validate executor configuration and documents
|
||||
func (e *BaremetalManagerExectuor) Validate() error {
|
||||
_, err := e.validate()
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *BaremetalManagerExectuor) validate() (inventoryifc.BaremetalOperation, error) {
|
||||
var result inventoryifc.BaremetalOperation
|
||||
var err error
|
||||
switch e.options.Spec.Operation {
|
||||
case airshipv1.BaremetalOperationPowerOn:
|
||||
result = inventoryifc.BaremetalOperationPowerOn
|
||||
case airshipv1.BaremetalOperationPowerOff:
|
||||
result = inventoryifc.BaremetalOperationPowerOff
|
||||
case airshipv1.BaremetalOperationEjectVirtualMedia:
|
||||
result = inventoryifc.BaremetalOperationEjectVirtualMedia
|
||||
case airshipv1.BaremetalOperationReboot:
|
||||
result = inventoryifc.BaremetalOperationReboot
|
||||
case airshipv1.BaremetalOperationRemoteDirect:
|
||||
// TODO add remote direct validation, make sure that ISO-URL is specified
|
||||
result = ""
|
||||
default:
|
||||
err = ErrUnknownExecutorAction{Action: string(e.options.Spec.Operation), ExecutorName: BMHManager}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Render baremetal hosts
|
||||
func (e *BaremetalManagerExectuor) Render(w io.Writer, _ ifc.RenderOptions) error {
|
||||
// add printing of baremetal hosts here
|
||||
_, err := w.Write([]byte{})
|
||||
return err
|
||||
}
|
||||
|
||||
func toCommandOptions(i inventoryifc.Inventory,
|
||||
spec v1alpha1.BaremetalManagerSpec,
|
||||
opts ifc.RunOptions) *inventory.CommandOptions {
|
||||
timeout := time.Duration(spec.Timeout) * time.Second
|
||||
if opts.Timeout != 0 {
|
||||
timeout = opts.Timeout
|
||||
}
|
||||
|
||||
return &inventory.CommandOptions{
|
||||
Invetnory: i,
|
||||
IsoURL: spec.OperationOptions.RemoteDirect.ISOURL,
|
||||
Labels: spec.HostSelector.LabelSelector,
|
||||
Name: spec.HostSelector.Name,
|
||||
Namespace: spec.HostSelector.Namespace,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
212
pkg/phase/executors/baremetal_manager_test.go
Normal file
212
pkg/phase/executors/baremetal_manager_test.go
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package executors_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/events"
|
||||
"opendev.org/airship/airshipctl/pkg/k8s/utils"
|
||||
"opendev.org/airship/airshipctl/pkg/phase/executors"
|
||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||
testdoc "opendev.org/airship/airshipctl/testutil/document"
|
||||
)
|
||||
|
||||
var bmhExecutorTemplate = `apiVersion: airshipit.org/v1alpha1
|
||||
kind: BaremetalManager
|
||||
metadata:
|
||||
name: RemoteDirectEphemeral
|
||||
labels:
|
||||
airshipit.org/deploy-k8s: "false"
|
||||
spec:
|
||||
operation: "%s"
|
||||
hostSelector:
|
||||
name: node02
|
||||
operationOptions:
|
||||
remoteDirect:
|
||||
isoURL: %s`
|
||||
|
||||
func TestNewBMHExecutor(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
execDoc := executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "reboot", "/home/iso-url"))
|
||||
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
|
||||
ExecutorDocument: execDoc,
|
||||
BundleFactory: testBundleFactory(singleExecutorBundlePath),
|
||||
Helper: makeDefaultHelper(t, "../testdata"),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, executor)
|
||||
})
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
exepectedErr := fmt.Errorf("ToAPI error")
|
||||
execDoc := &testdoc.MockDocument{
|
||||
MockToAPIObject: func() error { return exepectedErr },
|
||||
}
|
||||
executor, actualErr := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
|
||||
ExecutorDocument: execDoc,
|
||||
BundleFactory: testBundleFactory(singleExecutorBundlePath),
|
||||
Helper: makeDefaultHelper(t, "../testdata"),
|
||||
})
|
||||
assert.Equal(t, exepectedErr, actualErr)
|
||||
assert.Nil(t, executor)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBMHExecutorRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedErr string
|
||||
runOptions ifc.RunOptions
|
||||
execDoc document.Document
|
||||
}{
|
||||
{
|
||||
name: "error validate dry-run",
|
||||
expectedErr: "unknown action type",
|
||||
runOptions: ifc.RunOptions{
|
||||
DryRun: true,
|
||||
// any value but zero
|
||||
Timeout: 40,
|
||||
},
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "unknown", "")),
|
||||
},
|
||||
{
|
||||
name: "success validate dry-run",
|
||||
runOptions: ifc.RunOptions{
|
||||
DryRun: true,
|
||||
},
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "remote-direct", "/some/url")),
|
||||
},
|
||||
{
|
||||
name: "error unknown action type",
|
||||
runOptions: ifc.RunOptions{},
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "unknown", "")),
|
||||
expectedErr: "unknown action type",
|
||||
},
|
||||
{
|
||||
name: "error no kustomization.yaml for inventory remote-direct",
|
||||
runOptions: ifc.RunOptions{},
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "remote-direct", "")),
|
||||
expectedErr: "kustomization.yaml",
|
||||
},
|
||||
{
|
||||
name: "error no kustomization.yaml for inventory remote-direct",
|
||||
runOptions: ifc.RunOptions{},
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "reboot", "")),
|
||||
expectedErr: "kustomization.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
|
||||
ExecutorDocument: tt.execDoc,
|
||||
BundleFactory: testBundleFactory(singleExecutorBundlePath),
|
||||
Helper: makeDefaultHelper(t, "../testdata/"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, executor)
|
||||
ch := make(chan events.Event)
|
||||
go func() {
|
||||
executor.Run(ch, tt.runOptions)
|
||||
}()
|
||||
processor := events.NewDefaultProcessor(utils.Streams())
|
||||
defer processor.Close()
|
||||
err = processor.Process(ch)
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBMHValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedErr string
|
||||
execDoc document.Document
|
||||
}{
|
||||
{
|
||||
name: "error validate unknown action",
|
||||
expectedErr: "unknown action type",
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "unknown", "")),
|
||||
},
|
||||
{
|
||||
name: "success validate remote-direct",
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "remote-direct", "/some/url")),
|
||||
},
|
||||
{
|
||||
name: "success validate reboot",
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "reboot", "/some/url")),
|
||||
},
|
||||
{
|
||||
name: "success validate power-off",
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "power-off", "/some/url")),
|
||||
},
|
||||
{
|
||||
name: "success validate power-on",
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "power-on", "/some/url")),
|
||||
},
|
||||
{
|
||||
name: "success validate eject-virtual-media",
|
||||
execDoc: executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "eject-virtual-media", "/some/url")),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
|
||||
ExecutorDocument: tt.execDoc,
|
||||
BundleFactory: testBundleFactory(singleExecutorBundlePath),
|
||||
Helper: makeDefaultHelper(t, "../testdata/"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, executor)
|
||||
|
||||
actualErr := executor.Validate()
|
||||
if tt.expectedErr != "" {
|
||||
require.Error(t, actualErr)
|
||||
assert.Contains(t, actualErr.Error(), tt.expectedErr)
|
||||
} else {
|
||||
assert.NoError(t, actualErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Dummy test to keep up with coverage, develop better testcases when render is implemented
|
||||
func TestBMHManagerRender(t *testing.T) {
|
||||
execDoc := executorDoc(t, fmt.Sprintf(bmhExecutorTemplate, "reboot", "/home/iso-url"))
|
||||
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
|
||||
ExecutorDocument: execDoc,
|
||||
BundleFactory: testBundleFactory(singleExecutorBundlePath),
|
||||
Helper: makeDefaultHelper(t, "../testdata"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, executor)
|
||||
|
||||
err = executor.Render(bytes.NewBuffer([]byte{}), ifc.RenderOptions{})
|
||||
assert.NoError(t, err)
|
||||
}
|
@ -67,7 +67,7 @@ func (c *ClusterctlExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) {
|
||||
case airshipv1.Init:
|
||||
c.init(opts, evtCh)
|
||||
default:
|
||||
handleError(evtCh, ErrUnknownExecutorAction{Action: string(c.options.Action)})
|
||||
handleError(evtCh, ErrUnknownExecutorAction{Action: string(c.options.Action), ExecutorName: "clusterctl"})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ func TestExecutorRun(t *testing.T) {
|
||||
cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmpl, "someAction")),
|
||||
bundlePath: "testdata/executor_init",
|
||||
expectedEvt: []events.Event{
|
||||
wrapError(executors.ErrUnknownExecutorAction{Action: "someAction"}),
|
||||
wrapError(executors.ErrUnknownExecutorAction{Action: "someAction", ExecutorName: "clusterctl"}),
|
||||
},
|
||||
clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()),
|
||||
},
|
||||
|
@ -29,6 +29,7 @@ const (
|
||||
Isogen = "isogen"
|
||||
GenericContainer = "generic-container"
|
||||
Ephemeral = "ephemeral"
|
||||
BMHManager = "BaremetalManager"
|
||||
)
|
||||
|
||||
// RegisterExecutor adds executor to phase executor registry
|
||||
@ -53,6 +54,9 @@ func RegisterExecutor(executorName string, registry map[schema.GroupVersionKind]
|
||||
case Ephemeral:
|
||||
gvks, _, err = airshipv1.Scheme.ObjectKinds(airshipv1.DefaultBootConfiguration())
|
||||
execObj = NewEphemeralExecutor
|
||||
case BMHManager:
|
||||
gvks, _, err = airshipv1.Scheme.ObjectKinds(&airshipv1.BaremetalManager{})
|
||||
execObj = NewBaremetalExecutor
|
||||
default:
|
||||
return ErrUnknownExecutorName{ExecutorName: executorName}
|
||||
}
|
||||
|
@ -18,14 +18,16 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrUnknownExecutorAction is returned for unknown action parameter
|
||||
// in clusterctl configuration document
|
||||
// ErrUnknownExecutorAction is returned when unknown action or operation is requested
|
||||
// from one of the executors.
|
||||
type ErrUnknownExecutorAction struct {
|
||||
Action string
|
||||
Action string
|
||||
ExecutorName string
|
||||
}
|
||||
|
||||
func (e ErrUnknownExecutorAction) Error() string {
|
||||
return fmt.Sprintf("unknown action type '%s'", e.Action)
|
||||
return fmt.Sprintf("unknown action type '%s' was requested from executor '%s'",
|
||||
e.Action, e.ExecutorName)
|
||||
}
|
||||
|
||||
// ErrIsogenNilBundle is returned when isogen executor is not provided with bundle
|
||||
|
Loading…
Reference in New Issue
Block a user