1
0
Fork 0

More unit testing of Bootstrapper

master
Philip O'Toole 1 year ago
parent 86e76c4220
commit 6037fc4822

@ -11,6 +11,7 @@ import (
"net/http" "net/http"
"os" "os"
"strings" "strings"
"sync"
"time" "time"
rurl "github.com/rqlite/rqlite/http/url" rurl "github.com/rqlite/rqlite/http/url"
@ -26,6 +27,32 @@ var (
ErrBootTimeout = errors.New("boot timeout") ErrBootTimeout = errors.New("boot timeout")
) )
// BootStatus is status of the boot process, after it has completed.
type BootStatus int
const (
BootUnknown BootStatus = iota
BootJoin
BootDone
BootTimeout
)
// String returns a string representation of the BootStatus.
func (b BootStatus) String() string {
switch b {
case BootUnknown:
return "unknown"
case BootJoin:
return "join"
case BootDone:
return "done"
case BootTimeout:
return "timeout"
default:
panic("unknown boot status")
}
}
// AddressProvider is the interface types must implement to provide // AddressProvider is the interface types must implement to provide
// addresses to a Bootstrapper. // addresses to a Bootstrapper.
type AddressProvider interface { type AddressProvider interface {
@ -44,6 +71,9 @@ type Bootstrapper struct {
logger *log.Logger logger *log.Logger
Interval time.Duration Interval time.Duration
bootStatusMu sync.RWMutex
bootStatus BootStatus
} }
// NewBootstrapper returns an instance of a Bootstrapper. // NewBootstrapper returns an instance of a Bootstrapper.
@ -85,11 +115,13 @@ func (b *Bootstrapper) Boot(id, raftAddr string, done func() bool, timeout time.
for { for {
select { select {
case <-timeoutT.C: case <-timeoutT.C:
b.setBootStatus(BootTimeout)
return ErrBootTimeout return ErrBootTimeout
case <-tickerT.C: case <-tickerT.C:
if done() { if done() {
b.logger.Printf("boot operation marked done") b.logger.Printf("boot operation marked done")
b.setBootStatus(BootDone)
return nil return nil
} }
tickerT.Reset(jitter(b.Interval)) // Move to longer-period polling tickerT.Reset(jitter(b.Interval)) // Move to longer-period polling
@ -108,6 +140,7 @@ func (b *Bootstrapper) Boot(id, raftAddr string, done func() bool, timeout time.
b.joiner.SetBasicAuth(b.username, b.password) b.joiner.SetBasicAuth(b.username, b.password)
if j, err := b.joiner.Do(targets, id, raftAddr, true); err == nil { if j, err := b.joiner.Do(targets, id, raftAddr, true); err == nil {
b.logger.Printf("succeeded directly joining cluster via node at %s", j) b.logger.Printf("succeeded directly joining cluster via node at %s", j)
b.setBootStatus(BootJoin)
return nil return nil
} }
@ -128,6 +161,13 @@ func (b *Bootstrapper) Boot(id, raftAddr string, done func() bool, timeout time.
} }
} }
// Status returns the reason for the boot process completing.
func (b *Bootstrapper) Status() BootStatus {
b.bootStatusMu.RLock()
defer b.bootStatusMu.RUnlock()
return b.bootStatus
}
func (b *Bootstrapper) notify(targets []string, id, raftAddr string) error { func (b *Bootstrapper) notify(targets []string, id, raftAddr string) error {
// Create and configure the client to connect to the other node. // Create and configure the client to connect to the other node.
tr := &http.Transport{ tr := &http.Transport{
@ -190,6 +230,12 @@ func (b *Bootstrapper) notify(targets []string, id, raftAddr string) error {
return nil return nil
} }
func (b *Bootstrapper) setBootStatus(status BootStatus) {
b.bootStatusMu.Lock()
defer b.bootStatusMu.Unlock()
b.bootStatus = status
}
type stringAddressProvider struct { type stringAddressProvider struct {
ss []string ss []string
} }

@ -31,6 +31,9 @@ func Test_NewBootstrapper(t *testing.T) {
if bs == nil { if bs == nil {
t.Fatalf("failed to create a simple Bootstrapper") t.Fatalf("failed to create a simple Bootstrapper")
} }
if exp, got := BootUnknown, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
} }
func Test_BootstrapperBootDoneImmediately(t *testing.T) { func Test_BootstrapperBootDoneImmediately(t *testing.T) {
@ -46,6 +49,9 @@ func Test_BootstrapperBootDoneImmediately(t *testing.T) {
if err := bs.Boot("node1", "192.168.1.1:1234", done, 10*time.Second); err != nil { if err := bs.Boot("node1", "192.168.1.1:1234", done, 10*time.Second); err != nil {
t.Fatalf("failed to boot: %s", err) t.Fatalf("failed to boot: %s", err)
} }
if exp, got := BootDone, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
} }
func Test_BootstrapperBootTimeout(t *testing.T) { func Test_BootstrapperBootTimeout(t *testing.T) {
@ -66,6 +72,35 @@ func Test_BootstrapperBootTimeout(t *testing.T) {
if !errors.Is(err, ErrBootTimeout) { if !errors.Is(err, ErrBootTimeout) {
t.Fatalf("wrong error returned") t.Fatalf("wrong error returned")
} }
if exp, got := BootTimeout, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
}
func Test_BootstrapperBootSingleJoin(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/join" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
w.WriteHeader(http.StatusOK)
}))
done := func() bool {
return false
}
p := NewAddressProviderString([]string{ts.URL})
bs := NewBootstrapper(p, nil)
bs.Interval = time.Second
err := bs.Boot("node1", "192.168.1.1:1234", done, 60*time.Second)
if err != nil {
t.Fatalf("failed to boot: %s", err)
}
if exp, got := BootJoin, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
} }
func Test_BootstrapperBootSingleNotify(t *testing.T) { func Test_BootstrapperBootSingleNotify(t *testing.T) {
@ -115,6 +150,10 @@ func Test_BootstrapperBootSingleNotify(t *testing.T) {
if got, exp := body["addr"], "192.168.1.1:1234"; got != exp { if got, exp := body["addr"], "192.168.1.1:1234"; got != exp {
t.Fatalf("wrong address supplied, exp %s, got %s", exp, got) t.Fatalf("wrong address supplied, exp %s, got %s", exp, got)
} }
if exp, got := BootDone, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
} }
func Test_BootstrapperBootSingleNotifyHTTPS(t *testing.T) { func Test_BootstrapperBootSingleNotifyHTTPS(t *testing.T) {
@ -175,6 +214,10 @@ func Test_BootstrapperBootSingleNotifyHTTPS(t *testing.T) {
if got, exp := body["addr"], "192.168.1.1:1234"; got != exp { if got, exp := body["addr"], "192.168.1.1:1234"; got != exp {
t.Fatalf("wrong address supplied, exp %s, got %s", exp, got) t.Fatalf("wrong address supplied, exp %s, got %s", exp, got)
} }
if exp, got := BootDone, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
} }
func Test_BootstrapperBootSingleNotifyAuth(t *testing.T) { func Test_BootstrapperBootSingleNotifyAuth(t *testing.T) {
@ -214,6 +257,9 @@ func Test_BootstrapperBootSingleNotifyAuth(t *testing.T) {
if tsNotified != true { if tsNotified != true {
t.Fatalf("notify target not contacted") t.Fatalf("notify target not contacted")
} }
if exp, got := BootDone, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
} }
func Test_BootstrapperBootMultiNotify(t *testing.T) { func Test_BootstrapperBootMultiNotify(t *testing.T) {
@ -257,8 +303,10 @@ func Test_BootstrapperBootMultiNotify(t *testing.T) {
if ts1Join != true || ts2Join != true { if ts1Join != true || ts2Join != true {
t.Fatalf("all join targets not contacted") t.Fatalf("all join targets not contacted")
} }
if ts1Notified != true || ts2Notified != true { if ts1Notified != true || ts2Notified != true {
t.Fatalf("all notify targets not contacted") t.Fatalf("all notify targets not contacted")
} }
if exp, got := BootDone, bs.Status(); exp != got {
t.Fatalf("wrong status, exp %s, got %s", exp, got)
}
} }

Loading…
Cancel
Save