263 lines
7.6 KiB
Go
263 lines
7.6 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package persistence
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/hyperledger/fabric/common/chaincode"
|
|
"github.com/hyperledger/fabric/common/flogging"
|
|
"github.com/hyperledger/fabric/common/util"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var logger = flogging.MustGetLogger("chaincode.persistence")
|
|
|
|
// IOReadWriter defines the interface needed for reading, writing, removing, and
|
|
// checking for existence of a specified file
|
|
type IOReadWriter interface {
|
|
ReadDir(string) ([]os.FileInfo, error)
|
|
ReadFile(string) ([]byte, error)
|
|
Remove(name string) error
|
|
WriteFile(string, string, []byte) error
|
|
MakeDir(string, os.FileMode) error
|
|
Exists(path string) (bool, error)
|
|
}
|
|
|
|
// FilesystemIO is the production implementation of the IOWriter interface
|
|
type FilesystemIO struct{}
|
|
|
|
// WriteFile writes a file to the filesystem; it does so atomically
|
|
// by first writing to a temp file and then renaming the file so that
|
|
// if the operation crashes midway we're not stuck with a bad package
|
|
func (f *FilesystemIO) WriteFile(path, name string, data []byte) error {
|
|
if path == "" {
|
|
return errors.New("empty path not allowed")
|
|
}
|
|
tmpFile, err := ioutil.TempFile(path, ".ccpackage.")
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error creating temp file in directory '%s'", path)
|
|
}
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
if n, err := tmpFile.Write(data); err != nil || n != len(data) {
|
|
if err == nil {
|
|
err = errors.Errorf(
|
|
"failed to write the entire content of the file, expected %d, wrote %d",
|
|
len(data), n)
|
|
}
|
|
return errors.Wrapf(err, "error writing to temp file '%s'", tmpFile.Name())
|
|
}
|
|
|
|
if err := tmpFile.Close(); err != nil {
|
|
return errors.Wrapf(err, "error closing temp file '%s'", tmpFile.Name())
|
|
}
|
|
|
|
if err := os.Rename(tmpFile.Name(), filepath.Join(path, name)); err != nil {
|
|
return errors.Wrapf(err, "error renaming temp file '%s'", tmpFile.Name())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove removes a file from the filesystem - used for rolling back an in-flight
|
|
// Save operation upon a failure
|
|
func (f *FilesystemIO) Remove(name string) error {
|
|
return os.Remove(name)
|
|
}
|
|
|
|
// ReadFile reads a file from the filesystem
|
|
func (f *FilesystemIO) ReadFile(filename string) ([]byte, error) {
|
|
return ioutil.ReadFile(filename)
|
|
}
|
|
|
|
// ReadDir reads a directory from the filesystem
|
|
func (f *FilesystemIO) ReadDir(dirname string) ([]os.FileInfo, error) {
|
|
return ioutil.ReadDir(dirname)
|
|
}
|
|
|
|
// MakeDir makes a directory on the filesystem (and any
|
|
// necessary parent directories).
|
|
func (f *FilesystemIO) MakeDir(dirname string, mode os.FileMode) error {
|
|
return os.MkdirAll(dirname, mode)
|
|
}
|
|
|
|
// Exists checks whether a file exists
|
|
func (*FilesystemIO) Exists(path string) (bool, error) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
|
|
return false, errors.Wrapf(err, "could not determine whether file '%s' exists", path)
|
|
}
|
|
|
|
// Store holds the information needed for persisting a chaincode install package
|
|
type Store struct {
|
|
Path string
|
|
ReadWriter IOReadWriter
|
|
}
|
|
|
|
// NewStore creates a new chaincode persistence store using
|
|
// the provided path on the filesystem.
|
|
func NewStore(path string) *Store {
|
|
store := &Store{
|
|
Path: path,
|
|
ReadWriter: &FilesystemIO{},
|
|
}
|
|
store.Initialize()
|
|
return store
|
|
}
|
|
|
|
// Initialize checks for the existence of the _lifecycle chaincodes
|
|
// directory and creates it if it has not yet been created.
|
|
func (s *Store) Initialize() {
|
|
var (
|
|
exists bool
|
|
err error
|
|
)
|
|
if exists, err = s.ReadWriter.Exists(s.Path); exists {
|
|
return
|
|
}
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Initialization of chaincode store failed: %s", err))
|
|
}
|
|
if err = s.ReadWriter.MakeDir(s.Path, 0o750); err != nil {
|
|
panic(fmt.Sprintf("Could not create _lifecycle chaincodes install path: %s", err))
|
|
}
|
|
}
|
|
|
|
// Save persists chaincode install package bytes. It returns
|
|
// the hash of the chaincode install package
|
|
func (s *Store) Save(label string, ccInstallPkg []byte) (string, error) {
|
|
packageID := PackageID(label, ccInstallPkg)
|
|
|
|
ccInstallPkgFileName := CCFileName(packageID)
|
|
ccInstallPkgFilePath := filepath.Join(s.Path, ccInstallPkgFileName)
|
|
|
|
if exists, _ := s.ReadWriter.Exists(ccInstallPkgFilePath); exists {
|
|
// chaincode install package was already installed
|
|
return packageID, nil
|
|
}
|
|
|
|
if err := s.ReadWriter.WriteFile(s.Path, ccInstallPkgFileName, ccInstallPkg); err != nil {
|
|
err = errors.Wrapf(err, "error writing chaincode install package to %s", ccInstallPkgFilePath)
|
|
logger.Error(err.Error())
|
|
return "", err
|
|
}
|
|
|
|
return packageID, nil
|
|
}
|
|
|
|
// Load loads a persisted chaincode install package bytes with
|
|
// the given packageID.
|
|
func (s *Store) Load(packageID string) ([]byte, error) {
|
|
ccInstallPkgPath := filepath.Join(s.Path, CCFileName(packageID))
|
|
|
|
exists, err := s.ReadWriter.Exists(ccInstallPkgPath)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "could not determine whether chaincode install package '%s' exists", packageID)
|
|
}
|
|
if !exists {
|
|
return nil, &CodePackageNotFoundErr{
|
|
PackageID: packageID,
|
|
}
|
|
}
|
|
|
|
ccInstallPkg, err := s.ReadWriter.ReadFile(ccInstallPkgPath)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "error reading chaincode install package at %s", ccInstallPkgPath)
|
|
return nil, err
|
|
}
|
|
|
|
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 {
|
|
PackageID string
|
|
}
|
|
|
|
func (e CodePackageNotFoundErr) Error() string {
|
|
return fmt.Sprintf("chaincode install package '%s' not found", e.PackageID)
|
|
}
|
|
|
|
// ListInstalledChaincodes returns an array with information about the
|
|
// chaincodes installed in the persistence store
|
|
func (s *Store) ListInstalledChaincodes() ([]chaincode.InstalledChaincode, error) {
|
|
files, err := s.ReadWriter.ReadDir(s.Path)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading chaincode directory at %s", s.Path)
|
|
}
|
|
|
|
installedChaincodes := []chaincode.InstalledChaincode{}
|
|
for _, file := range files {
|
|
if instCC, isInstCC := installedChaincodeFromFilename(file.Name()); isInstCC {
|
|
installedChaincodes = append(installedChaincodes, instCC)
|
|
}
|
|
}
|
|
return installedChaincodes, nil
|
|
}
|
|
|
|
// GetChaincodeInstallPath returns the path where chaincodes
|
|
// are installed
|
|
func (s *Store) GetChaincodeInstallPath() string {
|
|
return s.Path
|
|
}
|
|
|
|
// PackageID returns the package ID with the label and hash of the chaincode install package
|
|
func PackageID(label string, ccInstallPkg []byte) string {
|
|
hash := util.ComputeSHA256(ccInstallPkg)
|
|
return packageID(label, hash)
|
|
}
|
|
|
|
func packageID(label string, hash []byte) string {
|
|
return fmt.Sprintf("%s:%x", label, hash)
|
|
}
|
|
|
|
func CCFileName(packageID string) string {
|
|
return strings.Replace(packageID, ":", ".", 1) + ".tar.gz"
|
|
}
|
|
|
|
var packageFileMatcher = regexp.MustCompile("^(.+)[.]([0-9a-f]{64})[.]tar[.]gz$")
|
|
|
|
func installedChaincodeFromFilename(fileName string) (chaincode.InstalledChaincode, bool) {
|
|
matches := packageFileMatcher.FindStringSubmatch(fileName)
|
|
if len(matches) == 3 {
|
|
label := matches[1]
|
|
hash, _ := hex.DecodeString(matches[2])
|
|
packageID := packageID(label, hash)
|
|
|
|
return chaincode.InstalledChaincode{
|
|
Label: label,
|
|
Hash: hash,
|
|
PackageID: packageID,
|
|
}, true
|
|
}
|
|
|
|
return chaincode.InstalledChaincode{}, false
|
|
}
|