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 })