Skip to content

Commit 230f2e6

Browse files
authored
Handle unexpected fsck exit codes in RepairVolume
1 parent e846994 commit 230f2e6

2 files changed

Lines changed: 108 additions & 0 deletions

File tree

utils/filesystem/filesystem.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ func (f *FSClient) RepairVolume(ctx context.Context, device, fstype string) {
225225
case "ext4":
226226
_, err = f.command.Execute(ctx, "fsck.ext4", "-p", device)
227227
default:
228+
logFields["reason"] = "unsupported_fstype"
228229
Logc(ctx).WithFields(logFields).Errorf("Unsupported file system type: %s.", fstype)
229230
}
230231

@@ -242,8 +243,12 @@ func (f *FSClient) RepairVolume(ctx context.Context, device, fstype string) {
242243
Logc(ctx).WithFields(logFields).Debug("Filesystem check errors.")
243244
case fsckFsErrorsUncorrected, fsckSyntaxError:
244245
Logc(ctx).WithError(err).WithFields(logFields).Error("Failed to repair filesystem errors.")
246+
default:
247+
logFields["reason"] = "fsck_unexpected_exit_code"
248+
Logc(ctx).WithError(err).WithFields(logFields).Error("Unexpected fsck exit code.")
245249
}
246250
} else {
251+
logFields["reason"] = "fsck_exec_error"
247252
Logc(ctx).WithError(err).WithFields(logFields).Error("Error executing fsck command.")
248253
}
249254
}

utils/filesystem/filesystem_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@ import (
66
"context"
77
"encoding/json"
88
"io/fs"
9+
"os"
10+
"os/exec"
11+
"strconv"
12+
"sync"
913
"testing"
1014
"time"
1115

16+
log "github.com/sirupsen/logrus"
17+
logtest "github.com/sirupsen/logrus/hooks/test"
1218
"github.com/spf13/afero"
1319
"github.com/stretchr/testify/assert"
1420
"go.uber.org/mock/gomock"
1521

22+
"github.com/netapp/trident/logging"
1623
"github.com/netapp/trident/mocks/mock_utils/mock_exec"
1724
"github.com/netapp/trident/utils/errors"
1825
execCmd "github.com/netapp/trident/utils/exec"
@@ -25,6 +32,8 @@ type MockFs struct {
2532
afero.MemMapFs
2633
}
2734

35+
var globalLogMutationMutex sync.Mutex
36+
2837
func (m *MockFs) Open(name string) (afero.File, error) {
2938
return nil, fs.ErrPermission
3039
}
@@ -343,6 +352,43 @@ func TestFormatVolumeRetry(t *testing.T) {
343352
}
344353
}
345354

355+
func newExitErrorForCode(t *testing.T, code int) error {
356+
t.Helper()
357+
exePath, err := os.Executable()
358+
assert.NoError(t, err)
359+
360+
// Spawn this test binary as a helper process and force it to exit with the requested code.
361+
// This gives us a real *exec.ExitError in a cross-platform way (no shell dependency).
362+
cmd := exec.Command(exePath, "-test.run=TestExitWithCodeHelperProcess", "--", strconv.Itoa(code))
363+
// Gate helper mode so normal test runs don't execute the forced os.Exit path.
364+
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
365+
err = cmd.Run()
366+
assert.Error(t, err)
367+
var exitErr *exec.ExitError
368+
assert.ErrorAs(t, err, &exitErr)
369+
return err
370+
}
371+
372+
func TestExitWithCodeHelperProcess(t *testing.T) {
373+
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
374+
return
375+
}
376+
377+
for i, arg := range os.Args {
378+
if arg != "--" || i+1 >= len(os.Args) {
379+
continue
380+
}
381+
382+
code, err := strconv.Atoi(os.Args[i+1])
383+
if err != nil {
384+
os.Exit(2)
385+
}
386+
os.Exit(code)
387+
}
388+
389+
os.Exit(2)
390+
}
391+
346392
func TestRepairVolume(t *testing.T) {
347393
mockCtrl := gomock.NewController(t)
348394
mockExec := mock_exec.NewMockCommand(mockCtrl)
@@ -352,6 +398,9 @@ func TestRepairVolume(t *testing.T) {
352398
device string
353399
fstype string
354400
mockSetup func()
401+
logLevel log.Level
402+
exitCode *int
403+
reason string
355404
}
356405
tests := map[string]parameters{
357406
"fsck.xfs does nothing": {
@@ -377,20 +426,74 @@ func TestRepairVolume(t *testing.T) {
377426
device: "/dev/sda1",
378427
fstype: "unsupported",
379428
mockSetup: func() {},
429+
logLevel: log.ErrorLevel,
430+
reason: "unsupported_fstype",
380431
},
381432
"Error executing fsck": {
382433
device: "/dev/sda1",
383434
fstype: Ext4,
384435
mockSetup: func() {
385436
mockExec.EXPECT().Execute(gomock.Any(), "fsck.ext4", "-p", "/dev/sda1").Return(nil, errors.New("mock error")).Times(1)
386437
},
438+
logLevel: log.ErrorLevel,
439+
reason: "fsck_exec_error",
440+
},
441+
"Unexpected fsck exit code is handled": {
442+
device: "/dev/sda1",
443+
fstype: Ext4,
444+
mockSetup: func() {
445+
mockExec.EXPECT().Execute(
446+
gomock.Any(), "fsck.ext4", "-p", "/dev/sda1",
447+
).Return(nil, newExitErrorForCode(t, 42)).Times(1)
448+
},
449+
logLevel: log.ErrorLevel,
450+
exitCode: func() *int { v := 42; return &v }(),
451+
reason: "fsck_unexpected_exit_code",
387452
},
388453
}
389454

390455
for name, params := range tests {
391456
t.Run(name, func(t *testing.T) {
457+
globalLogMutationMutex.Lock()
458+
originalDefaultLevel := logging.GetDefaultLogLevel()
459+
hook := logtest.NewGlobal()
460+
assert.NoError(t, logging.SetDefaultLogLevel("trace"))
461+
t.Cleanup(func() {
462+
hook.Reset()
463+
assert.NoError(t, logging.SetDefaultLogLevel(originalDefaultLevel))
464+
globalLogMutationMutex.Unlock()
465+
})
466+
392467
params.mockSetup()
393468
fsClient.RepairVolume(context.Background(), params.device, params.fstype)
469+
if params.exitCode != nil || params.reason != "" {
470+
found := false
471+
for _, e := range hook.AllEntries() {
472+
if e.Level != params.logLevel {
473+
continue
474+
}
475+
if params.exitCode != nil {
476+
rawCode, ok := e.Data["exitCode"]
477+
if !ok {
478+
continue
479+
}
480+
assert.Equal(t, *params.exitCode, rawCode)
481+
}
482+
if params.reason != "" {
483+
rawReason, ok := e.Data["reason"]
484+
if !ok {
485+
continue
486+
}
487+
assert.Equal(t, params.reason, rawReason)
488+
}
489+
found = true
490+
break
491+
}
492+
assert.True(
493+
t, found, "expected matching log criteria level=%s exitCode=%v reason=%q, got entries: %#v",
494+
params.logLevel, params.exitCode, params.reason, hook.AllEntries(),
495+
)
496+
}
394497
})
395498
}
396499
}

0 commit comments

Comments
 (0)