airshipctl/pkg/remote/remote_direct.go
Drew Walters cb59c859cb Add Dell Redfish client
The Dell Redfish implementation slightly deviates from the DMTF Redfish
specification. One variation is the Dell specification's classification
of virtual media, in which the Dell variation adds support for virtual
CDs and virtual floppy drives [0]. In order to perform some actions on Dell
hardware, airshipctl needs support for vendor specific clients.

This change introduces a vendor-specific Dell client to handle some
Redfish operations.

[0] https://www.dell.com/support/manuals/us/en/04/idrac9-lifecycle-controller-v3.2-series/idrac_3.21.21.21_redfishapiguide/virtualmedia?guid=guid-d9e76cf6-627d-4cb9-a3de-3f2b88b74cfb&lang=en-us

Relates-To: #139

Change-Id: Icc4500177e859d5a4607b98ec2bd2737521d00b1
Signed-off-by: Drew Walters <andrew.walters@att.com>
2020-04-17 19:51:24 +00:00

187 lines
4.9 KiB
Go

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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"
redfishDell "opendev.org/airship/airshipctl/pkg/remote/redfish/vendors/dell"
)
// Adapter bridges the gap between out-of-band clients. It can hold any type of OOB client, e.g. Redfish.
type Adapter struct {
OOBClient Client
Context context.Context
remoteConfig *config.RemoteDirect
remoteURL string
username string
password string
}
// configureClient retrieves a client for remoteDirect requests based on the RemoteType in the Airship config file.
func (a *Adapter) configureClient(remoteURL string) error {
switch a.remoteConfig.RemoteType {
case redfish.ClientType, redfishDell.ClientType:
rfURL, err := url.Parse(remoteURL)
if err != nil {
return err
}
baseURL := fmt.Sprintf("%s://%s", rfURL.Scheme, rfURL.Host)
schemeSplit := strings.Split(rfURL.Scheme, redfish.URLSchemeSeparator)
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]
if nodeID == "" {
return redfish.ErrRedfishMissingConfig{
What: "redfish ephemeral node id empty",
}
}
if a.remoteConfig.IsoURL == "" {
return redfish.ErrRedfishMissingConfig{
What: "redfish ephemeral node iso Path empty",
}
}
if a.remoteConfig.RemoteType == redfishDell.ClientType {
alog.Debug("Remote type: Redfish for Integrated Dell Remote Access Controller (iDrac) systems")
a.Context, a.OOBClient, err = redfishDell.NewClient(
nodeID,
a.remoteConfig.IsoURL,
baseURL,
a.remoteConfig.Insecure,
a.remoteConfig.UseProxy,
a.username,
a.password)
} else {
alog.Debug("Remote type: Redfish")
a.Context, a.OOBClient, err = redfish.NewClient(
nodeID,
a.remoteConfig.IsoURL,
baseURL,
a.remoteConfig.Insecure,
a.remoteConfig.UseProxy,
a.username,
a.password)
}
if err != nil {
return err
}
default:
return NewRemoteDirectErrorf("invalid remote type")
}
return nil
}
// initializeAdapter retrieves the remote direct configuration defined in the Airship configuration file.
func (a *Adapter) initializeAdapter(settings *environment.AirshipCTLSettings) error {
cfg := settings.Config
bootstrapSettings, err := cfg.CurrentContextBootstrapInfo()
if err != nil {
return err
}
a.remoteConfig = bootstrapSettings.RemoteDirect
if a.remoteConfig == nil {
return config.ErrMissingConfig{What: "RemoteDirect options not defined in bootstrap config"}
}
bundlePath, err := cfg.CurrentContextEntryPoint(config.Ephemeral, config.BootstrapPhase)
if err != nil {
return err
}
docBundle, err := document.NewBundleByPath(bundlePath)
if err != nil {
return err
}
selector := document.NewEphemeralBMHSelector()
doc, err := docBundle.SelectOne(selector)
if err != nil {
return err
}
a.remoteURL, err = document.GetBMHBMCAddress(doc)
if err != nil {
return err
}
a.username, a.password, err = document.GetBMHBMCCredentials(doc, docBundle)
if err != nil {
return err
}
return nil
}
// DoRemoteDirect executes remote direct based on remote type.
func (a *Adapter) DoRemoteDirect() error {
alog.Debugf("Using Remote Endpoint: %q", a.remoteURL)
/* Load ISO in manager's virtual media */
err := a.OOBClient.SetVirtualMedia(a.Context, a.remoteConfig.IsoURL)
if err != nil {
return err
}
alog.Debugf("Successfully loaded virtual media: %q", a.remoteConfig.IsoURL)
/* Set system's bootsource to selected media */
err = a.OOBClient.SetEphemeralBootSourceByType(a.Context)
if err != nil {
return err
}
/* Reboot system */
err = a.OOBClient.RebootSystem(a.Context, a.OOBClient.EphemeralNodeID())
if err != nil {
return err
}
alog.Debug("Restarted ephemeral host")
return nil
}
// NewAdapter provides an adapter that exposes the capability to perform remote direct functionality with any
// out-of-band client.
func NewAdapter(settings *environment.AirshipCTLSettings) (*Adapter, error) {
a := &Adapter{}
a.Context = context.Background()
err := a.initializeAdapter(settings)
if err != nil {
return a, err
}
if err := a.configureClient(a.remoteURL); err != nil {
return a, err
}
return a, nil
}