Add Redfish Authentication Support
This commit also introduces a dochelper concept. This provides some convenience methods to the document pkg that help extract data from known document types as well as walk document relationships to discover related information such as BMC credentials for baremetal hosts. Once merged, a follow up patchset will leverage these within the cloud-init code to deduplicate some of these lookups. Change-Id: Ie6a770ce4b34adbea30281917f0cb2fdc460b4fb
This commit is contained in:
parent
21c7c166ff
commit
e2cca32748
@ -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…
Reference in New Issue
Block a user