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 }