From 8ff17e1c8cfd39786c4fb944bcd1052a056e750d Mon Sep 17 00:00:00 2001 From: Jason Yellick Date: Fri, 8 Nov 2019 00:33:08 -0500 Subject: [PATCH] FAB-17001 Delete chaincode package on build fail The new lifecycle has no existing contract to honor with respect to installing chaincodes which are not buildable at install time, so it is odd that we adhere to that contract from the previous LSCC case. This CR changes the new lifecycle to delete the chaincode from the filesystem if the build is unsuccessful, under the assumption that future attempts to build this chaincode will similarly fail, and the package will simply be taking up space on the filesystem. Signed-off-by: Jason Yellick Change-Id: I7d12a767dbaf6c9d0b497139c4a99793b4d29a16 --- core/chaincode/lifecycle/lifecycle.go | 10 ++- core/chaincode/lifecycle/lifecycle_test.go | 6 ++ .../lifecycle/mock/chaincode_store.go | 73 +++++++++++++++++++ core/chaincode/persistence/persistence.go | 8 ++ .../chaincode/persistence/persistence_test.go | 34 +++++++++ 5 files changed, 127 insertions(+), 4 deletions(-) diff --git a/core/chaincode/lifecycle/lifecycle.go b/core/chaincode/lifecycle/lifecycle.go index db10f3fa382..80fd8b089ff 100644 --- a/core/chaincode/lifecycle/lifecycle.go +++ b/core/chaincode/lifecycle/lifecycle.go @@ -184,6 +184,7 @@ type ChaincodeStore interface { Save(label string, ccInstallPkg []byte) (string, error) ListInstalledChaincodes() ([]chaincode.InstalledChaincode, error) Load(packageID string) (ccInstallPkg []byte, err error) + Delete(packageID string) error } type PackageParser interface { @@ -495,10 +496,6 @@ func (ef *ExternalFunctions) InstallChaincode(chaincodeInstallPackage []byte) (* return nil, errors.WithMessage(err, "could not save cc install package") } - if ef.InstallListener != nil { - ef.InstallListener.HandleChaincodeInstalled(pkg.Metadata, packageID) - } - buildStatus, ok := ef.BuildRegistry.BuildStatus(packageID) if !ok { err := ef.ChaincodeBuilder.Build(packageID) @@ -506,9 +503,14 @@ func (ef *ExternalFunctions) InstallChaincode(chaincodeInstallPackage []byte) (* } <-buildStatus.Done() if err := buildStatus.Err(); err != nil { + ef.Resources.ChaincodeStore.Delete(packageID) return nil, errors.WithMessage(err, "could not build chaincode") } + if ef.InstallListener != nil { + ef.InstallListener.HandleChaincodeInstalled(pkg.Metadata, packageID) + } + logger.Infof("successfully installed chaincode with package ID '%s'", packageID) return &chaincode.InstalledChaincode{ diff --git a/core/chaincode/lifecycle/lifecycle_test.go b/core/chaincode/lifecycle/lifecycle_test.go index 839cef870b9..f66a97b3209 100644 --- a/core/chaincode/lifecycle/lifecycle_test.go +++ b/core/chaincode/lifecycle/lifecycle_test.go @@ -289,6 +289,12 @@ var _ = Describe("ExternalFunctions", func() { _, err := ef.InstallChaincode([]byte("cc-package")) Expect(err).To(MatchError("could not build chaincode: fake-build-error")) }) + + It("deletes the chaincode from disk", func() { + ef.InstallChaincode([]byte("cc-package")) + Expect(fakeCCStore.DeleteCallCount()).To(Equal(1)) + Expect(fakeCCStore.DeleteArgsForCall(0)).To(Equal("fake-hash")) + }) }) When("the chaincode is already being built", func() { diff --git a/core/chaincode/lifecycle/mock/chaincode_store.go b/core/chaincode/lifecycle/mock/chaincode_store.go index 29c4425d154..7e957f03818 100644 --- a/core/chaincode/lifecycle/mock/chaincode_store.go +++ b/core/chaincode/lifecycle/mock/chaincode_store.go @@ -8,6 +8,17 @@ import ( ) type ChaincodeStore struct { + DeleteStub func(string) error + deleteMutex sync.RWMutex + deleteArgsForCall []struct { + arg1 string + } + deleteReturns struct { + result1 error + } + deleteReturnsOnCall map[int]struct { + result1 error + } ListInstalledChaincodesStub func() ([]chaincode.InstalledChaincode, error) listInstalledChaincodesMutex sync.RWMutex listInstalledChaincodesArgsForCall []struct { @@ -51,6 +62,66 @@ type ChaincodeStore struct { invocationsMutex sync.RWMutex } +func (fake *ChaincodeStore) Delete(arg1 string) error { + fake.deleteMutex.Lock() + ret, specificReturn := fake.deleteReturnsOnCall[len(fake.deleteArgsForCall)] + fake.deleteArgsForCall = append(fake.deleteArgsForCall, struct { + arg1 string + }{arg1}) + fake.recordInvocation("Delete", []interface{}{arg1}) + fake.deleteMutex.Unlock() + if fake.DeleteStub != nil { + return fake.DeleteStub(arg1) + } + if specificReturn { + return ret.result1 + } + fakeReturns := fake.deleteReturns + return fakeReturns.result1 +} + +func (fake *ChaincodeStore) DeleteCallCount() int { + fake.deleteMutex.RLock() + defer fake.deleteMutex.RUnlock() + return len(fake.deleteArgsForCall) +} + +func (fake *ChaincodeStore) DeleteCalls(stub func(string) error) { + fake.deleteMutex.Lock() + defer fake.deleteMutex.Unlock() + fake.DeleteStub = stub +} + +func (fake *ChaincodeStore) DeleteArgsForCall(i int) string { + fake.deleteMutex.RLock() + defer fake.deleteMutex.RUnlock() + argsForCall := fake.deleteArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *ChaincodeStore) DeleteReturns(result1 error) { + fake.deleteMutex.Lock() + defer fake.deleteMutex.Unlock() + fake.DeleteStub = nil + fake.deleteReturns = struct { + result1 error + }{result1} +} + +func (fake *ChaincodeStore) DeleteReturnsOnCall(i int, result1 error) { + fake.deleteMutex.Lock() + defer fake.deleteMutex.Unlock() + fake.DeleteStub = nil + if fake.deleteReturnsOnCall == nil { + fake.deleteReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.deleteReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *ChaincodeStore) ListInstalledChaincodes() ([]chaincode.InstalledChaincode, error) { fake.listInstalledChaincodesMutex.Lock() ret, specificReturn := fake.listInstalledChaincodesReturnsOnCall[len(fake.listInstalledChaincodesArgsForCall)] @@ -241,6 +312,8 @@ func (fake *ChaincodeStore) SaveReturnsOnCall(i int, result1 string, result2 err func (fake *ChaincodeStore) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.deleteMutex.RLock() + defer fake.deleteMutex.RUnlock() fake.listInstalledChaincodesMutex.RLock() defer fake.listInstalledChaincodesMutex.RUnlock() fake.loadMutex.RLock() diff --git a/core/chaincode/persistence/persistence.go b/core/chaincode/persistence/persistence.go index a61d00b46ed..2859bcf1213 100644 --- a/core/chaincode/persistence/persistence.go +++ b/core/chaincode/persistence/persistence.go @@ -188,6 +188,14 @@ func (s *Store) Load(packageID string) ([]byte, error) { return ccInstallPkg, nil } +// Delete deletes a persisted chaincode. Note, there is no locking, +// so this should only be performed if the chaincode has already +// been marked built. +func (s *Store) Delete(packageID string) error { + ccInstallPkgPath := filepath.Join(s.Path, CCFileName(packageID)) + return s.ReadWriter.Remove(ccInstallPkgPath) +} + // CodePackageNotFoundErr is the error returned when a code package cannot // be found in the persistence store type CodePackageNotFoundErr struct { diff --git a/core/chaincode/persistence/persistence_test.go b/core/chaincode/persistence/persistence_test.go index 14ee4053ce1..70ee33dc748 100644 --- a/core/chaincode/persistence/persistence_test.go +++ b/core/chaincode/persistence/persistence_test.go @@ -261,6 +261,40 @@ var _ = Describe("Persistence", func() { }) }) + Describe("Delete", func() { + var ( + mockReadWriter *mock.IOReadWriter + store *persistence.Store + ) + + BeforeEach(func() { + mockReadWriter = &mock.IOReadWriter{} + store = &persistence.Store{ + ReadWriter: mockReadWriter, + Path: "foo", + } + }) + + It("removes the chaincode from the filesystem", func() { + err := store.Delete("hash") + Expect(err).NotTo(HaveOccurred()) + + Expect(mockReadWriter.RemoveCallCount()).To(Equal(1)) + Expect(mockReadWriter.RemoveArgsForCall(0)).To(Equal("foo/hash.tar.gz")) + }) + + When("remove returns an error", func() { + BeforeEach(func() { + mockReadWriter.RemoveReturns(fmt.Errorf("fake-remove-error")) + }) + + It("returns the error", func() { + err := store.Delete("hash") + Expect(err).To(MatchError("fake-remove-error")) + }) + }) + }) + Describe("Load", func() { var ( mockReadWriter *mock.IOReadWriter