Skip to content

Commit

Permalink
Retrieve SEL in both opinionated and raw formats
Browse files Browse the repository at this point in the history
  • Loading branch information
mattcburns committed Nov 29, 2023
1 parent b565dd4 commit 34de741
Show file tree
Hide file tree
Showing 22 changed files with 621 additions and 16 deletions.
15 changes: 13 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
{
"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"
// "image": "mcr.microsoft.com/devcontainers/go:1-1.21-bullseye",
"build": {
"dockerfile": "Dockerfile"
},

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
Expand All @@ -15,7 +18,15 @@
// "postCreateCommand": "go version",

// Configure tool-specific properties.
// "customizations": {},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.makefile-tools",
"zxh404.vscode-proto3",
"humao.rest-client"
]
}
}

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
Expand Down
128 changes: 128 additions & 0 deletions bmc/sel.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,31 @@ import (
"fmt"
"time"

"github.com/bmc-toolbox/bmclib/v2/internal"
"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)
GetSystemEventLog(ctx context.Context) (entries [][]string, err error)
GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error)
}

type systemEventLogProviders struct {
name string
systemEventLogProvider SystemEventLog
}

type SystemEventLogEntries []SystemEventLogEntry

type SystemEventLogEntry struct {
ID int32
Values []string
Raw string
}

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

Expand Down Expand Up @@ -67,3 +78,120 @@ func ClearSystemEventLogFromInterfaces(ctx context.Context, timeout time.Duratio
}
return clearSystemEventLog(ctx, timeout, selServices)
}

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

for _, elem := range s {
if elem.systemEventLogProvider == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return sel, metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

selRawEntries, selErr := elem.systemEventLogProvider.GetSystemEventLog(ctx)
if selErr != nil {
err = multierror.Append(err, errors.WithMessagef(selErr, "provider: %v", elem.name))
continue
}

for i, v := range selRawEntries {

// In most cases, the first value is the ID, but not always
k, err := internal.ParseInt32(v[0])
if err != nil {
k = int32(i)
}

selLocal = append(selLocal, SystemEventLogEntry{
ID: k,
Values: v,
})
}

metadataLocal.SuccessfulProvider = elem.name
return selLocal, metadataLocal, nil
}

}

return selLocal, metadataLocal, multierror.Append(err, errors.New("failed to get System Event Log"))
}

func GetSystemEventLogFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (sel SystemEventLogEntries, 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 sel, metadata, multierror.Append(err, errors.New("no SystemEventLog implementations found"))
}
return getSystemEventLog(ctx, timeout, selServices)
}

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

for _, elem := range s {
if elem.systemEventLogProvider == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return eventlog, metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

eventlog, selErr := elem.systemEventLogProvider.GetSystemEventLogRaw(ctx)
if selErr != nil {
err = multierror.Append(err, errors.WithMessagef(selErr, "provider: %v", elem.name))
continue
}

metadataLocal.SuccessfulProvider = elem.name
return eventlog, metadataLocal, nil
}

}

return eventlog, metadataLocal, multierror.Append(err, errors.New("failed to get System Event Log"))
}

func GetSystemEventLogRawFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (eventlog string, 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 eventlog, metadata, multierror.Append(err, errors.New("no SystemEventLog implementations found"))
}
return getSystemEventLogRaw(ctx, timeout, selServices)
}
74 changes: 74 additions & 0 deletions bmc/sel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ func (m *mockSystemEventLogService) ClearSystemEventLog(ctx context.Context) err
return m.err
}

func (m *mockSystemEventLogService) GetSystemEventLog(ctx context.Context) (entries [][]string, err error) {
return nil, m.err
}

func (m *mockSystemEventLogService) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {
return "", m.err
}

func (m *mockSystemEventLogService) Name() string {
return m.name
}
Expand Down Expand Up @@ -59,3 +67,69 @@ func TestClearSystemEventLogFromInterfaces(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, mockService.name, metadata.SuccessfulProvider)
}

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

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

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

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

// Test with an empty slice
_, _, err := GetSystemEventLogFromInterfaces(ctx, timeout, []interface{}{})
assert.NotNil(t, err)

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

