Switch remote direct to airshipctl configuration

Related #7
Change-Id: I015b178895359ea468748eb72e367b4ff56026bb
This commit is contained in:
Dmitry Ukov 2020-02-05 10:41:03 +04:00
parent 8b86e4135f
commit 97c114cb21
9 changed files with 228 additions and 138 deletions

View File

@ -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
} }

View File

@ -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")

View File

@ -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

View File

@ -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
} }

View File

@ -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
View 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=
...

View File

@ -0,0 +1,2 @@
resources:
- baremetal.yaml

View 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=
...

View File

@ -0,0 +1,2 @@
resources:
- baremetal.yaml