github.com/defanghe/fabric@v2.1.1+incompatible/core/cclifecycle/lifecycle_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package cclifecycle_test 8 9 import ( 10 "regexp" 11 "sort" 12 "strconv" 13 "strings" 14 "sync" 15 "testing" 16 "time" 17 18 "github.com/hyperledger/fabric-protos-go/peer" 19 "github.com/hyperledger/fabric/common/chaincode" 20 "github.com/hyperledger/fabric/common/flogging/floggingtest" 21 "github.com/hyperledger/fabric/core/cclifecycle" 22 "github.com/hyperledger/fabric/core/cclifecycle/mocks" 23 "github.com/hyperledger/fabric/core/common/ccprovider" 24 "github.com/hyperledger/fabric/core/common/privdata" 25 "github.com/hyperledger/fabric/core/ledger/cceventmgmt" 26 "github.com/hyperledger/fabric/protoutil" 27 . "github.com/onsi/gomega" 28 "github.com/onsi/gomega/gbytes" 29 "github.com/pkg/errors" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/mock" 32 ) 33 34 func TestNewQuery(t *testing.T) { 35 // This tests that the QueryCreatorFunc can cast the below function to the interface type 36 var q cclifecycle.Query 37 queryCreator := func() (cclifecycle.Query, error) { 38 q := &mocks.Query{} 39 q.On("Done") 40 return q, nil 41 } 42 q, _ = cclifecycle.QueryCreatorFunc(queryCreator).NewQuery() 43 q.Done() 44 } 45 46 func TestHandleMetadataUpdate(t *testing.T) { 47 f := func(channel string, chaincodes chaincode.MetadataSet) { 48 assert.Len(t, chaincodes, 2) 49 assert.Equal(t, "mychannel", channel) 50 } 51 cclifecycle.HandleMetadataUpdateFunc(f).HandleMetadataUpdate("mychannel", chaincode.MetadataSet{{}, {}}) 52 } 53 54 func TestEnumerate(t *testing.T) { 55 f := func() ([]chaincode.InstalledChaincode, error) { 56 return []chaincode.InstalledChaincode{{}, {}}, nil 57 } 58 ccs, err := cclifecycle.EnumerateFunc(f).Enumerate() 59 assert.NoError(t, err) 60 assert.Len(t, ccs, 2) 61 } 62 63 func TestLifecycleInitFailure(t *testing.T) { 64 listCCs := &mocks.Enumerator{} 65 listCCs.On("Enumerate").Return(nil, errors.New("failed accessing DB")) 66 m, err := cclifecycle.NewMetadataManager(listCCs) 67 assert.Nil(t, m) 68 assert.Contains(t, err.Error(), "failed accessing DB") 69 } 70 71 func TestHandleChaincodeDeployGreenPath(t *testing.T) { 72 recorder, restoreLogger := newLogRecorder(t) 73 defer restoreLogger() 74 75 cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 76 Name: "cc1", 77 Version: "1.0", 78 Id: []byte{42}, 79 Policy: []byte{1, 2, 3, 4, 5}, 80 }) 81 82 cc2Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 83 Name: "cc2", 84 Version: "1.0", 85 Id: []byte{42}, 86 }) 87 88 cc3Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 89 Name: "cc3", 90 Version: "1.0", 91 Id: []byte{42}, 92 }) 93 94 query := &mocks.Query{} 95 query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil) 96 query.On("GetState", "lscc", "cc2").Return(cc2Bytes, nil) 97 query.On("GetState", "lscc", "cc3").Return(cc3Bytes, nil).Once() 98 query.On("Done") 99 queryCreator := &mocks.QueryCreator{} 100 queryCreator.On("NewQuery").Return(query, nil) 101 102 enum := &mocks.Enumerator{} 103 enum.On("Enumerate").Return([]chaincode.InstalledChaincode{ 104 { 105 Name: "cc1", 106 Version: "1.0", 107 Hash: []byte{42}, 108 }, 109 { 110 // This chaincode has a different version installed than is instantiated 111 Name: "cc2", 112 Version: "1.1", 113 Hash: []byte{50}, 114 }, 115 { 116 // This chaincode isn't instantiated on the channel (the Id is 50 but in the state its 42), but is installed 117 Name: "cc3", 118 Version: "1.0", 119 Hash: []byte{50}, 120 }, 121 }, nil) 122 123 m, err := cclifecycle.NewMetadataManager(enum) 124 assert.NoError(t, err) 125 126 lsnr := &mocks.MetadataChangeListener{} 127 lsnr.On("HandleMetadataUpdate", mock.Anything, mock.Anything) 128 m.AddListener(lsnr) 129 130 sub, err := m.NewChannelSubscription("mychannel", queryCreator) 131 assert.NoError(t, err) 132 assert.NotNil(t, sub) 133 134 // Ensure that the listener was updated 135 assertLogged(t, recorder, "Listeners for channel mychannel invoked") 136 lsnr.AssertCalled(t, "HandleMetadataUpdate", "mychannel", chaincode.MetadataSet{chaincode.Metadata{ 137 Name: "cc1", 138 Version: "1.0", 139 Id: []byte{42}, 140 Policy: []byte{1, 2, 3, 4, 5}, 141 }}) 142 143 // Signal a deployment of a new chaincode and make sure the chaincode listener is updated with both chaincodes 144 cc3Bytes = protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 145 Name: "cc3", 146 Version: "1.0", 147 Id: []byte{50}, 148 }) 149 query.On("GetState", "lscc", "cc3").Return(cc3Bytes, nil).Once() 150 sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc3", Version: "1.0", Hash: []byte{50}}, nil) 151 sub.ChaincodeDeployDone(true) 152 // Ensure that the listener is called with the new chaincode and the old chaincode metadata 153 assertLogged(t, recorder, "Listeners for channel mychannel invoked") 154 assert.Len(t, lsnr.Calls, 2) 155 sortedMetadata := sortedMetadataSet(lsnr.Calls[1].Arguments.Get(1).(chaincode.MetadataSet)).sort() 156 assert.Equal(t, sortedMetadata, chaincode.MetadataSet{{ 157 Name: "cc1", 158 Version: "1.0", 159 Id: []byte{42}, 160 Policy: []byte{1, 2, 3, 4, 5}, 161 }, { 162 Name: "cc3", 163 Version: "1.0", 164 Id: []byte{50}, 165 }}) 166 167 // Next, update the chaincode metadata of the second chaincode to ensure that the listener is called with the updated 168 // metadata and not with the old metadata. 169 cc3Bytes = protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 170 Name: "cc3", 171 Version: "1.1", 172 Id: []byte{50}, 173 }) 174 query.On("GetState", "lscc", "cc3").Return(cc3Bytes, nil).Once() 175 sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc3", Version: "1.1", Hash: []byte{50}}, nil) 176 sub.ChaincodeDeployDone(true) 177 // Ensure that the listener is called with the new chaincode and the old chaincode metadata 178 assertLogged(t, recorder, "Listeners for channel mychannel invoked") 179 assert.Len(t, lsnr.Calls, 3) 180 sortedMetadata = sortedMetadataSet(lsnr.Calls[2].Arguments.Get(1).(chaincode.MetadataSet)).sort() 181 assert.Equal(t, sortedMetadata, chaincode.MetadataSet{{ 182 Name: "cc1", 183 Version: "1.0", 184 Id: []byte{42}, 185 Policy: []byte{1, 2, 3, 4, 5}, 186 }, { 187 Name: "cc3", 188 Version: "1.1", 189 Id: []byte{50}, 190 }}) 191 } 192 193 func TestHandleChaincodeDeployFailures(t *testing.T) { 194 recorder, restoreLogger := newLogRecorder(t) 195 defer restoreLogger() 196 197 cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 198 Name: "cc1", 199 Version: "1.0", 200 Id: []byte{42}, 201 Policy: []byte{1, 2, 3, 4, 5}, 202 }) 203 204 query := &mocks.Query{} 205 query.On("Done") 206 queryCreator := &mocks.QueryCreator{} 207 enum := &mocks.Enumerator{} 208 enum.On("Enumerate").Return([]chaincode.InstalledChaincode{ 209 { 210 Name: "cc1", 211 Version: "1.0", 212 Hash: []byte{42}, 213 }, 214 }, nil) 215 216 m, err := cclifecycle.NewMetadataManager(enum) 217 assert.NoError(t, err) 218 219 lsnr := &mocks.MetadataChangeListener{} 220 lsnr.On("HandleMetadataUpdate", mock.Anything, mock.Anything) 221 m.AddListener(lsnr) 222 223 // Scenario I: A channel subscription is made but obtaining a new query is not possible. 224 queryCreator.On("NewQuery").Return(nil, errors.New("failed accessing DB")).Once() 225 sub, err := m.NewChannelSubscription("mychannel", queryCreator) 226 assert.Nil(t, sub) 227 assert.Contains(t, err.Error(), "failed accessing DB") 228 lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 0) 229 230 // Scenario II: A channel subscription is made and obtaining a new query succeeds, however - obtaining it once 231 // a deployment notification occurs - fails. 232 queryCreator.On("NewQuery").Return(query, nil).Once() 233 queryCreator.On("NewQuery").Return(nil, errors.New("failed accessing DB")).Once() 234 query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once() 235 sub, err = m.NewChannelSubscription("mychannel", queryCreator) 236 assert.NoError(t, err) 237 assert.NotNil(t, sub) 238 lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 1) 239 sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.0", Hash: []byte{42}}, nil) 240 sub.ChaincodeDeployDone(true) 241 assertLogged(t, recorder, "Failed creating a new query for channel mychannel: failed accessing DB") 242 lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 1) 243 244 // Scenario III: A channel subscription is made and obtaining a new query succeeds both at subscription initialization 245 // and at deployment notification. However - GetState returns an error. 246 // Note: Since we subscribe twice to the same channel, the information isn't loaded from the stateDB because it already had. 247 queryCreator.On("NewQuery").Return(query, nil).Once() 248 query.On("GetState", "lscc", "cc1").Return(nil, errors.New("failed accessing DB")).Once() 249 sub, err = m.NewChannelSubscription("mychannel", queryCreator) 250 assert.NoError(t, err) 251 assert.NotNil(t, sub) 252 lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 2) 253 sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.0", Hash: []byte{42}}, nil) 254 sub.ChaincodeDeployDone(true) 255 assertLogged(t, recorder, "Query for channel mychannel for Name=cc1, Version=1.0, Hash=2a failed with error failed accessing DB") 256 lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 2) 257 258 // Scenario IV: A channel subscription is made successfully, and obtaining a new query succeeds at subscription initialization, 259 // however - the deployment notification indicates the deploy failed. 260 // Thus, the lifecycle change listener should not be called. 261 sub, err = m.NewChannelSubscription("mychannel", queryCreator) 262 lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 3) 263 assert.NoError(t, err) 264 assert.NotNil(t, sub) 265 sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.1", Hash: []byte{42}}, nil) 266 sub.ChaincodeDeployDone(false) 267 lsnr.AssertNumberOfCalls(t, "HandleMetadataUpdate", 3) 268 assertLogged(t, recorder, "Chaincode deploy for updates [Name=cc1, Version=1.1, Hash=2a] failed") 269 } 270 271 func TestMultipleUpdates(t *testing.T) { 272 recorder, restoreLogger := newLogRecorder(t) 273 defer restoreLogger() 274 275 cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 276 Name: "cc1", 277 Version: "1.1", 278 Id: []byte{42}, 279 Policy: []byte{1, 2, 3, 4, 5}, 280 }) 281 cc2Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 282 Name: "cc2", 283 Version: "1.0", 284 Id: []byte{50}, 285 Policy: []byte{1, 2, 3, 4, 5}, 286 }) 287 288 query := &mocks.Query{} 289 query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil) 290 query.On("GetState", "lscc", "cc2").Return(cc2Bytes, nil) 291 query.On("Done") 292 queryCreator := &mocks.QueryCreator{} 293 queryCreator.On("NewQuery").Return(query, nil) 294 295 enum := &mocks.Enumerator{} 296 enum.On("Enumerate").Return([]chaincode.InstalledChaincode{ 297 { 298 Name: "cc1", 299 Version: "1.1", 300 Hash: []byte{42}, 301 }, 302 { 303 Name: "cc2", 304 Version: "1.0", 305 Hash: []byte{50}, 306 }, 307 }, nil) 308 309 m, err := cclifecycle.NewMetadataManager(enum) 310 assert.NoError(t, err) 311 312 var lsnrCalled sync.WaitGroup 313 lsnrCalled.Add(3) 314 lsnr := &mocks.MetadataChangeListener{} 315 lsnr.On("HandleMetadataUpdate", mock.Anything, mock.Anything).Run(func(arguments mock.Arguments) { 316 lsnrCalled.Done() 317 }) 318 m.AddListener(lsnr) 319 320 sub, err := m.NewChannelSubscription("mychannel", queryCreator) 321 assert.NoError(t, err) 322 323 sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc1", Version: "1.1", Hash: []byte{42}}, nil) 324 sub.HandleChaincodeDeploy(&cceventmgmt.ChaincodeDefinition{Name: "cc2", Version: "1.0", Hash: []byte{50}}, nil) 325 sub.ChaincodeDeployDone(true) 326 327 cc1MD := chaincode.Metadata{ 328 Name: "cc1", 329 Version: "1.1", 330 Id: []byte{42}, 331 Policy: []byte{1, 2, 3, 4, 5}, 332 } 333 cc2MD := chaincode.Metadata{ 334 Name: "cc2", 335 Version: "1.0", 336 Id: []byte{50}, 337 Policy: []byte{1, 2, 3, 4, 5}, 338 } 339 metadataSetWithBothChaincodes := chaincode.MetadataSet{cc1MD, cc2MD} 340 341 lsnrCalled.Wait() 342 // We need to sort the metadata passed to the call because map iteration is involved in building the 343 // metadata set. 344 expectedMetadata := sortedMetadataSet(lsnr.Calls[2].Arguments.Get(1).(chaincode.MetadataSet)).sort() 345 assert.Equal(t, metadataSetWithBothChaincodes, expectedMetadata) 346 347 // Wait for all listeners to fire 348 g := NewGomegaWithT(t) 349 g.Eventually(func() []string { 350 return recorder.EntriesMatching("Listeners for channel mychannel invoked") 351 }, time.Second*10).Should(HaveLen(3)) 352 } 353 354 func TestMetadata(t *testing.T) { 355 recorder, restoreLogger := newLogRecorder(t) 356 defer restoreLogger() 357 358 cc1Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 359 Name: "cc1", 360 Version: "1.0", 361 Id: []byte{42}, 362 Policy: []byte{1, 2, 3, 4, 5}, 363 }) 364 365 cc2Bytes := protoutil.MarshalOrPanic(&ccprovider.ChaincodeData{ 366 Name: "cc2", 367 Version: "1.0", 368 Id: []byte{42}, 369 }) 370 371 query := &mocks.Query{} 372 query.On("GetState", "lscc", "cc3").Return(cc1Bytes, nil) 373 query.On("Done") 374 queryCreator := &mocks.QueryCreator{} 375 376 enum := &mocks.Enumerator{} 377 enum.On("Enumerate").Return([]chaincode.InstalledChaincode{ 378 { 379 Name: "cc1", 380 Version: "1.0", 381 Hash: []byte{42}, 382 }, 383 }, nil) 384 385 m, err := cclifecycle.NewMetadataManager(enum) 386 assert.NoError(t, err) 387 388 // Scenario I: No subscription was invoked on the lifecycle 389 md := m.Metadata("mychannel", "cc1") 390 assert.Nil(t, md) 391 assertLogged(t, recorder, "Requested Metadata for non-existent channel mychannel") 392 393 // Scenario II: A subscription was made on the lifecycle, and the metadata for the chaincode exists 394 // because the chaincode is installed prior to the subscription, hence it was loaded during the subscription. 395 query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once() 396 queryCreator.On("NewQuery").Return(query, nil).Once() 397 sub, err := m.NewChannelSubscription("mychannel", queryCreator) 398 defer sub.ChaincodeDeployDone(true) 399 assert.NoError(t, err) 400 assert.NotNil(t, sub) 401 md = m.Metadata("mychannel", "cc1") 402 assert.Equal(t, &chaincode.Metadata{ 403 Name: "cc1", 404 Version: "1.0", 405 Id: []byte{42}, 406 Policy: []byte{1, 2, 3, 4, 5}, 407 }, md) 408 assertLogged(t, recorder, "Returning metadata for channel mychannel , chaincode cc1") 409 410 // Scenario III: A metadata retrieval is made and the chaincode is not in memory yet, 411 // and when the query is attempted to be made - it fails. 412 queryCreator.On("NewQuery").Return(nil, errors.New("failed obtaining query executor")).Once() 413 md = m.Metadata("mychannel", "cc2") 414 assert.Nil(t, md) 415 assertLogged(t, recorder, "Failed obtaining new query for channel mychannel : failed obtaining query executor") 416 417 // Scenario IV: A metadata retrieval is made and the chaincode is not in memory yet, 418 // and when the query is attempted to be made - it succeeds, but GetState fails. 419 queryCreator.On("NewQuery").Return(query, nil).Once() 420 query.On("GetState", "lscc", "cc2").Return(nil, errors.New("GetState failed")).Once() 421 md = m.Metadata("mychannel", "cc2") 422 assert.Nil(t, md) 423 assertLogged(t, recorder, "Failed querying LSCC for channel mychannel : GetState failed") 424 425 // Scenario V: A metadata retrieval is made and the chaincode is not in memory yet, 426 // and both the query and the GetState succeed, however - GetState returns nil 427 queryCreator.On("NewQuery").Return(query, nil).Once() 428 query.On("GetState", "lscc", "cc2").Return(nil, nil).Once() 429 md = m.Metadata("mychannel", "cc2") 430 assert.Nil(t, md) 431 assertLogged(t, recorder, "Chaincode cc2 isn't defined in channel mychannel") 432 433 // Scenario VI: A metadata retrieval is made and the chaincode is not in memory yet, 434 // and both the query and the GetState succeed, however - GetState returns a valid metadata 435 queryCreator.On("NewQuery").Return(query, nil).Once() 436 query.On("GetState", "lscc", "cc2").Return(cc2Bytes, nil).Once() 437 md = m.Metadata("mychannel", "cc2") 438 assert.Equal(t, &chaincode.Metadata{ 439 Name: "cc2", 440 Version: "1.0", 441 Id: []byte{42}, 442 }, md) 443 444 // Scenario VII: A metadata retrieval is made and the chaincode is in the memory, 445 // but a collection is also specified, thus - the retrieval should bypass the memory cache 446 // and go straight into the stateDB. 447 queryCreator.On("NewQuery").Return(query, nil).Once() 448 query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once() 449 query.On("GetState", "lscc", privdata.BuildCollectionKVSKey("cc1")).Return(protoutil.MarshalOrPanic(&peer.CollectionConfigPackage{}), nil).Once() 450 md = m.Metadata("mychannel", "cc1", "col1") 451 assert.Equal(t, &chaincode.Metadata{ 452 Name: "cc1", 453 Version: "1.0", 454 Id: []byte{42}, 455 Policy: []byte{1, 2, 3, 4, 5}, 456 CollectionsConfig: &peer.CollectionConfigPackage{}, 457 }, md) 458 assertLogged(t, recorder, "Retrieved collection config for cc1 from cc1~collection") 459 460 // Scenario VIII: A metadata retrieval is made and the chaincode is in the memory, 461 // but a collection is also specified, thus - the retrieval should bypass the memory cache 462 // and go straight into the stateDB. However - the retrieval fails 463 queryCreator.On("NewQuery").Return(query, nil).Once() 464 query.On("GetState", "lscc", "cc1").Return(cc1Bytes, nil).Once() 465 query.On("GetState", "lscc", privdata.BuildCollectionKVSKey("cc1")).Return(nil, errors.New("foo")).Once() 466 md = m.Metadata("mychannel", "cc1", "col1") 467 assert.Nil(t, md) 468 assertLogged(t, recorder, "Failed querying lscc namespace for cc1~collection: foo") 469 } 470 471 func newLogRecorder(t *testing.T) (*floggingtest.Recorder, func()) { 472 oldLogger := cclifecycle.Logger 473 474 logger, recorder := floggingtest.NewTestLogger(t) 475 cclifecycle.Logger = logger 476 477 return recorder, func() { cclifecycle.Logger = oldLogger } 478 } 479 480 func assertLogged(t *testing.T, r *floggingtest.Recorder, msg string) { 481 gt := NewGomegaWithT(t) 482 gt.Eventually(r).Should(gbytes.Say(regexp.QuoteMeta(msg))) 483 } 484 485 type sortedMetadataSet chaincode.MetadataSet 486 487 func (mds sortedMetadataSet) Len() int { 488 return len(mds) 489 } 490 491 func (mds sortedMetadataSet) Less(i, j int) bool { 492 eI := strings.Replace(mds[i].Name, "cc", "", -1) 493 eJ := strings.Replace(mds[j].Name, "cc", "", -1) 494 nI, _ := strconv.ParseInt(eI, 10, 32) 495 nJ, _ := strconv.ParseInt(eJ, 10, 32) 496 return nI < nJ 497 } 498 499 func (mds sortedMetadataSet) Swap(i, j int) { 500 mds[i], mds[j] = mds[j], mds[i] 501 } 502 503 func (mds sortedMetadataSet) sort() chaincode.MetadataSet { 504 sort.Sort(mds) 505 return chaincode.MetadataSet(mds) 506 }