From 1f2f0d84745169b312f97b2f6b4db5eb58fd02df Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Thu, 16 Nov 2023 22:36:47 -0800 Subject: [PATCH] Add DeactivateSOL method This method will terminate an SOL (serial-over-lan) session currently active on the BMC (if there is one). The only provider implementing it is ipmitool, via 'ipmitool sol deactivate'. --- bmc/sol.go | 70 ++++++++++++++++++++++++++++++++++ client.go | 9 +++++ internal/ipmi/ipmi.go | 10 +++++ providers/ipmitool/ipmitool.go | 6 +++ providers/providers.go | 3 ++ 5 files changed, 98 insertions(+) create mode 100644 bmc/sol.go diff --git a/bmc/sol.go b/bmc/sol.go new file mode 100644 index 00000000..89c172b0 --- /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" +) + +// 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) +} diff --git a/client.go b/client.go index 106e58d1..f93d7602 100644 --- a/client.go +++ b/client.go @@ -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") diff --git a/internal/ipmi/ipmi.go b/internal/ipmi/ipmi.go index 427a76ae..c97c7a4e 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) 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 +} diff --git a/providers/ipmitool/ipmitool.go b/providers/ipmitool/ipmitool.go index d8b5d9b2..d285161f 100644 --- a/providers/ipmitool/ipmitool.go +++ b/providers/ipmitool/ipmitool.go @@ -30,6 +30,7 @@ var ( providers.FeatureClearSystemEventLog, providers.FeatureGetSystemEventLog, providers.FeatureGetSystemEventLogRaw, + providers.FeatureDeactivateSOL, } ) @@ -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) diff --git a/providers/providers.go b/providers/providers.go index dc300577..96eb97b6 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" + + // FeatureDeactivateSOL means an implementation that can deactivate active SOL sessions + FeatureDeactivateSOL registrar.Feature = "deactivatesol" )