github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/dirstore_test.go (about) 1 // Copyright 2012-2021 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package server 15 16 import ( 17 "bytes" 18 "crypto/sha256" 19 "fmt" 20 "math" 21 "math/rand" 22 "os" 23 "path/filepath" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/nats-io/jwt/v2" 29 "github.com/nats-io/nkeys" 30 ) 31 32 var ( 33 one, two, three, four = "", "", "", "" 34 jwt1, jwt2, jwt3, jwt4 = "", "", "", "" 35 op nkeys.KeyPair 36 ) 37 38 func init() { 39 op, _ = nkeys.CreateOperator() 40 41 nkone, _ := nkeys.CreateAccount() 42 pub, _ := nkone.PublicKey() 43 one = pub 44 ac := jwt.NewAccountClaims(pub) 45 jwt1, _ = ac.Encode(op) 46 47 nktwo, _ := nkeys.CreateAccount() 48 pub, _ = nktwo.PublicKey() 49 two = pub 50 ac = jwt.NewAccountClaims(pub) 51 jwt2, _ = ac.Encode(op) 52 53 nkthree, _ := nkeys.CreateAccount() 54 pub, _ = nkthree.PublicKey() 55 three = pub 56 ac = jwt.NewAccountClaims(pub) 57 jwt3, _ = ac.Encode(op) 58 59 nkfour, _ := nkeys.CreateAccount() 60 pub, _ = nkfour.PublicKey() 61 four = pub 62 ac = jwt.NewAccountClaims(pub) 63 jwt4, _ = ac.Encode(op) 64 } 65 66 func TestShardedDirStoreWriteAndReadonly(t *testing.T) { 67 t.Parallel() 68 dir := t.TempDir() 69 70 store, err := NewDirJWTStore(dir, true, false) 71 require_NoError(t, err) 72 73 expected := map[string]string{ 74 one: "alpha", 75 two: "beta", 76 three: "gamma", 77 four: "delta", 78 } 79 80 for k, v := range expected { 81 store.SaveAcc(k, v) 82 } 83 84 for k, v := range expected { 85 got, err := store.LoadAcc(k) 86 require_NoError(t, err) 87 require_Equal(t, v, got) 88 } 89 90 got, err := store.LoadAcc("random") 91 require_Error(t, err) 92 require_Equal(t, "", got) 93 94 got, err = store.LoadAcc("") 95 require_Error(t, err) 96 require_Equal(t, "", got) 97 98 err = store.SaveAcc("", "onetwothree") 99 require_Error(t, err) 100 store.Close() 101 102 // re-use the folder for readonly mode 103 store, err = NewImmutableDirJWTStore(dir, true) 104 require_NoError(t, err) 105 106 require_True(t, store.IsReadOnly()) 107 108 err = store.SaveAcc("five", "omega") 109 require_Error(t, err) 110 111 for k, v := range expected { 112 got, err := store.LoadAcc(k) 113 require_NoError(t, err) 114 require_Equal(t, v, got) 115 } 116 store.Close() 117 } 118 119 func TestUnshardedDirStoreWriteAndReadonly(t *testing.T) { 120 t.Parallel() 121 dir := t.TempDir() 122 123 store, err := NewDirJWTStore(dir, false, false) 124 require_NoError(t, err) 125 126 expected := map[string]string{ 127 one: "alpha", 128 two: "beta", 129 three: "gamma", 130 four: "delta", 131 } 132 133 require_False(t, store.IsReadOnly()) 134 135 for k, v := range expected { 136 store.SaveAcc(k, v) 137 } 138 139 for k, v := range expected { 140 got, err := store.LoadAcc(k) 141 require_NoError(t, err) 142 require_Equal(t, v, got) 143 } 144 145 got, err := store.LoadAcc("random") 146 require_Error(t, err) 147 require_Equal(t, "", got) 148 149 got, err = store.LoadAcc("") 150 require_Error(t, err) 151 require_Equal(t, "", got) 152 153 err = store.SaveAcc("", "onetwothree") 154 require_Error(t, err) 155 store.Close() 156 157 // re-use the folder for readonly mode 158 store, err = NewImmutableDirJWTStore(dir, false) 159 require_NoError(t, err) 160 161 require_True(t, store.IsReadOnly()) 162 163 err = store.SaveAcc("five", "omega") 164 require_Error(t, err) 165 166 for k, v := range expected { 167 got, err := store.LoadAcc(k) 168 require_NoError(t, err) 169 require_Equal(t, v, got) 170 } 171 store.Close() 172 } 173 174 func TestNoCreateRequiresDir(t *testing.T) { 175 t.Parallel() 176 _, err := NewDirJWTStore("/a/b/c", true, false) 177 require_Error(t, err) 178 } 179 180 func TestCreateMakesDir(t *testing.T) { 181 t.Parallel() 182 dir := t.TempDir() 183 184 fullPath := filepath.Join(dir, "a/b") 185 186 _, err := os.Stat(fullPath) 187 require_Error(t, err) 188 require_True(t, os.IsNotExist(err)) 189 190 s, err := NewDirJWTStore(fullPath, false, true) 191 require_NoError(t, err) 192 s.Close() 193 194 _, err = os.Stat(fullPath) 195 require_NoError(t, err) 196 } 197 198 func TestShardedDirStorePackMerge(t *testing.T) { 199 t.Parallel() 200 dir := t.TempDir() 201 dir2 := t.TempDir() 202 dir3 := t.TempDir() 203 204 store, err := NewDirJWTStore(dir, true, false) 205 require_NoError(t, err) 206 207 expected := map[string]string{ 208 one: "alpha", 209 two: "beta", 210 three: "gamma", 211 four: "delta", 212 } 213 214 require_False(t, store.IsReadOnly()) 215 216 for k, v := range expected { 217 store.SaveAcc(k, v) 218 } 219 220 for k, v := range expected { 221 got, err := store.LoadAcc(k) 222 require_NoError(t, err) 223 require_Equal(t, v, got) 224 } 225 226 got, err := store.LoadAcc("random") 227 require_Error(t, err) 228 require_Equal(t, "", got) 229 230 pack, err := store.Pack(-1) 231 require_NoError(t, err) 232 233 inc, err := NewDirJWTStore(dir2, true, false) 234 require_NoError(t, err) 235 236 inc.Merge(pack) 237 238 for k, v := range expected { 239 got, err := inc.LoadAcc(k) 240 require_NoError(t, err) 241 require_Equal(t, v, got) 242 } 243 244 got, err = inc.LoadAcc("random") 245 require_Error(t, err) 246 require_Equal(t, "", got) 247 248 limitedPack, err := inc.Pack(1) 249 require_NoError(t, err) 250 251 limited, err := NewDirJWTStore(dir3, true, false) 252 253 require_NoError(t, err) 254 255 limited.Merge(limitedPack) 256 257 count := 0 258 for k, v := range expected { 259 got, err := limited.LoadAcc(k) 260 if err == nil { 261 count++ 262 require_Equal(t, v, got) 263 } 264 } 265 266 require_Len(t, 1, count) 267 268 got, err = inc.LoadAcc("random") 269 require_Error(t, err) 270 require_Equal(t, "", got) 271 } 272 273 func TestShardedToUnsharedDirStorePackMerge(t *testing.T) { 274 t.Parallel() 275 dir := t.TempDir() 276 dir2 := t.TempDir() 277 278 store, err := NewDirJWTStore(dir, true, false) 279 require_NoError(t, err) 280 281 expected := map[string]string{ 282 one: "alpha", 283 two: "beta", 284 three: "gamma", 285 four: "delta", 286 } 287 288 require_False(t, store.IsReadOnly()) 289 290 for k, v := range expected { 291 store.SaveAcc(k, v) 292 } 293 294 for k, v := range expected { 295 got, err := store.LoadAcc(k) 296 require_NoError(t, err) 297 require_Equal(t, v, got) 298 } 299 300 got, err := store.LoadAcc("random") 301 require_Error(t, err) 302 require_Equal(t, "", got) 303 304 pack, err := store.Pack(-1) 305 require_NoError(t, err) 306 307 inc, err := NewDirJWTStore(dir2, false, false) 308 require_NoError(t, err) 309 310 inc.Merge(pack) 311 312 for k, v := range expected { 313 got, err := inc.LoadAcc(k) 314 require_NoError(t, err) 315 require_Equal(t, v, got) 316 } 317 318 got, err = inc.LoadAcc("random") 319 require_Error(t, err) 320 require_Equal(t, "", got) 321 322 err = store.Merge("foo") 323 require_Error(t, err) 324 325 err = store.Merge("") // will skip it 326 require_NoError(t, err) 327 328 err = store.Merge("a|something") // should fail on a for sharding 329 require_Error(t, err) 330 } 331 332 func TestMergeOnlyOnNewer(t *testing.T) { 333 t.Parallel() 334 dir := t.TempDir() 335 336 dirStore, err := NewDirJWTStore(dir, true, false) 337 require_NoError(t, err) 338 339 accountKey, err := nkeys.CreateAccount() 340 require_NoError(t, err) 341 342 pubKey, err := accountKey.PublicKey() 343 require_NoError(t, err) 344 345 account := jwt.NewAccountClaims(pubKey) 346 account.Name = "old" 347 olderJWT, err := account.Encode(accountKey) 348 require_NoError(t, err) 349 350 time.Sleep(2 * time.Second) 351 352 account.Name = "new" 353 newerJWT, err := account.Encode(accountKey) 354 require_NoError(t, err) 355 356 // Should work 357 err = dirStore.SaveAcc(pubKey, olderJWT) 358 require_NoError(t, err) 359 fromStore, err := dirStore.LoadAcc(pubKey) 360 require_NoError(t, err) 361 require_Equal(t, olderJWT, fromStore) 362 363 // should replace 364 err = dirStore.saveIfNewer(pubKey, newerJWT) 365 require_NoError(t, err) 366 fromStore, err = dirStore.LoadAcc(pubKey) 367 require_NoError(t, err) 368 require_Equal(t, newerJWT, fromStore) 369 370 // should fail 371 err = dirStore.saveIfNewer(pubKey, olderJWT) 372 require_NoError(t, err) 373 fromStore, err = dirStore.LoadAcc(pubKey) 374 require_NoError(t, err) 375 require_Equal(t, newerJWT, fromStore) 376 } 377 378 func createTestAccount(t *testing.T, dirStore *DirJWTStore, expSec int, accKey nkeys.KeyPair) string { 379 t.Helper() 380 pubKey, err := accKey.PublicKey() 381 require_NoError(t, err) 382 account := jwt.NewAccountClaims(pubKey) 383 if expSec > 0 { 384 account.Expires = time.Now().Round(time.Second).Add(time.Second * time.Duration(expSec)).Unix() 385 } 386 jwt, err := account.Encode(accKey) 387 require_NoError(t, err) 388 err = dirStore.SaveAcc(pubKey, jwt) 389 require_NoError(t, err) 390 return jwt 391 } 392 393 func assertStoreSize(t *testing.T, dirStore *DirJWTStore, length int) { 394 t.Helper() 395 f, err := os.ReadDir(dirStore.directory) 396 require_NoError(t, err) 397 require_Len(t, len(f), length) 398 dirStore.Lock() 399 require_Len(t, len(dirStore.expiration.idx), length) 400 require_Len(t, dirStore.expiration.lru.Len(), length) 401 require_Len(t, len(dirStore.expiration.heap), length) 402 dirStore.Unlock() 403 } 404 405 func TestExpiration(t *testing.T) { 406 t.Parallel() 407 dir := t.TempDir() 408 409 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*50, 10, true, 0, nil) 410 require_NoError(t, err) 411 defer dirStore.Close() 412 413 account := func(expSec int) { 414 accountKey, err := nkeys.CreateAccount() 415 require_NoError(t, err) 416 createTestAccount(t, dirStore, expSec, accountKey) 417 } 418 419 hBegin := dirStore.Hash() 420 account(100) 421 hNoExp := dirStore.Hash() 422 require_NotEqual(t, hBegin, hNoExp) 423 account(1) 424 nh2 := dirStore.Hash() 425 require_NotEqual(t, hNoExp, nh2) 426 assertStoreSize(t, dirStore, 2) 427 428 failAt := time.Now().Add(4 * time.Second) 429 for time.Now().Before(failAt) { 430 time.Sleep(100 * time.Millisecond) 431 f, err := os.ReadDir(dir) 432 require_NoError(t, err) 433 if len(f) == 1 { 434 lh := dirStore.Hash() 435 require_Equal(t, string(hNoExp[:]), string(lh[:])) 436 return 437 } 438 } 439 t.Fatalf("Waited more than 4 seconds for the file with expiration 1 second to expire") 440 } 441 442 func TestLimit(t *testing.T) { 443 t.Parallel() 444 dir := t.TempDir() 445 446 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*100, 5, true, 0, nil) 447 require_NoError(t, err) 448 defer dirStore.Close() 449 450 account := func(expSec int) { 451 accountKey, err := nkeys.CreateAccount() 452 require_NoError(t, err) 453 createTestAccount(t, dirStore, expSec, accountKey) 454 } 455 456 h := dirStore.Hash() 457 458 accountKey, err := nkeys.CreateAccount() 459 require_NoError(t, err) 460 // update first account 461 for i := 0; i < 10; i++ { 462 createTestAccount(t, dirStore, 50, accountKey) 463 assertStoreSize(t, dirStore, 1) 464 } 465 // new accounts 466 for i := 0; i < 10; i++ { 467 account(i) 468 nh := dirStore.Hash() 469 require_NotEqual(t, h, nh) 470 h = nh 471 } 472 // first account should be gone now accountKey.PublicKey() 473 key, _ := accountKey.PublicKey() 474 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, key)) 475 require_True(t, os.IsNotExist(err)) 476 477 // update first account 478 for i := 0; i < 10; i++ { 479 createTestAccount(t, dirStore, 50, accountKey) 480 assertStoreSize(t, dirStore, 5) 481 } 482 } 483 484 func TestLimitNoEvict(t *testing.T) { 485 t.Parallel() 486 dir := t.TempDir() 487 488 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*50, 2, false, 0, nil) 489 require_NoError(t, err) 490 defer dirStore.Close() 491 492 accountKey1, err := nkeys.CreateAccount() 493 require_NoError(t, err) 494 pKey1, err := accountKey1.PublicKey() 495 require_NoError(t, err) 496 accountKey2, err := nkeys.CreateAccount() 497 require_NoError(t, err) 498 accountKey3, err := nkeys.CreateAccount() 499 require_NoError(t, err) 500 pKey3, err := accountKey3.PublicKey() 501 require_NoError(t, err) 502 503 createTestAccount(t, dirStore, 100, accountKey1) 504 assertStoreSize(t, dirStore, 1) 505 createTestAccount(t, dirStore, 1, accountKey2) 506 assertStoreSize(t, dirStore, 2) 507 508 hBefore := dirStore.Hash() 509 // 2 jwt are already stored. third must result in an error 510 pubKey, err := accountKey3.PublicKey() 511 require_NoError(t, err) 512 account := jwt.NewAccountClaims(pubKey) 513 jwt, err := account.Encode(accountKey3) 514 require_NoError(t, err) 515 err = dirStore.SaveAcc(pubKey, jwt) 516 require_Error(t, err) 517 assertStoreSize(t, dirStore, 2) 518 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey1)) 519 require_NoError(t, err) 520 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey3)) 521 require_True(t, os.IsNotExist(err)) 522 // check that the hash did not change 523 hAfter := dirStore.Hash() 524 require_True(t, bytes.Equal(hBefore[:], hAfter[:])) 525 // wait for expiration of account2 526 time.Sleep(2200 * time.Millisecond) 527 err = dirStore.SaveAcc(pubKey, jwt) 528 require_NoError(t, err) 529 assertStoreSize(t, dirStore, 2) 530 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey1)) 531 require_NoError(t, err) 532 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey3)) 533 require_NoError(t, err) 534 } 535 536 func TestLruLoad(t *testing.T) { 537 t.Parallel() 538 dir := t.TempDir() 539 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*100, 2, true, 0, nil) 540 require_NoError(t, err) 541 defer dirStore.Close() 542 543 accountKey1, err := nkeys.CreateAccount() 544 require_NoError(t, err) 545 pKey1, err := accountKey1.PublicKey() 546 require_NoError(t, err) 547 accountKey2, err := nkeys.CreateAccount() 548 require_NoError(t, err) 549 accountKey3, err := nkeys.CreateAccount() 550 require_NoError(t, err) 551 pKey3, err := accountKey3.PublicKey() 552 require_NoError(t, err) 553 554 createTestAccount(t, dirStore, 10, accountKey1) 555 assertStoreSize(t, dirStore, 1) 556 createTestAccount(t, dirStore, 10, accountKey2) 557 assertStoreSize(t, dirStore, 2) 558 dirStore.LoadAcc(pKey1) // will reorder 1/2 559 createTestAccount(t, dirStore, 10, accountKey3) 560 assertStoreSize(t, dirStore, 2) 561 562 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey1)) 563 require_NoError(t, err) 564 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey3)) 565 require_NoError(t, err) 566 } 567 568 func TestLruVolume(t *testing.T) { 569 t.Parallel() 570 dir := t.TempDir() 571 572 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*50, 2, true, 0, nil) 573 require_NoError(t, err) 574 defer dirStore.Close() 575 replaceCnt := 500 // needs to be bigger than 2 due to loop unrolling 576 keys := make([]string, replaceCnt) 577 578 key, err := nkeys.CreateAccount() 579 require_NoError(t, err) 580 keys[0], err = key.PublicKey() 581 require_NoError(t, err) 582 createTestAccount(t, dirStore, 10000, key) // not intended to expire 583 assertStoreSize(t, dirStore, 1) 584 585 key, err = nkeys.CreateAccount() 586 require_NoError(t, err) 587 keys[1], err = key.PublicKey() 588 require_NoError(t, err) 589 createTestAccount(t, dirStore, 10000, key) 590 assertStoreSize(t, dirStore, 2) 591 592 for i := 2; i < replaceCnt; i++ { 593 k, err := nkeys.CreateAccount() 594 require_NoError(t, err) 595 keys[i], err = k.PublicKey() 596 require_NoError(t, err) 597 598 createTestAccount(t, dirStore, 10000+rand.Intn(10000), k) // not intended to expire 599 assertStoreSize(t, dirStore, 2) 600 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, keys[i-2])) 601 require_Error(t, err) 602 require_True(t, os.IsNotExist(err)) 603 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, keys[i-1])) 604 require_NoError(t, err) 605 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, keys[i])) 606 require_NoError(t, err) 607 } 608 } 609 610 func TestLru(t *testing.T) { 611 t.Parallel() 612 dir := t.TempDir() 613 614 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*50, 2, true, 0, nil) 615 require_NoError(t, err) 616 defer dirStore.Close() 617 618 accountKey1, err := nkeys.CreateAccount() 619 require_NoError(t, err) 620 pKey1, err := accountKey1.PublicKey() 621 require_NoError(t, err) 622 accountKey2, err := nkeys.CreateAccount() 623 require_NoError(t, err) 624 accountKey3, err := nkeys.CreateAccount() 625 require_NoError(t, err) 626 pKey3, err := accountKey3.PublicKey() 627 require_NoError(t, err) 628 629 createTestAccount(t, dirStore, 1000, accountKey1) 630 assertStoreSize(t, dirStore, 1) 631 createTestAccount(t, dirStore, 1000, accountKey2) 632 assertStoreSize(t, dirStore, 2) 633 createTestAccount(t, dirStore, 1000, accountKey3) 634 assertStoreSize(t, dirStore, 2) 635 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey1)) 636 require_Error(t, err) 637 require_True(t, os.IsNotExist(err)) 638 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey3)) 639 require_NoError(t, err) 640 641 // update -> will change this keys position for eviction 642 createTestAccount(t, dirStore, 1000, accountKey2) 643 assertStoreSize(t, dirStore, 2) 644 // recreate -> will evict 3 645 createTestAccount(t, dirStore, 1, accountKey1) 646 assertStoreSize(t, dirStore, 2) 647 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey3)) 648 require_True(t, os.IsNotExist(err)) 649 // let key1 expire. sleep expSec=1 + 1 for rounding 650 time.Sleep(2200 * time.Millisecond) 651 assertStoreSize(t, dirStore, 1) 652 _, err = os.Stat(fmt.Sprintf("%s/%s.jwt", dir, pKey1)) 653 require_True(t, os.IsNotExist(err)) 654 // recreate key3 - no eviction 655 createTestAccount(t, dirStore, 1000, accountKey3) 656 assertStoreSize(t, dirStore, 2) 657 } 658 659 func TestReload(t *testing.T) { 660 t.Parallel() 661 dir := t.TempDir() 662 notificationChan := make(chan struct{}, 5) 663 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*100, 2, true, 0, func(publicKey string) { 664 notificationChan <- struct{}{} 665 }) 666 require_NoError(t, err) 667 defer dirStore.Close() 668 newAccount := func() string { 669 t.Helper() 670 accKey, err := nkeys.CreateAccount() 671 require_NoError(t, err) 672 pKey, err := accKey.PublicKey() 673 require_NoError(t, err) 674 pubKey, err := accKey.PublicKey() 675 require_NoError(t, err) 676 account := jwt.NewAccountClaims(pubKey) 677 jwt, err := account.Encode(accKey) 678 require_NoError(t, err) 679 file := fmt.Sprintf("%s/%s.jwt", dir, pKey) 680 err = os.WriteFile(file, []byte(jwt), 0644) 681 require_NoError(t, err) 682 return file 683 } 684 files := make(map[string]struct{}) 685 assertStoreSize(t, dirStore, 0) 686 hash := dirStore.Hash() 687 emptyHash := [sha256.Size]byte{} 688 require_True(t, bytes.Equal(hash[:], emptyHash[:])) 689 for i := 0; i < 5; i++ { 690 files[newAccount()] = struct{}{} 691 err = dirStore.Reload() 692 require_NoError(t, err) 693 <-notificationChan 694 assertStoreSize(t, dirStore, i+1) 695 hash = dirStore.Hash() 696 require_False(t, bytes.Equal(hash[:], emptyHash[:])) 697 msg, err := dirStore.Pack(-1) 698 require_NoError(t, err) 699 require_Len(t, len(strings.Split(msg, "\n")), len(files)) 700 } 701 for k := range files { 702 hash = dirStore.Hash() 703 require_False(t, bytes.Equal(hash[:], emptyHash[:])) 704 removeFile(t, k) 705 err = dirStore.Reload() 706 require_NoError(t, err) 707 assertStoreSize(t, dirStore, len(files)-1) 708 delete(files, k) 709 msg, err := dirStore.Pack(-1) 710 require_NoError(t, err) 711 if len(files) != 0 { // when len is 0, we have an empty line 712 require_Len(t, len(strings.Split(msg, "\n")), len(files)) 713 } 714 } 715 require_True(t, len(notificationChan) == 0) 716 hash = dirStore.Hash() 717 require_True(t, bytes.Equal(hash[:], emptyHash[:])) 718 } 719 720 func TestExpirationUpdate(t *testing.T) { 721 t.Parallel() 722 dir := t.TempDir() 723 724 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, time.Millisecond*50, 10, true, 0, nil) 725 require_NoError(t, err) 726 defer dirStore.Close() 727 728 accountKey, err := nkeys.CreateAccount() 729 require_NoError(t, err) 730 731 h := dirStore.Hash() 732 733 createTestAccount(t, dirStore, 0, accountKey) 734 nh := dirStore.Hash() 735 require_NotEqual(t, h, nh) 736 h = nh 737 738 time.Sleep(1500 * time.Millisecond) 739 f, err := os.ReadDir(dir) 740 require_NoError(t, err) 741 require_Len(t, len(f), 1) 742 743 createTestAccount(t, dirStore, 2, accountKey) 744 nh = dirStore.Hash() 745 require_NotEqual(t, h, nh) 746 h = nh 747 748 time.Sleep(1500 * time.Millisecond) 749 f, err = os.ReadDir(dir) 750 require_NoError(t, err) 751 require_Len(t, len(f), 1) 752 753 createTestAccount(t, dirStore, 0, accountKey) 754 nh = dirStore.Hash() 755 require_NotEqual(t, h, nh) 756 h = nh 757 758 time.Sleep(1500 * time.Millisecond) 759 f, err = os.ReadDir(dir) 760 require_NoError(t, err) 761 require_Len(t, len(f), 1) 762 763 createTestAccount(t, dirStore, 1, accountKey) 764 nh = dirStore.Hash() 765 require_NotEqual(t, h, nh) 766 767 time.Sleep(1500 * time.Millisecond) 768 f, err = os.ReadDir(dir) 769 require_NoError(t, err) 770 require_Len(t, len(f), 0) 771 772 empty := [32]byte{} 773 h = dirStore.Hash() 774 require_Equal(t, string(h[:]), string(empty[:])) 775 } 776 777 func TestTTL(t *testing.T) { 778 t.Parallel() 779 dir := t.TempDir() 780 require_OneJWT := func() { 781 t.Helper() 782 f, err := os.ReadDir(dir) 783 require_NoError(t, err) 784 require_Len(t, len(f), 1) 785 } 786 test := func(op func(store *DirJWTStore, accountKey nkeys.KeyPair, accountPubKey string, jwt string)) { 787 dirStore, err := NewExpiringDirJWTStore(dir, false, false, NoDelete, 50*time.Millisecond, 10, true, 200*time.Millisecond, nil) 788 require_NoError(t, err) 789 defer dirStore.Close() 790 791 accountKey, err := nkeys.CreateAccount() 792 require_NoError(t, err) 793 pubKey, err := accountKey.PublicKey() 794 require_NoError(t, err) 795 jwt := createTestAccount(t, dirStore, 0, accountKey) 796 require_OneJWT() 797 // observe non expiration due to activity 798 for i := 0; i < 4; i++ { 799 time.Sleep(110 * time.Millisecond) 800 op(dirStore, accountKey, pubKey, jwt) 801 require_OneJWT() 802 } 803 // observe expiration 804 for i := 0; i < 40; i++ { 805 time.Sleep(50 * time.Millisecond) 806 f, err := os.ReadDir(dir) 807 require_NoError(t, err) 808 if len(f) == 0 { 809 return 810 } 811 } 812 t.Fatalf("jwt should have expired by now") 813 } 814 t.Run("no expiration due to load", func(t *testing.T) { 815 test(func(store *DirJWTStore, accountKey nkeys.KeyPair, pubKey string, jwt string) { 816 store.LoadAcc(pubKey) 817 }) 818 }) 819 t.Run("no expiration due to store", func(t *testing.T) { 820 test(func(store *DirJWTStore, accountKey nkeys.KeyPair, pubKey string, jwt string) { 821 store.SaveAcc(pubKey, jwt) 822 }) 823 }) 824 t.Run("no expiration due to overwrite", func(t *testing.T) { 825 test(func(store *DirJWTStore, accountKey nkeys.KeyPair, pubKey string, jwt string) { 826 createTestAccount(t, store, 0, accountKey) 827 }) 828 }) 829 } 830 831 func TestRemove(t *testing.T) { 832 for deleteType, test := range map[deleteType]struct { 833 expected int 834 moved int 835 }{ 836 HardDelete: {0, 0}, 837 RenameDeleted: {0, 1}, 838 NoDelete: {1, 0}, 839 } { 840 deleteType, test := deleteType, test // fixes govet capturing loop variables 841 t.Run("", func(t *testing.T) { 842 t.Parallel() 843 dir := t.TempDir() 844 require_OneJWT := func() { 845 t.Helper() 846 f, err := os.ReadDir(dir) 847 require_NoError(t, err) 848 require_Len(t, len(f), 1) 849 } 850 dirStore, err := NewExpiringDirJWTStore(dir, false, false, deleteType, 0, 10, true, 0, nil) 851 delPubKey := "" 852 dirStore.deleted = func(publicKey string) { 853 delPubKey = publicKey 854 } 855 require_NoError(t, err) 856 defer dirStore.Close() 857 accountKey, err := nkeys.CreateAccount() 858 require_NoError(t, err) 859 pubKey, err := accountKey.PublicKey() 860 require_NoError(t, err) 861 createTestAccount(t, dirStore, 0, accountKey) 862 require_OneJWT() 863 dirStore.delete(pubKey) 864 if deleteType == NoDelete { 865 require_True(t, delPubKey == "") 866 } else { 867 require_True(t, delPubKey == pubKey) 868 } 869 f, err := filepath.Glob(dir + string(os.PathSeparator) + "/*.jwt") 870 require_NoError(t, err) 871 require_Len(t, len(f), test.expected) 872 f, err = filepath.Glob(dir + string(os.PathSeparator) + "/*.jwt.deleted") 873 require_NoError(t, err) 874 require_Len(t, len(f), test.moved) 875 }) 876 } 877 } 878 879 const infDur = time.Duration(math.MaxInt64) 880 881 func TestNotificationOnPack(t *testing.T) { 882 t.Parallel() 883 jwts := map[string]string{ 884 one: jwt1, 885 two: jwt2, 886 three: jwt3, 887 four: jwt4, 888 } 889 notificationChan := make(chan struct{}, len(jwts)) // set to same len so all extra will block 890 notification := func(pubKey string) { 891 if _, ok := jwts[pubKey]; !ok { 892 t.Fatalf("Key not found: %s", pubKey) 893 } 894 notificationChan <- struct{}{} 895 } 896 dirPack := t.TempDir() 897 packStore, err := NewExpiringDirJWTStore(dirPack, false, false, NoDelete, infDur, 0, true, 0, notification) 898 require_NoError(t, err) 899 // prefill the store with data 900 for k, v := range jwts { 901 require_NoError(t, packStore.SaveAcc(k, v)) 902 } 903 for i := 0; i < len(jwts); i++ { 904 <-notificationChan 905 } 906 msg, err := packStore.Pack(-1) 907 require_NoError(t, err) 908 packStore.Close() 909 hash := packStore.Hash() 910 for _, shard := range []bool{true, false, true, false} { 911 dirMerge := t.TempDir() 912 mergeStore, err := NewExpiringDirJWTStore(dirMerge, shard, false, NoDelete, infDur, 0, true, 0, notification) 913 require_NoError(t, err) 914 // set 915 err = mergeStore.Merge(msg) 916 require_NoError(t, err) 917 assertStoreSize(t, mergeStore, len(jwts)) 918 hash1 := packStore.Hash() 919 require_True(t, bytes.Equal(hash[:], hash1[:])) 920 for i := 0; i < len(jwts); i++ { 921 <-notificationChan 922 } 923 // overwrite - assure 924 err = mergeStore.Merge(msg) 925 require_NoError(t, err) 926 assertStoreSize(t, mergeStore, len(jwts)) 927 hash2 := packStore.Hash() 928 require_True(t, bytes.Equal(hash1[:], hash2[:])) 929 930 hash = hash1 931 msg, err = mergeStore.Pack(-1) 932 require_NoError(t, err) 933 mergeStore.Close() 934 require_True(t, len(notificationChan) == 0) 935 936 for k, v := range jwts { 937 j, err := packStore.LoadAcc(k) 938 require_NoError(t, err) 939 require_Equal(t, j, v) 940 } 941 } 942 } 943 944 func TestNotificationOnPackWalk(t *testing.T) { 945 t.Parallel() 946 const storeCnt = 5 947 const keyCnt = 50 948 const iterCnt = 8 949 store := [storeCnt]*DirJWTStore{} 950 for i := 0; i < storeCnt; i++ { 951 dirMerge := t.TempDir() 952 mergeStore, err := NewExpiringDirJWTStore(dirMerge, true, false, NoDelete, infDur, 0, true, 0, nil) 953 require_NoError(t, err) 954 store[i] = mergeStore 955 } 956 for i := 0; i < iterCnt; i++ { //iterations 957 jwts := make(map[string]string) 958 for j := 0; j < keyCnt; j++ { 959 kp, _ := nkeys.CreateAccount() 960 key, _ := kp.PublicKey() 961 ac := jwt.NewAccountClaims(key) 962 jwts[key], _ = ac.Encode(op) 963 require_NoError(t, store[0].SaveAcc(key, jwts[key])) 964 } 965 for j := 0; j < storeCnt-1; j++ { // stores 966 err := store[j].PackWalk(3, func(partialPackMsg string) { 967 err := store[j+1].Merge(partialPackMsg) 968 require_NoError(t, err) 969 }) 970 require_NoError(t, err) 971 } 972 for i := 0; i < storeCnt-1; i++ { 973 h1 := store[i].Hash() 974 h2 := store[i+1].Hash() 975 require_True(t, bytes.Equal(h1[:], h2[:])) 976 } 977 } 978 for i := 0; i < storeCnt; i++ { 979 store[i].Close() 980 } 981 }