// Test with a slice containing a mock SystemEventLogService that returns nil
mockService := &mockSystemEventLogService{name: "mock1"}
_, _, err = GetSystemEventLogFromInterfaces(ctx, timeout, []interface{}{mockService})
assert.Nil(t, err)
}

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

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

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

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

// Test with an empty slice
_, _, err := GetSystemEventLogRawFromInterfaces(ctx, timeout, []interface{}{})
assert.NotNil(t, err)

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

// Test with a slice containing a mock SystemEventLogService that returns nil
mockService := &mockSystemEventLogService{name: "mock1"}
_, _, err = GetSystemEventLogRawFromInterfaces(ctx, timeout, []interface{}{mockService})
assert.Nil(t, err)
}
14 changes: 14 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,3 +656,17 @@ func (c *Client) FirmwareInstallUploaded(ctx context.Context, component, uploadV

return installTaskID, err
}

// GetSystemEventLog queries for the SEL and returns the entries in an opinionated format.
func (c *Client) GetSystemEventLog(ctx context.Context) (entries bmc.SystemEventLogEntries, err error) {
entries, metadata, err := bmc.GetSystemEventLogFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return entries, err
}

// GetSystemEventLogRaw queries for the SEL and returns the raw response.
func (c *Client) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {
eventlog, metadata, err := bmc.GetSystemEventLogRawFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return eventlog, err
}
28 changes: 24 additions & 4 deletions examples/sel/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func main() {
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")
action := flag.String("action", "get", "Action to perform on the System Event Log (clear|get)")
flag.Parse()

l := logrus.New()
Expand Down Expand Up @@ -60,9 +61,28 @@ func main() {
}
defer cl.Close(ctx)

err = cl.ClearSystemEventLog(ctx)
if err != nil {
l.WithError(err).Fatal(err, "failed to clear System Event Log")
if *action == "get" {
entries, err := cl.GetSystemEventLog(ctx)
if err != nil {
l.WithError(err).Fatal(err, "failed to get System Event Log")
}
l.Info("System Event Log entries", "entries", entries)
return
} else if *action == "get-raw" {
eventlog, err := cl.GetSystemEventLogRaw(ctx)
if err != nil {
l.WithError(err).Fatal(err, "failed to get System Event Log Raw")
}
l.Info("System Event Log", "eventlog", eventlog)
return
} else if *action == "clear" {

err = cl.ClearSystemEventLog(ctx)
if err != nil {
l.WithError(err).Fatal(err, "failed to clear System Event Log")
}
l.Info("System Event Log cleared")
} else {
l.Fatal("invalid action")
}
l.Info("System Event Log cleared")
}
9 changes: 0 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,10 @@ go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -91,11 +85,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
Expand Down
37 changes: 37 additions & 0 deletions internal/ipmi/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,40 @@ func (i *Ipmi) ClearSystemEventLog(ctx context.Context) (err error) {
_, err = i.run(ctx, []string{"sel", "clear"})
return err
}

// GetSystemEventLog returns the system event log entries in ID, Timestamp, Description, Message format
func (i *Ipmi) GetSystemEventLog(ctx context.Context) (entries [][]string, err error) {
output, err := i.GetSystemEventLogRaw(ctx)
if err != nil {
return nil, errors.Wrap(err, "error getting system event log")
}

scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
line := strings.Split(scanner.Text(), "|")
if len(line) < 3 {
continue
}
if line[0] == "ID" {
continue
}
for i := range line {
line[i] = strings.TrimSpace(line[i])
}

// ID, Timestamp (date time), Description, Message (message : assertion)
entries = append(entries, []string{line[0], fmt.Sprintf("%s %s", line[1], line[2]), line[3], fmt.Sprintf("%s : %s", line[4], line[5])})
}

return entries, nil
}

// GetSystemEventLogRaw returns the raw SEL output
func (i *Ipmi) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {
output, err := i.run(ctx, []string{"sel", "list"})
if err != nil {
return "", errors.Wrap(err, "error getting system event log")
}

return output, nil
}
Loading

0 comments on commit 34de741

Please sign in to comment.