Skip to content

Commit 4d7d946

Browse files
author
Jason Yellick
committed
FAB-16106 Persist bld dir for externalbuilders
This CR causes the bld directory for a chaincode to be persisted when built by an externalbuilder. If that externalbuilder is removed from the system, then it returns an error. Signed-off-by: Jason Yellick <jyellick@us.ibm.com> Change-Id: I748e9d98bb5974d9df6b7ce02888629e1496fca5
1 parent 146f249 commit 4d7d946

File tree

3 files changed

+184
-38
lines changed

3 files changed

+184
-38
lines changed

core/container/externalbuilders/externalbuilders.go

Lines changed: 98 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ var (
3333
const MetadataFile = "metadata.json"
3434

3535
type Instance struct {
36-
PackageID string
37-
DurablePath string
38-
Builder *Builder
36+
PackageID string
37+
BldDir string
38+
Builder *Builder
3939
}
4040

4141
func (i *Instance) Start(peerConnection *ccintf.PeerConnection) error {
42-
return i.Builder.Launch(i.PackageID, i.DurablePath, peerConnection)
42+
return i.Builder.Launch(i.PackageID, i.BldDir, peerConnection)
4343
}
4444

4545
func (i *Instance) Stop() error {
@@ -51,27 +51,63 @@ func (i *Instance) Wait() (int, error) {
5151
select {}
5252
}
5353

54+
type BuildInfo struct {
55+
BuilderName string `json:"builder_name"`
56+
}
57+
5458
type Detector struct {
5559
DurablePath string
56-
Builders []peer.ExternalBuilder
60+
Builders []*Builder
5761
}
5862

5963
func (d *Detector) Detect(buildContext *BuildContext) *Builder {
60-
for _, detectBuilder := range d.Builders {
61-
builder := &Builder{
62-
Location: detectBuilder.Path,
63-
Logger: logger.Named(filepath.Base(detectBuilder.Path)),
64-
Name: detectBuilder.Name,
65-
EnvWhitelist: detectBuilder.EnvironmentWhitelist,
66-
}
64+
for _, builder := range d.Builders {
6765
if builder.Detect(buildContext) {
6866
return builder
6967
}
7068
}
71-
7269
return nil
7370
}
7471

72+
// CachedBuild returns a build instance that was already built, or nil, or
73+
// when an unexpected error is encountered, an error.
74+
func (d *Detector) CachedBuild(ccid string) (*Instance, error) {
75+
durablePath := filepath.Join(d.DurablePath, ccid)
76+
77+
_, err := os.Stat(durablePath)
78+
if os.IsNotExist(err) {
79+
return nil, nil
80+
}
81+
82+
if err != nil {
83+
return nil, errors.WithMessage(err, "existing build detected, but something went wrong inspecting it")
84+
}
85+
86+
buildInfoPath := filepath.Join(durablePath, "build-info.json")
87+
buildInfoData, err := ioutil.ReadFile(buildInfoPath)
88+
if err != nil {
89+
return nil, errors.WithMessagef(err, "could not read '%s' for build info", buildInfoPath)
90+
}
91+
92+
buildInfo := &BuildInfo{}
93+
err = json.Unmarshal(buildInfoData, buildInfo)
94+
if err != nil {
95+
return nil, errors.WithMessagef(err, "malformed build info at '%s'", buildInfoPath)
96+
}
97+
98+
for _, builder := range d.Builders {
99+
if builder.Name == buildInfo.BuilderName {
100+
return &Instance{
101+
PackageID: ccid,
102+
Builder: builder,
103+
BldDir: filepath.Join(durablePath, "bld"),
104+
}, nil
105+
}
106+
}
107+
108+
return nil, errors.Errorf("chaincode '%s' was already built with builder '%s', but that builder is no longer available", ccid, buildInfo.BuilderName)
109+
}
110+
75111
func (d *Detector) Build(ccid string, md *persistence.ChaincodePackageMetadata, codeStream io.Reader) (*Instance, error) {
76112
if len(d.Builders) == 0 {
77113
// A small optimization, especially while the launcher feature is under development
@@ -80,7 +116,16 @@ func (d *Detector) Build(ccid string, md *persistence.ChaincodePackageMetadata,
80116
return nil, errors.Errorf("no builders defined")
81117
}
82118

83-
buildContext, err := NewBuildContext(string(ccid), md, codeStream)
119+
i, err := d.CachedBuild(ccid)
120+
if err != nil {
121+
return nil, errors.WithMessage(err, "existing build could not be restored")
122+
}
123+
124+
if i != nil {
125+
return i, nil
126+
}
127+
128+
buildContext, err := NewBuildContext(ccid, md, codeStream)
84129
if err != nil {
85130
return nil, errors.WithMessage(err, "could not create build context")
86131
}
@@ -89,37 +134,44 @@ func (d *Detector) Build(ccid string, md *persistence.ChaincodePackageMetadata,
89134

90135
builder := d.Detect(buildContext)
91136
if builder == nil {
92-
buildContext.Cleanup()
93137
return nil, errors.Errorf("no builder found")
94138
}
95139

96140
if err := builder.Build(buildContext); err != nil {
97-
buildContext.Cleanup()
98141
return nil, errors.WithMessage(err, "external builder failed to build")
99142
}
100143

101144
durablePath := filepath.Join(d.DurablePath, ccid)
102145

103-
// cleanup anything which is already persisted
104-
err = os.RemoveAll(durablePath)
146+
err = os.Mkdir(durablePath, 0700)
105147
if err != nil {
106-
return nil, errors.WithMessagef(err, "could not clean dir '%s' to persist build ouput", durablePath)
148+
return nil, errors.WithMessagef(err, "could not create dir '%s' to persist build ouput", durablePath)
107149
}
108150

109-
err = os.Mkdir(durablePath, 0700)
151+
buildInfo, err := json.Marshal(&BuildInfo{
152+
BuilderName: builder.Name,
153+
})
110154
if err != nil {
111-
return nil, errors.WithMessagef(err, "could not create dir '%s' to persist build ouput", durablePath)
155+
os.RemoveAll(durablePath)
156+
return nil, errors.WithMessage(err, "could not marshal for build-info.json")
157+
}
158+
159+
err = ioutil.WriteFile(filepath.Join(durablePath, "build-info.json"), buildInfo, 0600)
160+
if err != nil {
161+
os.RemoveAll(durablePath)
162+
return nil, errors.WithMessage(err, "could not write build-info.json")
112163
}
113164

114-
err = os.Rename(buildContext.OutputDir, filepath.Join(durablePath, "bld"))
165+
durableBldDir := filepath.Join(durablePath, "bld")
166+
err = os.Rename(buildContext.BldDir, durableBldDir)
115167
if err != nil {
116168
return nil, errors.WithMessagef(err, "could not move build context bld to persistent location '%s'", durablePath)
117169
}
118170

119171
return &Instance{
120-
PackageID: ccid,
121-
Builder: builder,
122-
DurablePath: durablePath,
172+
PackageID: ccid,
173+
Builder: builder,
174+
BldDir: durableBldDir,
123175
}, nil
124176
}
125177

@@ -129,7 +181,7 @@ type BuildContext struct {
129181
ScratchDir string
130182
SourceDir string
131183
MetadataDir string
132-
OutputDir string
184+
BldDir string
133185
}
134186

135187
var pkgIDreg = regexp.MustCompile("[^a-zA-Z0-9]+")
@@ -177,7 +229,7 @@ func NewBuildContext(ccid string, md *persistence.ChaincodePackageMetadata, code
177229
ScratchDir: scratchDir,
178230
SourceDir: sourceDir,
179231
MetadataDir: metadataDir,
180-
OutputDir: outputDir,
232+
BldDir: outputDir,
181233
Metadata: md,
182234
CCID: ccid,
183235
}, nil
@@ -214,6 +266,19 @@ type Builder struct {
214266
Name string
215267
}
216268

269+
func CreateBuilders(builderConfs []peer.ExternalBuilder) []*Builder {
270+
builders := []*Builder{}
271+
for _, builderConf := range builderConfs {
272+
builders = append(builders, &Builder{
273+
Location: builderConf.Path,
274+
Name: builderConf.Name,
275+
EnvWhitelist: builderConf.EnvironmentWhitelist,
276+
Logger: logger.Named(builderConf.Name),
277+
})
278+
}
279+
return builders
280+
}
281+
217282
func (b *Builder) Detect(buildContext *BuildContext) bool {
218283
detect := filepath.Join(b.Location, "bin", "detect")
219284
cmd := NewCommand(
@@ -225,7 +290,7 @@ func (b *Builder) Detect(buildContext *BuildContext) bool {
225290

226291
err := RunCommand(b.Logger, cmd)
227292
if err != nil {
228-
logger.Debugf("Detection for builder '%s' failed: %s", b.Name, err)
293+
logger.Debugf("Detection for builder '%s' detect failed: %s", b.Name, err)
229294
// XXX, we probably also want to differentiate between a 'not detected'
230295
// and a 'I failed nastily', but, again, good enough for now
231296
return false
@@ -241,12 +306,12 @@ func (b *Builder) Build(buildContext *BuildContext) error {
241306
b.EnvWhitelist,
242307
buildContext.SourceDir,
243308
buildContext.MetadataDir,
244-
buildContext.OutputDir,
309+
buildContext.BldDir,
245310
)
246311

247312
err := RunCommand(b.Logger, cmd)
248313
if err != nil {
249-
return errors.Wrapf(err, "builder '%s' failed", b.Name)
314+
return errors.Wrapf(err, "builder '%s' build failed", b.Name)
250315
}
251316

252317
return nil
@@ -261,7 +326,7 @@ type LaunchConfig struct {
261326
RootCert []byte `json:"root_cert"`
262327
}
263328

264-
func (b *Builder) Launch(ccid, durablePath string, peerConnection *ccintf.PeerConnection) error {
329+
func (b *Builder) Launch(ccid, bldDir string, peerConnection *ccintf.PeerConnection) error {
265330
lc := &LaunchConfig{
266331
PeerAddress: peerConnection.Address,
267332
CCID: ccid,
@@ -291,13 +356,13 @@ func (b *Builder) Launch(ccid, durablePath string, peerConnection *ccintf.PeerCo
291356
cmd := NewCommand(
292357
launch,
293358
b.EnvWhitelist,
294-
durablePath,
359+
bldDir,
295360
launchDir,
296361
)
297362

298363
err = RunCommand(b.Logger, cmd)
299364
if err != nil {
300-
return errors.Wrapf(err, "builder '%s' failed", b.Name)
365+
return errors.Wrapf(err, "builder '%s' launch failed", b.Name)
301366
}
302367

303368
return nil

core/container/externalbuilders/externalbuilders_test.go

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ var _ = Describe("Externalbuilders", func() {
9898
Expect(err).NotTo(HaveOccurred())
9999

100100
detector = &externalbuilders.Detector{
101-
Builders: []peer.ExternalBuilder{
101+
Builders: externalbuilders.CreateBuilders([]peer.ExternalBuilder{
102102
{
103103
Path: "bad1",
104104
Name: "bad1",
@@ -111,7 +111,7 @@ var _ = Describe("Externalbuilders", func() {
111111
Path: "bad2",
112112
Name: "bad2",
113113
},
114-
},
114+
}),
115115
DurablePath: durablePath,
116116
}
117117
})
@@ -140,6 +140,84 @@ var _ = Describe("Externalbuilders", func() {
140140
Expect(err).To(MatchError("no builders defined"))
141141
})
142142
})
143+
144+
It("persists the build output", func() {
145+
_, err := detector.Build("fake-package-id", md, codePackage)
146+
Expect(err).NotTo(HaveOccurred())
147+
148+
_, err = os.Stat(filepath.Join(durablePath, "fake-package-id", "bld"))
149+
Expect(err).NotTo(HaveOccurred())
150+
151+
_, err = os.Stat(filepath.Join(durablePath, "fake-package-id", "build-info.json"))
152+
Expect(err).NotTo(HaveOccurred())
153+
})
154+
155+
Context("when the durable path cannot be created", func() {
156+
BeforeEach(func() {
157+
detector.DurablePath = "/fake/path/to/nowhere"
158+
})
159+
160+
It("wraps and returns the error", func() {
161+
_, err := detector.Build("fake-package-id", md, codePackage)
162+
Expect(err).To(MatchError("could not create dir '/fake/path/to/nowhere/fake-package-id' to persist build ouput: mkdir /fake/path/to/nowhere/fake-package-id: no such file or directory"))
163+
})
164+
})
165+
})
166+
167+
Describe("CachedBuild", func() {
168+
var (
169+
existingInstance *externalbuilders.Instance
170+
)
171+
172+
BeforeEach(func() {
173+
var err error
174+
existingInstance, err = detector.Build("fake-package-id", md, codePackage)
175+
Expect(err).NotTo(HaveOccurred())
176+
177+
// ensure the builder will fail if invoked
178+
detector.Builders[0].Location = "bad-path"
179+
})
180+
181+
It("returns the existing built instance", func() {
182+
newInstance, err := detector.Build("fake-package-id", md, codePackage)
183+
Expect(err).NotTo(HaveOccurred())
184+
Expect(existingInstance).To(Equal(newInstance))
185+
})
186+
187+
When("the build-info is missing", func() {
188+
BeforeEach(func() {
189+
err := os.RemoveAll(filepath.Join(durablePath, "fake-package-id", "build-info.json"))
190+
Expect(err).NotTo(HaveOccurred())
191+
})
192+
193+
It("returns an error", func() {
194+
_, err := detector.Build("fake-package-id", md, codePackage)
195+
Expect(err).To(MatchError(ContainSubstring("existing build could not be restored: could not read '")))
196+
})
197+
})
198+
199+
When("the build-info is corrupted", func() {
200+
BeforeEach(func() {
201+
err := ioutil.WriteFile(filepath.Join(durablePath, "fake-package-id", "build-info.json"), []byte("{corrupted"), 0600)
202+
Expect(err).NotTo(HaveOccurred())
203+
})
204+
205+
It("returns an error", func() {
206+
_, err := detector.Build("fake-package-id", md, codePackage)
207+
Expect(err).To(MatchError(ContainSubstring("invalid character 'c' looking for beginning of object key string")))
208+
})
209+
})
210+
211+
When("the builder is no longer available", func() {
212+
BeforeEach(func() {
213+
detector.Builders = detector.Builders[:1]
214+
})
215+
216+
It("returns an error", func() {
217+
_, err := detector.Build("fake-package-id", md, codePackage)
218+
Expect(err).To(MatchError("existing build could not be restored: chaincode 'fake-package-id' was already built with builder 'testdata', but that builder is no longer available"))
219+
})
220+
})
143221
})
144222
})
145223

@@ -207,7 +285,7 @@ var _ = Describe("Externalbuilders", func() {
207285

208286
It("returns an error", func() {
209287
err := builder.Build(buildContext)
210-
Expect(err).To(MatchError("builder 'testdata' failed: exit status 1"))
288+
Expect(err).To(MatchError("builder 'testdata' build failed: exit status 1"))
211289
})
212290
})
213291
})
@@ -252,7 +330,7 @@ var _ = Describe("Externalbuilders", func() {
252330

253331
It("returns an error", func() {
254332
err := builder.Launch("test-ccid", bldDir, fakeConnection)
255-
Expect(err).To(MatchError("builder 'testdata' failed: exit status 1"))
333+
Expect(err).To(MatchError("builder 'testdata' launch failed: exit status 1"))
256334
})
257335
})
258336
})

internal/peer/node/start.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,9 +503,12 @@ func serve(args []string) error {
503503
}
504504

505505
externalBuilderOutput := filepath.Join(coreconfig.GetPath("peer.fileSystemPath"), "externalbuilders", "builds")
506+
if err := os.MkdirAll(externalBuilderOutput, 0700); err != nil {
507+
logger.Panicf("could not create externalbuilders build output dir: %s", err)
508+
}
506509

507510
externalVM := &externalbuilders.Detector{
508-
Builders: coreConfig.ExternalBuilders,
511+
Builders: externalbuilders.CreateBuilders(coreConfig.ExternalBuilders),
509512
DurablePath: externalBuilderOutput,
510513
}
511514

0 commit comments

Comments
 (0)