
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>
187 lines
4.9 KiB
Go
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
|
|
}
|