Skip to content

Commit

Permalink
Merge pull request #376 from mattcburns/get-systemeventlog
Browse files Browse the repository at this point in the history
Get systemeventlog
  • Loading branch information
joelrebel authored Dec 7, 2023
2 parents 1063371 + f7408e4 commit b299d27
Show file tree
Hide file tree
Showing 22 changed files with 604 additions and 19 deletions.
4 changes: 4 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM mcr.microsoft.com/devcontainers/go:1-1.21-bullseye
RUN apt update
RUN apt install ipmitool -y

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
106 changes: 106 additions & 0 deletions bmc/sel.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ import (
// 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 [][]string

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

Expand Down Expand Up @@ -67,3 +71,105 @@ 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 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()

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

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

}

return nil, 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)
}
20 changes: 20 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,3 +688,23 @@ func (c *Client) FirmwareInstallUploadAndInitiate(ctx context.Context, component

return taskID, 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) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "GetSystemEventLog")
defer span.End()

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) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "GetSystemEventLogRaw")
defer span.End()

eventlog, metadata, err := bmc.GetSystemEventLogRawFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return eventlog, err
}
29 changes: 25 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,29 @@ func main() {
}
defer cl.Close(ctx)

err = cl.ClearSystemEventLog(ctx)
if err != nil {
l.WithError(err).Fatal(err, "failed to clear System Event Log")
switch *action {
case "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
case "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
case "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")
return
default:
l.Fatal("invalid action")
}
l.Info("System Event Log cleared")
}
11 changes: 0 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stmcginnis/gofish v0.14.1-0.20231018151402-dddaff9168fb h1:+BpzUuFIEAs71bTshedsUHAAq21VZWvuokbN9ABEQeQ=
github.com/stmcginnis/gofish v0.14.1-0.20231018151402-dddaff9168fb/go.mod h1:BLDSFTp8pDlf/xDbLZa+F7f7eW0E/CHCboggsu8CznI=
github.com/stmcginnis/gofish v0.15.1-0.20231121142100-22a60a77be91 h1:WmABtU8y6kTgzoVUn3FWCQGAfyodve3uz3xno28BrRs=
github.com/stmcginnis/gofish v0.15.1-0.20231121142100-22a60a77be91/go.mod h1:BLDSFTp8pDlf/xDbLZa+F7f7eW0E/CHCboggsu8CznI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -75,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 @@ -93,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
44 changes: 44 additions & 0 deletions internal/ipmi/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,47 @@ 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")
}

entries = parseSystemEventLog(output)

return entries, nil
}

// parseSystemEventLogRaw parses the raw output of the system event log. Helper
// function for GetSystemEventLog to make testing the parser easier.
func parseSystemEventLog(raw string) (entries [][]string) {
scanner := bufio.NewScanner(strings.NewReader(raw))
for scanner.Scan() {
line := strings.Split(scanner.Text(), "|")
if len(line) < 6 {
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
}

// 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 b299d27

Please sign in to comment.