Skip to content

Commit e23dfc5

Browse files
authored
Merge pull request #381 from zevweiss/terminate-sol
Add TerminateSOL method
2 parents 6487b60 + 288790d commit e23dfc5

File tree

7 files changed

+215
-0
lines changed

7 files changed

+215
-0
lines changed

bmc/sol.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package bmc
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/hashicorp/go-multierror"
9+
"github.com/pkg/errors"
10+
)
11+
12+
// SOLDeactivator for deactivating SOL sessions on a BMC.
13+
type SOLDeactivator interface {
14+
DeactivateSOL(ctx context.Context) (err error)
15+
}
16+
17+
// deactivatorProvider is an internal struct to correlate an implementation/provider and its name
18+
type deactivatorProvider struct {
19+
name string
20+
solDeactivator SOLDeactivator
21+
}
22+
23+
// deactivateSOL tries all implementations for a successful SOL deactivation
24+
func deactivateSOL(ctx context.Context, timeout time.Duration, b []deactivatorProvider) (metadata Metadata, err error) {
25+
var metadataLocal Metadata
26+
27+
for _, elem := range b {
28+
if elem.solDeactivator == nil {
29+
continue
30+
}
31+
select {
32+
case <-ctx.Done():
33+
err = multierror.Append(err, ctx.Err())
34+
35+
return metadata, err
36+
default:
37+
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
38+
ctx, cancel := context.WithTimeout(ctx, timeout)
39+
defer cancel()
40+
newErr := elem.solDeactivator.DeactivateSOL(ctx)
41+
if newErr != nil {
42+
err = multierror.Append(err, errors.WithMessagef(newErr, "provider: %v", elem.name))
43+
continue
44+
}
45+
metadataLocal.SuccessfulProvider = elem.name
46+
return metadataLocal, nil
47+
}
48+
}
49+
return metadataLocal, multierror.Append(err, errors.New("failed to deactivate SOL session"))
50+
}
51+
52+
// DeactivateSOLFromInterfaces identifies implementations of the SOLDeactivator interface and passes them to the deactivateSOL() wrapper method.
53+
func DeactivateSOLFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (metadata Metadata, err error) {
54+
deactivators := make([]deactivatorProvider, 0)
55+
for _, elem := range generic {
56+
temp := deactivatorProvider{name: getProviderName(elem)}
57+
switch p := elem.(type) {
58+
case SOLDeactivator:
59+
temp.solDeactivator = p
60+
deactivators = append(deactivators, temp)
61+
default:
62+
e := fmt.Sprintf("not an SOLDeactivator implementation: %T", p)
63+
err = multierror.Append(err, errors.New(e))
64+
}
65+
}
66+
if len(deactivators) == 0 {
67+
return metadata, multierror.Append(err, errors.New("no SOLDeactivator implementations found"))
68+
}
69+
return deactivateSOL(ctx, timeout, deactivators)
70+
}

bmc/sol_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package bmc
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/google/go-cmp/cmp"
10+
"github.com/hashicorp/go-multierror"
11+
)
12+
13+
type solTermTester struct {
14+
MakeErrorOut bool
15+
}
16+
17+
func (r *solTermTester) DeactivateSOL(ctx context.Context) (err error) {
18+
if r.MakeErrorOut {
19+
return errors.New("SOL deactivation failed")
20+
}
21+
return nil
22+
}
23+
24+
func (r *solTermTester) Name() string {
25+
return "test provider"
26+
}
27+
28+
func TestDeactivateSOL(t *testing.T) {
29+
testCases := map[string]struct {
30+
makeErrorOut bool
31+
err error
32+
ctxTimeout time.Duration
33+
}{
34+
"success": {makeErrorOut: false},
35+
"error": {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New("provider: test provider: SOL deactivation failed"), errors.New("failed to deactivate SOL session")}}},
36+
"error context timeout": {makeErrorOut: false, err: &multierror.Error{Errors: []error{errors.New("context deadline exceeded")}}, ctxTimeout: time.Nanosecond * 1},
37+
}
38+
39+
for name, tc := range testCases {
40+
t.Run(name, func(t *testing.T) {
41+
testImplementation := solTermTester{MakeErrorOut: tc.makeErrorOut}
42+
if tc.ctxTimeout == 0 {
43+
tc.ctxTimeout = time.Second * 3
44+
}
45+
ctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)
46+
defer cancel()
47+
_, err := deactivateSOL(ctx, 0, []deactivatorProvider{{"test provider", &testImplementation}})
48+
var diff string
49+
if err != nil && tc.err != nil {
50+
diff = cmp.Diff(err.Error(), tc.err.Error())
51+
} else {
52+
diff = cmp.Diff(err, tc.err)
53+
}
54+
if diff != "" {
55+
t.Fatal(diff)
56+
}
57+
})
58+
}
59+
}
60+
61+
func TestDeactivateSOLFromInterfaces(t *testing.T) {
62+
testCases := map[string]struct {
63+
err error
64+
badImplementation bool
65+
withName bool
66+
}{
67+
"success": {},
68+
"success with metadata": {withName: true},
69+
"no implementations found": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New("not an SOLDeactivator implementation: *struct {}"), errors.New("no SOLDeactivator implementations found")}}},
70+
}
71+
72+
for name, tc := range testCases {
73+
t.Run(name, func(t *testing.T) {
74+
var generic []interface{}
75+
if tc.badImplementation {
76+
badImplementation := struct{}{}
77+
generic = []interface{}{&badImplementation}
78+
} else {
79+
testImplementation := solTermTester{}
80+
generic = []interface{}{&testImplementation}
81+
}
82+
metadata, err := DeactivateSOLFromInterfaces(context.Background(), 0, generic)
83+
var diff string
84+
if err != nil && tc.err != nil {
85+
diff = cmp.Diff(err.Error(), tc.err.Error())
86+
} else {
87+
diff = cmp.Diff(err, tc.err)
88+
}
89+
if diff != "" {
90+
t.Fatal(diff)
91+
}
92+
if tc.withName {
93+
if diff := cmp.Diff(metadata.SuccessfulProvider, "test provider"); diff != "" {
94+
t.Fatal(diff)
95+
}
96+
}
97+
})
98+
}
99+
}

