github.com/mailgun/holster/v4@v4.20.0/consul/lock_test.go (about)

     1  package consul_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/Shopify/toxiproxy"
    11  	"github.com/hashicorp/consul/api"
    12  	"github.com/mailgun/holster/v4/consul"
    13  	"github.com/mailgun/holster/v4/testutil"
    14  	"github.com/sirupsen/logrus"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func init() {
    20  	rand.Seed(time.Now().UnixNano())
    21  }
    22  
    23  func cleanUp(t *testing.T) {
    24  	client, _ := api.NewClient(api.DefaultConfig())
    25  	list, _, _ := client.KV().List("lock-test", nil)
    26  	for _, pair := range list {
    27  		_, err := client.KV().Delete(pair.Key, nil)
    28  		require.NoError(t, err)
    29  	}
    30  }
    31  
    32  func WithToxiProxy(t *testing.T, fn func(*toxiproxy.Proxy, *api.Client)) {
    33  	proxy := toxiproxy.NewProxy()
    34  	proxy.Name = fmt.Sprintf("consul-proxy-%d", rand.Int())
    35  	proxy.Listen = "127.0.0.1:0"
    36  	proxy.Upstream = "127.0.0.1:8500"
    37  
    38  	err := proxy.Start()
    39  	defer proxy.Stop()
    40  	require.NoError(t, err)
    41  
    42  	cfg := api.DefaultConfig()
    43  	cfg.Address = proxy.Listen
    44  	c, err := api.NewClient(cfg)
    45  	require.NoError(t, err)
    46  	fn(proxy, c)
    47  }
    48  
    49  func printKeys(prefix string) {
    50  	out := "-----------------\n"
    51  	client, _ := api.NewClient(api.DefaultConfig())
    52  	list, _, _ := client.KV().List(prefix, nil)
    53  	for _, pair := range list {
    54  		out += fmt.Sprintf("Pair: %s:%s\n", pair.Key, string(pair.Value))
    55  	}
    56  	fmt.Print(out + "-----\n")
    57  }
    58  
    59  func TestBehaviorRelease(t *testing.T) {
    60  	logrus.SetLevel(logrus.DebugLevel)
    61  	hasLockCh := make(chan bool, 5)
    62  	defer cleanUp(t)
    63  
    64  	WithToxiProxy(t, func(p *toxiproxy.Proxy, c *api.Client) {
    65  		printKeys("lock-test")
    66  		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
    67  		defer cancel()
    68  		name := fmt.Sprintf("lock-test-%d", rand.Int())
    69  		l, err := consul.SpawnLock(ctx, &consul.LockConfig{
    70  			Client: c,
    71  			LockOptions: &api.LockOptions{
    72  				SessionOpts: &api.SessionEntry{
    73  					Name:      name,
    74  					LockDelay: time.Second * 10,
    75  					Behavior:  api.SessionBehaviorRelease,
    76  					TTL:       "10s",
    77  				},
    78  				Value:       []byte("lock-value"),
    79  				SessionName: name,
    80  				Key:         name,
    81  			},
    82  			OnChange: func(hasLock bool) {
    83  				hasLockCh <- hasLock
    84  			},
    85  		})
    86  		require.NoError(t, err)
    87  		require.NotNil(t, l)
    88  
    89  		// Should have lock
    90  		printKeys("lock-test")
    91  		require.True(t, l.HasLock())
    92  		select {
    93  		case is := <-hasLockCh:
    94  			assert.True(t, is)
    95  		default:
    96  		}
    97  
    98  		// Interrupt connectivity to consul
    99  		p.Stop()
   100  
   101  		// Ensure the connectivity is broken
   102  		_, _, err = c.KV().Get(name, nil)
   103  		assert.Error(t, err)
   104  
   105  		// Wait until we notice we lost lock
   106  		is := <-hasLockCh
   107  		assert.False(t, is)
   108  		assert.False(t, l.HasLock())
   109  		printKeys("lock-test")
   110  
   111  		// Resume connectivity
   112  		err = p.Start()
   113  		require.NoError(t, err)
   114  
   115  		// Wait until we regain lock
   116  		is = <-hasLockCh
   117  		assert.True(t, is)
   118  		assert.True(t, l.HasLock())
   119  
   120  		// Unlock the key
   121  		l.Unlock([]byte("this is a test"))
   122  		printKeys("lock-test")
   123  
   124  		// Ensure the key is NOT deleted
   125  		list, _, err := c.KV().List("lock-test", nil)
   126  		require.NoError(t, err)
   127  		var found bool
   128  		for _, i := range list {
   129  			if i.Key == name {
   130  				found = true
   131  				assert.Equal(t, i.Value, []byte("this is a test"))
   132  			}
   133  		}
   134  		assert.True(t, found)
   135  
   136  		// Delete the key
   137  		_, err = c.KV().Delete(name, nil)
   138  		require.NoError(t, err)
   139  	})
   140  }
   141  
   142  func TestBehaviorDeleteOnUnlock(t *testing.T) {
   143  	logrus.SetLevel(logrus.DebugLevel)
   144  	hasLockCh := make(chan bool, 5)
   145  	defer cleanUp(t)
   146  
   147  	WithToxiProxy(t, func(p *toxiproxy.Proxy, c *api.Client) {
   148  		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
   149  		defer cancel()
   150  		name := fmt.Sprintf("lock-test-delete-%d", rand.Int())
   151  		l, err := consul.SpawnLock(ctx, &consul.LockConfig{
   152  			Client: c,
   153  			LockOptions: &api.LockOptions{
   154  				SessionOpts: &api.SessionEntry{
   155  					Name:      name,
   156  					LockDelay: time.Second * 10,
   157  					// When SessionBehavior is Delete
   158  					Behavior: api.SessionBehaviorDelete,
   159  					TTL:      "10s",
   160  				},
   161  				Value:       []byte("lock-value"),
   162  				SessionName: name,
   163  				Key:         name,
   164  			},
   165  			OnChange: func(hasLock bool) {
   166  				hasLockCh <- hasLock
   167  			},
   168  		})
   169  		require.NoError(t, err)
   170  		require.NotNil(t, l)
   171  
   172  		// Should have lock
   173  		require.True(t, l.HasLock())
   174  		select {
   175  		case is := <-hasLockCh:
   176  			assert.True(t, is)
   177  		default:
   178  		}
   179  
   180  		// Unlock the key
   181  		l.Unlock(nil)
   182  		printKeys("lock-test")
   183  
   184  		// Ensure the key was deleted
   185  		list, _, err := c.KV().List("lock-test", nil)
   186  		require.NoError(t, err)
   187  		var found bool
   188  		for _, i := range list {
   189  			if i.Key == name {
   190  				found = true
   191  			}
   192  		}
   193  		assert.False(t, found)
   194  	})
   195  }
   196  
   197  func TestBehaviorDeleteOnDisconnect(t *testing.T) {
   198  	logrus.SetLevel(logrus.DebugLevel)
   199  	defer cleanUp(t)
   200  
   201  	WithToxiProxy(t, func(p *toxiproxy.Proxy, c *api.Client) {
   202  		printKeys("lock-test")
   203  		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
   204  		defer cancel()
   205  		name := fmt.Sprintf("lock-test-disconnect%d", rand.Int())
   206  		l, err := consul.SpawnLock(ctx, &consul.LockConfig{
   207  			Client: c,
   208  			LockOptions: &api.LockOptions{
   209  				SessionOpts: &api.SessionEntry{
   210  					Name: name,
   211  					// When SessionBehavior is Delete
   212  					Behavior: api.SessionBehaviorDelete,
   213  					TTL:      "10s",
   214  				},
   215  				Value:       []byte("lock-value"),
   216  				SessionName: name,
   217  				Key:         name,
   218  			},
   219  		})
   220  		require.NoError(t, err)
   221  		require.NotNil(t, l)
   222  
   223  		// Should have lock
   224  		printKeys("lock-test")
   225  		require.True(t, l.HasLock())
   226  
   227  		// Interrupt connectivity to consul
   228  		p.Stop()
   229  
   230  		// Wait for the lock file to disappear
   231  		client, err := api.NewClient(api.DefaultConfig())
   232  		require.NoError(t, err)
   233  		testutil.UntilPass(t, 50, time.Second, func(t testutil.TestingT) {
   234  			kv, _, err := client.KV().Get(name, nil)
   235  			assert.NoError(t, err)
   236  			assert.Nil(t, kv)
   237  		})
   238  
   239  		// Resume connectivity
   240  		err = p.Start()
   241  		require.NoError(t, err)
   242  
   243  		// Wait until we regain lock
   244  		testutil.UntilPass(t, 50, time.Second, func(t testutil.TestingT) {
   245  			assert.True(t, l.HasLock())
   246  		})
   247  
   248  		// Unlock the key
   249  		l.Unlock(nil)
   250  	})
   251  }