get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/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  }