github.com/m3db/m3@v1.5.0/src/cluster/services/leader/election/client_test.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package election 22 23 import ( 24 "context" 25 "testing" 26 "time" 27 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 clientv3 "go.etcd.io/etcd/client/v3" 31 "go.etcd.io/etcd/client/v3/concurrency" 32 "go.etcd.io/etcd/tests/v3/framework/integration" 33 ) 34 35 type testCluster struct { 36 t *testing.T 37 cluster *integration.Cluster 38 } 39 40 func newTestCluster(t *testing.T) *testCluster { 41 integration.BeforeTestExternal(t) 42 return &testCluster{ 43 t: t, 44 cluster: integration.NewCluster(t, &integration.ClusterConfig{ 45 Size: 1, 46 }), 47 } 48 } 49 50 func (tc *testCluster) close() { 51 tc.cluster.Terminate(tc.t) 52 } 53 54 func (tc *testCluster) etcdClient() *clientv3.Client { 55 return tc.cluster.RandClient() 56 } 57 58 func (tc *testCluster) client(prefix string, options ...ClientOption) *Client { 59 options = append([]ClientOption{WithSessionOptions(concurrency.WithTTL(5))}, options...) 60 cl, err := NewClient(tc.etcdClient(), prefix, options...) 61 require.NoError(tc.t, err) 62 63 return cl 64 } 65 66 func TestNewClient(t *testing.T) { 67 tc := newTestCluster(t) 68 69 cl, err := NewClient(tc.etcdClient(), "foo") 70 assert.NoError(t, err) 71 assert.NotNil(t, cl) 72 73 tc.close() 74 75 _, err = NewClient(tc.etcdClient(), "foo") 76 assert.Error(t, err) 77 } 78 79 func TestCampaign(t *testing.T) { 80 tc := newTestCluster(t) 81 defer tc.close() 82 83 cl := tc.client("") 84 _, err := cl.Campaign(context.Background(), "foo") 85 assert.NoError(t, err) 86 87 ld, err := cl.Leader(context.Background()) 88 assert.NoError(t, err) 89 assert.Equal(t, "foo", ld) 90 } 91 92 func TestCampaign_Multi(t *testing.T) { 93 tc := newTestCluster(t) 94 defer tc.close() 95 96 cl1 := tc.client("foo") 97 _, err := cl1.Campaign(context.Background(), "1") 98 assert.NoError(t, err) 99 100 ld, _ := cl1.Leader(context.Background()) 101 assert.Equal(t, "1", ld) 102 103 cl2 := tc.client("foo") 104 ch := make(chan struct{}) 105 106 go func() { 107 _, err := cl2.Campaign(context.Background(), "2") 108 assert.NoError(t, err) 109 close(ch) 110 }() 111 112 err = cl1.Resign(context.Background()) 113 assert.NoError(t, err) 114 115 <-ch 116 ld, _ = cl1.Leader(context.Background()) 117 assert.Equal(t, "2", ld) 118 119 ld, _ = cl2.Leader(context.Background()) 120 assert.Equal(t, "2", ld) 121 } 122 123 func TestCampaign_DeadSession(t *testing.T) { 124 tc := newTestCluster(t) 125 defer tc.close() 126 127 cl1 := tc.client("") 128 cl2 := tc.client("") 129 130 _, err := cl1.Campaign(context.Background(), "1") 131 assert.NoError(t, err) 132 133 errC := make(chan error) 134 go func() { 135 _, err := cl2.Campaign(context.Background(), "2") 136 errC <- err 137 }() 138 139 time.Sleep(time.Second) 140 141 cl2.session.Close() 142 143 select { 144 case err := <-errC: 145 assert.Equal(t, context.Canceled, err) 146 case <-time.After(30 * time.Second): 147 t.Error("should receive err from client2 after orphaning session") 148 } 149 150 // Expect that even though cl2 has a dead session it can still lookup leader 151 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 152 defer cancel() 153 ld, err := cl2.Leader(ctx) 154 assert.Equal(t, "1", ld) 155 assert.NoError(t, err) 156 } 157 158 func TestCampaign_DeadSession_Background(t *testing.T) { 159 tc := newTestCluster(t) 160 defer tc.close() 161 162 cl := tc.client("") 163 164 ch, err := cl.Campaign(context.Background(), "1") 165 assert.NoError(t, err) 166 167 cl.session.Close() 168 169 sent := false 170 select { 171 case <-ch: 172 sent = true 173 case <-time.After(10 * time.Second): 174 } 175 176 assert.True(t, sent, "should have received session dead signal") 177 178 // new campaign should reset session 179 _, err = cl.Campaign(context.Background(), "1") 180 assert.NoError(t, err) 181 182 ld, err := cl.Leader(context.Background()) 183 assert.NoError(t, err) 184 assert.Equal(t, "1", ld) 185 } 186 187 func TestResign(t *testing.T) { 188 tc := newTestCluster(t) 189 defer tc.close() 190 191 cl := tc.client("") 192 _, err := cl.Campaign(context.Background(), "foo") 193 assert.NoError(t, err) 194 195 err = cl.Resign(context.Background()) 196 assert.NoError(t, err) 197 } 198 199 func TestCampaign_ResignActive(t *testing.T) { 200 tc := newTestCluster(t) 201 defer tc.close() 202 203 cl1 := tc.client("foo") 204 _, err := cl1.Campaign(context.Background(), "1") 205 assert.NoError(t, err) 206 207 ld, _ := cl1.Leader(context.Background()) 208 assert.Equal(t, "1", ld) 209 210 cl2 := tc.client("foo") 211 ch := make(chan struct{}) 212 ch2 := make(chan struct{}) 213 214 ctx, cancel := context.WithCancel(context.Background()) 215 216 go func() { 217 close(ch2) 218 _, err := cl2.Campaign(ctx, "2") 219 assert.Equal(t, context.Canceled, err) 220 close(ch) 221 }() 222 223 <-ch2 224 time.Sleep(200 * time.Millisecond) 225 cancel() 226 err = cl2.Resign(context.Background()) 227 assert.NoError(t, err) 228 229 <-ch 230 ld, _ = cl1.Leader(context.Background()) 231 assert.Equal(t, "1", ld) 232 233 ld, _ = cl2.Leader(context.Background()) 234 assert.Equal(t, "1", ld) 235 } 236 237 func TestObserve(t *testing.T) { 238 tc := newTestCluster(t) 239 defer tc.close() 240 241 cl1 := tc.client("a") 242 243 ctx1, cancel1 := context.WithCancel(context.Background()) 244 defer cancel1() 245 246 obsC, err := cl1.Observe(ctx1) 247 assert.NoError(t, err) 248 249 bufC := make(chan string, 100) 250 go func() { 251 for v := range obsC { 252 bufC <- v 253 } 254 }() 255 256 _, err = cl1.Campaign(context.Background(), "1") 257 assert.NoError(t, err) 258 259 select { 260 case <-time.After(time.Second): 261 t.Error("expected to receive leader update within 1s") 262 case v := <-bufC: 263 assert.Equal(t, "1", v) 264 } 265 266 cl2 := tc.client("a") 267 268 el2 := make(chan struct{}) 269 go func() { 270 _, err := cl2.Campaign(context.Background(), "2") 271 assert.NoError(t, err) 272 close(el2) 273 }() 274 275 err = cl1.Resign(context.Background()) 276 assert.NoError(t, err) 277 278 select { 279 case <-time.After(5 * time.Second): 280 t.Error("expected to be leader within 5s") 281 case <-el2: 282 } 283 284 select { 285 case <-time.After(time.Second): 286 t.Error("expected to receive new leader within 1s") 287 case v := <-bufC: 288 assert.Equal(t, "2", v) 289 } 290 291 cancel1() 292 select { 293 case <-time.After(time.Second): 294 t.Error("expected update channel to be closed within 1s") 295 case _, ok := <-obsC: 296 assert.False(t, ok) 297 } 298 } 299 300 func TestClose(t *testing.T) { 301 tc := newTestCluster(t) 302 defer tc.close() 303 304 cl := tc.client("") 305 306 ch, err := cl.Campaign(context.Background(), "1") 307 assert.NoError(t, err) 308 309 ld, err := cl.Leader(context.Background()) 310 assert.Equal(t, "1", ld) 311 assert.NoError(t, err) 312 313 err = cl.Close() 314 assert.NoError(t, err) 315 316 select { 317 case <-ch: 318 case <-time.After(30 * time.Second): 319 t.Error("should have received close on campaign ch") 320 } 321 322 _, err = cl.Campaign(context.Background(), "foo") 323 assert.Equal(t, ErrClientClosed, err) 324 325 err = cl.Resign(context.Background()) 326 assert.Equal(t, ErrClientClosed, err) 327 328 _, err = cl.Leader(context.Background()) 329 assert.Equal(t, ErrClientClosed, err) 330 }