client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,15 @@ func (c *Client) ResetBMC(ctx context.Context, resetType string) (ok bool, err e
526526
return ok, err
527527
}
528528

529+
// DeactivateSOL pass through library function to deactivate active SOL sessions
530+
func (c *Client) DeactivateSOL(ctx context.Context) (err error) {
531+
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "DeactivateSOL")
532+
defer span.End()
533+
metadata, err := bmc.DeactivateSOLFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
534+
c.setMetadata(metadata)
535+
return err
536+
}
537+
529538
// Inventory pass through library function to collect hardware and firmware inventory
530539
func (c *Client) Inventory(ctx context.Context) (device *common.Device, err error) {
531540
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "Inventory")

internal/ipmi/ipmi.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,3 +427,13 @@ func (i *Ipmi) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err e
427427

428428
return output, nil
429429
}
430+
431+
func (i *Ipmi) DeactivateSOL(ctx context.Context) (err error) {
432+
out, err := i.run(ctx, []string{"sol", "deactivate"})
433+
// Don't treat this as a failure (we just want to ensure there
434+
// isn't an active SOL session left open)
435+
if strings.TrimSpace(out) == "Info: SOL payload already de-activated" {
436+
err = nil
437+
}
438+
return err
439+
}

providers/ipmitool/ipmitool.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var (
3030
providers.FeatureClearSystemEventLog,
3131
providers.FeatureGetSystemEventLog,
3232
providers.FeatureGetSystemEventLogRaw,
33+
providers.FeatureDeactivateSOL,
3334
}
3435
)
3536

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

153+
// DeactivateSOL will deactivate active SOL sessions
154+
func (c *Conn) DeactivateSOL(ctx context.Context) (err error) {
155+
return c.ipmitool.DeactivateSOL(ctx)
156+
}
157+
152158
// UserRead list all users
153159
func (c *Conn) UserRead(ctx context.Context) (users []map[string]string, err error) {
154160
return c.ipmitool.ReadUsers(ctx)

providers/ipmitool/ipmitool_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ func TestBMCReset(t *testing.T) {
107107
t.Fatal()
108108
}
109109

110+
func TestDeactivateSOL(t *testing.T) {
111+
t.Skip("need real ipmi server")
112+
host := "127.0.0.1"
113+
port := "623"
114+
user := "ADMIN"
115+
pass := "ADMIN"
116+
i, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))
117+
if err != nil {
118+
t.Fatal(err)
119+
}
120+
err = i.DeactivateSOL(context.Background())
121+
if err != nil {
122+
t.Fatal(err)
123+
}
124+
t.Log(err != nil)
125+
t.Fatal()
126+
}
127+
110128
func TestSystemEventLogClear(t *testing.T) {
111129
t.Skip("need real ipmi server")
112130
host := "127.0.0.1"

providers/providers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,7 @@ const (
6060

6161
// FeatureFirmwareUploadInitiateInstall identifies an implementation that uploads firmware _and_ initiates the install process.
6262
FeatureFirmwareUploadInitiateInstall registrar.Feature = "uploadandinitiateinstall"
63+
64+
// FeatureDeactivateSOL means an implementation that can deactivate active SOL sessions
65+
FeatureDeactivateSOL registrar.Feature = "deactivatesol"
6366
)

0 commit comments

Comments
 (0)