package api
import (
"log"
"sync"
"testing"
"time"
)
func TestLock_LockUnlock ( t * testing . T ) {
c , s := makeClient ( t )
defer s . stop ( )
lock , err := c . LockKey ( "test/lock" )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
// Initial unlock should fail
err = lock . Unlock ( )
if err != ErrLockNotHeld {
t . Fatalf ( "err: %v" , err )
}
// Should work
leaderCh , err := lock . Lock ( nil )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
if leaderCh == nil {
t . Fatalf ( "not leader" )
}
// Double lock should fail
_ , err = lock . Lock ( nil )
if err != ErrLockHeld {
t . Fatalf ( "err: %v" , err )
}
// Should be leader
select {
case <- leaderCh :
t . Fatalf ( "should be leader" )
default :
}
// Initial unlock should work
err = lock . Unlock ( )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
// Double unlock should fail
err = lock . Unlock ( )
if err != ErrLockNotHeld {
t . Fatalf ( "err: %v" , err )
}
// Should loose leadership
select {
case <- leaderCh :
case <- time . After ( time . Second ) :
t . Fatalf ( "should not be leader" )
}
}
func TestLock_ForceInvalidate ( t * testing . T ) {
c , s := makeClient ( t )
defer s . stop ( )
lock , err := c . LockKey ( "test/lock" )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
// Should work
leaderCh , err := lock . Lock ( nil )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
if leaderCh == nil {
t . Fatalf ( "not leader" )
}
defer lock . Unlock ( )
go func ( ) {
// Nuke the session, simulator an operator invalidation
// or a health check failure
session := c . Session ( )
session . Destroy ( lock . lockSession , nil )
} ( )
// Should loose leadership
select {
case <- leaderCh :
case <- time . After ( time . Second ) :
t . Fatalf ( "should not be leader" )
}
}
func TestLock_DeleteKey ( t * testing . T ) {
c , s := makeClient ( t )
defer s . stop ( )
lock , err := c . LockKey ( "test/lock" )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
// Should work
leaderCh , err := lock . Lock ( nil )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
if leaderCh == nil {
t . Fatalf ( "not leader" )
}
defer lock . Unlock ( )
go func ( ) {
// Nuke the key, simulate an operator intervention
kv := c . KV ( )
kv . Delete ( "test/lock" , nil )
} ( )
// Should loose leadership
select {
case <- leaderCh :
case <- time . After ( time . Second ) :
t . Fatalf ( "should not be leader" )
}
}
func TestLock_Contend ( t * testing . T ) {
c , s := makeClient ( t )
defer s . stop ( )
wg := & sync . WaitGroup { }
acquired := make ( [ ] bool , 3 )
for idx := range acquired {
wg . Add ( 1 )
go func ( idx int ) {
defer wg . Done ( )
lock , err := c . LockKey ( "test/lock" )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
// Should work eventually, will contend
leaderCh , err := lock . Lock ( nil )
if err != nil {
t . Fatalf ( "err: %v" , err )
}
if leaderCh == nil {
t . Fatalf ( "not leader" )
}
defer lock . Unlock ( )
log . Printf ( "Contender %d acquired" , idx )
// Set acquired and then leave
acquired [ idx ] = true
} ( idx )
}
// Wait for termination
doneCh := make ( chan struct { } )
go func ( ) {
wg . Wait ( )
close ( doneCh )
} ( )
// Wait for everybody to get a turn
select {
case <- doneCh :
case <- time . After ( 3 * DefaultLockRetryTime ) :
t . Fatalf ( "timeout" )
}
for idx , did := range acquired {
if ! did {
t . Fatalf ( "contender %d never acquired" , idx )
}
}
}