mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-23 14:30:46 +02:00
* shell: bound the initial master connection wait behind -connectTimeout weed shell waited on WaitUntilConnected with a background context, so an unreachable master hung the command forever. The wait now runs on a 30s (configurable) timeout and reports the failure instead. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: stop the master reconnect loop when RunShell returns KeepConnectedToMaster ran on context.Background(), so once RunShell returned — including the new connect-timeout early return — the reconnect goroutine kept dialing forever, and repeated RunShell calls accumulated one each. Give it its own cancellable context, separate from connectCtx whose deadline must not cut reconnects under a live shell. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: bound filer discovery by -connectTimeout too The discovery WithClient and ListClusterNodes ran on the unbounded background context, so a master disconnect after the initial connect — or a persistently failing ListClusterNodes — could block startup past the deadline. Reusing connectCtx bounds the whole startup phase. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: report filer discovery failures instead of silently losing the filer target * shell: give filer discovery its own budget after master connects Discovery shared connectCtx, so a connection that landed near the -connectTimeout deadline left ListClusterNodes almost no time and no room for WithClient to retry. A fresh timeout keeps discovery bounded without starving it. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: cover the -connectTimeout master wait with a test Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: note filer discovery in the -connectTimeout help text Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package shell
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type fakeCommand struct {
|
|
name string
|
|
err error
|
|
}
|
|
|
|
func (c *fakeCommand) Name() string { return c.name }
|
|
func (c *fakeCommand) Help() string { return "test helper" }
|
|
func (c *fakeCommand) HasTag(CommandTag) bool { return false }
|
|
func (c *fakeCommand) Do([]string, *CommandEnv, io.Writer) error { return c.err }
|
|
|
|
func TestProcessEachCmdReturnsErrors(t *testing.T) {
|
|
|
|
exit, err := processEachCmd("definitely.not.a.command", nil)
|
|
if exit {
|
|
t.Errorf("unknown command must not request exit")
|
|
}
|
|
if err == nil {
|
|
t.Errorf("unknown command must return an error")
|
|
}
|
|
if err != nil && !strings.Contains(err.Error(), "unknown command") {
|
|
t.Errorf("unexpected error for unknown command: %v", err)
|
|
}
|
|
|
|
exit, err = processEachCmd("exit", nil)
|
|
if !exit {
|
|
t.Errorf("exit must request exit")
|
|
}
|
|
if err != nil {
|
|
t.Errorf("exit must not return an error, got: %v", err)
|
|
}
|
|
|
|
exit, err = processEachCmd(" ", nil)
|
|
if exit || err != nil {
|
|
t.Errorf("blank input must be a no-op, got exit=%v err=%v", exit, err)
|
|
}
|
|
}
|
|
|
|
func TestRunShellConnectTimeout(t *testing.T) {
|
|
masters := "127.0.0.1:1"
|
|
filerGroup := ""
|
|
|
|
start := time.Now()
|
|
err := RunShell(ShellOptions{
|
|
Masters: &masters,
|
|
FilerGroup: &filerGroup,
|
|
ConnectTimeout: 200 * time.Millisecond,
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "cannot connect to master") {
|
|
t.Fatalf("expected a master connection timeout, got: %v", err)
|
|
}
|
|
if elapsed := time.Since(start); elapsed > 10*time.Second {
|
|
t.Fatalf("RunShell should give up near the connect timeout, took %v", elapsed)
|
|
}
|
|
}
|
|
|
|
func TestProcessEachCmdPreservesRegisteredCommandError(t *testing.T) {
|
|
doErr := errors.New("daily_run: shard=3: recovery walk failed")
|
|
failing := &fakeCommand{name: "test.failing.command", err: doErr}
|
|
succeeding := &fakeCommand{name: "test.succeeding.command"}
|
|
Commands = append(Commands, failing, succeeding)
|
|
t.Cleanup(func() { Commands = Commands[:len(Commands)-2] })
|
|
|
|
exit, err := processEachCmd("test.failing.command -some -args", nil)
|
|
if exit {
|
|
t.Errorf("a failing command must not request exit")
|
|
}
|
|
if !errors.Is(err, doErr) {
|
|
t.Errorf("the command's own error must be preserved, got: %v", err)
|
|
}
|
|
|
|
exit, err = processEachCmd("test.succeeding.command", nil)
|
|
if exit || err != nil {
|
|
t.Errorf("a succeeding command must return no error, got exit=%v err=%v", exit, err)
|
|
}
|
|
}
|