github.com/yacovm/fabric@v2.0.0-alpha.0.20191128145320-c5d4087dc723+incompatible/core/container/externalbuilder/instance_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  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"time"
    15  
    16  	. "github.com/onsi/ginkgo"
    17  	. "github.com/onsi/gomega"
    18  	"go.uber.org/zap"
    19  	"go.uber.org/zap/zapcore"
    20  
    21  	"github.com/hyperledger/fabric/common/flogging"
    22  	"github.com/hyperledger/fabric/core/comm"
    23  	"github.com/hyperledger/fabric/core/container/ccintf"
    24  	"github.com/hyperledger/fabric/core/container/externalbuilder"
    25  )
    26  
    27  var _ = Describe("Instance", func() {
    28  	var (
    29  		logger   *flogging.FabricLogger
    30  		instance *externalbuilder.Instance
    31  	)
    32  
    33  	BeforeEach(func() {
    34  		enc := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{MessageKey: "msg"})
    35  		core := zapcore.NewCore(enc, zapcore.AddSync(GinkgoWriter), zap.NewAtomicLevel())
    36  		logger = flogging.NewFabricLogger(zap.New(core).Named("logger"))
    37  
    38  		instance = &externalbuilder.Instance{
    39  			PackageID: "test-ccid",
    40  			Builder: &externalbuilder.Builder{
    41  				Location: "testdata/goodbuilder",
    42  				Logger:   logger,
    43  			},
    44  		}
    45  	})
    46  
    47  	Describe("ChaincodeServerInfo", func() {
    48  		BeforeEach(func() {
    49  			var err error
    50  			instance.ReleaseDir, err = ioutil.TempDir("", "cc-conn-test")
    51  			Expect(err).NotTo(HaveOccurred())
    52  
    53  			err = os.MkdirAll(filepath.Join(instance.ReleaseDir, "chaincode", "server"), 0755)
    54  			Expect(err).NotTo(HaveOccurred())
    55  			//initiaze with a well-formed, all fields set, connection.json file
    56  			ccdata := `{"address": "ccaddress:12345", "tls_required": true, "dial_timeout": "10s", "client_auth_required": true, "key_path": "key.pem", "cert_path": "cert.pem", "root_cert_path": "root.pem"}`
    57  			err = ioutil.WriteFile(filepath.Join(instance.ChaincodeServerReleaseDir(), "connection.json"), []byte(ccdata), 0600)
    58  			Expect(err).NotTo(HaveOccurred())
    59  			err = ioutil.WriteFile(filepath.Join(instance.ChaincodeServerReleaseDir(), "key.pem"), []byte("fake-key"), 0600)
    60  			Expect(err).NotTo(HaveOccurred())
    61  			err = ioutil.WriteFile(filepath.Join(instance.ChaincodeServerReleaseDir(), "cert.pem"), []byte("fake-cert"), 0600)
    62  			Expect(err).NotTo(HaveOccurred())
    63  			err = ioutil.WriteFile(filepath.Join(instance.ChaincodeServerReleaseDir(), "root.pem"), []byte("fake-root-cert"), 0600)
    64  			Expect(err).NotTo(HaveOccurred())
    65  		})
    66  
    67  		AfterEach(func() {
    68  			os.RemoveAll(instance.ReleaseDir)
    69  		})
    70  
    71  		It("returns chaincode connection", func() {
    72  			ccinfo, err := instance.ChaincodeServerInfo()
    73  			Expect(err).NotTo(HaveOccurred())
    74  			Expect(ccinfo).To(Equal(&ccintf.ChaincodeServerInfo{
    75  				Address: "ccaddress:12345",
    76  				ClientConfig: comm.ClientConfig{
    77  					SecOpts: comm.SecureOptions{
    78  						UseTLS:            true,
    79  						RequireClientCert: true,
    80  						Certificate:       []byte("fake-cert"),
    81  						Key:               []byte("fake-key"),
    82  						ServerRootCAs:     [][]byte{[]byte("fake-root-cert")},
    83  					},
    84  					KaOpts:  comm.DefaultKeepaliveOptions,
    85  					Timeout: 10 * time.Second,
    86  				},
    87  			}))
    88  		})
    89  
    90  		When("connection.json is not provided", func() {
    91  			BeforeEach(func() {
    92  				err := os.Remove(filepath.Join(instance.ChaincodeServerReleaseDir(), "connection.json"))
    93  				Expect(err).NotTo(HaveOccurred())
    94  			})
    95  
    96  			It("returns nil server info", func() {
    97  				ccinfo, err := instance.ChaincodeServerInfo()
    98  				Expect(err).NotTo(HaveOccurred())
    99  				Expect(ccinfo).To(BeNil())
   100  			})
   101  		})
   102  
   103  		When("chaincode info is badly formed", func() {
   104  			BeforeEach(func() {
   105  				ccdata := `{"badly formed chaincode"}`
   106  				err := ioutil.WriteFile(filepath.Join(instance.ChaincodeServerReleaseDir(), "connection.json"), []byte(ccdata), 0600)
   107  				Expect(err).NotTo(HaveOccurred())
   108  			})
   109  
   110  			It("returns a malformed chaincode error", func() {
   111  				_, err := instance.ChaincodeServerInfo()
   112  				Expect(err).To(MatchError(ContainSubstring("malformed chaincode info")))
   113  			})
   114  		})
   115  	})
   116  
   117  	Describe("ChaincodeServerUserData", func() {
   118  		var (
   119  			ccuserdata *externalbuilder.ChaincodeServerUserData
   120  			releaseDir string
   121  		)
   122  
   123  		BeforeEach(func() {
   124  			var err error
   125  			releaseDir, err = ioutil.TempDir("", "cc-conn-test")
   126  			Expect(err).NotTo(HaveOccurred())
   127  
   128  			err = os.MkdirAll(filepath.Join(releaseDir, "chaincode", "server"), 0755)
   129  			Expect(err).NotTo(HaveOccurred())
   130  			err = ioutil.WriteFile(filepath.Join(releaseDir, "key.pem"), []byte("fake-key"), 0600)
   131  			Expect(err).NotTo(HaveOccurred())
   132  			err = ioutil.WriteFile(filepath.Join(releaseDir, "cert.pem"), []byte("fake-cert"), 0600)
   133  			Expect(err).NotTo(HaveOccurred())
   134  			err = ioutil.WriteFile(filepath.Join(releaseDir, "root.pem"), []byte("fake-root-cert"), 0600)
   135  			Expect(err).NotTo(HaveOccurred())
   136  
   137  			ccuserdata = &externalbuilder.ChaincodeServerUserData{
   138  				Address:            "ccaddress:12345",
   139  				DialTimeout:        externalbuilder.Duration{10 * time.Second},
   140  				TlsRequired:        true,
   141  				ClientAuthRequired: true,
   142  				KeyPath:            "key.pem",
   143  				CertPath:           "cert.pem",
   144  				RootCertPath:       "root.pem",
   145  			}
   146  		})
   147  		AfterEach(func() {
   148  			os.RemoveAll(releaseDir)
   149  		})
   150  		When("chaincode does not provide all info", func() {
   151  			Context("tls is not provided", func() {
   152  				It("returns TLS without client auth information", func() {
   153  					//"tls" missing
   154  					ccuserdata.TlsRequired = false
   155  
   156  					ccinfo, err := ccuserdata.ChaincodeServerInfo(releaseDir)
   157  					Expect(err).NotTo(HaveOccurred())
   158  					Expect(ccinfo).To(Equal(&ccintf.ChaincodeServerInfo{
   159  						Address: "ccaddress:12345",
   160  						ClientConfig: comm.ClientConfig{
   161  							Timeout: 10 * time.Second,
   162  							KaOpts:  comm.DefaultKeepaliveOptions,
   163  						},
   164  					}))
   165  				})
   166  			})
   167  			Context("client auth is not provided", func() {
   168  				It("returns TLS without client auth information", func() {
   169  					//"client_auth_required" missing
   170  					ccuserdata.ClientAuthRequired = false
   171  
   172  					ccinfo, err := ccuserdata.ChaincodeServerInfo(releaseDir)
   173  					Expect(err).NotTo(HaveOccurred())
   174  					Expect(ccinfo).To(Equal(&ccintf.ChaincodeServerInfo{
   175  						Address: "ccaddress:12345",
   176  						ClientConfig: comm.ClientConfig{
   177  							SecOpts: comm.SecureOptions{
   178  								UseTLS:        true,
   179  								ServerRootCAs: [][]byte{[]byte("fake-root-cert")},
   180  							},
   181  							KaOpts:  comm.DefaultKeepaliveOptions,
   182  							Timeout: 10 * time.Second,
   183  						},
   184  					}))
   185  				})
   186  			})
   187  			Context("dial timeout not provided", func() {
   188  				It("returns default dial timeout without dialtimeout", func() {
   189  					//"dial_timeout" missing
   190  					ccuserdata.DialTimeout = externalbuilder.Duration{}
   191  
   192  					ccinfo, err := ccuserdata.ChaincodeServerInfo(releaseDir)
   193  					Expect(err).NotTo(HaveOccurred())
   194  					Expect(ccinfo).To(Equal(&ccintf.ChaincodeServerInfo{
   195  						Address: "ccaddress:12345",
   196  						ClientConfig: comm.ClientConfig{
   197  							SecOpts: comm.SecureOptions{
   198  								UseTLS:            true,
   199  								RequireClientCert: true,
   200  								Certificate:       []byte("fake-cert"),
   201  								Key:               []byte("fake-key"),
   202  								ServerRootCAs:     [][]byte{[]byte("fake-root-cert")},
   203  							},
   204  							KaOpts:  comm.DefaultKeepaliveOptions,
   205  							Timeout: 3 * time.Second,
   206  						},
   207  					}))
   208  				})
   209  			})
   210  			Context("address is not provided", func() {
   211  				It("returns missing address error", func() {
   212  					//"address" missing
   213  					ccuserdata.Address = ""
   214  
   215  					_, err := ccuserdata.ChaincodeServerInfo(releaseDir)
   216  					Expect(err).To(MatchError("chaincode address not provided"))
   217  				})
   218  			})
   219  			Context("key is not provided", func() {
   220  				It("returns missing key error", func() {
   221  					//"key" missing
   222  					ccuserdata.KeyPath = ""
   223  
   224  					_, err := ccuserdata.ChaincodeServerInfo(releaseDir)
   225  					Expect(err).To(MatchError("chaincode tls key not provided"))
   226  				})
   227  			})
   228  			Context("cert is not provided", func() {
   229  				It("returns missing key error", func() {
   230  					//"cert" missing
   231  					ccuserdata.CertPath = ""
   232  
   233  					_, err := ccuserdata.ChaincodeServerInfo(releaseDir)
   234  					Expect(err).To(MatchError("chaincode tls cert not provided"))
   235  				})
   236  			})
   237  			Context("root cert is not provided", func() {
   238  				It("returns missing root cert error", func() {
   239  					//"root" missing
   240  					ccuserdata.RootCertPath = ""
   241  
   242  					_, err := ccuserdata.ChaincodeServerInfo(releaseDir)
   243  					Expect(err).To(MatchError("chaincode tls root cert not provided"))
   244  				})
   245  			})
   246  			Context("cert file is missing", func() {
   247  				It("returns missing cert file error", func() {
   248  					//cert file is missing
   249  					err := os.Remove(filepath.Join(releaseDir, "cert.pem"))
   250  					Expect(err).NotTo(HaveOccurred())
   251  
   252  					_, err = ccuserdata.ChaincodeServerInfo(releaseDir)
   253  					Expect(err).To(MatchError(ContainSubstring("error reading cert file")))
   254  				})
   255  			})
   256  			Context("key file is missing", func() {
   257  				It("returns missing key file error", func() {
   258  					//key file is missing
   259  					err := os.Remove(filepath.Join(releaseDir, "key.pem"))
   260  					Expect(err).NotTo(HaveOccurred())
   261  
   262  					_, err = ccuserdata.ChaincodeServerInfo(releaseDir)
   263  					Expect(err).To(MatchError(ContainSubstring("error reading key file")))
   264  				})
   265  			})
   266  			Context("root cert file is missing", func() {
   267  				It("returns missing root cert file error", func() {
   268  					//key file is missing
   269  					err := os.Remove(filepath.Join(releaseDir, "root.pem"))
   270  					Expect(err).NotTo(HaveOccurred())
   271  
   272  					_, err = ccuserdata.ChaincodeServerInfo(releaseDir)
   273  					Expect(err).To(MatchError(ContainSubstring("error reading root cert file")))
   274  				})
   275  			})
   276  		})
   277  	})
   278  
   279  	Describe("Start", func() {
   280  		It("invokes the builder's run command and sets the run status", func() {
   281  			err := instance.Start(&ccintf.PeerConnection{
   282  				Address: "fake-peer-address",
   283  				TLSConfig: &ccintf.TLSConfig{
   284  					ClientCert: []byte("fake-client-cert"),
   285  					ClientKey:  []byte("fake-client-key"),
   286  					RootCert:   []byte("fake-root-cert"),
   287  				},
   288  			})
   289  			Expect(err).NotTo(HaveOccurred())
   290  			Expect(instance.Session).NotTo(BeNil())
   291  
   292  			errCh := make(chan error)
   293  			go func() { errCh <- instance.Session.Wait() }()
   294  			Eventually(errCh).Should(Receive(BeNil()))
   295  		})
   296  	})
   297  
   298  	Describe("Stop", func() {
   299  		It("terminates the process", func() {
   300  			cmd := exec.Command("sleep", "90")
   301  			sess, err := externalbuilder.Start(logger, cmd)
   302  			Expect(err).NotTo(HaveOccurred())
   303  			instance.Session = sess
   304  			instance.TermTimeout = time.Minute
   305  
   306  			errCh := make(chan error)
   307  			go func() { errCh <- instance.Session.Wait() }()
   308  			Consistently(errCh).ShouldNot(Receive())
   309  
   310  			err = instance.Stop()
   311  			Expect(err).ToNot(HaveOccurred())
   312  			Eventually(errCh).Should(Receive(MatchError("signal: terminated")))
   313  		})
   314  
   315  		Context("when the process doesn't respond to SIGTERM within TermTimeout", func() {
   316  			It("kills the process with malice", func() {
   317  				cmd := exec.Command("testdata/ignoreterm.sh")
   318  				sess, err := externalbuilder.Start(logger, cmd)
   319  				Expect(err).NotTo(HaveOccurred())
   320  
   321  				instance.Session = sess
   322  				instance.TermTimeout = time.Second
   323  
   324  				errCh := make(chan error)
   325  				go func() { errCh <- instance.Session.Wait() }()
   326  				Consistently(errCh).ShouldNot(Receive())
   327  
   328  				err = instance.Stop()
   329  				Expect(err).ToNot(HaveOccurred())
   330  				Eventually(errCh).Should(Receive(MatchError("signal: killed")))
   331  			})
   332  		})
   333  
   334  		Context("when the instance session has not been started", func() {
   335  			It("returns an error", func() {
   336  				instance.Session = nil
   337  				err := instance.Stop()
   338  				Expect(err).To(MatchError("instance has not been started"))
   339  			})
   340  		})
   341  	})
   342  
   343  	Describe("Wait", func() {
   344  		BeforeEach(func() {
   345  			err := instance.Start(&ccintf.PeerConnection{
   346  				Address: "fake-peer-address",
   347  				TLSConfig: &ccintf.TLSConfig{
   348  					ClientCert: []byte("fake-client-cert"),
   349  					ClientKey:  []byte("fake-client-key"),
   350  					RootCert:   []byte("fake-root-cert"),
   351  				},
   352  			})
   353  			Expect(err).NotTo(HaveOccurred())
   354  		})
   355  
   356  		It("returns the exit status of the run", func() {
   357  			code, err := instance.Wait()
   358  			Expect(err).NotTo(HaveOccurred())
   359  			Expect(code).To(Equal(0))
   360  		})
   361  
   362  		Context("when run exits with a non-zero status", func() {
   363  			BeforeEach(func() {
   364  				instance.Builder.Location = "testdata/failbuilder"
   365  				instance.Builder.Name = "failbuilder"
   366  				err := instance.Start(&ccintf.PeerConnection{
   367  					Address: "fake-peer-address",
   368  				})
   369  				Expect(err).NotTo(HaveOccurred())
   370  			})
   371  
   372  			It("returns the exit status of the run and accompanying error", func() {
   373  				code, err := instance.Wait()
   374  				Expect(err).To(MatchError("builder 'failbuilder' run failed: exit status 1"))
   375  				Expect(code).To(Equal(1))
   376  			})
   377  		})
   378  
   379  		Context("when the instance session has not been started", func() {
   380  			It("returns an error", func() {
   381  				instance.Session = nil
   382  				exitCode, err := instance.Wait()
   383  				Expect(err).To(MatchError("instance was not successfully started"))
   384  				Expect(exitCode).To(Equal(-1))
   385  			})
   386  		})
   387  	})
   388  })