github.com/hyperledger-labs/bdls@v2.1.1+incompatible/core/chaincode/persistence/persistence_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package persistence_test
     8  
     9  import (
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  
    15  	"github.com/hyperledger/fabric/common/chaincode"
    16  	"github.com/hyperledger/fabric/common/util"
    17  	"github.com/hyperledger/fabric/core/chaincode/persistence"
    18  	"github.com/hyperledger/fabric/core/chaincode/persistence/mock"
    19  	. "github.com/onsi/ginkgo"
    20  	. "github.com/onsi/ginkgo/extensions/table"
    21  	. "github.com/onsi/gomega"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  var _ = Describe("Persistence", func() {
    26  	Describe("FilesystemWriter", func() {
    27  		var (
    28  			filesystemIO *persistence.FilesystemIO
    29  			testDir      string
    30  		)
    31  
    32  		BeforeEach(func() {
    33  			filesystemIO = &persistence.FilesystemIO{}
    34  
    35  			var err error
    36  			testDir, err = ioutil.TempDir("", "persistence-test")
    37  			Expect(err).NotTo(HaveOccurred())
    38  		})
    39  
    40  		AfterEach(func() {
    41  			os.RemoveAll(testDir)
    42  		})
    43  
    44  		It("writes a file", func() {
    45  			path := filepath.Join(testDir, "write")
    46  			err := filesystemIO.WriteFile(testDir, "write", []byte("test"))
    47  			Expect(err).NotTo(HaveOccurred())
    48  
    49  			_, err = os.Stat(path)
    50  			Expect(err).NotTo(HaveOccurred())
    51  		})
    52  
    53  		When("an empty path is supplied to WriteFile", func() {
    54  			It("returns error", func() {
    55  				err := filesystemIO.WriteFile("", "write", []byte("test"))
    56  				Expect(err.Error()).To(Equal("empty path not allowed"))
    57  			})
    58  		})
    59  
    60  		It("stats a file", func() {
    61  			path := filepath.Join(testDir, "stat")
    62  			err := ioutil.WriteFile(path, []byte("test"), 0600)
    63  			Expect(err).NotTo(HaveOccurred())
    64  
    65  			exists, err := filesystemIO.Exists(path)
    66  			Expect(err).NotTo(HaveOccurred())
    67  			Expect(exists).To(BeTrue())
    68  		})
    69  
    70  		It("stats a non-existent file", func() {
    71  			exists, err := filesystemIO.Exists("not quite")
    72  			Expect(err).NotTo(HaveOccurred())
    73  			Expect(exists).To(BeFalse())
    74  		})
    75  
    76  		It("removes a file", func() {
    77  			path := filepath.Join(testDir, "remove")
    78  			err := ioutil.WriteFile(path, []byte("test"), 0600)
    79  			Expect(err).NotTo(HaveOccurred())
    80  
    81  			_, err = os.Stat(path)
    82  			Expect(err).NotTo(HaveOccurred())
    83  
    84  			err = filesystemIO.Remove(path)
    85  			Expect(err).NotTo(HaveOccurred())
    86  
    87  			_, err = os.Stat(path)
    88  			Expect(err).To(HaveOccurred())
    89  		})
    90  
    91  		It("reads a file", func() {
    92  			path := filepath.Join(testDir, "readfile")
    93  			err := ioutil.WriteFile(path, []byte("test"), 0600)
    94  			Expect(err).NotTo(HaveOccurred())
    95  
    96  			_, err = os.Stat(path)
    97  			Expect(err).NotTo(HaveOccurred())
    98  
    99  			fileBytes, err := filesystemIO.ReadFile(path)
   100  			Expect(err).NotTo(HaveOccurred())
   101  			Expect(fileBytes).To(Equal([]byte("test")))
   102  		})
   103  
   104  		It("reads a directory", func() {
   105  			path := filepath.Join(testDir, "readdir")
   106  			err := ioutil.WriteFile(path, []byte("test"), 0600)
   107  			Expect(err).NotTo(HaveOccurred())
   108  
   109  			_, err = os.Stat(path)
   110  			Expect(err).NotTo(HaveOccurred())
   111  
   112  			files, err := filesystemIO.ReadDir(testDir)
   113  			Expect(err).NotTo(HaveOccurred())
   114  			Expect(files).To(HaveLen(1))
   115  		})
   116  
   117  		It("makes a directory (and any necessary parent directories)", func() {
   118  			path := filepath.Join(testDir, "make", "dir")
   119  			err := filesystemIO.MakeDir(path, 0755)
   120  			Expect(err).NotTo(HaveOccurred())
   121  
   122  			_, err = os.Stat(path)
   123  			Expect(err).NotTo(HaveOccurred())
   124  		})
   125  	})
   126  
   127  	Describe("NewStore", func() {
   128  		var (
   129  			err     error
   130  			tempDir string
   131  			store   *persistence.Store
   132  		)
   133  
   134  		BeforeEach(func() {
   135  			tempDir, err = ioutil.TempDir("", "NewStore")
   136  			Expect(err).NotTo(HaveOccurred())
   137  		})
   138  
   139  		AfterEach(func() {
   140  			os.RemoveAll(tempDir)
   141  		})
   142  
   143  		It("creates a persistence store with the specified path and creates the directory on the filesystem", func() {
   144  			store = persistence.NewStore(tempDir)
   145  			Expect(store.Path).To(Equal(tempDir))
   146  			_, err = os.Stat(tempDir)
   147  			Expect(err).NotTo(HaveOccurred())
   148  		})
   149  	})
   150  
   151  	Describe("Initialize", func() {
   152  		var (
   153  			mockReadWriter *mock.IOReadWriter
   154  			store          *persistence.Store
   155  		)
   156  
   157  		BeforeEach(func() {
   158  			mockReadWriter = &mock.IOReadWriter{}
   159  			mockReadWriter.ExistsReturns(false, nil)
   160  			mockReadWriter.MakeDirReturns(nil)
   161  
   162  			store = &persistence.Store{
   163  				ReadWriter: mockReadWriter,
   164  			}
   165  		})
   166  
   167  		It("creates the directory for the persistence store", func() {
   168  			store.Initialize()
   169  			Expect(mockReadWriter.ExistsCallCount()).To(Equal(1))
   170  			Expect(mockReadWriter.MakeDirCallCount()).To(Equal(1))
   171  		})
   172  
   173  		Context("when the directory already exists", func() {
   174  			BeforeEach(func() {
   175  				mockReadWriter.ExistsReturns(true, nil)
   176  			})
   177  
   178  			It("returns without creating the directory", func() {
   179  				store.Initialize()
   180  				Expect(mockReadWriter.ExistsCallCount()).To(Equal(1))
   181  				Expect(mockReadWriter.MakeDirCallCount()).To(Equal(0))
   182  			})
   183  		})
   184  
   185  		Context("when the existence of the directory cannot be determined", func() {
   186  			BeforeEach(func() {
   187  				mockReadWriter.ExistsReturns(false, errors.New("blurg"))
   188  			})
   189  
   190  			It("returns without creating the directory", func() {
   191  				Expect(store.Initialize).Should(Panic())
   192  				Expect(mockReadWriter.ExistsCallCount()).To(Equal(1))
   193  				Expect(mockReadWriter.MakeDirCallCount()).To(Equal(0))
   194  			})
   195  		})
   196  
   197  		Context("when the directory cannot be created", func() {
   198  			BeforeEach(func() {
   199  				mockReadWriter.MakeDirReturns(errors.New("blarg"))
   200  			})
   201  
   202  			It("returns without creating the directory", func() {
   203  				Expect(store.Initialize).Should(Panic())
   204  				Expect(mockReadWriter.ExistsCallCount()).To(Equal(1))
   205  				Expect(mockReadWriter.MakeDirCallCount()).To(Equal(1))
   206  			})
   207  		})
   208  	})
   209  
   210  	Describe("Save", func() {
   211  		var (
   212  			mockReadWriter *mock.IOReadWriter
   213  			store          *persistence.Store
   214  			pkgBytes       []byte
   215  		)
   216  
   217  		BeforeEach(func() {
   218  			mockReadWriter = &mock.IOReadWriter{}
   219  			mockReadWriter.ExistsReturns(false, nil)
   220  			mockReadWriter.WriteFileReturns(nil)
   221  
   222  			store = &persistence.Store{
   223  				ReadWriter: mockReadWriter,
   224  			}
   225  
   226  			pkgBytes = []byte("testpkg")
   227  		})
   228  
   229  		It("saves a new code package successfully", func() {
   230  			packageID, err := store.Save("testcc", pkgBytes)
   231  			Expect(err).NotTo(HaveOccurred())
   232  			Expect(packageID).To(Equal("testcc:3fec0187440286d404241e871b44725310b11aaf43d100b053eae712fcabc66d"))
   233  			Expect(mockReadWriter.WriteFileCallCount()).To(Equal(1))
   234  			pkgDataFilePath, pkgDataFileName, pkgData := mockReadWriter.WriteFileArgsForCall(0)
   235  			Expect(pkgDataFilePath).To(Equal(""))
   236  			Expect(pkgDataFileName).To(Equal("testcc.3fec0187440286d404241e871b44725310b11aaf43d100b053eae712fcabc66d.tar.gz"))
   237  			Expect(pkgData).To(Equal([]byte("testpkg")))
   238  		})
   239  
   240  		Context("when the code package was previously installed successfully", func() {
   241  			BeforeEach(func() {
   242  				mockReadWriter.ExistsReturns(true, nil)
   243  			})
   244  
   245  			It("does nothing and returns the packageID", func() {
   246  				packageID, err := store.Save("testcc", pkgBytes)
   247  				Expect(err).NotTo(HaveOccurred())
   248  				Expect(packageID).To(Equal("testcc:3fec0187440286d404241e871b44725310b11aaf43d100b053eae712fcabc66d"))
   249  				Expect(mockReadWriter.WriteFileCallCount()).To(Equal(0))
   250  			})
   251  		})
   252  
   253  		Context("when writing the package fails", func() {
   254  			BeforeEach(func() {
   255  				mockReadWriter.WriteFileReturns(errors.New("soccer"))
   256  			})
   257  
   258  			It("returns an error", func() {
   259  				packageID, err := store.Save("testcc", pkgBytes)
   260  				Expect(packageID).To(Equal(""))
   261  				Expect(err).To(MatchError(ContainSubstring("error writing chaincode install package to testcc.3fec0187440286d404241e871b44725310b11aaf43d100b053eae712fcabc66d.tar.gz: soccer")))
   262  			})
   263  		})
   264  	})
   265  
   266  	Describe("Delete", func() {
   267  		var (
   268  			mockReadWriter *mock.IOReadWriter
   269  			store          *persistence.Store
   270  		)
   271  
   272  		BeforeEach(func() {
   273  			mockReadWriter = &mock.IOReadWriter{}
   274  			store = &persistence.Store{
   275  				ReadWriter: mockReadWriter,
   276  				Path:       "foo",
   277  			}
   278  		})
   279  
   280  		It("removes the chaincode from the filesystem", func() {
   281  			err := store.Delete("hash")
   282  			Expect(err).NotTo(HaveOccurred())
   283  
   284  			Expect(mockReadWriter.RemoveCallCount()).To(Equal(1))
   285  			Expect(mockReadWriter.RemoveArgsForCall(0)).To(Equal("foo/hash.tar.gz"))
   286  		})
   287  
   288  		When("remove returns an error", func() {
   289  			BeforeEach(func() {
   290  				mockReadWriter.RemoveReturns(fmt.Errorf("fake-remove-error"))
   291  			})
   292  
   293  			It("returns the error", func() {
   294  				err := store.Delete("hash")
   295  				Expect(err).To(MatchError("fake-remove-error"))
   296  			})
   297  		})
   298  	})
   299  
   300  	Describe("Load", func() {
   301  		var (
   302  			mockReadWriter *mock.IOReadWriter
   303  			store          *persistence.Store
   304  		)
   305  
   306  		BeforeEach(func() {
   307  			mockReadWriter = &mock.IOReadWriter{}
   308  			mockReadWriter.ReadFileReturnsOnCall(0, []byte("cornerkick"), nil)
   309  			mockReadWriter.ExistsReturns(true, nil)
   310  			store = &persistence.Store{
   311  				ReadWriter: mockReadWriter,
   312  			}
   313  		})
   314  
   315  		It("loads successfully and returns the chaincode names/versions", func() {
   316  			ccInstallPkgBytes, err := store.Load("hash")
   317  			Expect(err).NotTo(HaveOccurred())
   318  			Expect(ccInstallPkgBytes).To(Equal([]byte("cornerkick")))
   319  		})
   320  
   321  		Context("when the package isn't there", func() {
   322  			BeforeEach(func() {
   323  				mockReadWriter.ExistsReturns(false, nil)
   324  			})
   325  
   326  			It("returns an error", func() {
   327  				ccInstallPkgBytes, err := store.Load("hash")
   328  				Expect(err).To(Equal(&persistence.CodePackageNotFoundErr{PackageID: "hash"}))
   329  				Expect(err).To(MatchError("chaincode install package 'hash' not found"))
   330  				Expect(ccInstallPkgBytes).To(HaveLen(0))
   331  			})
   332  		})
   333  
   334  		Context("when an IO error occurred during stat", func() {
   335  			BeforeEach(func() {
   336  				mockReadWriter.ExistsReturns(false, errors.New("goodness me!"))
   337  			})
   338  
   339  			It("returns an error", func() {
   340  				ccInstallPkgBytes, err := store.Load("hash")
   341  				Expect(err).To(MatchError("could not determine whether chaincode install package 'hash' exists: goodness me!"))
   342  				Expect(ccInstallPkgBytes).To(HaveLen(0))
   343  			})
   344  		})
   345  
   346  		Context("when reading the chaincode install package fails", func() {
   347  			BeforeEach(func() {
   348  				mockReadWriter.ReadFileReturnsOnCall(0, nil, errors.New("redcard"))
   349  			})
   350  
   351  			It("returns an error", func() {
   352  				ccInstallPkgBytes, err := store.Load("hash")
   353  				Expect(err).To(MatchError(ContainSubstring("error reading chaincode install package")))
   354  				Expect(ccInstallPkgBytes).To(HaveLen(0))
   355  			})
   356  		})
   357  	})
   358  
   359  	Describe("ListInstalledChaincodes", func() {
   360  		var (
   361  			mockReadWriter *mock.IOReadWriter
   362  			store          *persistence.Store
   363  			hash1, hash2   []byte
   364  		)
   365  
   366  		BeforeEach(func() {
   367  			hash1 = util.ComputeSHA256([]byte("hash1"))
   368  			hash2 = util.ComputeSHA256([]byte("hash2"))
   369  			mockReadWriter = &mock.IOReadWriter{}
   370  			mockFileInfo := &mock.OSFileInfo{}
   371  			mockFileInfo.NameReturns(fmt.Sprintf("%s.%x.tar.gz", "label1", hash1))
   372  			mockFileInfo2 := &mock.OSFileInfo{}
   373  			mockFileInfo2.NameReturns(fmt.Sprintf("%s.%x.tar.gz", "label2", hash2))
   374  			mockReadWriter.ReadDirReturns([]os.FileInfo{mockFileInfo, mockFileInfo2}, nil)
   375  			store = &persistence.Store{
   376  				ReadWriter: mockReadWriter,
   377  			}
   378  		})
   379  
   380  		It("returns the list of installed chaincodes", func() {
   381  			installedChaincodes, err := store.ListInstalledChaincodes()
   382  			Expect(err).NotTo(HaveOccurred())
   383  			Expect(installedChaincodes).To(HaveLen(2))
   384  			Expect(installedChaincodes[0]).To(Equal(chaincode.InstalledChaincode{
   385  				Hash:      hash1,
   386  				Label:     "label1",
   387  				PackageID: fmt.Sprintf("label1:%x", hash1),
   388  			}))
   389  			Expect(installedChaincodes[1]).To(Equal(chaincode.InstalledChaincode{
   390  				Hash:      hash2,
   391  				Label:     "label2",
   392  				PackageID: fmt.Sprintf("label2:%x", hash2),
   393  			}))
   394  		})
   395  
   396  		Context("when extraneous files are present", func() {
   397  			var hash1, hash2 []byte
   398  
   399  			BeforeEach(func() {
   400  				hash1 = util.ComputeSHA256([]byte("hash1"))
   401  				hash2 = util.ComputeSHA256([]byte("hash2"))
   402  				mockFileInfo := &mock.OSFileInfo{}
   403  				mockFileInfo.NameReturns(fmt.Sprintf("%s.%x.tar.gz", "label1", hash1))
   404  				mockFileInfo2 := &mock.OSFileInfo{}
   405  				mockFileInfo2.NameReturns(fmt.Sprintf("%s.%x.tar.gz", "label2", hash2))
   406  				mockFileInfo3 := &mock.OSFileInfo{}
   407  				mockFileInfo3.NameReturns(fmt.Sprintf("%s.%x.tar.gz", "", "Musha rain dum a doo, dum a da"))
   408  				mockFileInfo4 := &mock.OSFileInfo{}
   409  				mockFileInfo4.NameReturns(fmt.Sprintf("%s.%x.tar.gz", "", "barfity:barf.tar.gz"))
   410  				mockReadWriter.ReadDirReturns([]os.FileInfo{mockFileInfo, mockFileInfo2, mockFileInfo3}, nil)
   411  			})
   412  
   413  			It("returns the list of installed chaincodes", func() {
   414  				installedChaincodes, err := store.ListInstalledChaincodes()
   415  				Expect(err).NotTo(HaveOccurred())
   416  				Expect(installedChaincodes).To(HaveLen(2))
   417  				Expect(installedChaincodes[0]).To(Equal(chaincode.InstalledChaincode{
   418  					Hash:      hash1,
   419  					Label:     "label1",
   420  					PackageID: fmt.Sprintf("label1:%x", hash1),
   421  				}))
   422  				Expect(installedChaincodes[1]).To(Equal(chaincode.InstalledChaincode{
   423  					Hash:      hash2,
   424  					Label:     "label2",
   425  					PackageID: fmt.Sprintf("label2:%x", hash2),
   426  				}))
   427  			})
   428  		})
   429  
   430  		Context("when the directory can't be read", func() {
   431  			BeforeEach(func() {
   432  				mockReadWriter.ReadDirReturns([]os.FileInfo{}, errors.New("I'm illiterate and so obviously I can't read"))
   433  			})
   434  
   435  			It("returns an error", func() {
   436  				installedChaincodes, err := store.ListInstalledChaincodes()
   437  				Expect(err).To(HaveOccurred())
   438  				Expect(installedChaincodes).To(HaveLen(0))
   439  			})
   440  		})
   441  	})
   442  
   443  	Describe("GetChaincodeInstallPath", func() {
   444  		var store *persistence.Store
   445  
   446  		BeforeEach(func() {
   447  			store = &persistence.Store{
   448  				Path: "testPath",
   449  			}
   450  		})
   451  
   452  		It("returns the path where chaincodes are installed", func() {
   453  			path := store.GetChaincodeInstallPath()
   454  			Expect(path).To(Equal("testPath"))
   455  		})
   456  	})
   457  
   458  	DescribeTable("CCFileName",
   459  		func(packageID, expectedName string) {
   460  			Expect(persistence.CCFileName(packageID)).To(Equal(expectedName))
   461  		},
   462  		Entry("label with dot and without hash", "aaa.bbb", "aaa.bbb.tar.gz"),
   463  		Entry("label and hash with colon delimeter", "aaa:bbb", "aaa.bbb.tar.gz"),
   464  		Entry("missing label with colon delimeter", ":bbb", ".bbb.tar.gz"),
   465  		Entry("missing hash with colon delimeter", "aaa:", "aaa..tar.gz"),
   466  	)
   467  })