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"
)

// System Event Log Services for related services
type SystemEventLog interface {
ClearSystemEventLog(ctx context.Context) (err error)
}

type systemEventLogProviders struct {
name string
systemEventLogProvider SystemEventLog
}

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

for _, elem := range s {
if elem.systemEventLogProvider == nil {
continue

Check warning on line 27 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L27

Added line #L27 was not covered by tests
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

Check warning on line 31 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L30-L31

Added lines #L30 - L31 were not covered by tests

return metadata, err

Check warning on line 33 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L33

Added line #L33 was not covered by tests
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
selErr := elem.systemEventLogProvider.ClearSystemEventLog(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 System Event Log"))
}

func ClearSystemEventLogFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (metadata Metadata, err error) {
selServices := make([]systemEventLogProviders, 0)
for _, elem := range generic {
temp := systemEventLogProviders{name: getProviderName(elem)}
switch p := elem.(type) {
case SystemEventLog:
temp.systemEventLogProvider = p
selServices = append(selServices, temp)
default:
e := fmt.Sprintf("not a SystemEventLog service implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(selServices) == 0 {
return metadata, multierror.Append(err, errors.New("no SystemEventLog implementations found"))
}
return clearSystemEventLog(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 mockSystemEventLogService struct {
name string
err error
}

func (m *mockSystemEventLogService) ClearSystemEventLog(ctx context.Context) error {
return m.err
}

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

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

// Test with a mock SystemEventLogService that returns nil
mockService := &mockSystemEventLogService{name: "mock1", err: nil}
metadata, err := clearSystemEventLog(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})
assert.Nil(t, err)
assert.Equal(t, mockService.name, metadata.SuccessfulProvider)

// Test with a mock SystemEventLogService that returns an error
mockService = &mockSystemEventLogService{name: "mock2", err: errors.New("mock error")}
metadata, err = clearSystemEventLog(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})
assert.NotNil(t, err)
assert.NotEqual(t, mockService.name, metadata.SuccessfulProvider)
}

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

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

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

// Test with a slice containing a mock SystemEventLogService that returns nil
mockService := &mockSystemEventLogService{name: "mock1"}
metadata, err = ClearSystemEventLogFromInterfaces(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 @@

return image, fileType, err
}

func (c *Client) ClearSystemEventLog(ctx context.Context) (err error) {
metadata, err := bmc.ClearSystemEventLogFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return err

Check warning on line 412 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L409-L412

Added lines #L409 - L412 were not covered by tests
}
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.FeatureClearSystemEventLog)

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.ClearSystemEventLog(ctx)
if err != nil {
l.WithError(err).Fatal(err, "failed to clear System Event Log")
}
l.Info("System Event Log 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
}

// ClearSystemEventLog clears the system event log
func (i *Ipmi) ClearSystemEventLog(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"
)

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

Check warning on line 13 in internal/redfishwrapper/sel.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/sel.go#L11-L13

Added lines #L11 - L13 were not covered by tests
}

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

Check warning on line 18 in internal/redfishwrapper/sel.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/sel.go#L16-L18

Added lines #L16 - L18 were not covered by tests
}

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

Check warning on line 24 in internal/redfishwrapper/sel.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/sel.go#L21-L24

Added lines #L21 - L24 were not covered by tests
}

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

Check warning on line 30 in internal/redfishwrapper/sel.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/sel.go#L27-L30

Added lines #L27 - L30 were not covered by tests
}
}
}

return nil

Check warning on line 35 in internal/redfishwrapper/sel.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/sel.go#L35

Added line #L35 was not covered by tests
}
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 @@
providers.FeatureUserRead,
providers.FeatureBmcReset,
providers.FeatureBootDeviceSet,
providers.FeatureClearSystemEventLog,
}
)

Expand Down Expand Up @@ -180,3 +181,7 @@

return ok, err
}

func (c *Conn) ClearSystemEventLog(ctx context.Context) (err error) {
return c.ipmitool.ClearSystemEventLog(ctx)

Check warning on line 186 in providers/ipmitool/ipmitool.go

View check run for this annotation

Codecov / codecov/patch

providers/ipmitool/ipmitool.go#L185-L186

Added lines #L185 - L186 were not covered by tests
}
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 TestSystemEventLogClear(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.ClearSystemEventLog(context.Background())
if err != nil {
t.Fatal(err)
}
t.Log("System Event Log 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"
// FeatureClearSystemEventLog means an implementation that clears the BMC System Event Log (SEL)
FeatureClearSystemEventLog registrar.Feature = "clearsystemeventlog"
)
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.FeatureClearSystemEventLog,
}
)

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) ClearSystemEventLog(ctx context.Context) (err error) {
return c.redfishwrapper.ClearSystemEventLog(ctx)

Check warning on line 6 in providers/redfish/sel.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/sel.go#L5-L6

Added lines #L5 - L6 were not covered by tests
}