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 }