Improve resilience of api pkg tests (#4676)

* Add function to wait for serfHealth in api tests

* Disable connect when creating semaphore test clients

* Wait for serfHealth when creating sessions in their tests

* Add helper functions to create lock/semaphore sessions without checks

* Log passing tests to prevent timeout in Travis due to lack of output
This commit is contained in:
Freddy 2018-09-18 17:47:01 +01:00 committed by Paul Banks
parent fca5ed61e5
commit 54e4cd4801
5 changed files with 177 additions and 77 deletions

View File

@ -162,7 +162,7 @@ test-internal:
@# hide it from travis as it exceeds their log limits and causes job to be @# hide it from travis as it exceeds their log limits and causes job to be
@# terminated (over 4MB and over 10k lines in the UI). We need to output @# terminated (over 4MB and over 10k lines in the UI). We need to output
@# _something_ to stop them terminating us due to inactivity... @# _something_ to stop them terminating us due to inactivity...
{ go test $(GOTEST_FLAGS) -tags '$(GOTAGS)' $(GOTEST_PKGS) 2>&1 ; echo $$? > exit-code ; } | tee test.log | egrep '^(ok|FAIL|panic:|--- FAIL)' { go test -v $(GOTEST_FLAGS) -tags '$(GOTAGS)' $(GOTEST_PKGS) 2>&1 ; echo $$? > exit-code ; } | tee test.log | egrep '^(ok|FAIL|panic:|--- FAIL|--- PASS)'
@echo "Exit code: $$(cat exit-code)" @echo "Exit code: $$(cat exit-code)"
@# This prints all the race report between ====== lines @# This prints all the race report between ====== lines
@awk '/^WARNING: DATA RACE/ {do_print=1; print "=================="} do_print==1 {print} /^={10,}/ {do_print=0}' test.log || true @awk '/^WARNING: DATA RACE/ {do_print=1; print "=================="} do_print==1 {print} /^={10,}/ {do_print=0}' test.log || true

View File

@ -13,18 +13,44 @@ import (
"github.com/hashicorp/consul/testutil/retry" "github.com/hashicorp/consul/testutil/retry"
) )
func createTestLock(t *testing.T, c *Client, key string) (*Lock, *Session) {
t.Helper()
session := c.Session()
se := &SessionEntry{
Name: DefaultLockSessionName,
TTL: DefaultLockSessionTTL,
Behavior: SessionBehaviorDelete,
}
id, _, err := session.CreateNoChecks(se, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
opts := &LockOptions{
Key: key,
Session: id,
SessionName: se.Name,
SessionTTL: se.TTL,
}
lock, err := c.LockOpts(opts)
if err != nil {
t.Fatalf("err: %v", err)
}
return lock, session
}
func TestAPI_LockLockUnlock(t *testing.T) { func TestAPI_LockLockUnlock(t *testing.T) {
t.Parallel() t.Parallel()
c, s := makeClientWithoutConnect(t) c, s := makeClientWithoutConnect(t)
defer s.Stop() defer s.Stop()
lock, err := c.LockKey("test/lock") lock, session := createTestLock(t, c, "test/lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Initial unlock should fail // Initial unlock should fail
err = lock.Unlock() err := lock.Unlock()
if err != ErrLockNotHeld { if err != ErrLockNotHeld {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -77,10 +103,8 @@ func TestAPI_LockForceInvalidate(t *testing.T) {
defer s.Stop() defer s.Stop()
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
lock, err := c.LockKey("test/lock") lock, session := createTestLock(t, c, "test/lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
leaderCh, err := lock.Lock(nil) leaderCh, err := lock.Lock(nil)
@ -119,10 +143,8 @@ func TestAPI_LockDeleteKey(t *testing.T) {
// territory. // territory.
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
func() { func() {
lock, err := c.LockKey("test/lock") lock, session := createTestLock(t, c, "test/lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
leaderCh, err := lock.Lock(nil) leaderCh, err := lock.Lock(nil)
@ -161,10 +183,8 @@ func TestAPI_LockContend(t *testing.T) {
wg.Add(1) wg.Add(1)
go func(idx int) { go func(idx int) {
defer wg.Done() defer wg.Done()
lock, err := c.LockKey("test/lock") lock, session := createTestLock(t, c, "test/lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work eventually, will contend // Should work eventually, will contend
leaderCh, err := lock.Lock(nil) leaderCh, err := lock.Lock(nil)
@ -208,10 +228,8 @@ func TestAPI_LockDestroy(t *testing.T) {
c, s := makeClientWithoutConnect(t) c, s := makeClientWithoutConnect(t)
defer s.Stop() defer s.Stop()
lock, err := c.LockKey("test/lock") lock, session := createTestLock(t, c, "test/lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
leaderCh, err := lock.Lock(nil) leaderCh, err := lock.Lock(nil)
@ -234,10 +252,8 @@ func TestAPI_LockDestroy(t *testing.T) {
} }
// Acquire with a different lock // Acquire with a different lock
l2, err := c.LockKey("test/lock") l2, session := createTestLock(t, c, "test/lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
leaderCh, err = l2.Lock(nil) leaderCh, err = l2.Lock(nil)
@ -277,10 +293,8 @@ func TestAPI_LockConflict(t *testing.T) {
c, s := makeClientWithoutConnect(t) c, s := makeClientWithoutConnect(t)
defer s.Stop() defer s.Stop()
sema, err := c.SemaphorePrefix("test/lock/", 2) sema, session := createTestSemaphore(t, c, "test/lock/", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
lockCh, err := sema.Acquire(nil) lockCh, err := sema.Acquire(nil)
@ -292,10 +306,8 @@ func TestAPI_LockConflict(t *testing.T) {
} }
defer sema.Release() defer sema.Release()
lock, err := c.LockKey("test/lock/.lock") lock, session := createTestLock(t, c, "test/lock/.lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should conflict with semaphore // Should conflict with semaphore
_, err = lock.Lock(nil) _, err = lock.Lock(nil)
@ -315,6 +327,8 @@ func TestAPI_LockReclaimLock(t *testing.T) {
c, s := makeClientWithoutConnect(t) c, s := makeClientWithoutConnect(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session, _, err := c.Session().Create(&SessionEntry{}, nil) session, _, err := c.Session().Create(&SessionEntry{}, nil)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@ -383,6 +397,8 @@ func TestAPI_LockMonitorRetry(t *testing.T) {
raw, s := makeClientWithoutConnect(t) raw, s := makeClientWithoutConnect(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
// Set up a server that always responds with 500 errors. // Set up a server that always responds with 500 errors.
failer := func(w http.ResponseWriter, req *http.Request) { failer := func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
@ -498,6 +514,8 @@ func TestAPI_LockOneShot(t *testing.T) {
c, s := makeClientWithoutConnect(t) c, s := makeClientWithoutConnect(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
// Set up a lock as a one-shot. // Set up a lock as a one-shot.
opts := &LockOptions{ opts := &LockOptions{
Key: "test/lock", Key: "test/lock",

View File

@ -11,18 +11,45 @@ import (
"time" "time"
) )
func createTestSemaphore(t *testing.T, c *Client, prefix string, limit int) (*Semaphore, *Session) {
t.Helper()
session := c.Session()
se := &SessionEntry{
Name: DefaultSemaphoreSessionName,
TTL: DefaultSemaphoreSessionTTL,
Behavior: SessionBehaviorDelete,
}
id, _, err := session.CreateNoChecks(se, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
opts := &SemaphoreOptions{
Prefix: prefix,
Limit: limit,
Session: id,
SessionName: se.Name,
SessionTTL: se.TTL,
}
sema, err := c.SemaphoreOpts(opts)
if err != nil {
t.Fatalf("err: %v", err)
}
return sema, session
}
func TestAPI_SemaphoreAcquireRelease(t *testing.T) { func TestAPI_SemaphoreAcquireRelease(t *testing.T) {
t.Parallel() t.Parallel()
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
sema, err := c.SemaphorePrefix("test/semaphore", 2) sema, session := createTestSemaphore(t, c, "test/semaphore", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Initial release should fail // Initial release should fail
err = sema.Release() err := sema.Release()
if err != ErrSemaphoreNotHeld { if err != ErrSemaphoreNotHeld {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -74,10 +101,8 @@ func TestAPI_SemaphoreForceInvalidate(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
sema, err := c.SemaphorePrefix("test/semaphore", 2) sema, session := createTestSemaphore(t, c, "test/semaphore", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
lockCh, err := sema.Acquire(nil) lockCh, err := sema.Acquire(nil)
@ -109,10 +134,8 @@ func TestAPI_SemaphoreDeleteKey(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
sema, err := c.SemaphorePrefix("test/semaphore", 2) sema, session := createTestSemaphore(t, c, "test/semaphore", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
lockCh, err := sema.Acquire(nil) lockCh, err := sema.Acquire(nil)
@ -149,10 +172,8 @@ func TestAPI_SemaphoreContend(t *testing.T) {
wg.Add(1) wg.Add(1)
go func(idx int) { go func(idx int) {
defer wg.Done() defer wg.Done()
sema, err := c.SemaphorePrefix("test/semaphore", 2) sema, session := createTestSemaphore(t, c, "test/semaphore", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work eventually, will contend // Should work eventually, will contend
lockCh, err := sema.Acquire(nil) lockCh, err := sema.Acquire(nil)
@ -198,23 +219,19 @@ func TestAPI_SemaphoreBadLimit(t *testing.T) {
sema, err := c.SemaphorePrefix("test/semaphore", 0) sema, err := c.SemaphorePrefix("test/semaphore", 0)
if err == nil { if err == nil {
t.Fatalf("should error") t.Fatalf("should error, limit must be positive")
} }
sema, err = c.SemaphorePrefix("test/semaphore", 1) sema, session := createTestSemaphore(t, c, "test/semaphore", 1)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
_, err = sema.Acquire(nil) _, err = sema.Acquire(nil)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
sema2, err := c.SemaphorePrefix("test/semaphore", 2) sema2, session := createTestSemaphore(t, c, "test/semaphore", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
_, err = sema2.Acquire(nil) _, err = sema2.Acquire(nil)
if err.Error() != "semaphore limit conflict (lock: 1, local: 2)" { if err.Error() != "semaphore limit conflict (lock: 1, local: 2)" {
@ -227,17 +244,13 @@ func TestAPI_SemaphoreDestroy(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
sema, err := c.SemaphorePrefix("test/semaphore", 2) sema, session := createTestSemaphore(t, c, "test/semaphore", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
sema2, err := c.SemaphorePrefix("test/semaphore", 2) sema2, session := createTestSemaphore(t, c, "test/semaphore", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
_, err = sema.Acquire(nil) _, err := sema.Acquire(nil)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -283,10 +296,8 @@ func TestAPI_SemaphoreConflict(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
lock, err := c.LockKey("test/sema/.lock") lock, session := createTestLock(t, c, "test/sema/.lock")
if err != nil { defer session.Destroy(lock.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should work // Should work
leaderCh, err := lock.Lock(nil) leaderCh, err := lock.Lock(nil)
@ -298,10 +309,8 @@ func TestAPI_SemaphoreConflict(t *testing.T) {
} }
defer lock.Unlock() defer lock.Unlock()
sema, err := c.SemaphorePrefix("test/sema/", 2) sema, session := createTestSemaphore(t, c, "test/sema/", 2)
if err != nil { defer session.Destroy(sema.opts.Session, nil)
t.Fatalf("err: %v", err)
}
// Should conflict with lock // Should conflict with lock
_, err = sema.Acquire(nil) _, err = sema.Acquire(nil)
@ -321,6 +330,8 @@ func TestAPI_SemaphoreMonitorRetry(t *testing.T) {
raw, s := makeClient(t) raw, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
// Set up a server that always responds with 500 errors. // Set up a server that always responds with 500 errors.
failer := func(w http.ResponseWriter, req *http.Request) { failer := func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
@ -438,6 +449,8 @@ func TestAPI_SemaphoreOneShot(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
// Set up a semaphore as a one-shot. // Set up a semaphore as a one-shot.
opts := &SemaphoreOptions{ opts := &SemaphoreOptions{
Prefix: "test/sema/.lock", Prefix: "test/sema/.lock",

View File

@ -14,6 +14,8 @@ func TestAPI_SessionCreateDestroy(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
id, meta, err := session.Create(nil, nil) id, meta, err := session.Create(nil, nil)
@ -44,6 +46,8 @@ func TestAPI_SessionCreateRenewDestroy(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
se := &SessionEntry{ se := &SessionEntry{
@ -95,6 +99,8 @@ func TestAPI_SessionCreateRenewDestroyRenew(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
entry := &SessionEntry{ entry := &SessionEntry{
@ -149,6 +155,8 @@ func TestAPI_SessionCreateDestroyRenewPeriodic(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
entry := &SessionEntry{ entry := &SessionEntry{
@ -203,6 +211,8 @@ func TestAPI_SessionRenewPeriodic_Cancel(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
entry := &SessionEntry{ entry := &SessionEntry{
Behavior: SessionBehaviorDelete, Behavior: SessionBehaviorDelete,
@ -279,6 +289,8 @@ func TestAPI_SessionInfo(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
id, _, err := session.Create(nil, nil) id, _, err := session.Create(nil, nil)
@ -358,6 +370,8 @@ func TestAPI_SessionNode(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
id, _, err := session.Create(nil, nil) id, _, err := session.Create(nil, nil)
@ -393,6 +407,8 @@ func TestAPI_SessionList(t *testing.T) {
c, s := makeClient(t) c, s := makeClient(t)
defer s.Stop() defer s.Stop()
s.WaitForSerfCheck(t)
session := c.Session() session := c.Session()
id, _, err := session.Create(nil, nil) id, _, err := session.Create(nil, nil)

View File

@ -395,3 +395,56 @@ func (s *TestServer) waitForLeader() error {
} }
return nil return nil
} }
// WaitForSerfCheck ensures we have a node with serfHealth check registered
// Behavior mirrors testrpc.WaitForTestAgent but avoids the dependency cycle in api pkg
func (s *TestServer) WaitForSerfCheck(t *testing.T) {
retry.Run(t, func(r *retry.R) {
// Query the API and check the status code.
url := s.url("/v1/catalog/nodes?index=0")
resp, err := s.HTTPClient.Get(url)
if err != nil {
r.Fatal("failed http get", err)
}
defer resp.Body.Close()
if err := s.requireOK(resp); err != nil {
r.Fatal("failed OK response", err)
}
// Watch for the anti-entropy sync to finish.
var payload []map[string]interface{}
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(&payload); err != nil {
r.Fatal(err)
}
if len(payload) < 1 {
r.Fatal("No nodes")
}
// Ensure the serfHealth check is registered
url = s.url(fmt.Sprintf("/v1/health/node/%s", payload[0]["Node"]))
resp, err = s.HTTPClient.Get(url)
if err != nil {
r.Fatal("failed http get", err)
}
defer resp.Body.Close()
if err := s.requireOK(resp); err != nil {
r.Fatal("failed OK response", err)
}
dec = json.NewDecoder(resp.Body)
if err = dec.Decode(&payload); err != nil {
r.Fatal(err)
}
var found bool
for _, check := range payload {
if check["CheckID"].(string) == "serfHealth" {
found = true
break
}
}
if !found {
r.Fatal("missing serfHealth registration")
}
})
}