Switch remote direct to airshipctl configuration
Related #7 Change-Id: I015b178895359ea468748eb72e367b4ff56026bb
This commit is contained in:
parent
8b86e4135f
commit
97c114cb21
@ -4,69 +4,18 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
alog "opendev.org/airship/airshipctl/pkg/log"
|
|
||||||
"opendev.org/airship/airshipctl/pkg/remote"
|
"opendev.org/airship/airshipctl/pkg/remote"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoteDirect settings for remotedirect command
|
|
||||||
type RemoteDirectSettings struct {
|
|
||||||
*environment.AirshipCTLSettings
|
|
||||||
|
|
||||||
RemoteConfig remote.RemoteDirectConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitFlags adds flags and their default settings for Remote Direct command
|
|
||||||
func (cmdSetting *RemoteDirectSettings) InitFlags(cmd *cobra.Command) {
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
// TODO: Remove CLI flags after reading configuration from config documents
|
|
||||||
// ========================================================================
|
|
||||||
flags.StringVar(&cmdSetting.RemoteConfig.RemoteURL,
|
|
||||||
"remote-url",
|
|
||||||
"http://localhost:8000",
|
|
||||||
"[Temporary. Will be removed] Remote Redfish/Smash URL")
|
|
||||||
|
|
||||||
flags.StringVar(&cmdSetting.RemoteConfig.EphemeralNodeId,
|
|
||||||
"eph-node-id",
|
|
||||||
"",
|
|
||||||
"[Temporary. Will be removed] Ephemeral Node ID")
|
|
||||||
|
|
||||||
flags.StringVar(&cmdSetting.RemoteConfig.IsoPath,
|
|
||||||
"iso-path",
|
|
||||||
"",
|
|
||||||
"[Temporary. Will be removed] Remote ISO path for ephemeral node")
|
|
||||||
|
|
||||||
flags.StringVar(&cmdSetting.RemoteConfig.RemoteType,
|
|
||||||
"remote-type",
|
|
||||||
"redfish",
|
|
||||||
"Remote type e.g. redfish, smash etc.")
|
|
||||||
|
|
||||||
err := cmd.MarkFlagRequired("eph-node-id")
|
|
||||||
if err != nil {
|
|
||||||
alog.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.MarkFlagRequired("iso-path")
|
|
||||||
if err != nil {
|
|
||||||
alog.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New Bootstrap remote direct subcommand
|
// New Bootstrap remote direct subcommand
|
||||||
func NewRemoteDirectCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
|
func NewRemoteDirectCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
|
||||||
settings := &RemoteDirectSettings{AirshipCTLSettings: rootSettings}
|
|
||||||
remoteDirect := &cobra.Command{
|
remoteDirect := &cobra.Command{
|
||||||
Use: "remotedirect",
|
Use: "remotedirect",
|
||||||
Short: "Bootstrap ephemeral node",
|
Short: "Bootstrap ephemeral node",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
/* TODO: Get config from settings.GetCurrentContext() and remove cli arguments */
|
return remote.DoRemoteDirect(rootSettings)
|
||||||
|
|
||||||
/* Trigger remotedirect based on remote type */
|
|
||||||
return remote.DoRemoteDirect(settings.RemoteConfig)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.InitFlags(remoteDirect)
|
|
||||||
|
|
||||||
return remoteDirect
|
return remoteDirect
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,4 @@ Usage:
|
|||||||
remotedirect [flags]
|
remotedirect [flags]
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
--eph-node-id string [Temporary. Will be removed] Ephemeral Node ID
|
|
||||||
-h, --help help for remotedirect
|
-h, --help help for remotedirect
|
||||||
--iso-path string [Temporary. Will be removed] Remote ISO path for ephemeral node
|
|
||||||
--remote-type string Remote type e.g. redfish, smash etc. (default "redfish")
|
|
||||||
--remote-url string [Temporary. Will be removed] Remote Redfish/Smash URL (default "http://localhost:8000")
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
SystemRebootDelay = 2 * time.Second
|
SystemRebootDelay = 2 * time.Second
|
||||||
|
RedfishURLSchemeSeparator = "+"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redfish Id ref is a URI which contains resource Id
|
// Redfish Id ref is a URI which contains resource Id
|
||||||
|
@ -2,57 +2,58 @@ package remote
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/document"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
alog "opendev.org/airship/airshipctl/pkg/log"
|
alog "opendev.org/airship/airshipctl/pkg/log"
|
||||||
"opendev.org/airship/airshipctl/pkg/remote/redfish"
|
"opendev.org/airship/airshipctl/pkg/remote/redfish"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/v3/pkg/fs"
|
||||||
|
"sigs.k8s.io/kustomize/v3/pkg/gvk"
|
||||||
|
"sigs.k8s.io/kustomize/v3/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AirshipRemoteTypeRedfish string = "redfish"
|
AirshipRemoteTypeRedfish string = "redfish"
|
||||||
AirshipRemoteTypeSmash string = "smash"
|
AirshipRemoteTypeSmash string = "smash"
|
||||||
|
AirshipHostKind string = "BareMetalHost"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This structure defines the common remote direct config
|
|
||||||
// for all remote types.
|
|
||||||
type RemoteDirectConfig struct {
|
|
||||||
// remote type
|
|
||||||
RemoteType string
|
|
||||||
|
|
||||||
// remote URL
|
|
||||||
RemoteURL string
|
|
||||||
|
|
||||||
// ephemeral Host ID
|
|
||||||
EphemeralNodeId string
|
|
||||||
|
|
||||||
// ISO URL
|
|
||||||
IsoPath string
|
|
||||||
|
|
||||||
// TODO: Ephemeral Node IP
|
|
||||||
|
|
||||||
// TODO: kubeconfig (in object form or raw yaml?) for ephemeral node validation.
|
|
||||||
|
|
||||||
// TODO: More fields can be added on need basis
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface to be implemented by remoteDirect implementation
|
// Interface to be implemented by remoteDirect implementation
|
||||||
type RemoteDirectClient interface {
|
type RemoteDirectClient interface {
|
||||||
DoRemoteDirect() error
|
DoRemoteDirect() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get remotedirect client based on config
|
// Get remotedirect client based on config
|
||||||
func getRemoteDirectClient(remoteConfig RemoteDirectConfig) (RemoteDirectClient, error) {
|
func getRemoteDirectClient(remoteConfig *config.RemoteDirect, remoteURL string) (RemoteDirectClient, error) {
|
||||||
var client RemoteDirectClient
|
var client RemoteDirectClient
|
||||||
var err error
|
|
||||||
|
|
||||||
switch remoteConfig.RemoteType {
|
switch remoteConfig.RemoteType {
|
||||||
case AirshipRemoteTypeRedfish:
|
case AirshipRemoteTypeRedfish:
|
||||||
alog.Debug("Remote type redfish")
|
alog.Debug("Remote type redfish")
|
||||||
|
|
||||||
|
rfURL, err := url.Parse(remoteURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL := fmt.Sprintf("%s://%s", rfURL.Scheme, rfURL.Host)
|
||||||
|
schemeSplit := strings.Split(rfURL.Scheme, redfish.RedfishURLSchemeSeparator)
|
||||||
|
if len(schemeSplit) > 1 {
|
||||||
|
baseURL = fmt.Sprintf("%s://%s", schemeSplit[len(schemeSplit)-1], rfURL.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlPath := strings.Split(rfURL.Path, "/")
|
||||||
|
nodeID := urlPath[len(urlPath)-1]
|
||||||
|
|
||||||
client, err = redfish.NewRedfishRemoteDirectClient(
|
client, err = redfish.NewRedfishRemoteDirectClient(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
remoteConfig.RemoteURL,
|
baseURL,
|
||||||
remoteConfig.EphemeralNodeId,
|
nodeID,
|
||||||
remoteConfig.IsoPath)
|
remoteConfig.IsoURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
alog.Debugf("redfish remotedirect client creation failed")
|
alog.Debugf("redfish remotedirect client creation failed")
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -65,9 +66,56 @@ func getRemoteDirectClient(remoteConfig RemoteDirectConfig) (RemoteDirectClient,
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRemoteDirectConfig(settings *environment.AirshipCTLSettings) (*config.RemoteDirect, string, error) {
|
||||||
|
cfg := settings.Config()
|
||||||
|
manifest, err := cfg.CurrentContextManifest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
bootstrapSettings, err := cfg.CurrentContextBootstrapInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO (dukov) replace with the appropriate function once it's available
|
||||||
|
// in document module
|
||||||
|
docBundle, err := document.NewBundle(fs.MakeRealFS(), manifest.TargetPath, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := types.Selector{
|
||||||
|
Gvk: gvk.FromKind(AirshipHostKind),
|
||||||
|
LabelSelector: document.EphemeralClusterMarker,
|
||||||
|
}
|
||||||
|
docs, err := docBundle.Select(filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if len(docs) == 0 {
|
||||||
|
return nil, "", document.ErrDocNotFound{
|
||||||
|
Annotation: document.EphemeralClusterMarker,
|
||||||
|
Kind: AirshipHostKind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE If filter returned more than one document chose first
|
||||||
|
remoteURL, err := docs[0].GetString("spec.bmc.address")
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootstrapSettings.RemoteDirect, remoteURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Top level function to execute remote direct based on remote type
|
// Top level function to execute remote direct based on remote type
|
||||||
func DoRemoteDirect(remoteConfig RemoteDirectConfig) error {
|
func DoRemoteDirect(settings *environment.AirshipCTLSettings) error {
|
||||||
client, err := getRemoteDirectClient(remoteConfig)
|
remoteConfig, remoteURL, err := getRemoteDirectConfig(settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := getRemoteDirectClient(remoteConfig, remoteURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -4,75 +4,69 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/remote/redfish"
|
"opendev.org/airship/airshipctl/pkg/remote/redfish"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUnknownRemoteType(t *testing.T) {
|
func initSettings(t *testing.T, rd *config.RemoteDirect, testdata string) *environment.AirshipCTLSettings {
|
||||||
rdCfg := RemoteDirectConfig{
|
settings := &environment.AirshipCTLSettings{}
|
||||||
RemoteType: "new-remote",
|
settings.SetConfig(config.DummyConfig())
|
||||||
RemoteURL: "http://localhost:8000",
|
bi, err := settings.Config().CurrentContextBootstrapInfo()
|
||||||
EphemeralNodeId: "test-node",
|
require.NoError(t, err)
|
||||||
IsoPath: "/test.iso",
|
bi.RemoteDirect = rd
|
||||||
}
|
cm, err := settings.Config().CurrentContextManifest()
|
||||||
|
require.NoError(t, err)
|
||||||
|
cm.TargetPath = "testdata/" + testdata
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
err := DoRemoteDirect(rdCfg)
|
func TestUnknownRemoteType(t *testing.T) {
|
||||||
|
s := initSettings(
|
||||||
|
t,
|
||||||
|
&config.RemoteDirect{
|
||||||
|
RemoteType: "new-remote",
|
||||||
|
IsoURL: "/test.iso",
|
||||||
|
},
|
||||||
|
"base",
|
||||||
|
)
|
||||||
|
|
||||||
|
err := DoRemoteDirect(s)
|
||||||
|
|
||||||
_, ok := err.(*RemoteDirectError)
|
_, ok := err.(*RemoteDirectError)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedfishRemoteDirectWithBogusConfig(t *testing.T) {
|
|
||||||
rdCfg := RemoteDirectConfig{
|
|
||||||
RemoteType: "redfish",
|
|
||||||
RemoteURL: "http://nolocalhost:8888",
|
|
||||||
EphemeralNodeId: "test-node",
|
|
||||||
IsoPath: "/test.iso",
|
|
||||||
}
|
|
||||||
|
|
||||||
err := DoRemoteDirect(rdCfg)
|
|
||||||
|
|
||||||
_, ok := err.(*redfish.RedfishClientError)
|
|
||||||
assert.True(t, ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRedfishRemoteDirectWithEmptyURL(t *testing.T) {
|
func TestRedfishRemoteDirectWithEmptyURL(t *testing.T) {
|
||||||
rdCfg := RemoteDirectConfig{
|
s := initSettings(
|
||||||
|
t,
|
||||||
|
&config.RemoteDirect{
|
||||||
RemoteType: "redfish",
|
RemoteType: "redfish",
|
||||||
RemoteURL: "",
|
IsoURL: "/test.iso",
|
||||||
EphemeralNodeId: "test-node",
|
},
|
||||||
IsoPath: "/test.iso",
|
"emptyurl",
|
||||||
}
|
)
|
||||||
|
|
||||||
err := DoRemoteDirect(rdCfg)
|
err := DoRemoteDirect(s)
|
||||||
|
|
||||||
_, ok := err.(*redfish.RedfishConfigError)
|
|
||||||
assert.True(t, ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRedfishRemoteDirectWithEmptyNodeID(t *testing.T) {
|
|
||||||
rdCfg := RemoteDirectConfig{
|
|
||||||
RemoteType: "redfish",
|
|
||||||
RemoteURL: "http://nolocalhost:8888",
|
|
||||||
EphemeralNodeId: "",
|
|
||||||
IsoPath: "/test.iso",
|
|
||||||
}
|
|
||||||
|
|
||||||
err := DoRemoteDirect(rdCfg)
|
|
||||||
|
|
||||||
_, ok := err.(*redfish.RedfishConfigError)
|
_, ok := err.(*redfish.RedfishConfigError)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedfishRemoteDirectWithEmptyIsoPath(t *testing.T) {
|
func TestRedfishRemoteDirectWithEmptyIsoPath(t *testing.T) {
|
||||||
rdCfg := RemoteDirectConfig{
|
s := initSettings(
|
||||||
|
t,
|
||||||
|
&config.RemoteDirect{
|
||||||
RemoteType: "redfish",
|
RemoteType: "redfish",
|
||||||
RemoteURL: "http://nolocalhost:8888",
|
IsoURL: "",
|
||||||
EphemeralNodeId: "123",
|
},
|
||||||
IsoPath: "",
|
"base",
|
||||||
}
|
)
|
||||||
|
|
||||||
err := DoRemoteDirect(rdCfg)
|
err := DoRemoteDirect(s)
|
||||||
|
|
||||||
_, ok := err.(*redfish.RedfishConfigError)
|
_, ok := err.(*redfish.RedfishConfigError)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
49
pkg/remote/testdata/base/baremetal.yaml
vendored
Normal file
49
pkg/remote/testdata/base/baremetal.yaml
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
apiVersion: metal3.io/v1alpha1
|
||||||
|
kind: BareMetalHost
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
airshipit.org/clustertype: ephemeral
|
||||||
|
name: master-0
|
||||||
|
spec:
|
||||||
|
online: true
|
||||||
|
bootMACAddress: 00:3b:8b:0c:ec:8b
|
||||||
|
bmc:
|
||||||
|
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/test-node
|
||||||
|
credentialsName: master-0-bmc-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
airshipit.org/clustertype: ephemeral
|
||||||
|
name: master-0-bmc-secret
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
username: YWRtaW4=
|
||||||
|
password: cGFzc3dvcmQ=
|
||||||
|
---
|
||||||
|
apiVersion: metal3.io/v1alpha1
|
||||||
|
kind: BareMetalHost
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
airshipit.org/clustertype: target
|
||||||
|
name: master-1
|
||||||
|
spec:
|
||||||
|
online: true
|
||||||
|
bootMACAddress: 01:3b:8b:0c:ec:8b
|
||||||
|
bmc:
|
||||||
|
address: ipmi://192.168.111.2:6230
|
||||||
|
credentialsName: master-1-bmc-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
airshipit.org/clustertype: target
|
||||||
|
name: master-1-bmc-secret
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
username: YWRtaW4=
|
||||||
|
password: cGFzc3dvcmQ=
|
||||||
|
...
|
2
pkg/remote/testdata/base/kustomization.yaml
vendored
Normal file
2
pkg/remote/testdata/base/kustomization.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resources:
|
||||||
|
- baremetal.yaml
|
49
pkg/remote/testdata/emptyurl/baremetal.yaml
vendored
Normal file
49
pkg/remote/testdata/emptyurl/baremetal.yaml
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
apiVersion: metal3.io/v1alpha1
|
||||||
|
kind: BareMetalHost
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
airshipit.org/clustertype: ephemeral
|
||||||
|
name: master-0
|
||||||
|
spec:
|
||||||
|
online: true
|
||||||
|
bootMACAddress: 00:3b:8b:0c:ec:8b
|
||||||
|
bmc:
|
||||||
|
address: ""
|
||||||
|
credentialsName: master-0-bmc-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
airshipit.org/clustertype: ephemeral
|
||||||
|
name: master-0-bmc-secret
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
username: YWRtaW4=
|
||||||
|
password: cGFzc3dvcmQ=
|
||||||
|
---
|
||||||
|
apiVersion: metal3.io/v1alpha1
|
||||||
|
kind: BareMetalHost
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
airshipit.org/clustertype: target
|
||||||
|
name: master-1
|
||||||
|
spec:
|
||||||
|
online: true
|
||||||
|
bootMACAddress: 01:3b:8b:0c:ec:8b
|
||||||
|
bmc:
|
||||||
|
address: ipmi://192.168.111.2:6230
|
||||||
|
credentialsName: master-1-bmc-secret
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
airshipit.org/clustertype: target
|
||||||
|
name: master-1-bmc-secret
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
username: YWRtaW4=
|
||||||
|
password: cGFzc3dvcmQ=
|
||||||
|
...
|
2
pkg/remote/testdata/emptyurl/kustomization.yaml
vendored
Normal file
2
pkg/remote/testdata/emptyurl/kustomization.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resources:
|
||||||
|
- baremetal.yaml
|
Loading…
Reference in New Issue
Block a user