github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cluster/services/leader/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 leader 22 23 import ( 24 "fmt" 25 "reflect" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/cluster/services" 30 "github.com/m3db/m3/src/cluster/services/leader/campaign" 31 "github.com/m3db/m3/src/cluster/services/leader/election" 32 33 integration "github.com/m3db/m3/src/integration/resources/docker/dockerexternal/etcdintegration" 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 clientv3 "go.etcd.io/etcd/client/v3" 37 "golang.org/x/net/context" 38 ) 39 40 var ( 41 newStatus = campaign.NewStatus 42 newErr = campaign.NewErrorStatus 43 followerS = newStatus(campaign.Follower) 44 leaderS = newStatus(campaign.Leader) 45 ) 46 47 func waitForStates(ch <-chan campaign.Status, early bool, states ...campaign.Status) error { 48 var seen []campaign.Status 49 for s := range ch { 50 seen = append(seen, s) 51 // terminate early (before channel closes) 52 if early && reflect.DeepEqual(seen, states) { 53 return nil 54 } 55 } 56 57 if !reflect.DeepEqual(seen, states) { 58 return fmt.Errorf("states did not match: %v != %v", seen, states) 59 } 60 61 return nil 62 } 63 64 type testCluster struct { 65 t *testing.T 66 cluster *integration.Cluster 67 } 68 69 func newTestCluster(t *testing.T) *testCluster { 70 integration.BeforeTestExternal(t) 71 return &testCluster{ 72 t: t, 73 cluster: integration.NewCluster(t, &integration.ClusterConfig{ 74 Size: 1, 75 }), 76 } 77 } 78 79 func (tc *testCluster) close() { 80 tc.cluster.Terminate(tc.t) 81 } 82 83 func (tc *testCluster) etcdClient() *clientv3.Client { 84 return tc.cluster.RandClient() 85 } 86 87 func (tc *testCluster) options() Options { 88 sid := services.NewServiceID(). 89 SetEnvironment("e1"). 90 SetName("s1"). 91 SetZone("z1") 92 93 eopts := services.NewElectionOptions(). 94 SetTTLSecs(5) 95 96 return NewOptions(). 97 SetServiceID(sid). 98 SetElectionOpts(eopts) 99 } 100 101 func (tc *testCluster) client() *client { 102 svc, err := newClient(tc.etcdClient(), tc.options(), "") 103 require.NoError(tc.t, err) 104 105 return svc 106 } 107 108 func (tc *testCluster) service() services.LeaderService { 109 svc, err := NewService(tc.etcdClient(), tc.options()) 110 require.NoError(tc.t, err) 111 112 return svc 113 } 114 115 func (tc *testCluster) opts(val string) services.CampaignOptions { 116 opts, err := services.NewCampaignOptions() 117 require.NoError(tc.t, err) 118 return opts.SetLeaderValue(val) 119 } 120 121 func TestNewClient(t *testing.T) { 122 tc := newTestCluster(t) 123 defer tc.close() 124 125 svc, err := newClient(tc.etcdClient(), tc.options(), "") 126 assert.NoError(t, err) 127 assert.NotNil(t, svc) 128 } 129 130 // TODO: this test most likely wasn't testing what we thought it was. While using etcd/testing/framework/integration, 131 // the client gets closed 132 func TestNewClient_BadCluster(t *testing.T) { 133 t.Skip("This test only works with the etcd/testing/framework/integration package, " + 134 "and doesn't provide much signal on correctness of our code.") 135 tc := newTestCluster(t) 136 cl := tc.etcdClient() 137 tc.close() 138 require.NoError(t, cl.Close()) 139 _, err := newClient(cl, tc.options(), "") 140 assert.Error(t, err) 141 } 142 143 func TestCampaign(t *testing.T) { 144 tc := newTestCluster(t) 145 defer tc.close() 146 147 svc := tc.client() 148 149 sc, err := svc.campaign(tc.opts("foo")) 150 assert.NoError(t, err) 151 152 waitForStates(sc, true, followerS, leaderS) 153 154 _, err = svc.campaign(tc.opts("foo2")) 155 assert.Equal(t, ErrCampaignInProgress, err) 156 157 err = svc.resign() 158 assert.NoError(t, err) 159 160 errC := make(chan error) 161 go func() { 162 errC <- waitForStates(sc, false, followerS) 163 }() 164 165 err = <-errC 166 assert.NoError(t, err) 167 168 sc, err = svc.campaign(tc.opts("foo3")) 169 assert.NoError(t, err) 170 171 waitForStates(sc, true, followerS, leaderS) 172 173 err = svc.resign() 174 assert.NoError(t, err) 175 assert.NoError(t, waitForStates(sc, false, followerS)) 176 } 177 178 func TestCampaign_Override(t *testing.T) { 179 tc := newTestCluster(t) 180 defer tc.close() 181 182 svc := tc.client() 183 184 sc, err := svc.campaign(tc.opts("foo")) 185 assert.NoError(t, err) 186 assert.NoError(t, waitForStates(sc, true, followerS, leaderS)) 187 188 ld, err := svc.leader() 189 assert.NoError(t, err) 190 assert.Equal(t, "foo", ld) 191 } 192 193 func TestCampaign_Renew(t *testing.T) { 194 tc := newTestCluster(t) 195 defer tc.close() 196 197 svc := tc.client() 198 sc, err := svc.campaign(tc.opts("")) 199 assert.NoError(t, err) 200 assert.NoError(t, waitForStates(sc, true, followerS, leaderS)) 201 202 err = svc.resign() 203 assert.NoError(t, err) 204 assert.NoError(t, waitForStates(sc, false, followerS)) 205 206 _, err = svc.leader() 207 assert.Equal(t, ErrNoLeader, err) 208 209 sc2, err := svc.campaign(tc.opts("")) 210 assert.NoError(t, err) 211 assert.NoError(t, waitForStates(sc2, true, followerS, leaderS)) 212 } 213 214 func TestResign(t *testing.T) { 215 tc := newTestCluster(t) 216 defer tc.close() 217 218 svc := tc.client() 219 220 sc, err := svc.campaign(tc.opts("i1")) 221 assert.NoError(t, err) 222 223 assert.NoError(t, waitForStates(sc, true, followerS, leaderS)) 224 225 ld, err := svc.leader() 226 assert.NoError(t, err) 227 assert.Equal(t, "i1", ld) 228 229 err = svc.resign() 230 assert.NoError(t, err) 231 232 assert.NoError(t, waitForStates(sc, false, followerS)) 233 234 ld, err = svc.leader() 235 assert.Equal(t, ErrNoLeader, err) 236 assert.Equal(t, "", ld) 237 } 238 239 func TestResign_BlockingCampaign(t *testing.T) { 240 tc := newTestCluster(t) 241 defer tc.close() 242 243 svc1, svc2 := tc.client(), tc.client() 244 245 sc1, err := svc1.campaign(tc.opts("i1")) 246 assert.NoError(t, err) 247 assert.NoError(t, waitForStates(sc1, true, followerS, leaderS)) 248 249 sc2, err := svc2.campaign(tc.opts("i2")) 250 assert.NoError(t, err) 251 assert.NoError(t, waitForStates(sc2, true, followerS)) 252 253 err = svc2.resign() 254 assert.NoError(t, err) 255 assert.NoError(t, waitForStates(sc2, false, newErr(context.Canceled))) 256 } 257 258 func TestResign_Early(t *testing.T) { 259 tc := newTestCluster(t) 260 defer tc.close() 261 262 svc := tc.client() 263 264 err := svc.resign() 265 assert.NoError(t, err) 266 } 267 268 func TestObserve(t *testing.T) { 269 tc := newTestCluster(t) 270 defer tc.close() 271 272 svc1 := tc.client() 273 274 obsC, err := svc1.observe() 275 assert.NoError(t, err) 276 277 sc1, err := svc1.campaign(tc.opts("i1")) 278 assert.NoError(t, err) 279 assert.NoError(t, waitForStates(sc1, true, followerS, leaderS)) 280 281 select { 282 case <-time.After(time.Second): 283 t.Error("expected to receive leader update") 284 case v := <-obsC: 285 assert.Equal(t, "i1", v) 286 } 287 288 assert.NoError(t, svc1.close()) 289 select { 290 case <-time.After(5 * time.Second): 291 t.Error("expected client channel to be closed") 292 case _, ok := <-obsC: 293 assert.False(t, ok) 294 } 295 296 _, err = svc1.observe() 297 assert.Equal(t, errClientClosed, err) 298 } 299 300 func testHandoff(t *testing.T, resign bool) { 301 tc := newTestCluster(t) 302 defer tc.close() 303 304 svc1, svc2 := tc.client(), tc.client() 305 306 sc1, err := svc1.campaign(tc.opts("i1")) 307 assert.NoError(t, err) 308 309 assert.NoError(t, waitForStates(sc1, true, followerS, leaderS)) 310 311 sc2, err := svc2.campaign(tc.opts("i2")) 312 assert.NoError(t, err) 313 314 assert.NoError(t, waitForStates(sc2, true, followerS)) 315 316 ld, err := svc1.leader() 317 assert.NoError(t, err) 318 assert.Equal(t, ld, "i1") 319 320 if resign { 321 err = svc1.resign() 322 assert.NoError(t, waitForStates(sc1, false, followerS)) 323 } else { 324 err = svc1.close() 325 assert.NoError(t, waitForStates(sc1, false, newErr(election.ErrSessionExpired))) 326 } 327 assert.NoError(t, err) 328 329 assert.NoError(t, waitForStates(sc2, true, leaderS)) 330 331 ld, err = svc2.leader() 332 assert.NoError(t, err) 333 assert.Equal(t, ld, "i2") 334 } 335 336 func TestCampaign_Cancel_Resign(t *testing.T) { 337 testHandoff(t, true) 338 } 339 340 func TestCampaign_Cancel_Close(t *testing.T) { 341 testHandoff(t, false) 342 } 343 344 func TestCampaign_Close_NonLeader(t *testing.T) { 345 tc := newTestCluster(t) 346 defer tc.close() 347 348 svc1, svc2 := tc.client(), tc.client() 349 350 sc1, err := svc1.campaign(tc.opts("i1")) 351 assert.NoError(t, err) 352 assert.NoError(t, waitForStates(sc1, true, followerS, leaderS)) 353 354 sc2, err := svc2.campaign(tc.opts("i2")) 355 assert.NoError(t, err) 356 assert.NoError(t, waitForStates(sc2, true, followerS)) 357 358 ld, err := svc1.leader() 359 assert.NoError(t, err) 360 assert.Equal(t, ld, "i1") 361 362 err = svc2.close() 363 assert.NoError(t, err) 364 assert.NoError(t, waitForStates(sc2, false, newErr(context.Canceled))) 365 366 err = svc1.resign() 367 assert.NoError(t, err) 368 assert.NoError(t, waitForStates(sc1, false, followerS)) 369 370 _, err = svc2.leader() 371 assert.Equal(t, errClientClosed, err) 372 } 373 374 func TestClose(t *testing.T) { 375 tc := newTestCluster(t) 376 defer tc.close() 377 378 svc := tc.client() 379 380 sc, err := svc.campaign(tc.opts("i1")) 381 assert.NoError(t, err) 382 assert.NoError(t, waitForStates(sc, true, followerS, leaderS)) 383 384 ld, err := svc.leader() 385 assert.NoError(t, err) 386 assert.Equal(t, "i1", ld) 387 388 err = svc.close() 389 assert.NoError(t, err) 390 assert.True(t, svc.isClosed()) 391 assert.NoError(t, waitForStates(sc, false, newErr(election.ErrSessionExpired))) 392 393 err = svc.resign() 394 assert.Equal(t, errClientClosed, err) 395 396 _, err = svc.campaign(tc.opts("")) 397 assert.Equal(t, errClientClosed, err) 398 } 399 400 func TestLeader(t *testing.T) { 401 tc := newTestCluster(t) 402 defer tc.close() 403 404 svc1, svc2 := tc.client(), tc.client() 405 sc, err := svc1.campaign(tc.opts("i1")) 406 assert.NoError(t, err) 407 assert.NoError(t, waitForStates(sc, true, followerS, leaderS)) 408 409 ld, err := svc2.leader() 410 assert.NoError(t, err) 411 412 assert.Equal(t, "i1", ld) 413 } 414 415 func TestElectionPrefix(t *testing.T) { 416 for args, exp := range map[*struct { 417 env, name, eid string 418 }]string{ 419 {"", "svc", ""}: "_ld/svc/default", 420 {"env", "svc", ""}: "_ld/env/svc/default", 421 {"", "svc", "foo"}: "_ld/svc/foo", 422 {"env", "svc", "foo"}: "_ld/env/svc/foo", 423 } { 424 sid := services.NewServiceID(). 425 SetEnvironment(args.env). 426 SetName(args.name) 427 428 pfx := electionPrefix(sid, args.eid) 429 430 assert.Equal(t, exp, pfx) 431 } 432 }