github.com/m3db/m3@v1.5.0/src/cluster/kv/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 "bytes" 25 "encoding/json" 26 "fmt" 27 "io/ioutil" 28 "os" 29 "path" 30 "testing" 31 "time" 32 33 "github.com/m3db/m3/src/cluster/generated/proto/kvtest" 34 "github.com/m3db/m3/src/cluster/kv" 35 xclock "github.com/m3db/m3/src/x/clock" 36 "github.com/m3db/m3/src/x/retry" 37 38 "github.com/golang/protobuf/proto" 39 "github.com/stretchr/testify/assert" 40 "github.com/stretchr/testify/require" 41 clientv3 "go.etcd.io/etcd/client/v3" 42 "go.etcd.io/etcd/tests/v3/framework/integration" 43 "golang.org/x/net/context" 44 ) 45 46 func TestValue(t *testing.T) { 47 v1 := newValue(nil, 2, 100) 48 require.Equal(t, 2, v1.Version()) 49 50 v2 := newValue(nil, 1, 200) 51 require.Equal(t, 1, v2.Version()) 52 53 require.True(t, v2.IsNewer(v1)) 54 require.False(t, v1.IsNewer(v1)) 55 require.False(t, v1.IsNewer(v2)) 56 } 57 58 func TestGetAndSet(t *testing.T) { 59 ec, opts, closeFn := testStore(t) 60 defer closeFn() 61 62 store, err := NewStore(ec, opts) 63 require.NoError(t, err) 64 65 value, err := store.Get("foo") 66 require.Equal(t, kv.ErrNotFound, err) 67 require.Nil(t, value) 68 69 version, err := store.Set("foo", genProto("bar1")) 70 require.NoError(t, err) 71 require.Equal(t, 1, version) 72 73 value, err = store.Get("foo") 74 require.NoError(t, err) 75 verifyValue(t, value, "bar1", 1) 76 77 version, err = store.Set("foo", genProto("bar2")) 78 require.NoError(t, err) 79 require.Equal(t, 2, version) 80 81 value, err = store.Get("foo") 82 require.NoError(t, err) 83 verifyValue(t, value, "bar2", 2) 84 } 85 86 func TestNoCache(t *testing.T) { 87 ec, opts, closeFn := testStore(t) 88 89 store, err := NewStore(ec, opts) 90 require.NoError(t, err) 91 require.Equal(t, 0, len(store.(*client).cacheUpdatedCh)) 92 93 version, err := store.Set("foo", genProto("bar1")) 94 require.NoError(t, err) 95 require.Equal(t, 1, version) 96 97 value, err := store.Get("foo") 98 require.NoError(t, err) 99 verifyValue(t, value, "bar1", 1) 100 // the will send a notification but won't trigger a sync 101 // because no cache file is set 102 require.Equal(t, 1, len(store.(*client).cacheUpdatedCh)) 103 104 closeFn() 105 106 // from cache 107 value, err = store.Get("foo") 108 require.NoError(t, err) 109 verifyValue(t, value, "bar1", 1) 110 111 // new store but no cache file set 112 store, err = NewStore(ec, opts) 113 require.NoError(t, err) 114 115 _, err = store.Set("foo", genProto("bar1")) 116 require.Error(t, err) 117 118 _, err = store.Get("foo") 119 require.Error(t, err) 120 require.Equal(t, 0, len(store.(*client).cacheUpdatedCh)) 121 } 122 123 func TestCacheDirCreation(t *testing.T) { 124 ec, opts, closeFn := testStore(t) 125 defer closeFn() 126 127 tdir, err := ioutil.TempDir("", "m3tests") 128 require.NoError(t, err) 129 defer os.RemoveAll(tdir) 130 131 cdir := path.Join(tdir, "testCache") 132 opts = opts.SetCacheFileFn(func(string) string { 133 return path.Join(cdir, opts.Prefix()) 134 }) 135 136 store, err := NewStore(ec, opts) 137 require.NoError(t, err) 138 139 info, err := os.Stat(cdir) 140 require.NoError(t, err) 141 require.Equal(t, info.IsDir(), true) 142 143 _, err = store.Set("foo", genProto("bar")) 144 require.NoError(t, err) 145 146 value, err := store.Get("foo") 147 require.NoError(t, err) 148 verifyValue(t, value, "bar", 1) 149 require.Equal(t, 0, len(store.(*client).cacheUpdatedCh)) 150 } 151 152 func TestCache(t *testing.T) { 153 ec, opts, closeFn := testStore(t) 154 155 f, err := ioutil.TempFile("", "") 156 require.NoError(t, err) 157 158 opts = opts.SetCacheFileFn(func(string) string { 159 return f.Name() 160 }) 161 162 store, err := NewStore(ec, opts) 163 require.NoError(t, err) 164 require.Equal(t, 0, len(store.(*client).cacheUpdatedCh)) 165 166 version, err := store.Set("foo", genProto("bar1")) 167 require.NoError(t, err) 168 require.Equal(t, 1, version) 169 170 value, err := store.Get("foo") 171 require.NoError(t, err) 172 verifyValue(t, value, "bar1", 1) 173 for { 174 // the notification should be picked up and trigger a sync 175 if len(store.(*client).cacheUpdatedCh) == 0 { 176 break 177 } 178 } 179 closeFn() 180 181 // from cache 182 value, err = store.Get("foo") 183 require.NoError(t, err) 184 verifyValue(t, value, "bar1", 1) 185 require.Equal(t, 0, len(store.(*client).cacheUpdatedCh)) 186 187 // new store but with cache file 188 store, err = NewStore(ec, opts) 189 require.NoError(t, err) 190 191 _, err = store.Set("key", genProto("bar1")) 192 require.Error(t, err) 193 194 _, err = store.Get("key") 195 require.Error(t, err) 196 require.Equal(t, 0, len(store.(*client).cacheUpdatedCh)) 197 198 value, err = store.Get("foo") 199 require.NoError(t, err) 200 verifyValue(t, value, "bar1", 1) 201 require.Equal(t, 0, len(store.(*client).cacheUpdatedCh)) 202 } 203 204 func TestSetIfNotExist(t *testing.T) { 205 ec, opts, closeFn := testStore(t) 206 defer closeFn() 207 208 store, err := NewStore(ec, opts) 209 require.NoError(t, err) 210 211 version, err := store.SetIfNotExists("foo", genProto("bar")) 212 require.NoError(t, err) 213 require.Equal(t, 1, version) 214 215 _, err = store.SetIfNotExists("foo", genProto("bar")) 216 require.Equal(t, kv.ErrAlreadyExists, err) 217 218 value, err := store.Get("foo") 219 require.NoError(t, err) 220 verifyValue(t, value, "bar", 1) 221 } 222 223 func TestCheckAndSet(t *testing.T) { 224 ec, opts, closeFn := testStore(t) 225 defer closeFn() 226 227 store, err := NewStore(ec, opts) 228 require.NoError(t, err) 229 230 _, err = store.CheckAndSet("foo", 1, genProto("bar")) 231 require.Equal(t, kv.ErrVersionMismatch, err) 232 233 version, err := store.CheckAndSet("foo", 0, genProto("bar")) 234 require.NoError(t, err) 235 require.Equal(t, 1, version) 236 237 version, err = store.CheckAndSet("foo", 1, genProto("bar")) 238 require.NoError(t, err) 239 require.Equal(t, 2, version) 240 241 _, err = store.CheckAndSet("foo", 1, genProto("bar")) 242 require.Equal(t, kv.ErrVersionMismatch, err) 243 244 value, err := store.Get("foo") 245 require.NoError(t, err) 246 verifyValue(t, value, "bar", 2) 247 } 248 249 func TestWatchClose(t *testing.T) { 250 ec, opts, closeFn := testStore(t) 251 defer closeFn() 252 253 store, err := NewStore(ec, opts) 254 require.NoError(t, err) 255 256 _, err = store.Set("foo", genProto("bar1")) 257 require.NoError(t, err) 258 w1, err := store.Watch("foo") 259 require.NoError(t, err) 260 <-w1.C() 261 verifyValue(t, w1.Get(), "bar1", 1) 262 263 c := store.(*client) 264 _, ok := c.watchables["test/foo"] 265 require.True(t, ok) 266 267 // closing w1 will close the go routine for the watch updates 268 w1.Close() 269 270 // waits until the original watchable is cleaned up 271 for { 272 c.RLock() 273 _, ok = c.watchables["test/foo"] 274 c.RUnlock() 275 if !ok { 276 break 277 } 278 time.Sleep(1 * time.Millisecond) 279 } 280 281 // getting a new watch will create a new watchale and thread to watch for updates 282 w2, err := store.Watch("foo") 283 require.NoError(t, err) 284 <-w2.C() 285 verifyValue(t, w2.Get(), "bar1", 1) 286 287 // verify that w1 will no longer be updated because the original watchable is closed 288 _, err = store.Set("foo", genProto("bar2")) 289 require.NoError(t, err) 290 <-w2.C() 291 verifyValue(t, w2.Get(), "bar2", 2) 292 verifyValue(t, w1.Get(), "bar1", 1) 293 294 w1.Close() 295 w2.Close() 296 } 297 298 func TestWatchLastVersion(t *testing.T) { 299 ec, opts, closeFn := testStore(t) 300 defer closeFn() 301 302 store, err := NewStore(ec, opts) 303 require.NoError(t, err) 304 305 w, err := store.Watch("foo") 306 require.NoError(t, err) 307 require.Nil(t, w.Get()) 308 309 var ( 310 doneCh = make(chan struct{}) 311 lastVersion = 50 312 ) 313 314 go func() { 315 for i := 1; i <= lastVersion; i++ { 316 _, err := store.Set("foo", genProto(fmt.Sprintf("bar%d", i))) 317 assert.NoError(t, err) 318 } 319 }() 320 321 go func() { 322 defer close(doneCh) 323 for { 324 <-w.C() 325 value := w.Get() 326 if value.Version() == lastVersion { 327 return 328 } 329 } 330 }() 331 332 select { 333 case <-time.After(5 * time.Second): 334 t.Fatal("test timed out") 335 case <-doneCh: 336 } 337 verifyValue(t, w.Get(), fmt.Sprintf("bar%d", lastVersion), lastVersion) 338 } 339 340 func TestWatchFromExist(t *testing.T) { 341 ec, opts, closeFn := testStore(t) 342 defer closeFn() 343 344 store, err := NewStore(ec, opts) 345 require.NoError(t, err) 346 347 _, err = store.Set("foo", genProto("bar1")) 348 require.NoError(t, err) 349 value, err := store.Get("foo") 350 require.NoError(t, err) 351 verifyValue(t, value, "bar1", 1) 352 353 w, err := store.Watch("foo") 354 require.NoError(t, err) 355 356 <-w.C() 357 require.Equal(t, 0, len(w.C())) 358 verifyValue(t, w.Get(), "bar1", 1) 359 360 _, err = store.Set("foo", genProto("bar2")) 361 require.NoError(t, err) 362 363 <-w.C() 364 require.Equal(t, 0, len(w.C())) 365 verifyValue(t, w.Get(), "bar2", 2) 366 367 _, err = store.Set("foo", genProto("bar3")) 368 require.NoError(t, err) 369 370 <-w.C() 371 require.Equal(t, 0, len(w.C())) 372 verifyValue(t, w.Get(), "bar3", 3) 373 374 w.Close() 375 } 376 377 func TestWatchFromNotExist(t *testing.T) { 378 ec, opts, closeFn := testStore(t) 379 defer closeFn() 380 381 store, err := NewStore(ec, opts) 382 require.NoError(t, err) 383 384 w, err := store.Watch("foo") 385 require.NoError(t, err) 386 require.Equal(t, 0, len(w.C())) 387 require.Nil(t, w.Get()) 388 389 _, err = store.Set("foo", genProto("bar1")) 390 require.NoError(t, err) 391 392 <-w.C() 393 require.Equal(t, 0, len(w.C())) 394 verifyValue(t, w.Get(), "bar1", 1) 395 396 _, err = store.Set("foo", genProto("bar2")) 397 require.NoError(t, err) 398 399 <-w.C() 400 require.Equal(t, 0, len(w.C())) 401 verifyValue(t, w.Get(), "bar2", 2) 402 403 w.Close() 404 } 405 406 func TestGetFromKvNotFound(t *testing.T) { 407 ec, opts, closeFn := testStore(t) 408 defer closeFn() 409 store, err := NewStore(ec, opts) 410 require.NoError(t, err) 411 c := store.(*client) 412 _, err = c.Set("foo", genProto("bar1")) 413 require.NoError(t, err) 414 415 val, err := c.getFromKVStore("foo2") 416 require.NoError(t, err) 417 require.Nil(t, val) 418 } 419 420 func TestMultipleWatchesFromExist(t *testing.T) { 421 ec, opts, closeFn := testStore(t) 422 defer closeFn() 423 424 store, err := NewStore(ec, opts) 425 require.NoError(t, err) 426 427 _, err = store.Set("foo", genProto("bar1")) 428 require.NoError(t, err) 429 430 w1, err := store.Watch("foo") 431 require.NoError(t, err) 432 433 w2, err := store.Watch("foo") 434 require.NoError(t, err) 435 436 <-w1.C() 437 require.Equal(t, 0, len(w1.C())) 438 verifyValue(t, w1.Get(), "bar1", 1) 439 440 <-w2.C() 441 require.Equal(t, 0, len(w2.C())) 442 verifyValue(t, w2.Get(), "bar1", 1) 443 444 _, err = store.Set("foo", genProto("bar2")) 445 require.NoError(t, err) 446 447 <-w1.C() 448 require.Equal(t, 0, len(w1.C())) 449 verifyValue(t, w1.Get(), "bar2", 2) 450 451 <-w2.C() 452 require.Equal(t, 0, len(w2.C())) 453 verifyValue(t, w2.Get(), "bar2", 2) 454 455 _, err = store.Set("foo", genProto("bar3")) 456 require.NoError(t, err) 457 458 <-w1.C() 459 require.Equal(t, 0, len(w1.C())) 460 verifyValue(t, w1.Get(), "bar3", 3) 461 462 <-w2.C() 463 require.Equal(t, 0, len(w2.C())) 464 verifyValue(t, w2.Get(), "bar3", 3) 465 466 w1.Close() 467 w2.Close() 468 } 469 470 func TestMultipleWatchesFromNotExist(t *testing.T) { 471 ec, opts, closeFn := testStore(t) 472 defer closeFn() 473 474 store, err := NewStore(ec, opts) 475 require.NoError(t, err) 476 w1, err := store.Watch("foo") 477 require.NoError(t, err) 478 require.Equal(t, 0, len(w1.C())) 479 require.Nil(t, w1.Get()) 480 481 w2, err := store.Watch("foo") 482 require.NoError(t, err) 483 require.Equal(t, 0, len(w2.C())) 484 require.Nil(t, w2.Get()) 485 486 _, err = store.Set("foo", genProto("bar1")) 487 require.NoError(t, err) 488 489 <-w1.C() 490 require.Equal(t, 0, len(w1.C())) 491 verifyValue(t, w1.Get(), "bar1", 1) 492 493 <-w2.C() 494 require.Equal(t, 0, len(w2.C())) 495 verifyValue(t, w2.Get(), "bar1", 1) 496 497 _, err = store.Set("foo", genProto("bar2")) 498 require.NoError(t, err) 499 500 <-w1.C() 501 require.Equal(t, 0, len(w1.C())) 502 verifyValue(t, w1.Get(), "bar2", 2) 503 504 <-w2.C() 505 require.Equal(t, 0, len(w2.C())) 506 verifyValue(t, w2.Get(), "bar2", 2) 507 508 w1.Close() 509 w2.Close() 510 } 511 512 func TestWatchNonBlocking(t *testing.T) { 513 ecluster, opts, closeFn := testCluster(t) 514 defer closeFn() 515 516 ec := ecluster.Client(0) 517 518 opts = opts.SetWatchChanResetInterval(200 * time.Millisecond).SetWatchChanInitTimeout(500 * time.Millisecond) 519 520 store, err := NewStore(ec, opts) 521 require.NoError(t, err) 522 c := store.(*client) 523 524 _, err = c.Set("foo", genProto("bar1")) 525 require.NoError(t, err) 526 527 before := time.Now() 528 ecluster.Members[0].Bridge().Blackhole() 529 w1, err := c.Watch("foo") 530 require.WithinDuration(t, time.Now(), before, 100*time.Millisecond) 531 require.NoError(t, err) 532 require.Equal(t, 0, len(w1.C())) 533 ecluster.Members[0].Bridge().Unblackhole() 534 ecluster.Members[0].Bridge().DropConnections() 535 536 // watch channel will error out, but Get() will be tried 537 <-w1.C() 538 verifyValue(t, w1.Get(), "bar1", 1) 539 540 w1.Close() 541 } 542 543 func TestHistory(t *testing.T) { 544 ec, opts, closeFn := testStore(t) 545 defer closeFn() 546 547 store, err := NewStore(ec, opts) 548 require.NoError(t, err) 549 550 _, err = store.History("k1", 10, 5) 551 require.Error(t, err) 552 553 _, err = store.History("k1", 0, 5) 554 require.Error(t, err) 555 556 _, err = store.History("k1", -5, 0) 557 require.Error(t, err) 558 559 totalVersion := 10 560 for i := 1; i <= totalVersion; i++ { 561 store.Set("k1", genProto(fmt.Sprintf("bar%d", i))) 562 store.Set("k2", genProto(fmt.Sprintf("bar%d", i))) 563 } 564 565 res, err := store.History("k1", 5, 5) 566 require.NoError(t, err) 567 require.Equal(t, 0, len(res)) 568 569 res, err = store.History("k1", 15, 20) 570 require.NoError(t, err) 571 require.Equal(t, 0, len(res)) 572 573 res, err = store.History("k1", 6, 10) 574 require.NoError(t, err) 575 require.Equal(t, 4, len(res)) 576 for i := 0; i < len(res); i++ { 577 version := i + 6 578 value := res[i] 579 verifyValue(t, value, fmt.Sprintf("bar%d", version), version) 580 } 581 582 res, err = store.History("k1", 3, 7) 583 require.NoError(t, err) 584 require.Equal(t, 4, len(res)) 585 for i := 0; i < len(res); i++ { 586 version := i + 3 587 value := res[i] 588 verifyValue(t, value, fmt.Sprintf("bar%d", version), version) 589 } 590 591 res, err = store.History("k1", 5, 15) 592 require.NoError(t, err) 593 require.Equal(t, totalVersion-5+1, len(res)) 594 for i := 0; i < len(res); i++ { 595 version := i + 5 596 value := res[i] 597 verifyValue(t, value, fmt.Sprintf("bar%d", version), version) 598 } 599 } 600 601 func TestDelete(t *testing.T) { 602 ec, opts, closeFn := testStore(t) 603 defer closeFn() 604 605 store, err := NewStore(ec, opts) 606 require.NoError(t, err) 607 608 _, err = store.Delete("foo") 609 require.Equal(t, kv.ErrNotFound, err) 610 611 version, err := store.Set("foo", genProto("bar1")) 612 require.NoError(t, err) 613 require.Equal(t, 1, version) 614 615 version, err = store.Set("foo", genProto("bar2")) 616 require.NoError(t, err) 617 require.Equal(t, 2, version) 618 619 v, err := store.Get("foo") 620 require.NoError(t, err) 621 verifyValue(t, v, "bar2", 2) 622 623 v, err = store.Delete("foo") 624 require.NoError(t, err) 625 verifyValue(t, v, "bar2", 2) 626 627 _, err = store.Delete("foo") 628 require.Equal(t, kv.ErrNotFound, err) 629 630 _, err = store.Get("foo") 631 require.Equal(t, kv.ErrNotFound, err) 632 633 version, err = store.SetIfNotExists("foo", genProto("bar1")) 634 require.NoError(t, err) 635 require.Equal(t, 1, version) 636 } 637 638 func TestDelete_UpdateCache(t *testing.T) { 639 ec, opts, closeFn := testStore(t) 640 defer closeFn() 641 642 c, err := NewStore(ec, opts) 643 require.NoError(t, err) 644 645 store := c.(*client) 646 version, err := store.Set("foo", genProto("bar1")) 647 require.NoError(t, err) 648 require.Equal(t, 1, version) 649 650 v, err := store.Get("foo") 651 require.NoError(t, err) 652 verifyValue(t, v, "bar1", 1) 653 require.Equal(t, 1, len(store.cache.Values)) 654 require.Equal(t, 1, len(store.cacheUpdatedCh)) 655 656 // drain the notification 657 <-store.cacheUpdatedCh 658 659 v, err = store.Delete("foo") 660 require.NoError(t, err) 661 verifyValue(t, v, "bar1", 1) 662 // make sure the cache is cleaned up 663 require.Equal(t, 0, len(store.cache.Values)) 664 require.Equal(t, 1, len(store.cacheUpdatedCh)) 665 } 666 667 func TestDelete_UpdateWatcherCache(t *testing.T) { 668 ec, opts, closeFn := testStore(t) 669 defer closeFn() 670 671 setStore, err := NewStore(ec, opts) 672 require.NoError(t, err) 673 674 setClient := setStore.(*client) 675 version, err := setClient.Set("foo", genProto("bar1")) 676 require.NoError(t, err) 677 require.Equal(t, 1, version) 678 679 setV, err := setClient.Get("foo") 680 require.NoError(t, err) 681 verifyValue(t, setV, "bar1", 1) 682 require.Equal(t, 1, len(setClient.cache.Values)) 683 require.Equal(t, 1, len(setClient.cacheUpdatedCh)) 684 685 // drain the notification to ensure set received update 686 <-setClient.cacheUpdatedCh 687 688 // make a custom cache path for the get client 689 clientCachePath, err := ioutil.TempDir("", "client-cache-dir") 690 require.NoError(t, err) 691 defer os.RemoveAll(clientCachePath) 692 693 getStore, err := NewStore(ec, opts.SetCacheFileFn(func(ns string) string { 694 nsFile := path.Join(clientCachePath, fmt.Sprintf("%s.json", ns)) 695 return nsFile 696 })) 697 require.NoError(t, err) 698 getClient := getStore.(*client) 699 700 getW, err := getClient.Watch("foo") 701 require.NoError(t, err) 702 <-getW.C() 703 verifyValue(t, getW.Get(), "bar1", 1) 704 require.True(t, xclock.WaitUntil(func() bool { 705 getClient.cache.RLock() 706 defer getClient.cache.RUnlock() 707 return 1 == len(getClient.cache.Values) 708 }, time.Minute)) 709 710 var ( 711 originalBytes []byte 712 ) 713 require.True(t, xclock.WaitUntil(func() bool { 714 originalBytes, err = readCacheJSON(clientCachePath) 715 return err == nil 716 }, time.Minute)) 717 718 setV, err = setClient.Delete("foo") 719 require.NoError(t, err) 720 verifyValue(t, setV, "bar1", 1) 721 722 // make sure the cache is cleaned up on set client 723 require.Equal(t, 0, len(setClient.cache.Values)) 724 require.Equal(t, 1, len(setClient.cacheUpdatedCh)) 725 726 // make sure the cache is cleaned up on get client (mem and disk) 727 var ( 728 updatedBytes []byte 729 ) 730 require.True(t, xclock.WaitUntil(func() bool { 731 getClient.cache.RLock() 732 defer getClient.cache.RUnlock() 733 if len(getClient.cache.Values) != 0 { 734 return false 735 } 736 updatedBytes, err = readCacheJSON(clientCachePath) 737 if err != nil { 738 return false 739 } 740 return !bytes.Equal(updatedBytes, originalBytes) 741 }, time.Minute), "did not observe any invalidation of the cache file") 742 } 743 744 func TestDelete_TriggerWatch(t *testing.T) { 745 ec, opts, closeFn := testStore(t) 746 defer closeFn() 747 748 store, err := NewStore(ec, opts) 749 require.NoError(t, err) 750 751 vw, err := store.Watch("foo") 752 require.NoError(t, err) 753 754 _, err = store.Set("foo", genProto("bar1")) 755 require.NoError(t, err) 756 757 <-vw.C() 758 verifyValue(t, vw.Get(), "bar1", 1) 759 760 _, err = store.Set("foo", genProto("bar2")) 761 require.NoError(t, err) 762 763 <-vw.C() 764 verifyValue(t, vw.Get(), "bar2", 2) 765 766 _, err = store.Delete("foo") 767 require.NoError(t, err) 768 769 <-vw.C() 770 require.Nil(t, vw.Get()) 771 772 _, err = store.Set("foo", genProto("bar3")) 773 require.NoError(t, err) 774 775 <-vw.C() 776 verifyValue(t, vw.Get(), "bar3", 1) 777 } 778 779 func TestStaleDelete__FromGet(t *testing.T) { 780 integration.BeforeTestExternal(t) 781 // in this test we ensure clients who did not receive a delete for a key in 782 // their caches, evict the value in their cache the next time they communicate 783 // with an etcd which is unaware of the key (e.g. it's been compacted). 784 785 // first, we find the bytes required to be created in the cache file 786 serverCachePath, err := ioutil.TempDir("", "server-cache-dir") 787 require.NoError(t, err) 788 defer os.RemoveAll(serverCachePath) 789 ec, opts, closeFn := testStore(t, func(tc *testStoreOpts) { 790 tc.etcdBeforeTestExternal = false 791 }) 792 793 setStore, err := NewStore(ec, opts.SetCacheFileFn(func(ns string) string { 794 return path.Join(serverCachePath, fmt.Sprintf("%s.json", ns)) 795 })) 796 require.NoError(t, err) 797 798 setClient := setStore.(*client) 799 version, err := setClient.Set("foo", genProto("bar1")) 800 require.NoError(t, err) 801 require.Equal(t, 1, version) 802 803 setV, err := setClient.Get("foo") 804 require.NoError(t, err) 805 verifyValue(t, setV, "bar1", 1) 806 require.Equal(t, 1, len(setClient.cache.Values)) 807 808 // drain the notification to ensure set received update 809 var ( 810 fileName string 811 cacheBytes []byte 812 ) 813 require.True(t, xclock.WaitUntil(func() bool { 814 fileName, cacheBytes, err = readCacheJSONAndFilename(serverCachePath) 815 return err == nil && isValidJSON(cacheBytes) 816 }, time.Minute), "timed out waiting to read cache file") 817 closeFn() 818 819 // make a new etcd cluster (to mimic the case where etcd has deleted and compacted 820 // the value). 821 newServerCachePath, err := ioutil.TempDir("", "new-server-cache-dir") 822 require.NoError(t, err) 823 defer os.RemoveAll(newServerCachePath) 824 825 // write the cache file with correct contents in the new location 826 f, err := os.Create(path.Join(newServerCachePath, fileName)) 827 require.NoError(t, err) 828 _, err = f.Write(cacheBytes) 829 require.NoError(t, err) 830 require.NoError(t, f.Sync()) 831 require.NoError(t, f.Close()) 832 833 require.True(t, xclock.WaitUntil(func() bool { 834 _, newBytes, err := readCacheJSONAndFilename(newServerCachePath) 835 return err == nil && bytes.Equal(cacheBytes, newBytes) 836 }, time.Minute), "timed out waiting to flush new cache file") 837 838 // create new etcd cluster 839 ec2, opts, closeFn2 := testStore(t, withNoEtcdBeforeTestExternal()) 840 defer closeFn2() 841 getStore, err := NewStore(ec2, opts.SetCacheFileFn(func(ns string) string { 842 nsFile := path.Join(newServerCachePath, fmt.Sprintf("%s.json", ns)) 843 return nsFile 844 })) 845 require.NoError(t, err) 846 getClient := getStore.(*client) 847 848 require.True(t, xclock.WaitUntil(func() bool { 849 getClient.cache.RLock() 850 defer getClient.cache.RUnlock() 851 return 1 == len(getClient.cache.Values) 852 }, time.Minute), "timed out waiting for client to read values from cache") 853 854 // get value and ensure it's not able to serve the read 855 v, err := getClient.Get("foo") 856 require.Error(t, kv.ErrNotFound) 857 require.Nil(t, v) 858 859 require.True(t, xclock.WaitUntil(func() bool { 860 _, updatedBytes, err := readCacheJSONAndFilename(newServerCachePath) 861 return err == nil && !bytes.Equal(cacheBytes, updatedBytes) 862 }, time.Minute), "timed out waiting to flush cache file delete") 863 } 864 865 func TestStaleDelete__FromWatch(t *testing.T) { 866 integration.BeforeTestExternal(t) 867 // in this test we ensure clients who did not receive a delete for a key in 868 // their caches, evict the value in their cache the next time they communicate 869 // with an etcd which is unaware of the key (e.g. it's been compacted). 870 // first, we find the bytes required to be created in the cache file 871 serverCachePath, err := ioutil.TempDir("", "server-cache-dir") 872 require.NoError(t, err) 873 defer os.RemoveAll(serverCachePath) 874 ec, opts, closeFn := testStore(t, withNoEtcdBeforeTestExternal()) 875 876 setStore, err := NewStore(ec, opts.SetCacheFileFn(func(ns string) string { 877 return path.Join(serverCachePath, fmt.Sprintf("%s.json", ns)) 878 })) 879 require.NoError(t, err) 880 881 setClient := setStore.(*client) 882 version, err := setClient.Set("foo", genProto("bar1")) 883 require.NoError(t, err) 884 require.Equal(t, 1, version) 885 886 setV, err := setClient.Get("foo") 887 require.NoError(t, err) 888 verifyValue(t, setV, "bar1", 1) 889 require.Equal(t, 1, len(setClient.cache.Values)) 890 891 // drain the notification to ensure set received update 892 var ( 893 fileName string 894 cacheBytes []byte 895 ) 896 require.True(t, xclock.WaitUntil(func() bool { 897 fileName, cacheBytes, err = readCacheJSONAndFilename(serverCachePath) 898 // Need to make sure it is valid JSON to ensure we're not reading the 899 // bytes before they've been completely written out. 900 return err == nil && isValidJSON(cacheBytes) 901 }, time.Minute), "timed out waiting to read cache file") 902 closeFn() 903 904 // make a new etcd cluster (to mimic the case where etcd has deleted and compacted 905 // the value). 906 newServerCachePath, err := ioutil.TempDir("", "new-server-cache-dir") 907 require.NoError(t, err) 908 defer os.RemoveAll(newServerCachePath) 909 910 // write the cache file with correct contents in the new location 911 f, err := os.Create(path.Join(newServerCachePath, fileName)) 912 require.NoError(t, err) 913 _, err = f.Write(cacheBytes) 914 require.NoError(t, err) 915 require.NoError(t, f.Sync()) 916 require.NoError(t, f.Close()) 917 918 require.True(t, xclock.WaitUntil(func() bool { 919 _, newBytes, err := readCacheJSONAndFilename(newServerCachePath) 920 return err == nil && bytes.Equal(cacheBytes, newBytes) && isValidJSON(newBytes) 921 }, time.Minute), "timed out waiting to flush new cache file") 922 923 // create new etcd cluster 924 ec2, opts, closeFn2 := testStore(t, withNoEtcdBeforeTestExternal()) 925 defer closeFn2() 926 getStore, err := NewStore(ec2, opts.SetCacheFileFn(func(ns string) string { 927 nsFile := path.Join(newServerCachePath, fmt.Sprintf("%s.json", ns)) 928 return nsFile 929 })) 930 require.NoError(t, err) 931 getClient := getStore.(*client) 932 933 require.True(t, xclock.WaitUntil(func() bool { 934 getClient.cache.RLock() 935 defer getClient.cache.RUnlock() 936 return 1 == len(getClient.cache.Values) 937 }, time.Minute), "timed out waiting for client to read values from cache") 938 require.Equal(t, 1, len(getClient.cache.Values)) 939 940 // get value and ensure it's not able to serve the read 941 w, err := getClient.Watch("foo") 942 require.NoError(t, err) 943 require.NotNil(t, w) 944 require.Nil(t, w.Get()) 945 946 require.True(t, xclock.WaitUntil(func() bool { 947 _, updatedBytes, err := readCacheJSONAndFilename(newServerCachePath) 948 return err == nil && !bytes.Equal(cacheBytes, updatedBytes) 949 }, time.Minute), "timed out waiting to flush cache file delete") 950 require.Equal(t, 0, len(getClient.cache.Values)) 951 require.Nil(t, w.Get()) 952 } 953 954 func isValidJSON(b []byte) bool { 955 var j interface{} 956 return json.Unmarshal(b, &j) == nil 957 } 958 959 func TestTxn(t *testing.T) { 960 ec, opts, closeFn := testStore(t) 961 defer closeFn() 962 963 store, err := NewStore(ec, opts) 964 require.NoError(t, err) 965 966 r, err := store.Commit( 967 []kv.Condition{ 968 kv.NewCondition(). 969 SetCompareType(kv.CompareEqual). 970 SetTargetType(kv.TargetVersion). 971 SetKey("foo"). 972 SetValue(0), 973 }, 974 []kv.Op{kv.NewSetOp("foo", genProto("bar1"))}, 975 ) 976 require.NoError(t, err) 977 require.Equal(t, 1, len(r.Responses())) 978 require.Equal(t, "foo", r.Responses()[0].Key()) 979 require.Equal(t, kv.OpSet, r.Responses()[0].Type()) 980 require.Equal(t, 1, r.Responses()[0].Value()) 981 982 v, err := store.Set("key", genProto("bar1")) 983 require.NoError(t, err) 984 require.Equal(t, 1, v) 985 986 r, err = store.Commit( 987 []kv.Condition{ 988 kv.NewCondition(). 989 SetCompareType(kv.CompareEqual). 990 SetTargetType(kv.TargetVersion). 991 SetKey("key"). 992 SetValue(1), 993 }, 994 []kv.Op{kv.NewSetOp("foo", genProto("bar1"))}, 995 ) 996 require.NoError(t, err) 997 require.Equal(t, 1, len(r.Responses())) 998 require.Equal(t, "foo", r.Responses()[0].Key()) 999 require.Equal(t, kv.OpSet, r.Responses()[0].Type()) 1000 require.Equal(t, 2, r.Responses()[0].Value()) 1001 1002 r, err = store.Commit( 1003 []kv.Condition{ 1004 kv.NewCondition(). 1005 SetCompareType(kv.CompareEqual). 1006 SetTargetType(kv.TargetVersion). 1007 SetKey("foo"). 1008 SetValue(2), 1009 kv.NewCondition(). 1010 SetCompareType(kv.CompareEqual). 1011 SetTargetType(kv.TargetVersion). 1012 SetKey("key"). 1013 SetValue(1), 1014 }, 1015 []kv.Op{ 1016 kv.NewSetOp("key", genProto("bar1")), 1017 kv.NewSetOp("foo", genProto("bar2")), 1018 }, 1019 ) 1020 require.NoError(t, err) 1021 require.Equal(t, 2, len(r.Responses())) 1022 require.Equal(t, "key", r.Responses()[0].Key()) 1023 require.Equal(t, kv.OpSet, r.Responses()[0].Type()) 1024 require.Equal(t, 2, r.Responses()[0].Value()) 1025 require.Equal(t, "foo", r.Responses()[1].Key()) 1026 require.Equal(t, kv.OpSet, r.Responses()[1].Type()) 1027 require.Equal(t, 3, r.Responses()[1].Value()) 1028 } 1029 1030 func TestTxn_ConditionFail(t *testing.T) { 1031 ec, opts, closeFn := testStore(t) 1032 defer closeFn() 1033 1034 store, err := NewStore(ec, opts) 1035 require.NoError(t, err) 1036 1037 _, err = store.Commit( 1038 []kv.Condition{ 1039 kv.NewCondition(). 1040 SetCompareType(kv.CompareEqual). 1041 SetTargetType(kv.TargetVersion). 1042 SetKey("foo"). 1043 SetValue(1), 1044 }, 1045 []kv.Op{kv.NewSetOp("foo", genProto("bar1"))}, 1046 ) 1047 require.Error(t, err) 1048 1049 store.Set("key1", genProto("v1")) 1050 store.Set("key2", genProto("v2")) 1051 _, err = store.Commit( 1052 []kv.Condition{ 1053 kv.NewCondition(). 1054 SetCompareType(kv.CompareEqual). 1055 SetTargetType(kv.TargetVersion). 1056 SetKey("key1"). 1057 SetValue(1), 1058 kv.NewCondition(). 1059 SetCompareType(kv.CompareEqual). 1060 SetTargetType(kv.TargetVersion). 1061 SetKey("key2"). 1062 SetValue(2), 1063 }, 1064 []kv.Op{kv.NewSetOp("foo", genProto("bar1"))}, 1065 ) 1066 require.Error(t, err) 1067 } 1068 1069 func TestTxn_UnknownType(t *testing.T) { 1070 ec, opts, closeFn := testStore(t) 1071 defer closeFn() 1072 1073 store, err := NewStore(ec, opts) 1074 require.NoError(t, err) 1075 1076 _, err = store.Commit( 1077 []kv.Condition{ 1078 kv.NewCondition(). 1079 SetTargetType(kv.TargetVersion). 1080 SetKey("foo"). 1081 SetValue(1), 1082 }, 1083 []kv.Op{kv.NewSetOp("foo", genProto("bar1"))}, 1084 ) 1085 require.Equal(t, kv.ErrUnknownCompareType, err) 1086 } 1087 1088 // TestWatchWithStartRevision that watching from 1) an old compacted start 1089 // revision and 2) a start revision in the future are both safe 1090 func TestWatchWithStartRevision(t *testing.T) { 1091 tests := map[string]int64{ 1092 "old_revision": 1, 1093 "future_revision": 100000, 1094 } 1095 1096 for name, rev := range tests { 1097 t.Run(name, func(t *testing.T) { 1098 ec, opts, closeFn := testStore(t) 1099 defer closeFn() 1100 1101 opts = opts.SetWatchWithRevision(rev) 1102 1103 store, err := NewStore(ec, opts) 1104 require.NoError(t, err) 1105 1106 for i := 1; i <= 50; i++ { 1107 _, err = store.Set("foo", genProto(fmt.Sprintf("bar-%d", i))) 1108 require.NoError(t, err) 1109 } 1110 1111 cl := store.(*client).kv 1112 1113 resp, err := cl.Get(context.Background(), "foo") 1114 require.NoError(t, err) 1115 compactRev := resp.Header.Revision 1116 1117 _, err = cl.Compact(context.Background(), compactRev-1) 1118 require.NoError(t, err) 1119 1120 w1, err := store.Watch("foo") 1121 require.NoError(t, err) 1122 <-w1.C() 1123 verifyValue(t, w1.Get(), "bar-50", 50) 1124 }) 1125 } 1126 } 1127 1128 func TestSerializedGets(t *testing.T) { 1129 ec, opts, closeFn := testStore(t) 1130 defer closeFn() 1131 1132 opts = opts.SetEnableFastGets(true) 1133 require.NoError(t, opts.Validate()) 1134 1135 store, err := NewStore(ec, opts) 1136 require.NoError(t, err) 1137 1138 v, err := store.Set("foo", genProto("bar")) 1139 require.EqualValues(t, 1, v) 1140 require.NoError(t, err) 1141 1142 val, err := store.Get("foo") 1143 verifyValue(t, val, "bar", 1) 1144 require.NoError(t, err) 1145 1146 v, err = store.Set("foo", genProto("42")) 1147 require.EqualValues(t, 2, v) 1148 require.NoError(t, err) 1149 1150 val, err = store.Get("foo") 1151 verifyValue(t, val, "42", 2) 1152 require.NoError(t, err) 1153 } 1154 1155 func verifyValue(t *testing.T, v kv.Value, value string, version int) { 1156 var testMsg kvtest.Foo 1157 err := v.Unmarshal(&testMsg) 1158 require.NoError(t, err) 1159 require.Equal(t, value, testMsg.Msg) 1160 require.Equal(t, version, v.Version()) 1161 } 1162 1163 func genProto(msg string) proto.Message { 1164 return &kvtest.Foo{Msg: msg} 1165 } 1166 1167 func testCluster(t *testing.T, testOpts ...testStoreOption) (*integration.Cluster, Options, func()) { 1168 cfg := testStoreOpts{etcdBeforeTestExternal: true} 1169 for _, opt := range testOpts { 1170 opt(&cfg) 1171 } 1172 1173 if cfg.etcdBeforeTestExternal { 1174 integration.BeforeTestExternal(t) 1175 } 1176 1177 ecluster := integration.NewCluster(t, &integration.ClusterConfig{ 1178 Size: 1, 1179 UseBridge: true, 1180 }) 1181 closer := func() { 1182 ecluster.Terminate(t) 1183 } 1184 1185 opts := NewOptions(). 1186 SetWatchChanCheckInterval(100 * time.Millisecond). 1187 SetWatchChanResetInterval(200 * time.Millisecond). 1188 SetWatchChanInitTimeout(200 * time.Millisecond). 1189 SetRequestTimeout(200 * time.Millisecond). 1190 SetRetryOptions(retry.NewOptions().SetMaxRetries(1).SetMaxBackoff(0)). 1191 SetPrefix("test") 1192 1193 return ecluster, opts, closer 1194 } 1195 1196 type testStoreOpts struct { 1197 etcdBeforeTestExternal bool 1198 } 1199 1200 type testStoreOption func(tc *testStoreOpts) 1201 1202 func withNoEtcdBeforeTestExternal() testStoreOption { 1203 return func(tc *testStoreOpts) { 1204 tc.etcdBeforeTestExternal = false 1205 } 1206 } 1207 1208 func testStore(t *testing.T, testOpts ...testStoreOption) (*clientv3.Client, Options, func()) { 1209 ecluster, opts, closer := testCluster(t, testOpts...) 1210 return ecluster.RandClient(), opts, closer 1211 } 1212 1213 func readCacheJSONAndFilename(dirPath string) (string, []byte, error) { 1214 files, err := ioutil.ReadDir(dirPath) 1215 if err != nil { 1216 return "", nil, err 1217 } 1218 if len(files) != 1 { 1219 return "", nil, fmt.Errorf("expected 1 file, found files: %+v", files) 1220 } 1221 fileName := files[0].Name() 1222 filepath := path.Join(dirPath, fileName) 1223 f, err := os.Open(filepath) 1224 if err != nil { 1225 return "", nil, err 1226 } 1227 b, err := ioutil.ReadAll(f) 1228 if err != nil { 1229 return "", nil, err 1230 } 1231 return fileName, b, nil 1232 } 1233 1234 func readCacheJSON(dirPath string) ([]byte, error) { 1235 _, b, err := readCacheJSONAndFilename(dirPath) 1236 return b, err 1237 }