github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/transientstore/delete_store_test.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package transientstore
     8  
     9  import (
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/golang/protobuf/proto"
    17  	"github.com/hechain20/hechain/common/ledger/util/leveldbhelper"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestSystemNamespaceIsEmptyString(t *testing.T) {
    22  	require.Equal(t, "", systemNamespace)
    23  }
    24  
    25  func TestUnderDeletionValue(t *testing.T) {
    26  	require.Equal(t, []byte("UNDER_DELETION"), underDeletionKey)
    27  }
    28  
    29  func TestNewStoreProvider(t *testing.T) {
    30  	tempdir, err := ioutil.TempDir("", "ts")
    31  	require.NoErrorf(t, err, "failed to create test directory [%s]", tempdir)
    32  	defer os.RemoveAll(tempdir)
    33  
    34  	storedir := filepath.Join(tempdir, "transientstore")
    35  	p, err := NewStoreProvider(storedir)
    36  	require.NoError(t, err)
    37  	require.NotNil(t, p)
    38  }
    39  
    40  func TestMarkStorageForDelete(t *testing.T) {
    41  	env := initTestEnv(t)
    42  	defer env.cleanup()
    43  	ledgerID := "doomed-transient-storage"
    44  
    45  	// env.storeProvider is an interface type StoreProvider.  Use a golang
    46  	// type assertion to cast this as a *storeProvider, so that we may test
    47  	// the package level functions received by this struct.
    48  	sp := env.storeProvider.(*storeProvider)
    49  	require.NotNil(t, sp)
    50  
    51  	// level db used to store UNDER_DELETION status
    52  	syshandle := sp.dbProvider.GetDBHandle(systemNamespace)
    53  	require.NotNil(t, syshandle)
    54  
    55  	isEmpty, err := syshandle.IsEmpty()
    56  	require.NoError(t, err)
    57  	require.True(t, isEmpty)
    58  
    59  	// Mark the transient storage for deletion.
    60  	err = sp.markStorageForDelete(ledgerID)
    61  	require.NoError(t, err)
    62  
    63  	// sysdb should now have a key UNDER_DELETION containing a proto message
    64  	isEmpty, err = syshandle.IsEmpty()
    65  	require.NoError(t, err)
    66  	require.False(t, isEmpty)
    67  
    68  	val, err := syshandle.Get(underDeletionKey)
    69  	require.NoError(t, err)
    70  	require.NotNil(t, val)
    71  
    72  	// the value should be a proto.Message containing a list with the doomed ledger ID
    73  	delete := PendingDeleteStorageList{}
    74  	err = proto.Unmarshal(val, &delete)
    75  	require.NoError(t, err)
    76  	require.NotNil(t, delete.List)
    77  	require.Contains(t, delete.List, ledgerID)
    78  }
    79  
    80  func TestMultipleStoragesMarkedForDeletion(t *testing.T) {
    81  	env := initTestEnv(t)
    82  	defer env.cleanup()
    83  
    84  	doomed1 := "doomed-1"
    85  	doomed2 := "doomed-2"
    86  	doomed3 := "doomed-3"
    87  
    88  	// env.storeProvider is an interface type StoreProvider.  Use a golang
    89  	// type assertion to cast this as a *storeProvider, so that we may test
    90  	// the package level functions received by this struct.
    91  	sp := env.storeProvider.(*storeProvider)
    92  	require.NotNil(t, sp)
    93  
    94  	// Delete list is empty to start.
    95  	dl, err := sp.getStorageMarkedForDeletion()
    96  	require.NoError(t, err)
    97  	require.Equal(t, 0, len(dl.List))
    98  
    99  	// mark a deletion
   100  	require.NoError(t, sp.markStorageForDelete(doomed1))
   101  	dl, err = sp.getStorageMarkedForDeletion()
   102  	require.NoError(t, err)
   103  	require.Equal(t, 1, len(dl.List))
   104  	require.Contains(t, dl.List, doomed1)
   105  
   106  	// mark it again - it should not contain duplicate entries
   107  	require.NoError(t, sp.markStorageForDelete(doomed1))
   108  	dl, err = sp.getStorageMarkedForDeletion()
   109  	require.NoError(t, err)
   110  	require.Equal(t, 1, len(dl.List))
   111  	require.Contains(t, dl.List, doomed1)
   112  
   113  	// add multiple entries
   114  	require.NoError(t, sp.markStorageForDelete(doomed2))
   115  	dl, err = sp.getStorageMarkedForDeletion()
   116  	require.NoError(t, err)
   117  	require.Equal(t, 2, len(dl.List))
   118  	require.Contains(t, dl.List, doomed1)
   119  	require.Contains(t, dl.List, doomed2)
   120  
   121  	require.NoError(t, sp.markStorageForDelete(doomed3))
   122  	dl, err = sp.getStorageMarkedForDeletion()
   123  	require.NoError(t, err)
   124  	require.Equal(t, 3, len(dl.List))
   125  	require.Contains(t, dl.List, doomed1)
   126  	require.Contains(t, dl.List, doomed2)
   127  	require.Contains(t, dl.List, doomed3)
   128  }
   129  
   130  func TestUnmarkDeletionTag(t *testing.T) {
   131  	env := initTestEnv(t)
   132  	defer env.cleanup()
   133  
   134  	doomed1 := "doomed-1"
   135  	doomed2 := "doomed-2"
   136  	doomed3 := "doomed-3"
   137  
   138  	// env.storeProvider is an interface type StoreProvider.  Use a golang
   139  	// type assertion to cast this as a *storeProvider, so that we may test
   140  	// the package level functions received by this struct.
   141  	sp := env.storeProvider.(*storeProvider)
   142  	require.NotNil(t, sp)
   143  
   144  	// Delete list is empty to start.
   145  	dl, err := sp.getStorageMarkedForDeletion()
   146  	require.NoError(t, err)
   147  	require.Equal(t, 0, len(dl.List))
   148  
   149  	// doom some transient storage entries
   150  	require.NoError(t, sp.markStorageForDelete(doomed1))
   151  	require.NoError(t, sp.markStorageForDelete(doomed2))
   152  	require.NoError(t, sp.markStorageForDelete(doomed3))
   153  
   154  	dl, err = sp.getStorageMarkedForDeletion()
   155  	require.NoError(t, err)
   156  	require.Equal(t, 3, len(dl.List))
   157  	require.Contains(t, dl.List, doomed1)
   158  	require.Contains(t, dl.List, doomed2)
   159  	require.Contains(t, dl.List, doomed3)
   160  
   161  	// clear out the doomed flag for one of the storage
   162  	require.NoError(t, sp.clearStorageDeletionStatus(doomed2))
   163  
   164  	dl, err = sp.getStorageMarkedForDeletion()
   165  	require.NoError(t, err)
   166  	require.Equal(t, 2, len(dl.List))
   167  	require.Contains(t, dl.List, doomed1)
   168  	require.NotContains(t, dl.List, doomed2)
   169  	require.Contains(t, dl.List, doomed3)
   170  }
   171  
   172  func TestClearDeletionTagNotPresent(t *testing.T) {
   173  	env := initTestEnv(t)
   174  	defer env.cleanup()
   175  
   176  	doomed1 := "doomed-1"
   177  	doomed2 := "doomed-2"
   178  	doomed3 := "doomed-3"
   179  	invalid := "invalid-storage-does-not-exist"
   180  
   181  	// env.storeProvider is an interface type StoreProvider.  Use a golang
   182  	// type assertion to cast this as a *storeProvider, so that we may test
   183  	// the package level functions received by this struct.
   184  	sp := env.storeProvider.(*storeProvider)
   185  	require.NotNil(t, sp)
   186  
   187  	// Delete list is empty to start.
   188  	dl, err := sp.getStorageMarkedForDeletion()
   189  	require.NoError(t, err)
   190  	require.Equal(t, 0, len(dl.List))
   191  
   192  	// Check the boundary case of removing an invalid ledger from an empty set.
   193  	require.NoError(t, sp.clearStorageDeletionStatus(invalid))
   194  
   195  	// doom some transient storage entries
   196  	require.NoError(t, sp.markStorageForDelete(doomed1))
   197  	require.NoError(t, sp.markStorageForDelete(doomed2))
   198  	require.NoError(t, sp.markStorageForDelete(doomed3))
   199  
   200  	dl, err = sp.getStorageMarkedForDeletion()
   201  	require.NoError(t, err)
   202  	require.Equal(t, 3, len(dl.List))
   203  	require.Contains(t, dl.List, doomed1)
   204  	require.Contains(t, dl.List, doomed2)
   205  	require.Contains(t, dl.List, doomed3)
   206  
   207  	// Try again to clear the deletion flag for an invalid ledger.
   208  	require.NotContains(t, dl.List, invalid)
   209  	require.NoError(t, sp.clearStorageDeletionStatus(invalid))
   210  
   211  	dl, err = sp.getStorageMarkedForDeletion()
   212  	require.NoError(t, err)
   213  	require.Equal(t, 3, len(dl.List))
   214  	require.Contains(t, dl.List, doomed1)
   215  	require.Contains(t, dl.List, doomed2)
   216  	require.Contains(t, dl.List, doomed3)
   217  
   218  	// Invalid should not have been doomed.
   219  	require.NotContains(t, dl.List, invalid)
   220  }
   221  
   222  func TestProcessPendingStorageDeletions(t *testing.T) {
   223  	env := initTestEnv(t)
   224  	defer env.cleanup()
   225  
   226  	sp := env.storeProvider.(*storeProvider)
   227  	require.NotNil(t, sp)
   228  
   229  	doomed1 := "doomed-1"
   230  	doomed2 := "doomed-2"
   231  	doomed3 := "doomed-3"
   232  
   233  	// Set up some pending deletions
   234  	require.NoError(t, sp.markStorageForDelete(doomed1))
   235  	require.NoError(t, sp.markStorageForDelete(doomed2))
   236  	require.NoError(t, sp.markStorageForDelete(doomed3))
   237  
   238  	dl, err := sp.getStorageMarkedForDeletion()
   239  	require.NoError(t, err)
   240  	require.Equal(t, 3, len(dl.List))
   241  	require.Contains(t, dl.List, doomed1)
   242  	require.Contains(t, dl.List, doomed2)
   243  	require.Contains(t, dl.List, doomed3)
   244  
   245  	// process the pending deletions
   246  	err = sp.processPendingStorageDeletions()
   247  	require.NoError(t, err)
   248  
   249  	// storages are no longer pending deletion
   250  	dl, err = sp.getStorageMarkedForDeletion()
   251  	require.NoError(t, err)
   252  	require.Equal(t, 0, len(dl.List))
   253  }
   254  
   255  // Drop a storage without access to a provider.
   256  func TestPackageDropTransientStorage(t *testing.T) {
   257  	env := initTestEnv(t)
   258  	defer env.cleanup()
   259  
   260  	populateTestStore(t, env.store)
   261  	empty, err := env.store.db.IsEmpty()
   262  	require.NoError(t, err)
   263  	require.False(t, empty)
   264  
   265  	// Storage must be closed before dropping.
   266  	ledgerID := env.store.ledgerID
   267  	env.storeProvider.Close()
   268  
   269  	// drop the storage
   270  	require.NoError(t, Drop(env.storedir, ledgerID))
   271  
   272  	sp, err := NewStoreProvider(env.storedir)
   273  	require.NoError(t, err)
   274  	require.NotNil(t, sp)
   275  	defer sp.Close()
   276  
   277  	store, err := sp.OpenStore(ledgerID)
   278  	require.NoError(t, err)
   279  	require.NotNil(t, store)
   280  
   281  	verifyStoreDropped(t, store)
   282  }
   283  
   284  func TestLockFileIsAdjacentToTransientStorageFolder(t *testing.T) {
   285  	env := initTestEnv(t)
   286  	defer env.cleanup()
   287  
   288  	sp := env.storeProvider.(*storeProvider)
   289  	require.NotNil(t, sp)
   290  	require.NotNil(t, sp.fileLock)
   291  	require.True(t, sp.fileLock.IsLocked())
   292  
   293  	// we can't quite get to the path of the lock.  But we can construct a new lock with the correct path.
   294  	lockPath := filepath.Join(env.tempdir, transientStorageLockName) // note: PARENT dir of the transient storage.
   295  	fileLock := leveldbhelper.NewFileLock(lockPath)
   296  
   297  	err := fileLock.Lock()
   298  	require.ErrorContains(t, err, "lock is already acquired on file")
   299  }
   300  
   301  // Make sure that closing the transient storage releases the peer start file lock.
   302  func TestCloseStoreReleasesFileLock(t *testing.T) {
   303  	env := initTestEnv(t)
   304  	defer env.cleanup()
   305  
   306  	require.NotNil(t, env.storeProvider)
   307  	sp := env.storeProvider.(*storeProvider)
   308  
   309  	// lock should be held and locked
   310  	require.NotNil(t, sp.fileLock)
   311  	require.True(t, sp.fileLock.IsLocked())
   312  	require.ErrorContains(t, sp.fileLock.Lock(), "lock is already acquired on file")
   313  
   314  	// after close the lock may be acquired
   315  	sp.Close()
   316  	require.False(t, sp.fileLock.IsLocked())
   317  
   318  	require.NoError(t, sp.fileLock.Lock())
   319  	sp.fileLock.Unlock()
   320  }
   321  
   322  // There may be only one transient storage provider open at a time.
   323  func TestOneAndOnlyOneTransientStorageProviderMayBeOpened(t *testing.T) {
   324  	env := initTestEnv(t)
   325  	defer env.cleanup()
   326  
   327  	// env already has a storage provider opened.  Close it to make the double open case explicit.
   328  	env.storeProvider.Close()
   329  
   330  	// open the first provider
   331  	sp, err := NewStoreProvider(env.storedir)
   332  	require.NoError(t, err)
   333  	require.NotNil(t, sp)
   334  
   335  	// opening a second provider is an error
   336  	_, err = NewStoreProvider(env.storedir)
   337  	require.ErrorContains(t, err, "as another peer node command is executing, wait for that command to complete its execution or terminate it before retrying: lock is already acquired on file")
   338  
   339  	// After closing the provider it may be reopened.
   340  	sp.Close()
   341  
   342  	sp, err = NewStoreProvider(env.storedir)
   343  	require.NoError(t, err)
   344  	require.NotNil(t, sp)
   345  	defer sp.Close()
   346  }
   347  
   348  // Drop() while the peer is open is a lock error.
   349  func TestDropWithRunningPeerIsError(t *testing.T) {
   350  	env := initTestEnv(t)
   351  	defer env.cleanup()
   352  
   353  	// env maintains an opened transient storage provider.
   354  	sp := env.storeProvider.(*storeProvider)
   355  	require.NotNil(t, sp)
   356  	require.True(t, sp.fileLock.IsLocked())
   357  
   358  	// Dropping while a provider is open is an error.
   359  	err := Drop(env.storedir, "some-magical-invalid-ledger-which-does-not-exist")
   360  	require.Error(t, err, "as another peer node command is executing,"+
   361  		" wait for that command to complete its execution or terminate it before retrying")
   362  }
   363  
   364  // Same test as above, but checks with a simulated lock and a closed provider.
   365  func TestPackageDropWithPeerLockIsError(t *testing.T) {
   366  	env := initTestEnv(t)
   367  	defer env.cleanup()
   368  
   369  	env.storeProvider.Close()
   370  
   371  	// lock is at the PARENT directory of the storage folder.
   372  	lockPath := filepath.Join(env.tempdir, transientStorageLockName)
   373  	fileLock := leveldbhelper.NewFileLock(lockPath)
   374  	require.NoError(t, fileLock.Lock())
   375  	defer fileLock.Unlock()
   376  
   377  	require.ErrorContains(t, Drop(env.storedir, "drop-with-lock"),
   378  		"as another peer node command is executing, wait for that command to complete its execution or terminate it before retrying")
   379  }
   380  
   381  // Mimic the crash recovery scenario: pending transient stores are deleted at peer / provider launch
   382  func TestProviderRestartAfterFailedDeletionScrubsPendingDeletions(t *testing.T) {
   383  	env := initTestEnv(t)
   384  	defer env.cleanup()
   385  
   386  	ledgerID := "vanishing-ledger"
   387  	sp := env.storeProvider.(*storeProvider)
   388  
   389  	// write some data to the transient storage
   390  	populateTestStore(t, env.store)
   391  	empty, err := env.store.db.IsEmpty()
   392  	require.NoError(t, err)
   393  	require.False(t, empty)
   394  
   395  	// mark a storage for deletion, but do not actually delete it.
   396  	require.NoError(t, sp.markStorageForDelete(ledgerID))
   397  	doomed, err := sp.getStorageMarkedForDeletion()
   398  	require.NoError(t, err)
   399  	require.Equal(t, 1, len(doomed.List))
   400  	require.Contains(t, doomed.List, ledgerID)
   401  
   402  	// close and re-open the provider.
   403  	env.storeProvider.Close()
   404  
   405  	// re-opening the provider will trigger the processPendingStorageDeletions()
   406  	env.storeProvider, err = NewStoreProvider(env.storedir)
   407  	require.NoError(t, err)
   408  	sp = env.storeProvider.(*storeProvider)
   409  
   410  	// nothing tagged for deletion
   411  	doomed, err = sp.getStorageMarkedForDeletion()
   412  	require.NoError(t, err)
   413  	require.Equal(t, 0, len(doomed.List))
   414  
   415  	// storage should be empty after re-opening
   416  	store, err := sp.OpenStore(ledgerID)
   417  	require.NoError(t, err)
   418  	require.NotNil(t, store)
   419  
   420  	verifyStoreDropped(t, store)
   421  }
   422  
   423  func TestDeleteTransientStorageIsCaseSensitive(t *testing.T) {
   424  	env := initTestEnv(t)
   425  	defer env.cleanup()
   426  
   427  	sp := env.storeProvider.(*storeProvider)
   428  
   429  	store1ID := "case-sensitive-storage"
   430  	store2ID := "CaSe-SeNsItIvE-sToRaGe"
   431  	require.Equal(t, strings.ToLower(store1ID), strings.ToLower(store2ID))
   432  
   433  	store1, err := sp.OpenStore(store1ID)
   434  	require.NoError(t, err)
   435  	require.NotNil(t, store1)
   436  	populateTestStore(t, store1)
   437  
   438  	store2, err := sp.OpenStore(store2ID)
   439  	require.NoError(t, err)
   440  	require.NotNil(t, store2)
   441  	populateTestStore(t, store2)
   442  
   443  	// drop store1.
   444  	require.NoError(t, sp.deleteStore(store1ID))
   445  	verifyStoreDropped(t, store1)
   446  
   447  	// store2 should not have been dropped.
   448  	empty, err := store2.db.IsEmpty()
   449  	require.NoError(t, err)
   450  	require.False(t, empty)
   451  }
   452  
   453  // Write some transactions into the transient storage.
   454  func populateTestStore(t *testing.T, store *Store) {
   455  	samplePvtSimResWithConfig := samplePvtDataWithConfigInfo(t)
   456  	testTxid := "testTxid"
   457  	numEntries := 5
   458  	for i := 0; i < numEntries; i++ {
   459  		require.NoError(t, store.Persist(testTxid, uint64(i), samplePvtSimResWithConfig))
   460  	}
   461  }
   462  
   463  // After dropping a store, verify it is empty
   464  func verifyStoreDropped(t *testing.T, store *Store) {
   465  	height, err := store.GetMinTransientBlkHt()
   466  	require.Error(t, err, "Transient store is empty")
   467  	require.Equal(t, height, uint64(0))
   468  
   469  	isEmpty, err := store.db.IsEmpty()
   470  	require.NoError(t, err)
   471  	require.True(t, isEmpty)
   472  }