github.com/m3db/m3@v1.5.0/src/cluster/services/heartbeat/etcd/store_test.go (about) 1 // Copyright (c) 2016 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 etcd 22 23 import ( 24 "testing" 25 "time" 26 27 "github.com/m3db/m3/src/cluster/mocks" 28 "github.com/m3db/m3/src/cluster/placement" 29 "github.com/m3db/m3/src/cluster/services" 30 31 "github.com/stretchr/testify/require" 32 clientv3 "go.etcd.io/etcd/client/v3" 33 "go.etcd.io/etcd/tests/v3/framework/integration" 34 ) 35 36 func TestKeys(t *testing.T) { 37 sid := services.NewServiceID().SetName("service") 38 id := "instance" 39 40 require.Equal(t, "_hb/service", servicePrefix(sid)) 41 require.Equal(t, "_hb/service/instance", heartbeatKey(sid, id)) 42 43 sid = sid.SetEnvironment("test") 44 require.Equal(t, "_hb/test/service/instance", heartbeatKey(sid, id)) 45 require.Equal(t, "_hb/test/service", servicePrefix(sid)) 46 require.Equal(t, "instance", instanceFromKey(heartbeatKey(sid, id), servicePrefix(sid))) 47 } 48 49 func TestReuseLeaseID(t *testing.T) { 50 sid := services.NewServiceID().SetName("s1").SetEnvironment("e1") 51 ec, opts, closeFn := testStore(t, sid) 52 defer closeFn() 53 54 i1 := placement.NewInstance().SetID("i1") 55 56 c, err := NewStore(ec, opts) 57 require.NoError(t, err) 58 store := c.(*client) 59 60 err = store.Heartbeat(i1, time.Minute) 61 require.NoError(t, err) 62 63 store.RLock() 64 require.Equal(t, 1, len(store.cache.leases)) 65 var leaseID clientv3.LeaseID 66 for _, leases := range store.cache.leases { 67 for _, v := range leases { 68 leaseID = v 69 } 70 } 71 store.RUnlock() 72 73 err = store.Heartbeat(i1, time.Minute) 74 require.NoError(t, err) 75 76 store.RLock() 77 require.Equal(t, 1, len(store.cache.leases)) 78 for _, leases := range store.cache.leases { 79 for _, v := range leases { 80 require.Equal(t, leaseID, v) 81 } 82 } 83 store.RUnlock() 84 } 85 86 func TestHeartbeat(t *testing.T) { 87 sid := services.NewServiceID().SetName("s1").SetEnvironment("e1") 88 ec, opts, closeFn := testStore(t, sid) 89 defer closeFn() 90 91 i1 := placement.NewInstance().SetID("i1") 92 i2 := placement.NewInstance().SetID("i2") 93 94 c, err := NewStore(ec, opts) 95 require.NoError(t, err) 96 store := c.(*client) 97 98 ids, err := store.Get() 99 require.Equal(t, 0, len(ids)) 100 require.NoError(t, err) 101 102 err = store.Heartbeat(i1, 2*time.Second) 103 require.NoError(t, err) 104 err = store.Heartbeat(i2, time.Minute) 105 require.NoError(t, err) 106 107 ids, err = store.Get() 108 require.NoError(t, err) 109 require.Equal(t, 2, len(ids)) 110 require.Contains(t, ids, "i1") 111 require.Contains(t, ids, "i2") 112 113 // ensure that both Get and GetInstances return the same instances 114 // with their respective serialization methods 115 instances, err := store.GetInstances() 116 require.NoError(t, err) 117 require.Equal(t, 2, len(instances)) 118 require.Contains(t, instances, i1) 119 require.Contains(t, instances, i2) 120 121 for { 122 ids, err = store.Get() 123 require.NoError(t, err) 124 instances, err2 := store.GetInstances() 125 require.NoError(t, err) 126 require.NoError(t, err2) 127 if len(ids) == 1 && len(instances) == 1 { 128 break 129 } 130 time.Sleep(10 * time.Millisecond) 131 } 132 require.Equal(t, 1, len(ids)) 133 require.NotContains(t, ids, "i1") 134 require.Contains(t, ids, "i2") 135 136 err = store.Delete(i2.ID()) 137 require.NoError(t, err) 138 139 ids, err = store.Get() 140 require.NoError(t, err) 141 require.Equal(t, 0, len(ids)) 142 require.NotContains(t, ids, "i1") 143 require.NotContains(t, ids, "i2") 144 } 145 146 func TestDelete(t *testing.T) { 147 sid := services.NewServiceID().SetName("s1").SetEnvironment("e1") 148 ec, opts, closeFn := testStore(t, sid) 149 defer closeFn() 150 151 i1 := placement.NewInstance().SetID("i1") 152 i2 := placement.NewInstance().SetID("i2") 153 154 c, err := NewStore(ec, opts) 155 require.NoError(t, err) 156 store := c.(*client) 157 158 err = store.Heartbeat(i1, time.Hour) 159 require.NoError(t, err) 160 161 err = store.Heartbeat(i2, time.Hour) 162 require.NoError(t, err) 163 164 ids, err := store.Get() 165 require.NoError(t, err) 166 require.Equal(t, 2, len(ids)) 167 require.Contains(t, ids, "i1") 168 require.Contains(t, ids, "i2") 169 170 instances, err := store.GetInstances() 171 require.NoError(t, err) 172 require.Equal(t, 2, len(instances)) 173 require.Contains(t, instances, i1) 174 require.Contains(t, instances, i2) 175 176 err = store.Delete(i1.ID()) 177 require.NoError(t, err) 178 179 err = store.Delete(i1.ID()) 180 require.Error(t, err) 181 182 ids, err = store.Get() 183 require.NoError(t, err) 184 require.Equal(t, 1, len(ids)) 185 require.Contains(t, ids, "i2") 186 187 instances, err = store.GetInstances() 188 require.NoError(t, err) 189 require.Equal(t, 1, len(instances)) 190 require.Contains(t, instances, i2) 191 192 err = store.Heartbeat(i1, time.Hour) 193 require.NoError(t, err) 194 195 for { 196 ids, _ = store.Get() 197 instances, _ = store.GetInstances() 198 if len(ids) == 2 && len(instances) == 2 { 199 break 200 } 201 } 202 } 203 204 func TestWatch(t *testing.T) { 205 sid := services.NewServiceID().SetName("s2").SetEnvironment("e2") 206 ec, opts, closeFn := testStore(t, sid) 207 defer closeFn() 208 209 store, err := NewStore(ec, opts) 210 require.NoError(t, err) 211 212 i1 := placement.NewInstance().SetID("i1") 213 i2 := placement.NewInstance().SetID("i2") 214 215 w1, err := store.Watch() 216 require.NoError(t, err) 217 <-w1.C() 218 require.Empty(t, w1.Get()) 219 220 err = store.Heartbeat(i1, 2*time.Second) 221 require.NoError(t, err) 222 223 for range w1.C() { 224 if len(w1.Get().([]string)) == 1 { 225 break 226 } 227 } 228 require.Equal(t, []string{"i1"}, w1.Get()) 229 230 err = store.Heartbeat(i2, 2*time.Second) 231 require.NoError(t, err) 232 233 for range w1.C() { 234 if len(w1.Get().([]string)) == 2 { 235 break 236 } 237 } 238 require.Equal(t, []string{"i1", "i2"}, w1.Get()) 239 240 for range w1.C() { 241 if len(w1.Get().([]string)) == 0 { 242 break 243 } 244 } 245 246 err = store.Heartbeat(i2, time.Second) 247 require.NoError(t, err) 248 249 for range w1.C() { 250 val := w1.Get().([]string) 251 if len(val) == 1 && val[0] == "i2" { 252 break 253 } 254 } 255 256 w1.Close() 257 } 258 259 func TestWatchClose(t *testing.T) { 260 sid := services.NewServiceID().SetName("s1").SetEnvironment("e1") 261 ec, opts, closeFn := testStore(t, sid) 262 defer closeFn() 263 264 store, err := NewStore(ec, opts.SetWatchChanCheckInterval(10*time.Millisecond)) 265 require.NoError(t, err) 266 267 i1 := placement.NewInstance().SetID("i1") 268 i2 := placement.NewInstance().SetID("i2") 269 270 err = store.Heartbeat(i1, 100*time.Second) 271 require.NoError(t, err) 272 273 w1, err := store.Watch() 274 require.NoError(t, err) 275 <-w1.C() 276 require.Equal(t, []string{"i1"}, w1.Get()) 277 278 c := store.(*client) 279 _, ok := c.watchables["_hb/e1/s1"] 280 require.True(t, ok) 281 282 // closing w1 will close the go routine for the watch updates 283 w1.Close() 284 285 // waits until the original watchable is cleaned up 286 for { 287 c.RLock() 288 _, ok = c.watchables["_hb/e1/s1"] 289 c.RUnlock() 290 if !ok { 291 break 292 } 293 } 294 295 // getting a new watch will create a new watchale and thread to watch for updates 296 w2, err := store.Watch() 297 require.NoError(t, err) 298 <-w2.C() 299 require.Equal(t, []string{"i1"}, w2.Get()) 300 301 // verify that w1 will no longer be updated because the original watchable is closed 302 err = store.Heartbeat(i2, 100*time.Second) 303 require.NoError(t, err) 304 <-w2.C() 305 require.Equal(t, []string{"i1", "i2"}, w2.Get()) 306 require.Equal(t, []string{"i1"}, w1.Get()) 307 308 w1.Close() 309 w2.Close() 310 } 311 312 func TestMultipleWatchesFromNotExist(t *testing.T) { 313 sid := services.NewServiceID().SetName("s1").SetEnvironment("e1") 314 ec, opts, closeFn := testStore(t, sid) 315 defer closeFn() 316 317 store, err := NewStore(ec, opts) 318 require.NoError(t, err) 319 320 i1 := placement.NewInstance().SetID("i1") 321 i2 := placement.NewInstance().SetID("i2") 322 323 w1, err := store.Watch() 324 require.NoError(t, err) 325 <-w1.C() 326 require.Empty(t, w1.Get()) 327 328 w2, err := store.Watch() 329 require.NoError(t, err) 330 <-w2.C() 331 require.Empty(t, w2.Get()) 332 333 err = store.Heartbeat(i1, 2*time.Second) 334 require.NoError(t, err) 335 336 for { 337 g := w1.Get() 338 if g == nil { 339 continue 340 } 341 if len(g.([]string)) == 1 { 342 break 343 } 344 } 345 346 <-w1.C() 347 require.Equal(t, 0, len(w1.C())) 348 require.Equal(t, []string{"i1"}, w1.Get()) 349 350 <-w2.C() 351 require.Equal(t, 0, len(w2.C())) 352 require.Equal(t, []string{"i1"}, w2.Get()) 353 354 err = store.Heartbeat(i2, 4*time.Second) 355 require.NoError(t, err) 356 357 for { 358 if len(w1.Get().([]string)) == 2 { 359 break 360 } 361 } 362 <-w1.C() 363 require.Equal(t, []string{"i1", "i2"}, w1.Get()) 364 <-w2.C() 365 require.Equal(t, []string{"i1", "i2"}, w2.Get()) 366 367 for { 368 if len(w1.Get().([]string)) == 1 { 369 break 370 } 371 } 372 <-w1.C() 373 require.Equal(t, []string{"i2"}, w1.Get()) 374 <-w2.C() 375 require.Equal(t, []string{"i2"}, w2.Get()) 376 377 for { 378 if len(w1.Get().([]string)) == 0 { 379 break 380 } 381 } 382 <-w1.C() 383 require.Equal(t, []string{}, w1.Get()) 384 <-w2.C() 385 require.Equal(t, []string{}, w2.Get()) 386 387 w1.Close() 388 w2.Close() 389 } 390 391 func TestWatchNonBlocking(t *testing.T) { 392 sid := services.NewServiceID().SetName("s1").SetEnvironment("e1") 393 ec, opts, closeFn := testStore(t, sid) 394 defer closeFn() 395 396 opts = opts.SetWatchChanResetInterval(200 * time.Millisecond).SetWatchChanInitTimeout(200 * time.Millisecond) 397 398 i1 := placement.NewInstance().SetID("i1") 399 i2 := placement.NewInstance().SetID("i2") 400 401 store, err := NewStore(ec, opts) 402 require.NoError(t, err) 403 c := store.(*client) 404 405 err = store.Heartbeat(i1, 100*time.Second) 406 require.NoError(t, err) 407 408 failTotal := 1 409 mw := mocks.NewBlackholeWatcher(ec, failTotal, func() { time.Sleep(time.Minute) }) 410 c.watcher = mw 411 412 before := time.Now() 413 w1, err := c.Watch() 414 require.WithinDuration(t, time.Now(), before, 100*time.Millisecond) 415 require.NoError(t, err) 416 417 // watch channel will error out, but Get() will be tried 418 select { 419 case <-w1.C(): 420 case <-time.After(200 * time.Millisecond): 421 require.Fail(t, "notification came too late") 422 } 423 require.Equal(t, []string{"i1"}, w1.Get()) 424 425 time.Sleep(5 * (opts.WatchChanResetInterval() + opts.WatchChanInitTimeout())) 426 427 err = store.Heartbeat(i2, 100*time.Second) 428 require.NoError(t, err) 429 430 for { 431 if len(w1.Get().([]string)) == 2 { 432 break 433 } 434 } 435 require.Equal(t, []string{"i1", "i2"}, w1.Get()) 436 437 w1.Close() 438 } 439 440 func testStore(t *testing.T, sid services.ServiceID) (*clientv3.Client, Options, func()) { 441 integration.BeforeTestExternal(t) 442 ecluster := integration.NewCluster(t, &integration.ClusterConfig{Size: 1}) 443 ec := ecluster.RandClient() 444 445 closer := func() { 446 ecluster.Terminate(t) 447 ec.Close() 448 } 449 return ec, NewOptions().SetServiceID(sid).SetRequestTimeout(20 * time.Second), closer 450 }