mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-24 23:10:41 +02:00
* shell: discover the filer on first use instead of at startup The startup retry loop made every weed shell invocation wait out the connect timeout on clusters that have no filer, even for commands that only talk to the master. Discovery now runs once, on the first command that actually needs a filer, keeping the retry-until-registered fix for the fresh-cluster race without stalling master-only commands. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: stop filer discovery retries on non-transient errors A master that keeps answering ListClusterNodes with a permanent error (permission denied, unimplemented) cannot gain a filer by retrying, so discovery returns instead of polling until the deadline. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: explain an empty filer list when discovery times out * shell: retry filer discovery while the address is still empty 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)
|
|
}
|
|
}
|