-
Notifications
You must be signed in to change notification settings - Fork 8.8k
/
persistence.go
258 lines (215 loc) · 7.37 KB
/
persistence.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
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, 0750); 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) {
hash := util.ComputeSHA256(ccInstallPkg)
packageID := packageID(label, hash)
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
}
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
}