github.com/yacovm/fabric@v2.0.0-alpha.0.20191128145320-c5d4087dc723+incompatible/core/container/externalbuilder/externalbuilder_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package externalbuilder_test 8 9 import ( 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strings" 15 16 "github.com/hyperledger/fabric/common/flogging" 17 "github.com/hyperledger/fabric/core/chaincode/persistence" 18 "github.com/hyperledger/fabric/core/container/ccintf" 19 "github.com/hyperledger/fabric/core/container/externalbuilder" 20 "github.com/hyperledger/fabric/core/peer" 21 . "github.com/onsi/ginkgo" 22 . "github.com/onsi/gomega" 23 "go.uber.org/zap" 24 "go.uber.org/zap/zapcore" 25 ) 26 27 var _ = Describe("externalbuilder", func() { 28 var ( 29 codePackage *os.File 30 logger *flogging.FabricLogger 31 md *persistence.ChaincodePackageMetadata 32 ) 33 34 BeforeEach(func() { 35 var err error 36 codePackage, err = os.Open("testdata/normal_archive.tar.gz") 37 Expect(err).NotTo(HaveOccurred()) 38 39 md = &persistence.ChaincodePackageMetadata{ 40 Path: "fake-path", 41 Type: "fake-type", 42 } 43 44 enc := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{MessageKey: "msg"}) 45 core := zapcore.NewCore(enc, zapcore.AddSync(GinkgoWriter), zap.NewAtomicLevel()) 46 logger = flogging.NewFabricLogger(zap.New(core).Named("logger")) 47 }) 48 49 AfterEach(func() { 50 if codePackage != nil { 51 codePackage.Close() 52 } 53 }) 54 55 Describe("NewBuildContext", func() { 56 It("creates a new context, including temporary locations", func() { 57 buildContext, err := externalbuilder.NewBuildContext("fake-package-id", md, codePackage) 58 Expect(err).NotTo(HaveOccurred()) 59 defer buildContext.Cleanup() 60 61 Expect(buildContext.ScratchDir).NotTo(BeEmpty()) 62 Expect(buildContext.ScratchDir).To(BeADirectory()) 63 64 Expect(buildContext.SourceDir).NotTo(BeEmpty()) 65 Expect(buildContext.SourceDir).To(BeADirectory()) 66 67 Expect(buildContext.ReleaseDir).NotTo(BeEmpty()) 68 Expect(buildContext.ReleaseDir).To(BeADirectory()) 69 70 Expect(buildContext.BldDir).NotTo(BeEmpty()) 71 Expect(buildContext.BldDir).To(BeADirectory()) 72 73 Expect(filepath.Join(buildContext.SourceDir, "a/test.file")).To(BeARegularFile()) 74 }) 75 76 Context("when the archive cannot be extracted", func() { 77 It("returns an error", func() { 78 codePackage, err := os.Open("testdata/archive_with_absolute.tar.gz") 79 Expect(err).NotTo(HaveOccurred()) 80 defer codePackage.Close() 81 _, err = externalbuilder.NewBuildContext("fake-package-id", md, codePackage) 82 Expect(err).To(MatchError(ContainSubstring("could not untar source package"))) 83 }) 84 }) 85 86 Context("when package id contains inappropriate chars", func() { 87 It("replaces them with dash", func() { 88 buildContext, err := externalbuilder.NewBuildContext("i&am/pkg:id", md, codePackage) 89 Expect(err).NotTo(HaveOccurred()) 90 Expect(buildContext.ScratchDir).To(ContainSubstring("fabric-i-am-pkg-id")) 91 }) 92 }) 93 }) 94 95 Describe("Detector", func() { 96 var ( 97 durablePath string 98 detector *externalbuilder.Detector 99 ) 100 101 BeforeEach(func() { 102 var err error 103 durablePath, err = ioutil.TempDir("", "detect-test") 104 Expect(err).NotTo(HaveOccurred()) 105 106 detector = &externalbuilder.Detector{ 107 Builders: externalbuilder.CreateBuilders([]peer.ExternalBuilder{ 108 {Path: "bad1", Name: "bad1"}, 109 {Path: "testdata/goodbuilder", Name: "goodbuilder"}, 110 {Path: "bad2", Name: "bad2"}, 111 }), 112 DurablePath: durablePath, 113 } 114 }) 115 116 AfterEach(func() { 117 if durablePath != "" { 118 err := os.RemoveAll(durablePath) 119 Expect(err).NotTo(HaveOccurred()) 120 } 121 }) 122 123 Describe("Build", func() { 124 It("iterates over all detectors and chooses the one that matches", func() { 125 instance, err := detector.Build("fake-package-id", md, codePackage) 126 Expect(err).NotTo(HaveOccurred()) 127 Expect(instance.Builder.Name).To(Equal("goodbuilder")) 128 }) 129 130 Context("when no builder can be found", func() { 131 BeforeEach(func() { 132 detector.Builders = externalbuilder.CreateBuilders([]peer.ExternalBuilder{ 133 {Path: "bad1", Name: "bad1"}, 134 }) 135 }) 136 137 It("returns a nil instance", func() { 138 i, err := detector.Build("fake-package-id", md, codePackage) 139 Expect(err).NotTo(HaveOccurred()) 140 Expect(i).To(BeNil()) 141 }) 142 }) 143 144 It("persists the build output", func() { 145 _, err := detector.Build("fake-package-id", md, codePackage) 146 Expect(err).NotTo(HaveOccurred()) 147 148 Expect(filepath.Join(durablePath, "fake-package-id", "bld")).To(BeADirectory()) 149 Expect(filepath.Join(durablePath, "fake-package-id", "release")).To(BeADirectory()) 150 Expect(filepath.Join(durablePath, "fake-package-id", "build-info.json")).To(BeARegularFile()) 151 }) 152 153 Context("when the durable path cannot be created", func() { 154 BeforeEach(func() { 155 detector.DurablePath = "path/to/nowhere" 156 }) 157 158 It("wraps and returns the error", func() { 159 _, err := detector.Build("fake-package-id", md, codePackage) 160 Expect(err).To(MatchError("could not create dir 'path/to/nowhere/fake-package-id' to persist build output: mkdir path/to/nowhere/fake-package-id: no such file or directory")) 161 }) 162 }) 163 }) 164 165 Describe("CachedBuild", func() { 166 var existingInstance *externalbuilder.Instance 167 168 BeforeEach(func() { 169 var err error 170 existingInstance, err = detector.Build("fake-package-id", md, codePackage) 171 Expect(err).NotTo(HaveOccurred()) 172 173 // ensure the builder will fail if invoked 174 detector.Builders[0].Location = "bad-path" 175 }) 176 177 It("returns the existing built instance", func() { 178 newInstance, err := detector.Build("fake-package-id", md, codePackage) 179 Expect(err).NotTo(HaveOccurred()) 180 Expect(existingInstance).To(Equal(newInstance)) 181 }) 182 183 When("the build-info is missing", func() { 184 BeforeEach(func() { 185 err := os.RemoveAll(filepath.Join(durablePath, "fake-package-id", "build-info.json")) 186 Expect(err).NotTo(HaveOccurred()) 187 }) 188 189 It("returns an error", func() { 190 _, err := detector.Build("fake-package-id", md, codePackage) 191 Expect(err).To(MatchError(ContainSubstring("existing build could not be restored: could not read '"))) 192 }) 193 }) 194 195 When("the build-info is corrupted", func() { 196 BeforeEach(func() { 197 err := ioutil.WriteFile(filepath.Join(durablePath, "fake-package-id", "build-info.json"), []byte("{corrupted"), 0600) 198 Expect(err).NotTo(HaveOccurred()) 199 }) 200 201 It("returns an error", func() { 202 _, err := detector.Build("fake-package-id", md, codePackage) 203 Expect(err).To(MatchError(ContainSubstring("invalid character 'c' looking for beginning of object key string"))) 204 }) 205 }) 206 207 When("the builder is no longer available", func() { 208 BeforeEach(func() { 209 detector.Builders = detector.Builders[:1] 210 }) 211 212 It("returns an error", func() { 213 _, err := detector.Build("fake-package-id", md, codePackage) 214 Expect(err).To(MatchError("existing build could not be restored: chaincode 'fake-package-id' was already built with builder 'goodbuilder', but that builder is no longer available")) 215 }) 216 }) 217 }) 218 }) 219 220 Describe("Builders", func() { 221 var ( 222 builder *externalbuilder.Builder 223 buildContext *externalbuilder.BuildContext 224 ) 225 226 BeforeEach(func() { 227 builder = &externalbuilder.Builder{ 228 Location: "testdata/goodbuilder", 229 Name: "goodbuilder", 230 Logger: logger, 231 } 232 233 var err error 234 buildContext, err = externalbuilder.NewBuildContext("fake-package-id", md, codePackage) 235 Expect(err).NotTo(HaveOccurred()) 236 }) 237 238 AfterEach(func() { 239 buildContext.Cleanup() 240 }) 241 242 Describe("Detect", func() { 243 It("detects when the package is handled by the external builder", func() { 244 result := builder.Detect(buildContext) 245 Expect(result).To(BeTrue()) 246 }) 247 248 Context("when the detector exits with a non-zero status", func() { 249 BeforeEach(func() { 250 builder.Location = "testdata/failbuilder" 251 }) 252 253 It("returns false", func() { 254 result := builder.Detect(buildContext) 255 Expect(result).To(BeFalse()) 256 }) 257 }) 258 }) 259 260 Describe("Build", func() { 261 It("builds the package by invoking external builder", func() { 262 err := builder.Build(buildContext) 263 Expect(err).NotTo(HaveOccurred()) 264 }) 265 266 Context("when the builder exits with a non-zero status", func() { 267 BeforeEach(func() { 268 builder.Location = "testdata/failbuilder" 269 builder.Name = "failbuilder" 270 }) 271 272 It("returns an error", func() { 273 err := builder.Build(buildContext) 274 Expect(err).To(MatchError("external builder 'failbuilder' failed: exit status 1")) 275 }) 276 }) 277 }) 278 279 Describe("Release", func() { 280 It("releases the package by invoking external builder", func() { 281 err := builder.Release(buildContext) 282 Expect(err).NotTo(HaveOccurred()) 283 }) 284 285 When("the release binary is not in the builder", func() { 286 BeforeEach(func() { 287 builder.Location = "bad-builder-location" 288 }) 289 290 It("returns no error as release is optional", func() { 291 err := builder.Release(buildContext) 292 Expect(err).NotTo(HaveOccurred()) 293 }) 294 }) 295 296 When("the builder exits with a non-zero status", func() { 297 BeforeEach(func() { 298 builder.Location = "testdata/failbuilder" 299 builder.Name = "failbuilder" 300 }) 301 302 It("returns an error", func() { 303 err := builder.Release(buildContext) 304 Expect(err).To(MatchError("builder 'failbuilder' release failed: exit status 1")) 305 }) 306 }) 307 }) 308 309 Describe("Run", func() { 310 var ( 311 fakeConnection *ccintf.PeerConnection 312 bldDir string 313 ) 314 315 BeforeEach(func() { 316 var err error 317 bldDir, err = ioutil.TempDir("", "run-test") 318 Expect(err).NotTo(HaveOccurred()) 319 320 fakeConnection = &ccintf.PeerConnection{ 321 Address: "fake-peer-address", 322 TLSConfig: &ccintf.TLSConfig{ 323 ClientCert: []byte("fake-client-cert"), 324 ClientKey: []byte("fake-client-key"), 325 RootCert: []byte("fake-root-cert"), 326 }, 327 } 328 }) 329 330 AfterEach(func() { 331 if bldDir != "" { 332 err := os.RemoveAll(bldDir) 333 Expect(err).NotTo(HaveOccurred()) 334 } 335 }) 336 337 It("runs the package by invoking external builder", func() { 338 sess, err := builder.Run("test-ccid", bldDir, fakeConnection) 339 Expect(err).NotTo(HaveOccurred()) 340 341 errCh := make(chan error) 342 go func() { errCh <- sess.Wait() }() 343 Eventually(errCh).Should(Receive(BeNil())) 344 }) 345 }) 346 347 Describe("NewCommand", func() { 348 It("only propagates expected variables", func() { 349 var expectedEnv []string 350 for _, key := range externalbuilder.DefaultEnvWhitelist { 351 if val, ok := os.LookupEnv(key); ok { 352 expectedEnv = append(expectedEnv, fmt.Sprintf("%s=%s", key, val)) 353 } 354 } 355 356 cmd := builder.NewCommand("/usr/bin/env") 357 Expect(cmd.Env).To(ConsistOf(expectedEnv)) 358 359 output, err := cmd.CombinedOutput() 360 Expect(err).NotTo(HaveOccurred()) 361 env := strings.Split(strings.TrimSuffix(string(output), "\n"), "\n") 362 Expect(env).To(ConsistOf(expectedEnv)) 363 }) 364 }) 365 }) 366 367 Describe("SanitizeCCIDPath", func() { 368 It("forbids the set of forbidden windows characters", func() { 369 sanitizedPath := externalbuilder.SanitizeCCIDPath(`<>:"/\|?*&`) 370 Expect(sanitizedPath).To(Equal("----------")) 371 }) 372 }) 373 })