package cluster import ( "fmt" "net" "testing" "time" "github.com/rqlite/rqlite/v8/cluster/proto" "github.com/rqlite/rqlite/v8/cluster/servicetest" pb "google.golang.org/protobuf/proto" ) func Test_NewRemover(t *testing.T) { r := NewRemover(nil, 0, nil) if r == nil { t.Fatal("expected remover, got nil") } } func Test_RemoveOK(t *testing.T) { ch := make(chan struct{}, 1) defer close(ch) srv := makeServiceRemoveOK(t, ch) defer srv.Close() control := &mockControl{leader: srv.Addr()} client := NewClient(&simpleDialer{}, 0) r := NewRemover(client, time.Second, control) if err := r.Do("node-id", true); err != nil { t.Fatalf("failed to remove node: %s", err.Error()) } waitForChan(t, ch, time.Second) } func Test_RemoveWaitForLeaderFail(t *testing.T) { srv := makeServiceRemoveOK(t, nil) defer srv.Close() control := &mockControl{ leader: srv.Addr(), waitForLeaderErr: fmt.Errorf("timeout waiting for leader"), } client := NewClient(&simpleDialer{}, 0) r := NewRemover(client, time.Second, control) if err := r.Do("node-id", true); err == nil { t.Fatalf("failed to detect timeout waiting for leader") } } func Test_RemoveWaitForRemoveFail(t *testing.T) { ch := make(chan struct{}, 1) defer close(ch) srv := makeServiceRemoveOK(t, ch) defer srv.Close() control := &mockControl{ leader: srv.Addr(), waitForRemovalErr: fmt.Errorf("timeout waiting for removal"), } client := NewClient(&simpleDialer{}, 0) r := NewRemover(client, time.Second, control) if err := r.Do("node-id", true); err == nil { t.Fatalf("failed to detect timeout waiting for removal") } waitForChan(t, ch, time.Second) } func Test_RemoveWaitForRemoveFailOK(t *testing.T) { ch := make(chan struct{}, 1) defer close(ch) srv := makeServiceRemoveOK(t, ch) defer srv.Close() control := &mockControl{ leader: srv.Addr(), waitForRemovalErr: fmt.Errorf("timeout waiting for removal"), } client := NewClient(&simpleDialer{}, 0) r := NewRemover(client, time.Second, control) if err := r.Do("node-id", false); err != nil { t.Fatalf("removal timeout not ignored: %s", err.Error()) } waitForChan(t, ch, time.Second) } // makeServiceRemoveOK returns a service that responds to a remove node // request with a successful response. If ch is nil, the service will // not instantiate the handler, as the assumption is the caller does not // expect it to be called. func makeServiceRemoveOK(t *testing.T, ch chan struct{}) *servicetest.Service { srv := servicetest.NewService() if ch != nil { srv.Handler = func(conn net.Conn) { var p []byte var err error _ = readCommand(conn) p, err = pb.Marshal(&proto.CommandRemoveNodeResponse{}) if err != nil { t.Fatalf("failed to marshal response: %s", err.Error()) } writeBytesWithLength(conn, p) } ch <- struct{}{} } else { srv.Handler = func(conn net.Conn) { t.Fatalf("unexpected call to handler") } } srv.Start() return srv } type mockControl struct { leader string waitForLeaderErr error waitForRemovalErr error } func (m *mockControl) WaitForLeader(timeout time.Duration) (string, error) { return m.leader, m.waitForLeaderErr } func (m *mockControl) WaitForRemoval(id string, timeout time.Duration) error { return m.waitForRemovalErr } func waitForChan(t *testing.T, ch chan struct{}, timeout time.Duration) { tmr := time.NewTimer(timeout) defer tmr.Stop() select { case <-ch: return case <-tmr.C: t.Fatal("timed out waiting for channel to receive signal") } }