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 SEL Clear Support #349

Merged
merged 11 commits into from
Sep 23, 2023
22 changes: 22 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go
{
"name": "Go",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/go:1-1.21-bullseye"

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "go version",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
69 changes: 69 additions & 0 deletions bmc/sel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package bmc

import (
"context"
"fmt"
"time"

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

// SELServices for various SEL related services
type SELService interface {
ClearSEL(ctx context.Context) (err error)
}

type selProviders struct {
name string
selProvider SELService
}

func clearSEL(ctx context.Context, timeout time.Duration, s []selProviders) (metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range s {
if elem.selProvider == 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()
selErr := elem.selProvider.ClearSEL(ctx)
if selErr != nil {
err = multierror.Append(err, errors.WithMessagef(selErr, "provider: %v", elem.name))
continue
}
metadataLocal.SuccessfulProvider = elem.name
return metadataLocal, nil
}

}

return metadataLocal, multierror.Append(err, errors.New("failed to reset SEL"))
}

func ClearSELFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (metadata Metadata, err error) {
selServices := make([]selProviders, 0)
for _, elem := range generic {
temp := selProviders{name: getProviderName(elem)}
switch p := elem.(type) {
case SELService:
temp.selProvider = p
selServices = append(selServices, temp)
default:
e := fmt.Sprintf("not a SELService implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(selServices) == 0 {
return metadata, multierror.Append(err, errors.New("no SelService implementations found"))
}
return clearSEL(ctx, timeout, selServices)
}
61 changes: 61 additions & 0 deletions bmc/sel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package bmc

import (
"context"
"testing"
"time"

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

type mockSELService struct {
name string
err error
}

func (m *mockSELService) ClearSEL(ctx context.Context) error {
return m.err
}

func (m *mockSELService) Name() string {
return m.name
}

func TestClearSEL(t *testing.T) {
ctx := context.Background()
timeout := 1 * time.Second

// Test with a mock SELService that returns nil
mockService := &mockSELService{name: "mock1", err: nil}
metadata, err := clearSEL(ctx, timeout, []selProviders{{name: mockService.name, selProvider: mockService}})
assert.Nil(t, err)
assert.Equal(t, mockService.name, metadata.SuccessfulProvider)

// Test with a mock SELService that returns an error
mockService = &mockSELService{name: "mock2", err: errors.New("mock error")}
metadata, err = clearSEL(ctx, timeout, []selProviders{{name: mockService.name, selProvider: mockService}})
assert.NotNil(t, err)
assert.NotEqual(t, mockService.name, metadata.SuccessfulProvider)
}

func TestClearSELFromInterfaces(t *testing.T) {
ctx := context.Background()
timeout := 1 * time.Second

// Test with an empty slice
metadata, err := ClearSELFromInterfaces(ctx, timeout, []interface{}{})
assert.NotNil(t, err)
assert.Empty(t, metadata.SuccessfulProvider)

// Test with a slice containing a non-SELService object
metadata, err = ClearSELFromInterfaces(ctx, timeout, []interface{}{"not a SELService"})
assert.NotNil(t, err)
assert.Empty(t, metadata.SuccessfulProvider)

// Test with a slice containing a mock SELService
mockService := &mockSELService{name: "mock1"}
metadata, err = ClearSELFromInterfaces(ctx, timeout, []interface{}{mockService})
assert.Nil(t, err)
assert.Equal(t, mockService.name, metadata.SuccessfulProvider)
}
6 changes: 6 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,9 @@ func (c *Client) Screenshot(ctx context.Context) (image []byte, fileType string,

return image, fileType, err
}

func (c *Client) ClearSEL(ctx context.Context) (err error) {
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
metadata, err := bmc.ClearSELFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return err
}
68 changes: 68 additions & 0 deletions examples/sel/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"context"
"crypto/x509"
"flag"
"io/ioutil"
"time"

"github.com/bmc-toolbox/bmclib/v2"
"github.com/bmc-toolbox/bmclib/v2/providers"
"github.com/bombsimon/logrusr/v2"
"github.com/sirupsen/logrus"
)

func main() {
user := flag.String("user", "", "Username to login with")
pass := flag.String("password", "", "Username to login with")
host := flag.String("host", "", "BMC hostname to connect to")
withSecureTLS := flag.Bool("secure-tls", false, "Enable secure TLS")
certPoolFile := flag.String("cert-pool", "", "Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true")
flag.Parse()

l := logrus.New()
l.Level = logrus.DebugLevel
logger := logrusr.New(l)

if *host == "" || *user == "" || *pass == "" {
l.Fatal("required host/user/pass parameters not defined")
}

clientOpts := []bmclib.Option{
bmclib.WithLogger(logger),
bmclib.WithRedfishUseBasicAuth(true),
}

if *withSecureTLS {
var pool *x509.CertPool
if *certPoolFile != "" {
pool = x509.NewCertPool()
data, err := ioutil.ReadFile(*certPoolFile)
if err != nil {
l.Fatal(err)
}
pool.AppendCertsFromPEM(data)
}
// a nil pool uses the system certs
clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))
}

cl := bmclib.NewClient(*host, *user, *pass, clientOpts...)
cl.Registry.Drivers = cl.Registry.Supports(providers.FeatureSELClear)

ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

err := cl.Open(ctx)
if err != nil {
l.WithError(err).Fatal(err, "BMC login failed")
}
defer cl.Close(ctx)

err = cl.ClearSEL(ctx)
if err != nil {
l.WithError(err).Error()
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
}
l.Info("SEL cleared")
}
6 changes: 6 additions & 0 deletions internal/ipmi/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,9 @@ func (i *Ipmi) ReadUsers(ctx context.Context) (users []map[string]string, err er

return users, err
}

// Clear SEL clears the SEL
func (i *Ipmi) ClearSEL(ctx context.Context) (err error) {
_, err = i.run(ctx, []string{"sel", "clear"})
return err
}
36 changes: 36 additions & 0 deletions internal/redfishwrapper/sel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package redfishwrapper

import (
"context"

bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
"github.com/pkg/errors"
)

// Clear SEL clears all of the LogServices logs
func (c *Client) ClearSEL(ctx context.Context) (err error) {
if err := c.SessionActive(); err != nil {
return errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())
}

chassis, err := c.client.Service.Chassis()
if err != nil {
return err
}

for _, c := range chassis {
logServices, err := c.LogServices()
if err != nil {
return err
}

for _, logService := range logServices {
err = logService.ClearLog()
if err != nil {
return err
}
}
}

return nil
}
5 changes: 5 additions & 0 deletions providers/ipmitool/ipmitool.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
providers.FeatureUserRead,
providers.FeatureBmcReset,
providers.FeatureBootDeviceSet,
providers.FeatureSELClear,
}
)

