-
Notifications
You must be signed in to change notification settings - Fork 8.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FAB-11334] Adds a new 'peer node unjoin' feature #2732
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,15 +11,56 @@ import ( | |
"github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset" | ||
"github.com/hyperledger/fabric-protos-go/peer" | ||
"github.com/hyperledger/fabric/common/ledger/blkstorage" | ||
"github.com/hyperledger/fabric/common/ledger/util/leveldbhelper" | ||
"github.com/hyperledger/fabric/common/metrics/disabled" | ||
"github.com/hyperledger/fabric/core/ledger" | ||
"github.com/hyperledger/fabric/core/ledger/confighistory" | ||
"github.com/hyperledger/fabric/core/ledger/kvledger/bookkeeping" | ||
"github.com/hyperledger/fabric/core/ledger/kvledger/history" | ||
"github.com/hyperledger/fabric/core/ledger/kvledger/msgs" | ||
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/privacyenabledstate" | ||
"github.com/hyperledger/fabric/core/ledger/pvtdatastorage" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// UnjoinChannel removes the data for a ledger and sets the status to UNDER_DELETION. This function is to be | ||
// invoked while the peer is shut down. | ||
func UnjoinChannel(config *ledger.Config, ledgerID string) error { | ||
// Ensure the routine is invoked while the peer is down. | ||
fileLock := leveldbhelper.NewFileLock(fileLockPath(config.RootFSPath)) | ||
if err := fileLock.Lock(); err != nil { | ||
return errors.WithMessage(err, "as another peer node command is executing,"+ | ||
" wait for that command to complete its execution or terminate it before retrying") | ||
} | ||
defer fileLock.Unlock() | ||
|
||
idStore, err := openIDStore(LedgerProviderPath(config.RootFSPath)) | ||
if err != nil { | ||
return errors.WithMessagef(err, "unjoin channel [%s]", ledgerID) | ||
} | ||
defer idStore.db.Close() | ||
|
||
// Set the ledger to a pending deletion status. If the contents of the ledger are not | ||
// fully removed (e.g. a crash during deletion, i/o error, etc.), the deletion may be | ||
// resumed at the next peer start. | ||
if err := idStore.updateLedgerStatus(ledgerID, msgs.Status_UNDER_DELETION); err != nil { | ||
return errors.WithMessagef(err, "unjoin channel [%s]", ledgerID) | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// remove the ledger data | ||
if err := removeLedgerData(config, ledgerID); err != nil { | ||
return errors.WithMessagef(err, "deleting ledger [%s]", ledgerID) | ||
} | ||
|
||
// Delete the ledger from the ID storage after the contents have been purged. | ||
if err := idStore.deleteLedgerID(ledgerID); err != nil { | ||
return errors.WithMessagef(err, "deleting ledger [%s]", ledgerID) | ||
} | ||
|
||
logger.Infow("channel has been successfully unjoined", "ledgerID", ledgerID) | ||
return nil | ||
Comment on lines
+60
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unlike error strings, log statements would typically start with a full sentence. |
||
} | ||
|
||
// removeLedgerData removes the data for a given ledger. This function should be invoked when the peer is not running and the caller should hold the file lock for the KVLedgerProvider | ||
func removeLedgerData(config *ledger.Config, ledgerID string) error { | ||
blkStoreProvider, err := blkstorage.NewProvider( | ||
|
@@ -39,6 +80,7 @@ func removeLedgerData(config *ledger.Config, ledgerID string) error { | |
if err != nil { | ||
return err | ||
} | ||
defer bookkeepingProvider.Close() | ||
|
||
dbProvider, err := privacyenabledstate.NewDBProvider( | ||
bookkeepingProvider, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
|
||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package kvledger | ||
|
||
import ( | ||
"testing" | ||
|
||
configtxtest "github.com/hyperledger/fabric/common/configtx/test" | ||
"github.com/hyperledger/fabric/core/ledger/mock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestUnjoinChannel(t *testing.T) { | ||
conf, cleanup := testConfig(t) | ||
conf.HistoryDBConfig.Enabled = true | ||
defer cleanup() | ||
|
||
ledgerID := "ledger_unjoin" | ||
|
||
provider := testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{}) | ||
activeLedgerIDs, err := provider.List() | ||
require.NoError(t, err) | ||
require.Len(t, activeLedgerIDs, 0) | ||
|
||
genesisBlock, err := configtxtest.MakeGenesisBlock(ledgerID) | ||
require.NoError(t, err) | ||
_, err = provider.CreateFromGenesisBlock(genesisBlock) | ||
require.NoError(t, err) | ||
|
||
activeLedgerIDs, err = provider.List() | ||
require.NoError(t, err) | ||
require.Len(t, activeLedgerIDs, 1) | ||
require.Contains(t, activeLedgerIDs, ledgerID) | ||
provider.Close() | ||
|
||
// Unjoin the channel from the peer | ||
err = UnjoinChannel(conf, ledgerID) | ||
require.NoError(t, err) | ||
|
||
// channel should no longer be present in the channel list | ||
provider = testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{}) | ||
defer provider.Close() | ||
|
||
activeLedgerIDs, err = provider.List() | ||
require.NoError(t, err) | ||
require.Len(t, activeLedgerIDs, 0) | ||
require.NotContains(t, activeLedgerIDs, ledgerID) | ||
|
||
// check underlying databases have been removed | ||
verifyLedgerDoesNotExist(t, provider, ledgerID) | ||
} | ||
|
||
// Unjoining an unjoined channel is an error. | ||
func TestUnjoinUnjoinedChannelErrors(t *testing.T) { | ||
conf, cleanup := testConfig(t) | ||
conf.HistoryDBConfig.Enabled = false | ||
defer cleanup() | ||
|
||
ledgerID := "ledger_unjoin_unjoined" | ||
|
||
provider := testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{}) | ||
genesisBlock, err := configtxtest.MakeGenesisBlock(ledgerID) | ||
require.NoError(t, err) | ||
_, err = provider.CreateFromGenesisBlock(genesisBlock) | ||
require.NoError(t, err) | ||
provider.Close() | ||
|
||
// unjoin the channel | ||
require.NoError(t, UnjoinChannel(conf, ledgerID)) | ||
|
||
provider = testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{}) | ||
activeLedgerIDs, err := provider.List() | ||
require.NoError(t, err) | ||
require.Len(t, activeLedgerIDs, 0) | ||
provider.Close() | ||
|
||
// unjoining an unjoined channel is an error. | ||
require.EqualError(t, UnjoinChannel(conf, ledgerID), | ||
"unjoin channel [ledger_unjoin_unjoined]: cannot update ledger status, ledger [ledger_unjoin_unjoined] does not exist") | ||
} | ||
|
||
func TestUnjoinWithRunningPeerErrors(t *testing.T) { | ||
conf, cleanup := testConfig(t) | ||
conf.HistoryDBConfig.Enabled = false | ||
defer cleanup() | ||
|
||
provider := testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{}) | ||
defer provider.Close() | ||
|
||
ledgerID := constructTestLedgerID(1) | ||
genesisBlock, _ := configtxtest.MakeGenesisBlock(ledgerID) | ||
_, err := provider.CreateFromGenesisBlock(genesisBlock) | ||
require.NoError(t, err) | ||
|
||
// Fail when provider is open (e.g. peer is running) | ||
require.ErrorContains(t, UnjoinChannel(conf, ledgerID), | ||
"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") | ||
} | ||
|
||
func TestUnjoinWithMissingChannelErrors(t *testing.T) { | ||
conf, cleanup := testConfig(t) | ||
conf.HistoryDBConfig.Enabled = false | ||
defer cleanup() | ||
|
||
// fail if channel does not exist | ||
require.EqualError(t, UnjoinChannel(conf, "__invalid_channel"), | ||
"unjoin channel [__invalid_channel]: cannot update ledger status, ledger [__invalid_channel] does not exist") | ||
} | ||
|
||
func TestUnjoinChannelWithInvalidMetadataErrors(t *testing.T) { | ||
conf, cleanup := testConfig(t) | ||
conf.HistoryDBConfig.Enabled = false | ||
defer cleanup() | ||
|
||
provider := testutilNewProvider(conf, t, &mock.DeployedChaincodeInfoProvider{}) | ||
|
||
ledgerID := constructTestLedgerID(99) | ||
genesisBlock, _ := configtxtest.MakeGenesisBlock(ledgerID) | ||
_, err := provider.CreateFromGenesisBlock(genesisBlock) | ||
require.NoError(t, err) | ||
|
||
// purposely set an invalid metatdata | ||
require.NoError(t, provider.idStore.db.Put(metadataKey(ledgerID), []byte("invalid"), true)) | ||
provider.Close() | ||
|
||
// fail if metadata can not be unmarshaled | ||
require.EqualError(t, UnjoinChannel(conf, ledgerID), | ||
"unjoin channel [ledger_000099]: error unmarshalling ledger metadata: unexpected EOF") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale function comment as this function now clears the UNDER_DELETION status.