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