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"
"opendev.org/airship/airshipctl/pkg/environment"
alog "opendev.org/airship/airshipctl/pkg/log"
"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
func NewRemoteDirectCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
settings := &RemoteDirectSettings{AirshipCTLSettings: rootSettings}
remoteDirect := &cobra.Command{
Use: "remotedirect",
Short: "Bootstrap ephemeral node",
RunE: func(cmd *cobra.Command, args []string) error {
/* TODO: Get config from settings.GetCurrentContext() and remove cli arguments */
/* Trigger remotedirect based on remote type */
return remote.DoRemoteDirect(settings.RemoteConfig)
return remote.DoRemoteDirect(rootSettings)
},
}
settings.InitFlags(remoteDirect)
return remoteDirect
}

View File

@ -4,8 +4,4 @@ Usage:
remotedirect [flags]
Flags:
--eph-node-id string [Temporary. Will be removed] Ephemeral Node ID
-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")
-h, --help help for remotedirect

View File

@ -13,7 +13,8 @@ import (
)
const (
SystemRebootDelay = 2 * time.Second
SystemRebootDelay = 2 * time.Second
RedfishURLSchemeSeparator = "+"
)
// Redfish Id ref is a URI which contains resource Id

View File

@ -2,57 +2,58 @@ package remote
import (
"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"
"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 (
AirshipRemoteTypeRedfish string = "redfish"
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
type RemoteDirectClient interface {
DoRemoteDirect() error
}
// 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 err error
switch remoteConfig.RemoteType {
case AirshipRemoteTypeRedfish:
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(
context.Background(),
remoteConfig.RemoteURL,
remoteConfig.EphemeralNodeId,
remoteConfig.IsoPath)
baseURL,
nodeID,
remoteConfig.IsoURL)
if err != nil {
alog.Debugf("redfish remotedirect client creation failed")
return nil, err
@ -65,9 +66,56 @@ func getRemoteDirectClient(remoteConfig RemoteDirectConfig) (RemoteDirectClient,
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
func DoRemoteDirect(remoteConfig RemoteDirectConfig) error {
client, err := getRemoteDirectClient(remoteConfig)
func DoRemoteDirect(settings *environment.AirshipCTLSettings) error {
remoteConfig, remoteURL, err := getRemoteDirectConfig(settings)
if err != nil {
return err
}
client, err := getRemoteDirectClient(remoteConfig, remoteURL)
if err != nil {
return err
}

View File

@ -4,75 +4,69 @@ import (
"testing"
"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"
)
func TestUnknownRemoteType(t *testing.T) {
rdCfg := RemoteDirectConfig{
RemoteType: "new-remote",
RemoteURL: "http://localhost:8000",
EphemeralNodeId: "test-node",
IsoPath: "/test.iso",
}
func initSettings(t *testing.T, rd *config.RemoteDirect, testdata string) *environment.AirshipCTLSettings {
settings := &environment.AirshipCTLSettings{}
settings.SetConfig(config.DummyConfig())
bi, err := settings.Config().CurrentContextBootstrapInfo()
require.NoError(t, err)
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)
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) {
rdCfg := RemoteDirectConfig{
RemoteType: "redfish",
RemoteURL: "",
EphemeralNodeId: "test-node",
IsoPath: "/test.iso",
}
s := initSettings(
t,
&config.RemoteDirect{
RemoteType: "redfish",
IsoURL: "/test.iso",
},
"emptyurl",
)
err := DoRemoteDirect(rdCfg)
_, 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)
err := DoRemoteDirect(s)
_, ok := err.(*redfish.RedfishConfigError)
assert.True(t, ok)
}
func TestRedfishRemoteDirectWithEmptyIsoPath(t *testing.T) {
rdCfg := RemoteDirectConfig{
RemoteType: "redfish",
RemoteURL: "http://nolocalhost:8888",
EphemeralNodeId: "123",
IsoPath: "",
}
s := initSettings(
t,
&config.RemoteDirect{
RemoteType: "redfish",
IsoURL: "",
},
"base",
)
err := DoRemoteDirect(rdCfg)
err := DoRemoteDirect(s)
_, ok := err.(*redfish.RedfishConfigError)
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