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

Get systemeventlog #376

Merged
merged 6 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
// 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 @@
}
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

Check warning on line 80 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L80

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

Check warning on line 84 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L83-L84

Added lines #L83 - L84 were not covered by tests

return sel, metadata, err

Check warning on line 86 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L86

Added line #L86 was not covered by tests
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

Check warning on line 131 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L131

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

Check warning on line 135 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L134-L135

Added lines #L134 - L135 were not covered by tests

return eventlog, metadata, err

Check warning on line 137 in bmc/sel.go

View check run for this annotation

Codecov / codecov/patch

bmc/sel.go#L137

Added line #L137 was not covered by tests
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 @@

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include tracing here, for an example, see -

ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "Close")

ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "GetSystemEventLog")
defer span.End()

Check warning on line 695 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L693-L695

Added lines #L693 - L695 were not covered by tests

entries, metadata, err := bmc.GetSystemEventLogFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return entries, err

Check warning on line 699 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L697-L699

Added lines #L697 - L699 were not covered by tests
}

// 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()

Check warning on line 705 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L703-L705

Added lines #L703 - L705 were not covered by tests

eventlog, metadata, err := bmc.GetSystemEventLogRawFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return eventlog, err

Check warning on line 709 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L707-L709

Added lines #L707 - L709 were not covered by tests
}
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you include some fixture data for this, along with a test for the parsing (move this scanner block into another method if that makes it easier to test)

entries = append(entries, []string{line[0], fmt.Sprintf("%s %s", line[1], line[2]), line[3], fmt.Sprintf("%s : %s", line[4], line[5])})
Copy link
Member

@joelrebel joelrebel Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this references elems in the slice line[3], line[4], line[5] - it would be worth adding a len() check before referencing those elem indexes.

}

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