Expand Down Expand Up @@ -180,3 +181,7 @@ func (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error)

return ok, err
}

func (c *Conn) ClearSEL(ctx context.Context) (err error) {
return c.ipmitool.ClearSEL(ctx)
}
18 changes: 18 additions & 0 deletions providers/ipmitool/ipmitool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,21 @@ func TestBMCReset(t *testing.T) {
t.Log(state)
t.Fatal()
}

func TestSELClear(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.ClearSEL(context.Background())
if err != nil {
t.Fatal(err)
}
t.Log("SEL cleared")
t.Fatal()
}
2 changes: 2 additions & 0 deletions providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ const (
FeaturePostCodeRead registrar.Feature = "postcoderead"
// FeatureScreenshot means an implementation that returns a screenshot of the video.
FeatureScreenshot registrar.Feature = "screenshot"
// FeatureSELClear means an implementation that clears the BMC SEL
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
FeatureSELClear registrar.Feature = "selclear"
)
1 change: 1 addition & 0 deletions providers/redfish/redfish.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var (
providers.FeatureFirmwareInstall,
providers.FeatureFirmwareInstallStatus,
providers.FeatureBmcReset,
providers.FeatureSELClear,
}
)

Expand Down
7 changes: 7 additions & 0 deletions providers/redfish/sel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package redfish

import "context"

func (c *Conn) ClearSEL(ctx context.Context) (err error) {
return c.redfishwrapper.ClearSEL(ctx)
}