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