bootstrapper: fix journald ram usage (#1553)

This commit is contained in:
miampf 2023-04-03 13:58:34 +02:00 committed by GitHub
parent cbdaec65da
commit 8964e3e90c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 32 deletions

View file

@ -12,5 +12,8 @@ go_test(
name = "journald_test", name = "journald_test",
srcs = ["journald_test.go"], srcs = ["journald_test.go"],
embed = [":journald"], embed = [":journald"],
deps = ["@com_github_stretchr_testify//assert"], deps = [
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
) )

View file

@ -11,18 +11,20 @@ package journald
import ( import (
"context" "context"
"errors" "io"
"fmt"
"os/exec" "os/exec"
) )
type command interface { type command interface {
Output() ([]byte, error) Start() error
Wait() error
} }
// Collector collects logs from journald. // Collector collects logs from journald.
type Collector struct { type Collector struct {
cmd command cmd command
stdoutPipe io.ReadCloser
stderrPipe io.ReadCloser
} }
// NewCollector creates a new Collector for journald logs. // NewCollector creates a new Collector for journald logs.
@ -31,17 +33,33 @@ func NewCollector(ctx context.Context) (*Collector, error) {
if cmd.Err != nil { if cmd.Err != nil {
return nil, cmd.Err return nil, cmd.Err
} }
return &Collector{cmd}, nil stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderrPipe, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
collector := Collector{cmd, stdoutPipe, stderrPipe}
return &collector, nil
} }
// Collect gets all journald logs from a service and returns a byte slice with the plain text logs. // Start returns a pipe to read the systemd logs. This should be read with a bufio Reader.
func (c *Collector) Collect() ([]byte, error) { func (c *Collector) Start() (io.ReadCloser, error) {
out, err := c.cmd.Output() if err := c.cmd.Start(); err != nil {
var exitErr *exec.ExitError return nil, err
if errors.As(err, &exitErr) {
return nil, fmt.Errorf("executing %q failed: %s", c.cmd, exitErr.Stderr)
} else if err != nil {
return nil, fmt.Errorf("executing command: %w", err)
} }
return out, nil return c.stdoutPipe, nil
}
// Error returns output to stderr as bytes as well
// as the exit code in form of an error.
func (c *Collector) Error() ([]byte, error) {
stderr, err := io.ReadAll(c.stderrPipe)
if err != nil {
return nil, err
}
exitCode := c.cmd.Wait()
return stderr, exitCode
} }

View file

@ -7,58 +7,131 @@ SPDX-License-Identifier: AGPL-3.0-only
package journald package journald
import ( import (
"bytes"
"errors" "errors"
"io"
"os/exec" "os/exec"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
type stubJournaldCommand struct { func (s *stubReadCloser) Read(p []byte) (n int, err error) {
outputReturn []byte if s.readErr != nil {
outputError error return 0, s.readErr
}
return s.reader.Read(p)
} }
func (j *stubJournaldCommand) Output() ([]byte, error) { func (s *stubReadCloser) Close() error {
return j.outputReturn, j.outputError return s.closeErr
} }
func TestCollect(t *testing.T) { func TestPipe(t *testing.T) {
someError := errors.New("failed") someError := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
command *stubJournaldCommand command *stubCommand
stdoutPipe io.ReadCloser
wantedOutput []byte wantedOutput []byte
wantErr bool wantErr bool
}{ }{
"success": { "success": {
command: &stubJournaldCommand{}, command: &stubCommand{},
wantedOutput: []byte("asdf"),
stdoutPipe: &stubReadCloser{reader: bytes.NewReader([]byte("asdf"))},
}, },
"execution failed": { "execution failed": {
command: &stubJournaldCommand{outputError: someError}, command: &stubCommand{startErr: someError},
wantErr: true, wantErr: true,
}, },
"exit error": { "exit error": {
command: &stubJournaldCommand{outputError: &exec.ExitError{}}, command: &stubCommand{startErr: &exec.ExitError{}},
wantErr: true, wantErr: true,
}, },
"output check": {
command: &stubJournaldCommand{outputReturn: []byte("asdf")},
wantedOutput: []byte("asdf"),
},
} }
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
collector := Collector{cmd: tc.command} collector := Collector{cmd: tc.command, stdoutPipe: tc.stdoutPipe}
out, err := collector.Collect() pipe, err := collector.Start()
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
} else {
stdout := make([]byte, 4)
_, err = io.ReadFull(pipe, stdout)
require.NoError(t, err)
assert.Equal(tc.wantedOutput, stdout)
} }
assert.Equal(out, tc.wantedOutput)
}) })
} }
} }
func TestError(t *testing.T) {
someError := errors.New("failed")
testCases := map[string]struct {
stderrPipe io.ReadCloser
exitCode error
wantErr bool
}{
"success": {
stderrPipe: &stubReadCloser{readErr: io.EOF},
},
"reading error": {
stderrPipe: &stubReadCloser{readErr: someError},
wantErr: true,
},
"close error": {
stderrPipe: &stubReadCloser{closeErr: someError, readErr: io.EOF},
},
"command exit": {
stderrPipe: &stubReadCloser{readErr: io.EOF},
exitCode: someError,
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
collector := Collector{
stderrPipe: tc.stderrPipe,
cmd: &stubCommand{waitErr: tc.exitCode},
}
stderrOut, err := collector.Error()
if tc.wantErr {
assert.Error(err)
} else {
assert.Equal(stderrOut, []byte{})
}
})
}
}
type stubCommand struct {
startCalled bool
startErr error
waitErr error
}
func (j *stubCommand) Start() error {
j.startCalled = true
return j.startErr
}
func (j *stubCommand) Wait() error {
return j.waitErr
}
type stubReadCloser struct {
reader io.Reader
readErr error
closeErr error
}