go_study/fabric-main/core/cclifecycle/lifecycle_test.go

507 lines
18 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package cclifecycle_test
import (
"regexp"
"sort"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/common/chaincode"
"github.com/hyperledger/fabric/common/flogging/floggingtest"
"github.com/hyperledger/fabric/core/cclifecycle"
"github.com/hyperledger/fabric/core/cclifecycle/mocks"
"github.com/hyperledger/fabric/core/common/ccprovider"
"github.com/hyperledger/fabric/core/common/privdata"
"github.com/hyperledger/fabric/core/ledger/cceventmgmt"
"github.com/hyperledger/fabric/protoutil"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/pkg/errors"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestNewQuery(t *testing.T) {
// This tests that the QueryCreatorFunc can cast the below function to the interface type
var q cclifecycle.Query
queryCreator := func() (cclifecycle.Query, error) {
q := &mocks.Query{}
q.On("Done")
return q, nil
}
q, _ = cclifecycle.QueryCreatorFunc(queryCreator).NewQuery()
q.Done()
}
func TestHandleMetadataUpdate(t *testing.T) {
f := func(channel string, chaincodes chaincode.MetadataSet) {
require.Len(t, chaincodes, 2)
require.Equal(t, "mychannel", channel)
}
cclifecycle.HandleMetadataUpdateFunc(f).HandleMetadataUpdate("mychannel", chaincode.MetadataSet{{}, {}})
}
func TestEnumerate(t *testing.T) {
f := func() ([]chaincode.InstalledChaincode, error) {
return []chaincode.InstalledChaincode{{}, {}}, nil
}
ccs, err := cclifecycle.EnumerateFunc(f).Enumerate()
require.NoError(t, err)
require.Len(t, ccs, 2)
}
func TestLifecycleInitFailure(t *testing.T) {
listCCs := &mocks.Enumerator{}
listCCs.On("Enumerate").Return(nil, errors.New("failed accessing DB"))
m, err := cclifecycle.NewMetadataManager(listCCs)
require.Nil(t, m)
require.Contains(t, err.Error(), "failed accessing DB")
}
func TestHandleChaincodeDeployGreenPath(t *testing.T) {
recorder, restoreLogger := newLogRecorder(t)
defer restoreLogger()
cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
})
cc2Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc2",
Version: "1.0",
Id: []byte{42},
})
cc3Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc3",
Version: "1.0",
Id: []byte{42},
})
query := &mocks.Query{}
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil)
query.On("GetState", "lscc", "cc2").Return(cc2Bytes, nil)
query.On("GetState", "lscc", "cc3").Return(cc3Bytes, nil).Once()
query.On("Done")
queryCreator := &mocks.QueryCreator{}
queryCreator.On("NewQuery").Return(query, nil)
enum := &mocks.Enumerator{}
enum.On("Enumerate").Return([]chaincode.InstalledChaincode{
{
Name: "cc1",
Version: "1.0",
Hash: []byte{42},
},
{
// This chaincode has a different version installed than is instantiated
Name: "cc2",
Version: "1.1",
Hash: []byte{50},
},
{
// This chaincode isn't instantiated on the channel (the Id is 50 but in the state its 42), but is installed
Name: "cc3",
Version: "1.0",
Hash: []byte{50},
},
}, nil)
m, err := cclifecycle.NewMetadataManager(enum)
require.NoError(t, err)
lsnr := &mocks.MetadataChangeListener{}
lsnr.On("HandleMetadataUpdate", mock.Anything, mock.Anything)
m.AddListener(lsnr)
sub, err := m.NewChannelSubscription("mychannel", queryCreator)
require.NoError(t, err)
require.NotNil(t, sub)
// Ensure that the listener was updated
assertLogged(t, recorder, "Listeners for channel mychannel invoked")
lsnr.AssertCalled(t, "HandleMetadataUpdate", "mychannel", chaincode.MetadataSet{chaincode.Metadata{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
}})
// Signal a deployment of a new chaincode and make sure the chaincode listener is updated with both chaincodes
cc3Bytes = protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc3",
Version: "1.0",
Id: []byte{50},
})
query.On("GetState", "lscc", "cc3").Return(cc3Bytes, nil).Once()
sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc3", Version: "1.0", Hash: []byte{50}}, nil)
sub.ChaincodeDeployDone(true)
// Ensure that the listener is called with the new chaincode and the old chaincode metadata
assertLogged(t, recorder, "Listeners for channel mychannel invoked")
require.Len(t, lsnr.Calls, 2)
sortedMetadata := sortedMetadataSet(lsnr.Calls[1].Arguments.Get(1).(chaincode.MetadataSet)).sort()
require.Equal(t, sortedMetadata, chaincode.MetadataSet{{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
}, {
Name: "cc3",
Version: "1.0",
Id: []byte{50},
}})
// Next, update the chaincode metadata of the second chaincode to ensure that the listener is called with the updated
// metadata and not with the old metadata.
cc3Bytes = protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc3",
Version: "1.1",
Id: []byte{50},
})
query.On("GetState", "lscc", "cc3").Return(cc3Bytes, nil).Once()
sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc3", Version: "1.1", Hash: []byte{50}}, nil)
sub.ChaincodeDeployDone(true)
// Ensure that the listener is called with the new chaincode and the old chaincode metadata
assertLogged(t, recorder, "Listeners for channel mychannel invoked")
require.Len(t, lsnr.Calls, 3)
sortedMetadata = sortedMetadataSet(lsnr.Calls[2].Arguments.Get(1).(chaincode.MetadataSet)).sort()
require.Equal(t, sortedMetadata, chaincode.MetadataSet{{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
}, {
Name: "cc3",
Version: "1.1",
Id: []byte{50},
}})
}
func TestHandleChaincodeDeployFailures(t *testing.T) {
recorder, restoreLogger := newLogRecorder(t)
defer restoreLogger()
cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
})
query := &mocks.Query{}
query.On("Done")
queryCreator := &mocks.QueryCreator{}
enum := &mocks.Enumerator{}
enum.On("Enumerate").Return([]chaincode.InstalledChaincode{
{
Name: "cc1",
Version: "1.0",
Hash: []byte{42},
},
}, nil)
m, err := cclifecycle.NewMetadataManager(enum)
require.NoError(t, err)
lsnr := &mocks.MetadataChangeListener{}
lsnr.On("HandleMetadataUpdate", mock.Anything, mock.Anything)
m.AddListener(lsnr)
// Scenario I: A channel subscription is made but obtaining a new query is not possible.
queryCreator.On("NewQuery").Return(nil, errors.New("failed accessing DB")).Once()
sub, err := m.NewChannelSubscription("mychannel", queryCreator)
require.Nil(t, sub)
require.Contains(t, err.Error(), "failed accessing DB")
lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 0)
// Scenario II: A channel subscription is made and obtaining a new query succeeds, however - obtaining it once
// a deployment notification occurs - fails.
queryCreator.On("NewQuery").Return(query, nil).Once()
queryCreator.On("NewQuery").Return(nil, errors.New("failed accessing DB")).Once()
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once()
sub, err = m.NewChannelSubscription("mychannel", queryCreator)
require.NoError(t, err)
require.NotNil(t, sub)
lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 1)
sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.0", Hash: []byte{42}}, nil)
sub.ChaincodeDeployDone(true)
assertLogged(t, recorder, "Failed creating a new query for channel mychannel: failed accessing DB")
lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 1)
// Scenario III: A channel subscription is made and obtaining a new query succeeds both at subscription initialization
// and at deployment notification. However - GetState returns an error.
// Note: Since we subscribe twice to the same channel, the information isn't loaded from the stateDB because it already had.
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc1").Return(nil, errors.New("failed accessing DB")).Once()
sub, err = m.NewChannelSubscription("mychannel", queryCreator)
require.NoError(t, err)
require.NotNil(t, sub)
lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 2)
sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.0", Hash: []byte{42}}, nil)
sub.ChaincodeDeployDone(true)
assertLogged(t, recorder, "Query for channel mychannel for Name=cc1, Version=1.0, Hash=2a failed with error failed accessing DB")
lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 2)
// Scenario IV: A channel subscription is made successfully, and obtaining a new query succeeds at subscription initialization,
// however - the deployment notification indicates the deploy failed.
// Thus, the lifecycle change listener should not be called.
sub, err = m.NewChannelSubscription("mychannel", queryCreator)
lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 3)
require.NoError(t, err)
require.NotNil(t, sub)
sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.1", Hash: []byte{42}}, nil)
sub.ChaincodeDeployDone(false)
lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 3)
assertLogged(t, recorder, "Chaincode deploy for updates [Name=cc1, Version=1.1, Hash=2a] failed")
}
func TestMultipleUpdates(t *testing.T) {
recorder, restoreLogger := newLogRecorder(t)
defer restoreLogger()
cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc1",
Version: "1.1",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
})
cc2Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc2",
Version: "1.0",
Id: []byte{50},
Policy: []byte{1, 2, 3, 4, 5},
})
query := &mocks.Query{}
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil)
query.On("GetState", "lscc", "cc2").Return(cc2Bytes, nil)
query.On("Done")
queryCreator := &mocks.QueryCreator{}
queryCreator.On("NewQuery").Return(query, nil)
enum := &mocks.Enumerator{}
enum.On("Enumerate").Return([]chaincode.InstalledChaincode{
{
Name: "cc1",
Version: "1.1",
Hash: []byte{42},
},
{
Name: "cc2",
Version: "1.0",
Hash: []byte{50},
},
}, nil)
m, err := cclifecycle.NewMetadataManager(enum)
require.NoError(t, err)
var lsnrCalled sync.WaitGroup
lsnrCalled.Add(3)
lsnr := &mocks.MetadataChangeListener{}
lsnr.On("HandleMetadataUpdate", mock.Anything, mock.Anything).Run(func(arguments mock.Arguments) {
lsnrCalled.Done()
})
m.AddListener(lsnr)
sub, err := m.NewChannelSubscription("mychannel", queryCreator)
require.NoError(t, err)
sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.1", Hash: []byte{42}}, nil)
sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc2", Version: "1.0", Hash: []byte{50}}, nil)
sub.ChaincodeDeployDone(true)
cc1MD := chaincode.Metadata{
Name: "cc1",
Version: "1.1",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
}
cc2MD := chaincode.Metadata{
Name: "cc2",
Version: "1.0",
Id: []byte{50},
Policy: []byte{1, 2, 3, 4, 5},
}
metadataSetWithBothChaincodes := chaincode.MetadataSet{cc1MD, cc2MD}
lsnrCalled.Wait()
// We need to sort the metadata passed to the call because map iteration is involved in building the
// metadata set.
expectedMetadata := sortedMetadataSet(lsnr.Calls[2].Arguments.Get(1).(chaincode.MetadataSet)).sort()
require.Equal(t, metadataSetWithBothChaincodes, expectedMetadata)
// Wait for all listeners to fire
g := NewGomegaWithT(t)
g.Eventually(func() []string {
return recorder.EntriesMatching("Listeners for channel mychannel invoked")
}, time.Second*10).Should(HaveLen(3))
}
func TestMetadata(t *testing.T) {
recorder, restoreLogger := newLogRecorder(t)
defer restoreLogger()
cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
})
cc2Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{
Name: "cc2",
Version: "1.0",
Id: []byte{42},
})
query := &mocks.Query{}
query.On("GetState", "lscc", "cc3").Return(cc1Bytes, nil)
query.On("Done")
queryCreator := &mocks.QueryCreator{}
enum := &mocks.Enumerator{}
enum.On("Enumerate").Return([]chaincode.InstalledChaincode{
{
Name: "cc1",
Version: "1.0",
Hash: []byte{42},
},
}, nil)
m, err := cclifecycle.NewMetadataManager(enum)
require.NoError(t, err)
// Scenario I: No subscription was invoked on the lifecycle
md := m.Metadata("mychannel", "cc1")
require.Nil(t, md)
assertLogged(t, recorder, "Requested Metadata for non-existent channel mychannel")
// Scenario II: A subscription was made on the lifecycle, and the metadata for the chaincode exists
// because the chaincode is installed prior to the subscription, hence it was loaded during the subscription.
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once()
queryCreator.On("NewQuery").Return(query, nil).Once()
sub, err := m.NewChannelSubscription("mychannel", queryCreator)
defer sub.ChaincodeDeployDone(true)
require.NoError(t, err)
require.NotNil(t, sub)
md = m.Metadata("mychannel", "cc1")
require.Equal(t, &chaincode.Metadata{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
}, md)
assertLogged(t, recorder, "Returning metadata for channel mychannel , chaincode cc1")
// Scenario III: A metadata retrieval is made and the chaincode is not in memory yet,
// and when the query is attempted to be made - it fails.
queryCreator.On("NewQuery").Return(nil, errors.New("failed obtaining query executor")).Once()
md = m.Metadata("mychannel", "cc2")
require.Nil(t, md)
assertLogged(t, recorder, "Failed obtaining new query for channel mychannel : failed obtaining query executor")
// Scenario IV: A metadata retrieval is made and the chaincode is not in memory yet,
// and when the query is attempted to be made - it succeeds, but GetState fails.
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc2").Return(nil, errors.New("GetState failed")).Once()
md = m.Metadata("mychannel", "cc2")
require.Nil(t, md)
assertLogged(t, recorder, "Failed querying LSCC for channel mychannel : GetState failed")
// Scenario V: A metadata retrieval is made and the chaincode is not in memory yet,
// and both the query and the GetState succeed, however - GetState returns nil
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc2").Return(nil, nil).Once()
md = m.Metadata("mychannel", "cc2")
require.Nil(t, md)
assertLogged(t, recorder, "Chaincode cc2 isn't defined in channel mychannel")
// Scenario VI: A metadata retrieval is made and the chaincode is not in memory yet,
// and both the query and the GetState succeed, however - GetState returns a valid metadata
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc2").Return(cc2Bytes, nil).Once()
md = m.Metadata("mychannel", "cc2")
require.Equal(t, &chaincode.Metadata{
Name: "cc2",
Version: "1.0",
Id: []byte{42},
}, md)
// Scenario VII: A metadata retrieval is made and the chaincode is in the memory,
// but a collection is also specified, thus - the retrieval should bypass the memory cache
// and go straight into the stateDB.
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once()
query.On("GetState", "lscc", privdata.BuildCollectionKVSKey("cc1")).Return(protoutil.MarshalOrPanic(&peer.CollectionConfigPackage{}), nil).Once()
md = m.Metadata("mychannel", "cc1", "col1")
require.Equal(t, &chaincode.Metadata{
Name: "cc1",
Version: "1.0",
Id: []byte{42},
Policy: []byte{1, 2, 3, 4, 5},
CollectionsConfig: &peer.CollectionConfigPackage{},
}, md)
assertLogged(t, recorder, "Retrieved collection config for cc1 from cc1~collection")
// Scenario VIII: A metadata retrieval is made and the chaincode is in the memory,
// but a collection is also specified, thus - the retrieval should bypass the memory cache
// and go straight into the stateDB. However - the retrieval fails
queryCreator.On("NewQuery").Return(query, nil).Once()
query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once()
query.On("GetState", "lscc", privdata.BuildCollectionKVSKey("cc1")).Return(nil, errors.New("foo")).Once()
md = m.Metadata("mychannel", "cc1", "col1")
require.Nil(t, md)
assertLogged(t, recorder, "Failed querying lscc namespace for cc1~collection: foo")
}
func newLogRecorder(t *testing.T) (*floggingtest.Recorder, func()) {
oldLogger := cclifecycle.Logger
logger, recorder := floggingtest.NewTestLogger(t)
cclifecycle.Logger = logger
return recorder, func() { cclifecycle.Logger = oldLogger }
}
func assertLogged(t *testing.T, r *floggingtest.Recorder, msg string) {
gt := NewGomegaWithT(t)
gt.Eventually(r).Should(gbytes.Say(regexp.QuoteMeta(msg)))
}
type sortedMetadataSet chaincode.MetadataSet
func (mds sortedMetadataSet) Len() int {
return len(mds)
}
func (mds sortedMetadataSet) Less(i, j int) bool {
eI := strings.Replace(mds[i].Name, "cc", "", -1)
eJ := strings.Replace(mds[j].Name, "cc", "", -1)
nI, _ := strconv.ParseInt(eI, 10, 32)
nJ, _ := strconv.ParseInt(eJ, 10, 32)
return nI < nJ
}
func (mds sortedMetadataSet) Swap(i, j int) {
mds[i], mds[j] = mds[j], mds[i]
}
func (mds sortedMetadataSet) sort() chaincode.MetadataSet {
sort.Sort(mds)
return chaincode.MetadataSet(mds)
}