Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TerminateSOL method #381

Merged
merged 2 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions bmc/sol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package bmc

import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)

// SOLDeactivator for deactivating SOL sessions on a BMC.
type SOLDeactivator interface {
DeactivateSOL(ctx context.Context) (err error)
}

// deactivatorProvider is an internal struct to correlate an implementation/provider and its name
type deactivatorProvider struct {
name string
solDeactivator SOLDeactivator
}

// deactivateSOL tries all implementations for a successful SOL deactivation
func deactivateSOL(ctx context.Context, timeout time.Duration, b []deactivatorProvider) (metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range b {
if elem.solDeactivator == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
newErr := elem.solDeactivator.DeactivateSOL(ctx)
if newErr != nil {
err = multierror.Append(err, errors.WithMessagef(newErr, "provider: %v", elem.name))
continue
}
metadataLocal.SuccessfulProvider = elem.name
return metadataLocal, nil
}
}
return metadataLocal, multierror.Append(err, errors.New("failed to deactivate SOL session"))
}

// DeactivateSOLFromInterfaces identifies implementations of the SOLDeactivator interface and passes them to the deactivateSOL() wrapper method.
func DeactivateSOLFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (metadata Metadata, err error) {
deactivators := make([]deactivatorProvider, 0)
for _, elem := range generic {
temp := deactivatorProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case SOLDeactivator:
temp.solDeactivator = p
deactivators = append(deactivators, temp)
default:
e := fmt.Sprintf("not an SOLDeactivator implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(deactivators) == 0 {
return metadata, multierror.Append(err, errors.New("no SOLDeactivator implementations found"))
}
return deactivateSOL(ctx, timeout, deactivators)
}
99 changes: 99 additions & 0 deletions bmc/sol_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package bmc

import (
"context"
"errors"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-multierror"
)

type solTermTester struct {
MakeErrorOut bool
}

func (r *solTermTester) DeactivateSOL(ctx context.Context) (err error) {
if r.MakeErrorOut {
return errors.New("SOL deactivation failed")
}
return nil
}

func (r *solTermTester) Name() string {
return "test provider"
}

func TestDeactivateSOL(t *testing.T) {
testCases := map[string]struct {
makeErrorOut bool
err error
ctxTimeout time.Duration
}{
"success": {makeErrorOut: false},
"error": {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New("provider: test provider: SOL deactivation failed"), errors.New("failed to deactivate SOL session")}}},
"error context timeout": {makeErrorOut: false, err: &multierror.Error{Errors: []error{errors.New("context deadline exceeded")}}, ctxTimeout: time.Nanosecond * 1},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
testImplementation := solTermTester{MakeErrorOut: tc.makeErrorOut}
if tc.ctxTimeout == 0 {
tc.ctxTimeout = time.Second * 3
}
ctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)
defer cancel()
_, err := deactivateSOL(ctx, 0, []deactivatorProvider{{"test provider", &testImplementation}})
var diff string
if err != nil && tc.err != nil {
diff = cmp.Diff(err.Error(), tc.err.Error())
} else {
diff = cmp.Diff(err, tc.err)
}
if diff != "" {
t.Fatal(diff)
}
})
}
}

func TestDeactivateSOLFromInterfaces(t *testing.T) {
testCases := map[string]struct {
err error
badImplementation bool
withName bool
}{
"success": {},
"success with metadata": {withName: true},
"no implementations found": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New("not an SOLDeactivator implementation: *struct {}"), errors.New("no SOLDeactivator implementations found")}}},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
var generic []interface{}
if tc.badImplementation {
badImplementation := struct{}{}
generic = []interface{}{&badImplementation}
} else {
testImplementation := solTermTester{}
generic = []interface{}{&testImplementation}
}
metadata, err := DeactivateSOLFromInterfaces(context.Background(), 0, generic)
var diff string
if err != nil && tc.err != nil {
diff = cmp.Diff(err.Error(), tc.err.Error())
} else {
diff = cmp.Diff(err, tc.err)
}
if diff != "" {
t.Fatal(diff)
}
if tc.withName {
if diff := cmp.Diff(metadata.SuccessfulProvider, "test provider"); diff != "" {
t.Fatal(diff)
}
}
})
}
}
9 changes: 9 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,15 @@ func (c *Client) ResetBMC(ctx context.Context, resetType string) (ok bool, err e
return ok, err
}

// DeactivateSOL pass through library function to deactivate active SOL sessions
func (c *Client) DeactivateSOL(ctx context.Context) (err error) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "DeactivateSOL")
defer span.End()
metadata, err := bmc.DeactivateSOLFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return err
}

// Inventory pass through library function to collect hardware and firmware inventory
func (c *Client) Inventory(ctx context.Context) (device *common.Device, err error) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "Inventory")
Expand Down
10 changes: 10 additions & 0 deletions internal/ipmi/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,13 @@ func (i *Ipmi) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err e

return output, nil
}

func (i *Ipmi) DeactivateSOL(ctx context.Context) (err error) {
out, err := i.run(ctx, []string{"sol", "deactivate"})
// Don't treat this as a failure (we just want to ensure there
// isn't an active SOL session left open)
if strings.TrimSpace(out) == "Info: SOL payload already de-activated" {
err = nil
}
return err
}
6 changes: 6 additions & 0 deletions providers/ipmitool/ipmitool.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var (
providers.FeatureClearSystemEventLog,
providers.FeatureGetSystemEventLog,
providers.FeatureGetSystemEventLogRaw,
providers.FeatureDeactivateSOL,
}
)

Expand Down Expand Up @@ -149,6 +150,11 @@ func (c *Conn) BmcReset(ctx context.Context, resetType string) (ok bool, err err
return c.ipmitool.PowerResetBmc(ctx, resetType)
}

// DeactivateSOL will deactivate active SOL sessions
func (c *Conn) DeactivateSOL(ctx context.Context) (err error) {
return c.ipmitool.DeactivateSOL(ctx)
}

// UserRead list all users
func (c *Conn) UserRead(ctx context.Context) (users []map[string]string, err error) {
return c.ipmitool.ReadUsers(ctx)
Expand Down
18 changes: 18 additions & 0 deletions providers/ipmitool/ipmitool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,24 @@ func TestBMCReset(t *testing.T) {
t.Fatal()
}

func TestDeactivateSOL(t *testing.T) {
t.Skip("need real ipmi server")
host := "127.0.0.1"
port := "623"
user := "ADMIN"
pass := "ADMIN"
i, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))
if err != nil {
t.Fatal(err)
}
err = i.DeactivateSOL(context.Background())
if err != nil {
t.Fatal(err)
}
t.Log(err != nil)
t.Fatal()
}

func TestSystemEventLogClear(t *testing.T) {
t.Skip("need real ipmi server")
host := "127.0.0.1"
Expand Down
3 changes: 3 additions & 0 deletions providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,7 @@ const (

// FeatureFirmwareUploadInitiateInstall identifies an implementation that uploads firmware _and_ initiates the install process.
FeatureFirmwareUploadInitiateInstall registrar.Feature = "uploadandinitiateinstall"

// FeatureDeactivateSOL means an implementation that can deactivate active SOL sessions
FeatureDeactivateSOL registrar.Feature = "deactivatesol"
)