Merge "Add Redfish Authentication Support"
This commit is contained in:
commit
6ea552bbbc
@ -74,7 +74,7 @@ func getNetworkData(docBundle document.Bundle) ([]byte, error) {
|
||||
}
|
||||
|
||||
// try and find these documents in our bundle
|
||||
selector, err = document.NewEphemeralNetworkDataSelector(bmhDoc)
|
||||
selector, err = document.NewNetworkDataSelector(bmhDoc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ type Bundle interface {
|
||||
SetFileSystem(FileSystem) error
|
||||
GetFileSystem() FileSystem
|
||||
Select(selector Selector) ([]Document, error)
|
||||
SelectOne(selector Selector) (Document, error)
|
||||
SelectBundle(selector Selector) (Bundle, error)
|
||||
SelectByFieldValue(string, func(interface{}) bool) (Bundle, error)
|
||||
GetByGvk(string, string, string) ([]Document, error)
|
||||
@ -220,6 +221,29 @@ func (b *BundleFactory) Select(selector Selector) ([]Document, error) {
|
||||
return docSet, err
|
||||
}
|
||||
|
||||
// SelectOne serves the common use case where you expect one match
|
||||
// and only one match to your selector -- in other words, you want to
|
||||
// error if you didn't find any documents, and error if you found
|
||||
// more than one. This reduces code repetition that would otherwise
|
||||
// be scattered around that evaluates the length of the doc set returned
|
||||
// for this common case
|
||||
func (b *BundleFactory) SelectOne(selector Selector) (Document, error) {
|
||||
docSet, err := b.Select(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// evaluate docSet for at least one document, and no more than
|
||||
// one document and raise errors as appropriate
|
||||
switch numDocsFound := len(docSet); {
|
||||
case numDocsFound == 0:
|
||||
return nil, ErrDocNotFound{Selector: selector}
|
||||
case numDocsFound > 1:
|
||||
return nil, ErrMultipleDocsFound{Selector: selector}
|
||||
}
|
||||
return docSet[0], nil
|
||||
}
|
||||
|
||||
// SelectBundle offers an interface to pass a Selector, built on top of kustomize Selector
|
||||
// to the bundle returning a new Bundle that matches the criteria. This is useful
|
||||
// where you want to actually prune the underlying bundle you are working with
|
||||
|
60
pkg/document/dochelper_baremetalhost.go
Normal file
60
pkg/document/dochelper_baremetalhost.go
Normal file
@ -0,0 +1,60 @@
|
||||
package document
|
||||
|
||||
// GetBMHNetworkData retrieves the associated network data string
|
||||
// for the bmh document supplied from the bundle supplied
|
||||
func GetBMHNetworkData(bmh Document, bundle Bundle) (string, error) {
|
||||
// try and find these documents in our bundle
|
||||
selector, err := NewNetworkDataSelector(bmh)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
networkData, err := GetSecretDataKey(doc, "networkData")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return networkData, nil
|
||||
}
|
||||
|
||||
// GetBMHBMCAddress returns the bmc address for a particular the document supplied
|
||||
func GetBMHBMCAddress(bmh Document) (string, error) {
|
||||
bmcAddress, err := bmh.GetString("spec.bmc.address")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return bmcAddress, nil
|
||||
}
|
||||
|
||||
// GetBMHBMCCredentials returns the BMC credentials for the bmh document supplied from
|
||||
// the supplied bundle
|
||||
func GetBMHBMCCredentials(bmh Document, bundle Bundle) (username string, password string, err error) {
|
||||
// extract the secret document name
|
||||
bmcCredentialsName, err := bmh.GetString("spec.bmc.credentialsName")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// find the secret within the bundle supplied
|
||||
selector := NewBMCCredentialsSelector(bmcCredentialsName)
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
username, err = GetSecretDataKey(doc, "username")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
password, err = GetSecretDataKey(doc, "password")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// extract the username and password from them
|
||||
return username, password, nil
|
||||
}
|
55
pkg/document/dochelper_test.go
Normal file
55
pkg/document/dochelper_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package document_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/testutil"
|
||||
)
|
||||
|
||||
func TestDocHelpers(t *testing.T) {
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
fSys := testutil.SetupTestFs(t, "testdata/dochelper")
|
||||
bundle, err := document.NewBundle(fSys, "/", "/")
|
||||
require.NoError(err, "Building Bundle Failed")
|
||||
require.NotNil(bundle)
|
||||
|
||||
t.Run("GetBMHNetworkData", func(t *testing.T) {
|
||||
// retrieve our single bmh in the dataset
|
||||
selector := document.NewSelector().ByKind("BareMetalHost")
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
require.NoError(err)
|
||||
|
||||
networkData, err := document.GetBMHNetworkData(doc, bundle)
|
||||
require.NoError(err, "Unexpected error trying to GetBMHNetworkData")
|
||||
assert.Equal(networkData, "some network data")
|
||||
})
|
||||
|
||||
t.Run("GetBMHBMCAddress", func(t *testing.T) {
|
||||
// retrieve our single bmh in the dataset
|
||||
selector := document.NewSelector().ByKind("BareMetalHost")
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
require.NoError(err)
|
||||
|
||||
bmcAddress, err := document.GetBMHBMCAddress(doc)
|
||||
require.NoError(err, "Unexpected error trying to GetBMHBMCAddress")
|
||||
assert.Equal(bmcAddress, "redfish+https://192.168.111.1/v1/Redfish/Foo/Bar")
|
||||
})
|
||||
|
||||
t.Run("GetBMHBMCCredentials", func(t *testing.T) {
|
||||
// retrieve our single bmh in the dataset
|
||||
selector := document.NewSelector().ByKind("BareMetalHost")
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
require.NoError(err)
|
||||
|
||||
bmcUsername, bmcPassword, err := document.GetBMHBMCCredentials(doc, bundle)
|
||||
require.NoError(err, "Unexpected error trying to GetBMHBMCCredentials")
|
||||
assert.Equal(bmcUsername, "username")
|
||||
assert.Equal(bmcPassword, "password")
|
||||
})
|
||||
}
|
44
pkg/document/dochelper_utils.go
Normal file
44
pkg/document/dochelper_utils.go
Normal file
@ -0,0 +1,44 @@
|
||||
package document
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
)
|
||||
|
||||
// GetSecretDataKey understands how to retrieve a specific top level key from a secret
|
||||
// that may have the data stored under a data or stringData field in which
|
||||
// case the key may be base64 encoded or it may be plain text
|
||||
//
|
||||
// it is meant to be used by other high level dochelpers
|
||||
func GetSecretDataKey(cfg Document, key string) (string, error) {
|
||||
var needsBase64Decode = true
|
||||
var docName = cfg.GetName()
|
||||
|
||||
// this purposely doesn't handle binaryData as that isn't
|
||||
// something we could support anyways
|
||||
data, err := cfg.GetStringMap("stringData")
|
||||
if err == nil {
|
||||
needsBase64Decode = false
|
||||
} else {
|
||||
data, err = cfg.GetStringMap("data")
|
||||
if err != nil {
|
||||
return "", ErrDocumentMalformed{
|
||||
DocName: docName,
|
||||
Message: "The secret document lacks a data or stringData top level field",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res, ok := data[key]
|
||||
if !ok {
|
||||
return "", ErrDocumentDataKeyNotFound{DocName: docName, Key: key}
|
||||
}
|
||||
|
||||
if needsBase64Decode {
|
||||
byteSlice, err := b64.StdEncoding.DecodeString(res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(byteSlice), nil
|
||||
}
|
||||
return res, nil
|
||||
}
|
@ -9,6 +9,19 @@ type ErrDocNotFound struct {
|
||||
Selector Selector
|
||||
}
|
||||
|
||||
// ErrDocumentDataKeyNotFound returned if desired key within a document not found
|
||||
type ErrDocumentDataKeyNotFound struct {
|
||||
DocName string
|
||||
Key string
|
||||
}
|
||||
|
||||
// ErrDocumentMalformed returned if the document is structurally malformed
|
||||
// (e.g. missing required low level keys)
|
||||
type ErrDocumentMalformed struct {
|
||||
DocName string
|
||||
Message string
|
||||
}
|
||||
|
||||
// ErrMultipleDocsFound returned if desired document not found
|
||||
type ErrMultipleDocsFound struct {
|
||||
Selector Selector
|
||||
@ -18,6 +31,14 @@ func (e ErrDocNotFound) Error() string {
|
||||
return fmt.Sprintf("Document filtered by selector %q found no documents", e.Selector)
|
||||
}
|
||||
|
||||
func (e ErrDocumentDataKeyNotFound) Error() string {
|
||||
return fmt.Sprintf("Document %q cannot retrieve data key %q", e.DocName, e.Key)
|
||||
}
|
||||
|
||||
func (e ErrDocumentMalformed) Error() string {
|
||||
return fmt.Sprintf("Document %q is malformed: %q", e.DocName, e.Message)
|
||||
}
|
||||
|
||||
func (e ErrMultipleDocsFound) Error() string {
|
||||
return fmt.Sprintf("Document filtered by selector %q found more than one document", e.Selector)
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func (s Selector) ByAnnotation(annotationSelector string) Selector {
|
||||
return s
|
||||
}
|
||||
|
||||
// EphemeralCloudDataSelector returns selector to get BaremetalHost for ephemeral node
|
||||
// NewEphemeralCloudDataSelector returns selector to get BaremetalHost for ephemeral node
|
||||
func NewEphemeralCloudDataSelector() Selector {
|
||||
return NewSelector().ByKind(SecretKind).ByLabel(EphemeralUserDataSelector)
|
||||
}
|
||||
@ -74,11 +74,16 @@ func NewEphemeralBMHSelector() Selector {
|
||||
return NewSelector().ByKind(BareMetalHostKind).ByLabel(EphemeralHostSelector)
|
||||
}
|
||||
|
||||
// NewEphemeralNetworkDataSelector returns selector that can be used to get secret with
|
||||
// NewBMCCredentialsSelector returns selector to get BaremetalHost BMC credentials
|
||||
func NewBMCCredentialsSelector(name string) Selector {
|
||||
return NewSelector().ByKind(SecretKind).ByName(name)
|
||||
}
|
||||
|
||||
// NewNetworkDataSelector returns selector that can be used to get secret with
|
||||
// network data bmhDoc argument is a document interface, that should hold fields
|
||||
// spec.networkData.name and spec.networkData.namespace where to find the secret,
|
||||
// if either of these fields are not defined in Document error will be returned
|
||||
func NewEphemeralNetworkDataSelector(bmhDoc Document) (Selector, error) {
|
||||
func NewNetworkDataSelector(bmhDoc Document) (Selector, error) {
|
||||
selector := NewSelector()
|
||||
// extract the network data document pointer from the bmh document
|
||||
netConfDocName, err := bmhDoc.GetString("spec.networkData.name")
|
||||
|
@ -24,7 +24,7 @@ func TestSelectorsPositive(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
bmhDoc := docs[0]
|
||||
selector, err := document.NewEphemeralNetworkDataSelector(bmhDoc)
|
||||
selector, err := document.NewNetworkDataSelector(bmhDoc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "validName", selector.Name)
|
||||
})
|
||||
@ -42,12 +42,12 @@ func TestSelectorsNegative(t *testing.T) {
|
||||
// test coverage
|
||||
bundle := testutil.NewTestBundle(t, "testdata/selectors/invalid")
|
||||
|
||||
t.Run("TestNewEphemeralNetworkDataSelectorErr", func(t *testing.T) {
|
||||
t.Run("TestNewNetworkDataSelectorErr", func(t *testing.T) {
|
||||
docs, err := bundle.Select(document.NewEphemeralBMHSelector())
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 2)
|
||||
bmhDoc := docs[0]
|
||||
_, err = document.NewEphemeralNetworkDataSelector(bmhDoc)
|
||||
_, err = document.NewNetworkDataSelector(bmhDoc)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
@ -56,7 +56,7 @@ func TestSelectorsNegative(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 2)
|
||||
bmhDoc := docs[1]
|
||||
_, err = document.NewEphemeralNetworkDataSelector(bmhDoc)
|
||||
_, err = document.NewNetworkDataSelector(bmhDoc)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@ -67,7 +67,7 @@ func TestSelectorsSkip(t *testing.T) {
|
||||
// test coverage
|
||||
bundle := testutil.NewTestBundle(t, "testdata/selectors/exclude-from-k8s")
|
||||
|
||||
t.Run("TestNewEphemeralNetworkDataSelectorErr", func(t *testing.T) {
|
||||
t.Run("TestNewNetworkDataSelectorErr", func(t *testing.T) {
|
||||
selector := document.NewDeployToK8sSelector()
|
||||
docs, err := bundle.Select(selector)
|
||||
require.NoError(t, err)
|
||||
|
16
pkg/document/testdata/dochelper/baremetalhost.yaml
vendored
Normal file
16
pkg/document/testdata/dochelper/baremetalhost.yaml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
apiVersion: metal3.io/v1alpha1
|
||||
kind: BareMetalHost
|
||||
metadata:
|
||||
labels:
|
||||
airshipit.org/ephemeral-node: "true"
|
||||
name: master-0
|
||||
spec:
|
||||
online: true
|
||||
bootMACAddress: 00:3b:8b:0c:ec:8b
|
||||
bmc:
|
||||
address: redfish+https://192.168.111.1/v1/Redfish/Foo/Bar
|
||||
credentialsName: master-0-bmc
|
||||
networkData:
|
||||
name: master-0-networkdata
|
||||
namespace: metal3
|
3
pkg/document/testdata/dochelper/kustomization.yaml
vendored
Normal file
3
pkg/document/testdata/dochelper/kustomization.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
resources:
|
||||
- baremetalhost.yaml
|
||||
- secret.yaml
|
28
pkg/document/testdata/dochelper/secret.yaml
vendored
Normal file
28
pkg/document/testdata/dochelper/secret.yaml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
airshipit.org/ephemeral-user-data: "true"
|
||||
name: ephemeral-user-data
|
||||
type: Opaque
|
||||
stringData:
|
||||
userData: cloud-init
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: master-0-bmc
|
||||
namespace: metal3
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: username
|
||||
password: password
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: master-0-networkdata
|
||||
namespace: metal3
|
||||
type: Opaque
|
||||
stringData:
|
||||
networkData: some network data
|
@ -17,5 +17,4 @@ metadata:
|
||||
namespace: validNamespace
|
||||
type: Opaque
|
||||
stringData:
|
||||
userData: cloud-init
|
||||
|
||||
userData: cloud-init
|
@ -29,14 +29,18 @@ type RemoteDirect struct {
|
||||
|
||||
// Redfish Client implementation
|
||||
RedfishAPI redfishApi.RedfishAPI
|
||||
|
||||
// optional Username Authentication
|
||||
Username string
|
||||
|
||||
// optional Password
|
||||
Password string
|
||||
}
|
||||
|
||||
// Top level function to handle Redfish remote direct
|
||||
func (cfg RemoteDirect) DoRemoteDirect() error {
|
||||
alog.Debugf("Using Redfish Endpoint: '%s'", cfg.RemoteURL.String())
|
||||
|
||||
/* TODO: Add Authentication when redfish library supports it. */
|
||||
|
||||
/* Get system details */
|
||||
systemID := cfg.EphemeralNodeID
|
||||
system, _, err := cfg.RedfishAPI.GetSystem(cfg.Context, systemID)
|
||||
@ -79,10 +83,12 @@ func (cfg RemoteDirect) DoRemoteDirect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates a new Redfish remote direct client.
|
||||
func NewRedfishRemoteDirectClient(ctx context.Context,
|
||||
// NewRedfishRemoteDirectClient creates a new Redfish remote direct client.
|
||||
func NewRedfishRemoteDirectClient(
|
||||
remoteURL string,
|
||||
ephNodeID string,
|
||||
username string,
|
||||
password string,
|
||||
isoPath string,
|
||||
insecure bool,
|
||||
useproxy bool,
|
||||
@ -101,6 +107,17 @@ func NewRedfishRemoteDirectClient(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
if username != "" && password != "" {
|
||||
ctx = context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: username, Password: password},
|
||||
)
|
||||
} else {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
if isoPath == "" {
|
||||
return RemoteDirect{},
|
||||
ErrRedfishMissingConfig{
|
||||
|
@ -28,37 +28,44 @@ func TestRedfishRemoteDirectNormal(t *testing.T) {
|
||||
|
||||
systemID := computerSystemID
|
||||
httpResp := &http.Response{StatusCode: 200}
|
||||
m.On("GetSystem", context.Background(), systemID).Times(1).
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: "username", Password: "password"},
|
||||
)
|
||||
|
||||
m.On("GetSystem", ctx, systemID).Times(1).
|
||||
Return(getTestSystem(), httpResp, nil)
|
||||
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
|
||||
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
|
||||
Return(redfishClient.RedfishError{}, httpResp, nil)
|
||||
|
||||
m.On("GetSystem", context.Background(), systemID).Times(1).
|
||||
m.On("GetSystem", ctx, systemID).Times(1).
|
||||
Return(getTestSystem(), httpResp, nil)
|
||||
systemReq := redfishClient.ComputerSystem{
|
||||
Boot: redfishClient.Boot{
|
||||
BootSourceOverrideTarget: redfishClient.BOOTSOURCE_CD,
|
||||
},
|
||||
}
|
||||
m.On("SetSystem", context.Background(), systemID, systemReq).
|
||||
m.On("SetSystem", ctx, systemID, systemReq).
|
||||
Times(1).
|
||||
Return(redfishClient.ComputerSystem{}, httpResp, nil)
|
||||
|
||||
resetReq := redfishClient.ResetRequestBody{}
|
||||
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
|
||||
m.On("ResetSystem", context.Background(), systemID, resetReq).
|
||||
m.On("ResetSystem", ctx, systemID, resetReq).
|
||||
Times(1).
|
||||
Return(redfishClient.RedfishError{}, httpResp, nil)
|
||||
|
||||
m.On("GetSystem", context.Background(), systemID).Times(1).
|
||||
m.On("GetSystem", ctx, systemID).Times(1).
|
||||
Return(redfishClient.ComputerSystem{PowerState: redfishClient.POWERSTATE_OFF}, httpResp, nil)
|
||||
|
||||
resetReq.ResetType = redfishClient.RESETTYPE_ON
|
||||
m.On("ResetSystem", context.Background(), systemID, resetReq).
|
||||
m.On("ResetSystem", ctx, systemID, resetReq).
|
||||
Times(1).
|
||||
Return(redfishClient.RedfishError{}, httpResp, nil)
|
||||
|
||||
m.On("GetSystem", context.Background(), systemID).Times(1).
|
||||
m.On("GetSystem", ctx, systemID).Times(1).
|
||||
Return(redfishClient.ComputerSystem{PowerState: redfishClient.POWERSTATE_ON}, httpResp, nil)
|
||||
|
||||
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
|
||||
@ -72,13 +79,19 @@ func TestRedfishRemoteDirectInvalidSystemId(t *testing.T) {
|
||||
m := &redfishMocks.RedfishAPI{}
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: "username", Password: "password"},
|
||||
)
|
||||
|
||||
systemID := "invalid-server"
|
||||
localRDCfg := getDefaultRedfishRemoteDirectObj(t, m)
|
||||
|
||||
localRDCfg.EphemeralNodeID = systemID
|
||||
|
||||
realErr := fmt.Errorf("%s system do not exist", systemID)
|
||||
m.On("GetSystem", context.Background(), systemID).
|
||||
m.On("GetSystem", ctx, systemID).
|
||||
Times(1).
|
||||
Return(redfishClient.ComputerSystem{}, nil, realErr)
|
||||
|
||||
@ -92,10 +105,16 @@ func TestRedfishRemoteDirectGetSystemNetworkError(t *testing.T) {
|
||||
m := &redfishMocks.RedfishAPI{}
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: "username", Password: "password"},
|
||||
)
|
||||
|
||||
systemID := computerSystemID
|
||||
realErr := fmt.Errorf("server request timeout")
|
||||
httpResp := &http.Response{StatusCode: 408}
|
||||
m.On("GetSystem", context.Background(), systemID).
|
||||
m.On("GetSystem", ctx, systemID).
|
||||
Times(1).
|
||||
Return(redfishClient.ComputerSystem{}, httpResp, realErr)
|
||||
|
||||
@ -111,6 +130,12 @@ func TestRedfishRemoteDirectInvalidIsoPath(t *testing.T) {
|
||||
m := &redfishMocks.RedfishAPI{}
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: "username", Password: "password"},
|
||||
)
|
||||
|
||||
systemID := computerSystemID
|
||||
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
|
||||
localRDCfg := rDCfg
|
||||
@ -118,11 +143,11 @@ func TestRedfishRemoteDirectInvalidIsoPath(t *testing.T) {
|
||||
|
||||
realErr := redfishClient.GenericOpenAPIError{}
|
||||
httpResp := &http.Response{StatusCode: 500}
|
||||
m.On("GetSystem", context.Background(), systemID).
|
||||
m.On("GetSystem", ctx, systemID).
|
||||
Times(1).
|
||||
Return(getTestSystem(), nil, nil)
|
||||
|
||||
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
|
||||
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
|
||||
Return(redfishClient.RedfishError{}, httpResp, realErr)
|
||||
|
||||
err := localRDCfg.DoRemoteDirect()
|
||||
@ -135,6 +160,12 @@ func TestRedfishRemoteDirectCdDvdNotAvailableInBootSources(t *testing.T) {
|
||||
m := &redfishMocks.RedfishAPI{}
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: "username", Password: "password"},
|
||||
)
|
||||
|
||||
systemID := computerSystemID
|
||||
invalidSystem := getTestSystem()
|
||||
invalidSystem.Boot.BootSourceOverrideTargetRedfishAllowableValues = []redfishClient.BootSource{
|
||||
@ -142,10 +173,10 @@ func TestRedfishRemoteDirectCdDvdNotAvailableInBootSources(t *testing.T) {
|
||||
redfishClient.BOOTSOURCE_PXE,
|
||||
}
|
||||
|
||||
m.On("GetSystem", context.Background(), systemID).
|
||||
m.On("GetSystem", ctx, systemID).
|
||||
Return(invalidSystem, nil, nil)
|
||||
|
||||
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
|
||||
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
|
||||
Return(redfishClient.RedfishError{}, nil, nil)
|
||||
|
||||
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
|
||||
@ -160,15 +191,21 @@ func TestRedfishRemoteDirectSetSystemBootSourceFailed(t *testing.T) {
|
||||
m := &redfishMocks.RedfishAPI{}
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: "username", Password: "password"},
|
||||
)
|
||||
|
||||
systemID := computerSystemID
|
||||
httpSuccResp := &http.Response{StatusCode: 200}
|
||||
m.On("GetSystem", context.Background(), systemID).
|
||||
m.On("GetSystem", ctx, systemID).
|
||||
Return(getTestSystem(), httpSuccResp, nil)
|
||||
|
||||
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
|
||||
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
|
||||
Return(redfishClient.RedfishError{}, httpSuccResp, nil)
|
||||
|
||||
m.On("SetSystem", context.Background(), systemID, mock.Anything).
|
||||
m.On("SetSystem", ctx, systemID, mock.Anything).
|
||||
Times(1).
|
||||
Return(redfishClient.ComputerSystem{}, &http.Response{StatusCode: 401},
|
||||
redfishClient.GenericOpenAPIError{})
|
||||
@ -185,21 +222,27 @@ func TestRedfishRemoteDirectSystemRebootFailed(t *testing.T) {
|
||||
m := &redfishMocks.RedfishAPI{}
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
redfishClient.ContextBasicAuth,
|
||||
redfishClient.BasicAuth{UserName: "username", Password: "password"},
|
||||
)
|
||||
|
||||
systemID := computerSystemID
|
||||
httpSuccResp := &http.Response{StatusCode: 200}
|
||||
m.On("GetSystem", context.Background(), systemID).
|
||||
m.On("GetSystem", ctx, systemID).
|
||||
Return(getTestSystem(), httpSuccResp, nil)
|
||||
|
||||
m.On("InsertVirtualMedia", context.Background(), mock.Anything, mock.Anything, mock.Anything).
|
||||
m.On("InsertVirtualMedia", ctx, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(redfishClient.RedfishError{}, httpSuccResp, nil)
|
||||
|
||||
m.On("SetSystem", context.Background(), systemID, mock.Anything).
|
||||
m.On("SetSystem", ctx, systemID, mock.Anything).
|
||||
Times(1).
|
||||
Return(redfishClient.ComputerSystem{}, httpSuccResp, nil)
|
||||
|
||||
resetReq := redfishClient.ResetRequestBody{}
|
||||
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
|
||||
m.On("ResetSystem", context.Background(), systemID, resetReq).
|
||||
m.On("ResetSystem", ctx, systemID, resetReq).
|
||||
Times(1).
|
||||
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 401},
|
||||
redfishClient.GenericOpenAPIError{})
|
||||
@ -244,9 +287,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
_, err := NewRedfishRemoteDirectClient(
|
||||
context.Background(),
|
||||
defaultURL,
|
||||
computerSystemID,
|
||||
"username",
|
||||
"password",
|
||||
"/tmp/test.iso",
|
||||
true,
|
||||
false,
|
||||
@ -255,9 +299,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
|
||||
|
||||
// Test with empty remote URL
|
||||
_, err = NewRedfishRemoteDirectClient(
|
||||
context.Background(),
|
||||
"",
|
||||
computerSystemID,
|
||||
"username",
|
||||
"password",
|
||||
"/tmp/test.iso",
|
||||
false,
|
||||
false,
|
||||
@ -267,9 +312,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
|
||||
|
||||
// Test with empty ephemeral NodeID
|
||||
_, err = NewRedfishRemoteDirectClient(
|
||||
context.Background(),
|
||||
defaultURL,
|
||||
"",
|
||||
"username",
|
||||
"password",
|
||||
"/tmp/test.iso",
|
||||
false,
|
||||
false,
|
||||
@ -279,9 +325,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
|
||||
|
||||
// Test with empty Iso Path
|
||||
_, err = NewRedfishRemoteDirectClient(
|
||||
context.Background(),
|
||||
defaultURL,
|
||||
computerSystemID,
|
||||
"username",
|
||||
"password",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
@ -294,9 +341,10 @@ func getDefaultRedfishRemoteDirectObj(t *testing.T, api redfishAPI.RedfishAPI) R
|
||||
t.Helper()
|
||||
|
||||
rDCfg, err := NewRedfishRemoteDirectClient(
|
||||
context.Background(),
|
||||
defaultURL,
|
||||
computerSystemID,
|
||||
"username",
|
||||
"password",
|
||||
"/tmp/test.iso",
|
||||
false,
|
||||
false,
|
||||
|
@ -1,7 +1,6 @@
|
||||
package remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -23,7 +22,11 @@ type Client interface {
|
||||
}
|
||||
|
||||
// Get remotedirect client based on config
|
||||
func getRemoteDirectClient(remoteConfig *config.RemoteDirect, remoteURL string) (Client, error) {
|
||||
func getRemoteDirectClient(
|
||||
remoteConfig *config.RemoteDirect,
|
||||
remoteURL string,
|
||||
username string,
|
||||
password string) (Client, error) {
|
||||
var client Client
|
||||
switch remoteConfig.RemoteType {
|
||||
case AirshipRemoteTypeRedfish:
|
||||
@ -44,9 +47,10 @@ func getRemoteDirectClient(remoteConfig *config.RemoteDirect, remoteURL string)
|
||||
nodeID := urlPath[len(urlPath)-1]
|
||||
|
||||
client, err = redfish.NewRedfishRemoteDirectClient(
|
||||
context.Background(),
|
||||
baseURL,
|
||||
nodeID,
|
||||
username,
|
||||
password,
|
||||
remoteConfig.IsoURL,
|
||||
remoteConfig.Insecure,
|
||||
remoteConfig.UseProxy,
|
||||
@ -63,56 +67,60 @@ func getRemoteDirectClient(remoteConfig *config.RemoteDirect, remoteURL string)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func getRemoteDirectConfig(settings *environment.AirshipCTLSettings) (*config.RemoteDirect, string, error) {
|
||||
func getRemoteDirectConfig(settings *environment.AirshipCTLSettings) (
|
||||
remoteConfig *config.RemoteDirect,
|
||||
remoteURL string,
|
||||
username string,
|
||||
password string,
|
||||
err error) {
|
||||
cfg := settings.Config()
|
||||
bootstrapSettings, err := cfg.CurrentContextBootstrapInfo()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
remoteConfig := bootstrapSettings.RemoteDirect
|
||||
remoteConfig = bootstrapSettings.RemoteDirect
|
||||
if remoteConfig == nil {
|
||||
return nil, "", config.ErrMissingConfig{What: "RemoteDirect options not defined in bootstrap config"}
|
||||
return nil, "", "", "", config.ErrMissingConfig{What: "RemoteDirect options not defined in bootstrap config"}
|
||||
}
|
||||
|
||||
root, err := cfg.CurrentContextEntryPoint(config.Ephemeral, "")
|
||||
bundlePath, err := cfg.CurrentContextEntryPoint(config.Ephemeral, "")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
docBundle, err := document.NewBundleByPath(root)
|
||||
docBundle, err := document.NewBundleByPath(bundlePath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
selector := document.NewEphemeralBMHSelector()
|
||||
docs, err := docBundle.Select(selector)
|
||||
doc, err := docBundle.SelectOne(selector)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if len(docs) == 0 {
|
||||
return nil, "", document.ErrDocNotFound{
|
||||
Selector: selector,
|
||||
}
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
// NOTE If filter returned more than one document chose first
|
||||
remoteURL, err := docs[0].GetString("spec.bmc.address")
|
||||
remoteURL, err = document.GetBMHBMCAddress(doc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return remoteConfig, remoteURL, nil
|
||||
username, password, err = document.GetBMHBMCCredentials(doc, docBundle)
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return remoteConfig, remoteURL, username, password, nil
|
||||
}
|
||||
|
||||
// Top level function to execute remote direct based on remote type
|
||||
func DoRemoteDirect(settings *environment.AirshipCTLSettings) error {
|
||||
remoteConfig, remoteURL, err := getRemoteDirectConfig(settings)
|
||||
remoteConfig, remoteURL, username, password, err := getRemoteDirectConfig(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := getRemoteDirectClient(remoteConfig, remoteURL)
|
||||
client, err := getRemoteDirectClient(remoteConfig, remoteURL, username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user