mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-09-20 21:14:37 -04:00
bootstrapper: fix journald ram usage (#1553)
This commit is contained in:
parent
cbdaec65da
commit
8964e3e90c
3 changed files with 126 additions and 32 deletions
|
@ -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",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue