Merge "Add Dell Redfish client"
This commit is contained in:
commit
60eff66884
pkg
config
remote
@ -14,6 +14,8 @@
|
||||
|
||||
package config
|
||||
|
||||
import "opendev.org/airship/airshipctl/pkg/remote/redfish"
|
||||
|
||||
// Constants related to the ClusterType type
|
||||
const (
|
||||
Ephemeral = "ephemeral"
|
||||
@ -50,7 +52,7 @@ const (
|
||||
// Modules
|
||||
AirshipDefaultBootstrapImage = "quay.io/airshipit/isogen:latest"
|
||||
AirshipDefaultIsoURL = "http://localhost:8099/debian-custom.iso"
|
||||
AirshipDefaultRemoteType = "redfish"
|
||||
AirshipDefaultRemoteType = redfish.ClientType
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -39,7 +39,8 @@ type Client struct {
|
||||
ephemeralNodeID string
|
||||
isoPath string
|
||||
redfishURL url.URL
|
||||
redfishAPI redfishAPI.RedfishAPI
|
||||
RedfishAPI redfishAPI.RedfishAPI
|
||||
RedfishCFG *redfishClient.Configuration
|
||||
}
|
||||
|
||||
// EphemeralNodeID retrieves the ephemeral node ID.
|
||||
@ -57,7 +58,7 @@ func (c *Client) RebootSystem(ctx context.Context, systemID string) error {
|
||||
}
|
||||
|
||||
for retry := 0; retry <= totalRetries; retry++ {
|
||||
system, httpResp, err := c.redfishAPI.GetSystem(ctx, systemID)
|
||||
system, httpResp, err := c.RedfishAPI.GetSystem(ctx, systemID)
|
||||
if err = ScreenRedfishError(httpResp, err); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -73,7 +74,7 @@ func (c *Client) RebootSystem(ctx context.Context, systemID string) error {
|
||||
|
||||
// Send PowerOff request
|
||||
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
|
||||
_, httpResp, err := c.redfishAPI.ResetSystem(ctx, systemID, resetReq)
|
||||
_, httpResp, err := c.RedfishAPI.ResetSystem(ctx, systemID, resetReq)
|
||||
if err = ScreenRedfishError(httpResp, err); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -85,7 +86,7 @@ func (c *Client) RebootSystem(ctx context.Context, systemID string) error {
|
||||
|
||||
// Send PowerOn request
|
||||
resetReq.ResetType = redfishClient.RESETTYPE_ON
|
||||
_, httpResp, err = c.redfishAPI.ResetSystem(ctx, systemID, resetReq)
|
||||
_, httpResp, err = c.RedfishAPI.ResetSystem(ctx, systemID, resetReq)
|
||||
if err = ScreenRedfishError(httpResp, err); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -97,13 +98,13 @@ func (c *Client) RebootSystem(ctx context.Context, systemID string) error {
|
||||
// SetEphemeralBootSourceByType sets the boot source of the ephemeral node to one that's compatible with the boot
|
||||
// source type.
|
||||
func (c *Client) SetEphemeralBootSourceByType(ctx context.Context) error {
|
||||
_, vMediaType, err := GetVirtualMediaID(ctx, c.redfishAPI, c.ephemeralNodeID)
|
||||
_, vMediaType, err := GetVirtualMediaID(ctx, c.RedfishAPI, c.ephemeralNodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve system information, containing available boot sources
|
||||
system, _, err := c.redfishAPI.GetSystem(ctx, c.ephemeralNodeID)
|
||||
system, _, err := c.RedfishAPI.GetSystem(ctx, c.ephemeralNodeID)
|
||||
if err != nil {
|
||||
return ErrRedfishClient{Message: fmt.Sprintf("Get System[%s] failed with err: %v", c.ephemeralNodeID, err)}
|
||||
}
|
||||
@ -114,7 +115,7 @@ func (c *Client) SetEphemeralBootSourceByType(ctx context.Context) error {
|
||||
/* set boot source */
|
||||
systemReq := redfishClient.ComputerSystem{}
|
||||
systemReq.Boot.BootSourceOverrideTarget = bootSource
|
||||
_, httpResp, err := c.redfishAPI.SetSystem(ctx, c.ephemeralNodeID, systemReq)
|
||||
_, httpResp, err := c.RedfishAPI.SetSystem(ctx, c.ephemeralNodeID, systemReq)
|
||||
return ScreenRedfishError(httpResp, err)
|
||||
}
|
||||
}
|
||||
@ -127,14 +128,14 @@ func (c *Client) SetEphemeralBootSourceByType(ctx context.Context) error {
|
||||
func (c *Client) SetVirtualMedia(ctx context.Context, isoPath string) error {
|
||||
log.Debugf("Ephemeral Node System ID: '%s'", c.ephemeralNodeID)
|
||||
|
||||
managerID, err := getManagerID(ctx, c.redfishAPI, c.ephemeralNodeID)
|
||||
managerID, err := GetManagerID(ctx, c.RedfishAPI, c.ephemeralNodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Ephemeral node managerID: '%s'", managerID)
|
||||
|
||||
vMediaID, _, err := GetVirtualMediaID(ctx, c.redfishAPI, c.ephemeralNodeID)
|
||||
vMediaID, _, err := GetVirtualMediaID(ctx, c.RedfishAPI, c.ephemeralNodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -142,7 +143,7 @@ func (c *Client) SetVirtualMedia(ctx context.Context, isoPath string) error {
|
||||
vMediaReq := redfishClient.InsertMediaRequestBody{}
|
||||
vMediaReq.Image = isoPath
|
||||
vMediaReq.Inserted = true
|
||||
_, httpResp, err := c.redfishAPI.InsertVirtualMedia(ctx, managerID, vMediaID, vMediaReq)
|
||||
_, httpResp, err := c.RedfishAPI.InsertVirtualMedia(ctx, managerID, vMediaID, vMediaReq)
|
||||
return ScreenRedfishError(httpResp, err)
|
||||
}
|
||||
|
||||
@ -151,14 +152,14 @@ func (c *Client) SystemPowerOff(ctx context.Context, systemID string) error {
|
||||
resetReq := redfishClient.ResetRequestBody{}
|
||||
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
|
||||
|
||||
_, httpResp, err := c.redfishAPI.ResetSystem(ctx, systemID, resetReq)
|
||||
_, httpResp, err := c.RedfishAPI.ResetSystem(ctx, systemID, resetReq)
|
||||
|
||||
return ScreenRedfishError(httpResp, err)
|
||||
}
|
||||
|
||||
// SystemPowerStatus retrieves the power status of a host as a human-readable string.
|
||||
func (c *Client) SystemPowerStatus(ctx context.Context, systemID string) (string, error) {
|
||||
computerSystem, httpResp, err := c.redfishAPI.GetSystem(ctx, systemID)
|
||||
computerSystem, httpResp, err := c.RedfishAPI.GetSystem(ctx, systemID)
|
||||
if err = ScreenRedfishError(httpResp, err); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -197,8 +198,9 @@ func NewClient(ephemeralNodeID string,
|
||||
cfg := &redfishClient.Configuration{
|
||||
BasePath: redfishURL,
|
||||
DefaultHeader: make(map[string]string),
|
||||
UserAgent: "airshipctl/client",
|
||||
UserAgent: headerUserAgent,
|
||||
}
|
||||
|
||||
// see https://github.com/golang/go/issues/26013
|
||||
// We clone the default transport to ensure when we customize the transport
|
||||
// that we are providing it sane timeouts and other defaults that we would
|
||||
@ -224,7 +226,8 @@ func NewClient(ephemeralNodeID string,
|
||||
ephemeralNodeID: ephemeralNodeID,
|
||||
isoPath: isoPath,
|
||||
redfishURL: *parsedURL,
|
||||
redfishAPI: redfishClient.NewAPIClient(cfg).DefaultApi,
|
||||
RedfishAPI: redfishClient.NewAPIClient(cfg).DefaultApi,
|
||||
RedfishCFG: cfg,
|
||||
}
|
||||
|
||||
return ctx, c, nil
|
||||
|
@ -78,7 +78,7 @@ func TestRebootSystem(t *testing.T) {
|
||||
Return(redfishClient.ComputerSystem{PowerState: redfishClient.POWERSTATE_ON}, httpResp, nil)
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.RebootSystem(ctx, ephemeralNodeID)
|
||||
assert.NoError(t, err)
|
||||
@ -99,7 +99,7 @@ func TestRebootSystemShutdownError(t *testing.T) {
|
||||
&http.Response{StatusCode: 401}, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.RebootSystem(ctx, ephemeralNodeID)
|
||||
_, ok := err.(ErrRedfishClient)
|
||||
@ -133,7 +133,7 @@ func TestRebootSystemStartupError(t *testing.T) {
|
||||
&http.Response{StatusCode: 401}, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.RebootSystem(ctx, systemID)
|
||||
_, ok := err.(ErrRedfishClient)
|
||||
@ -160,7 +160,7 @@ func TestRebootSystemTimeout(t *testing.T) {
|
||||
Return(redfishClient.ComputerSystem{}, &http.Response{StatusCode: 200}, nil)
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.RebootSystem(ctx, systemID)
|
||||
assert.Equal(t, ErrOperationRetriesExceeded{}, err)
|
||||
@ -178,7 +178,7 @@ func TestSetEphemeralBootSourceByTypeGetSystemError(t *testing.T) {
|
||||
&http.Response{StatusCode: 500}, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.SetEphemeralBootSourceByType(ctx)
|
||||
assert.Error(t, err)
|
||||
@ -201,7 +201,7 @@ func TestSetEphemeralBootSourceByTypeSetSystemError(t *testing.T) {
|
||||
redfishClient.ComputerSystem{}, &http.Response{StatusCode: 401}, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.SetEphemeralBootSourceByType(ctx)
|
||||
assert.Error(t, err)
|
||||
@ -228,7 +228,7 @@ func TestSetEphemeralBootSourceByTypeBootSourceUnavailable(t *testing.T) {
|
||||
Return(testutil.GetVirtualMedia([]string{"CD"}), httpResp, nil)
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.SetEphemeralBootSourceByType(ctx)
|
||||
_, ok := err.(ErrRedfishClient)
|
||||
@ -247,7 +247,7 @@ func TestSetVirtualMediaGetSystemError(t *testing.T) {
|
||||
nil, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.SetVirtualMedia(ctx, client.isoPath)
|
||||
assert.Error(t, err)
|
||||
@ -271,7 +271,7 @@ func TestSetVirtualMediaInsertVirtualMediaError(t *testing.T) {
|
||||
redfishClient.RedfishError{}, &http.Response{StatusCode: 500}, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.redfishAPI = m
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.SetVirtualMedia(ctx, client.isoPath)
|
||||
_, ok := err.(ErrRedfishClient)
|
||||
|
15
pkg/remote/redfish/constants.go
Normal file
15
pkg/remote/redfish/constants.go
Normal file
@ -0,0 +1,15 @@
|
||||
// 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 redfish
|
||||
|
||||
const headerUserAgent string = "airshipctl/client"
|
@ -29,10 +29,18 @@ const (
|
||||
URLSchemeSeparator = "+"
|
||||
)
|
||||
|
||||
// GetManagerID retrieves the manager ID for a redfish system.
|
||||
func GetManagerID(ctx context.Context, api redfishAPI.RedfishAPI, systemID string) (string, error) {
|
||||
system, _, err := api.GetSystem(ctx, systemID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return GetResourceIDFromURL(system.Links.ManagedBy[0].OdataId), nil
|
||||
}
|
||||
|
||||
// GetResourceIDFromURL returns a parsed Redfish resource ID
|
||||
// from the Redfish URL
|
||||
// Redfish Id ref is a URI which contains resource Id
|
||||
// as the last part.
|
||||
func GetResourceIDFromURL(refURL string) string {
|
||||
u, err := url.Parse(refURL)
|
||||
if err != nil {
|
||||
@ -59,7 +67,7 @@ func IsIDInList(idRefList []redfishClient.IdRef, id string) bool {
|
||||
|
||||
// GetVirtualMediaID retrieves the ID of a Redfish virtual media resource if it supports type "CD" or "DVD".
|
||||
func GetVirtualMediaID(ctx context.Context, api redfishAPI.RedfishAPI, systemID string) (string, string, error) {
|
||||
managerID, err := getManagerID(ctx, api, systemID)
|
||||
managerID, err := GetManagerID(ctx, api, systemID)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -119,12 +127,3 @@ func ScreenRedfishError(httpResp *http.Response, clientErr error) error {
|
||||
|
||||
return ErrRedfishClient{Message: resp.Error.Message}
|
||||
}
|
||||
|
||||
func getManagerID(ctx context.Context, api redfishAPI.RedfishAPI, systemID string) (string, error) {
|
||||
system, _, err := api.GetSystem(ctx, systemID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return GetResourceIDFromURL(system.Links.ManagedBy[0].OdataId), nil
|
||||
}
|
||||
|
56
pkg/remote/redfish/vendors/dell/client.go
vendored
Normal file
56
pkg/remote/redfish/vendors/dell/client.go
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// 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 dell wraps the standard Redfish client in order to provide additional functionality required to perform
|
||||
// actions on iDRAC servers.
|
||||
package dell
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
redfishAPI "opendev.org/airship/go-redfish/api"
|
||||
redfishClient "opendev.org/airship/go-redfish/client"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/remote/redfish"
|
||||
)
|
||||
|
||||
const (
|
||||
// ClientType is used by other packages as the identifier of the Redfish client.
|
||||
ClientType string = "redfish-dell"
|
||||
)
|
||||
|
||||
// Client is a wrapper around the standard airshipctl Redfish client. This allows vendor specific Redfish clients to
|
||||
// override methods without duplicating the entire client.
|
||||
type Client struct {
|
||||
redfish.Client
|
||||
RedfishAPI redfishAPI.RedfishAPI
|
||||
RedfishCFG *redfishClient.Configuration
|
||||
}
|
||||
|
||||
// NewClient returns a client with the capability to make Redfish requests.
|
||||
func NewClient(ephemeralNodeID string,
|
||||
isoPath string,
|
||||
redfishURL string,
|
||||
insecure bool,
|
||||
useProxy bool,
|
||||
username string,
|
||||
password string) (context.Context, *Client, error) {
|
||||
ctx, genericClient, err := redfish.NewClient(
|
||||
ephemeralNodeID, isoPath, redfishURL, insecure, useProxy, username, password)
|
||||
if err != nil {
|
||||
return ctx, nil, err
|
||||
}
|
||||
|
||||
c := &Client{*genericClient, genericClient.RedfishAPI, genericClient.RedfishCFG}
|
||||
|
||||
return ctx, c, nil
|
||||
}
|
34
pkg/remote/redfish/vendors/dell/client_test.go
vendored
Normal file
34
pkg/remote/redfish/vendors/dell/client_test.go
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// 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 dell
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
ephemeralNodeID = "System.Embedded.1"
|
||||
isoPath = "https://localhost:8080/debian.iso"
|
||||
redfishURL = "redfish+https://localhost/Systems/System.Embedded.1"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
// NOTE(drewwalters96): The Dell client implementation of this method simply creates the standard Redfish
|
||||
// client. This test verifies that the Dell client creates and stores an instance of the standard client.
|
||||
|
||||
// Create the Dell client
|
||||
_, _, err := NewClient(ephemeralNodeID, isoPath, redfishURL, false, false, "username", "password")
|
||||
assert.NoError(t, err)
|
||||
}
|
@ -23,6 +23,7 @@ import (
|
||||
"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.
|
||||
@ -38,9 +39,7 @@ type Adapter struct {
|
||||
// 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:
|
||||
alog.Debug("Remote type redfish")
|
||||
|
||||
case redfish.ClientType, redfishDell.ClientType:
|
||||
rfURL, err := url.Parse(remoteURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -66,16 +65,29 @@ func (a *Adapter) configureClient(remoteURL string) error {
|
||||
}
|
||||
}
|
||||
|
||||
a.Context, a.OOBClient, err = redfish.NewClient(
|
||||
nodeID,
|
||||
a.remoteConfig.IsoURL,
|
||||
baseURL,
|
||||
a.remoteConfig.Insecure,
|
||||
a.remoteConfig.UseProxy,
|
||||
a.username,
|
||||
a.password)
|
||||
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 {
|
||||
alog.Debugf("redfish remotedirect client creation failed")
|
||||
return err
|
||||
}
|
||||
default:
|
||||
|
Loading…
x
Reference in New Issue
Block a user