diff --git a/bmc/sol.go b/bmc/sol.go new file mode 100644 index 00000000..daafb607 --- /dev/null +++ b/bmc/sol.go @@ -0,0 +1,70 @@ +package bmc + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" +) + +// SOLTerminator for terminating SOL sessions on a BMC. +type SOLTerminator interface { + TerminateSOL(ctx context.Context) (err error) +} + +// terminatorProvider is an internal struct to correlate an implementation/provider and its name +type terminatorProvider struct { + name string + solTerminator SOLTerminator +} + +// terminateSOL tries all implementations for a successful SOL termination +func terminateSOL(ctx context.Context, timeout time.Duration, b []terminatorProvider) (ok bool, metadata Metadata, err error) { + var metadataLocal Metadata + + for _, elem := range b { + if elem.solTerminator == nil { + continue + } + select { + case <-ctx.Done(): + err = multierror.Append(err, ctx.Err()) + + return false, metadata, err + default: + metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name) + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + err := elem.solTerminator.TerminateSOL(ctx) + if err != nil { + err = multierror.Append(err, errors.WithMessagef(err, "provider: %v", elem.name)) + continue + } + metadataLocal.SuccessfulProvider = elem.name + return true, metadataLocal, nil + } + } + return false, metadataLocal, multierror.Append(err, errors.New("failed to terminate SOL session")) +} + +// TerminateSOLFromInterfaces identifies implementations of the SOLTerminator interface and passes them to the terminateSOL() wrapper method. +func TerminateSOLFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (ok bool, metadata Metadata, err error) { + terminators := make([]terminatorProvider, 0) + for _, elem := range generic { + temp := terminatorProvider{name: getProviderName(elem)} + switch p := elem.(type) { + case SOLTerminator: + temp.solTerminator = p + terminators = append(terminators, temp) + default: + e := fmt.Sprintf("not an SOLTerminator implementation: %T", p) + err = multierror.Append(err, errors.New(e)) + } + } + if len(terminators) == 0 { + return false, metadata, multierror.Append(err, errors.New("no SOLTerminator implementations found")) + } + return terminateSOL(ctx, timeout, terminators) +} diff --git a/client.go b/client.go index 106e58d1..ea478cab 100644 --- a/client.go +++ b/client.go @@ -526,6 +526,13 @@ func (c *Client) ResetBMC(ctx context.Context, resetType string) (ok bool, err e return ok, err } +// TerminateSOL pass through library function to terminate active SOL sessions +func (c *Client) TerminateSOL(ctx context.Context) (ok bool, err error) { + ok, metadata, err := bmc.TerminateSOLFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces()) + c.setMetadata(metadata) + return ok, 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") diff --git a/internal/ipmi/ipmi.go b/internal/ipmi/ipmi.go index 427a76ae..97bbeeb6 100644 --- a/internal/ipmi/ipmi.go +++ b/internal/ipmi/ipmi.go @@ -427,3 +427,13 @@ func (i *Ipmi) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err e return output, nil } + +func (i *Ipmi) TerminateSOL(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 +} diff --git a/providers/ipmitool/ipmitool.go b/providers/ipmitool/ipmitool.go index d8b5d9b2..c489a6d3 100644 --- a/providers/ipmitool/ipmitool.go +++ b/providers/ipmitool/ipmitool.go @@ -30,6 +30,7 @@ var ( providers.FeatureClearSystemEventLog, providers.FeatureGetSystemEventLog, providers.FeatureGetSystemEventLogRaw, + providers.FeatureTerminateSOL, } ) @@ -149,6 +150,11 @@ func (c *Conn) BmcReset(ctx context.Context, resetType string) (ok bool, err err return c.ipmitool.PowerResetBmc(ctx, resetType) } +// TerminateSOL will terminate active SOL sessions +func (c *Conn) TerminateSOL(ctx context.Context) (err error) { + return c.ipmitool.TerminateSOL(ctx) +} + // UserRead list all users func (c *Conn) UserRead(ctx context.Context) (users []map[string]string, err error) { return c.ipmitool.ReadUsers(ctx) diff --git a/providers/providers.go b/providers/providers.go index dc300577..6a33f98d 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -60,4 +60,7 @@ const ( // FeatureFirmwareUploadInitiateInstall identifies an implementation that uploads firmware _and_ initiates the install process. FeatureFirmwareUploadInitiateInstall registrar.Feature = "uploadandinitiateinstall" + + // FeatureTerminateSOL means an implementation that can terminate active SOL sessions + FeatureTerminateSOL registrar.Feature = "terminatesol" )