github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+incompatible/core/chaincode/runtime_launcher_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package chaincode_test
     8  
     9  import (
    10  	"time"
    11  
    12  	"github.com/hyperledger/fabric/common/metrics/metricsfakes"
    13  	"github.com/hyperledger/fabric/core/chaincode"
    14  	"github.com/hyperledger/fabric/core/chaincode/accesscontrol"
    15  	"github.com/hyperledger/fabric/core/chaincode/extcc"
    16  	extccmock "github.com/hyperledger/fabric/core/chaincode/extcc/mock"
    17  	"github.com/hyperledger/fabric/core/chaincode/fake"
    18  	"github.com/hyperledger/fabric/core/chaincode/mock"
    19  	"github.com/hyperledger/fabric/core/container/ccintf"
    20  	. "github.com/onsi/ginkgo"
    21  	. "github.com/onsi/gomega"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  var _ = Describe("RuntimeLauncher", func() {
    26  	var (
    27  		fakeRuntime        *mock.Runtime
    28  		fakeRegistry       *fake.LaunchRegistry
    29  		launchState        *chaincode.LaunchState
    30  		fakeLaunchDuration *metricsfakes.Histogram
    31  		fakeLaunchFailures *metricsfakes.Counter
    32  		fakeLaunchTimeouts *metricsfakes.Counter
    33  		fakeCertGenerator  *mock.CertGenerator
    34  		exitedCh           chan int
    35  		extCCConnExited    chan struct{}
    36  		fakeConnHandler    *mock.ConnectionHandler
    37  		fakeStreamHandler  *extccmock.StreamHandler
    38  
    39  		runtimeLauncher *chaincode.RuntimeLauncher
    40  	)
    41  
    42  	BeforeEach(func() {
    43  		launchState = chaincode.NewLaunchState()
    44  		fakeRegistry = &fake.LaunchRegistry{}
    45  		fakeRegistry.LaunchingReturns(launchState, false)
    46  
    47  		fakeRuntime = &mock.Runtime{}
    48  		fakeRuntime.StartStub = func(string, *ccintf.PeerConnection) error {
    49  			launchState.Notify(nil)
    50  			return nil
    51  		}
    52  		exitedCh = make(chan int)
    53  		waitExitCh := exitedCh // shadow to avoid race
    54  		fakeRuntime.WaitStub = func(string) (int, error) {
    55  			return <-waitExitCh, nil
    56  		}
    57  
    58  		fakeConnHandler = &mock.ConnectionHandler{}
    59  		fakeStreamHandler = &extccmock.StreamHandler{}
    60  
    61  		extCCConnExited = make(chan struct{})
    62  		connExited := extCCConnExited // shadow to avoid race
    63  		fakeConnHandler.StreamStub = func(string, *ccintf.ChaincodeServerInfo, extcc.StreamHandler) error {
    64  			launchState.Notify(nil)
    65  			<-connExited
    66  			return nil
    67  		}
    68  
    69  		fakeLaunchDuration = &metricsfakes.Histogram{}
    70  		fakeLaunchDuration.WithReturns(fakeLaunchDuration)
    71  		fakeLaunchFailures = &metricsfakes.Counter{}
    72  		fakeLaunchFailures.WithReturns(fakeLaunchFailures)
    73  		fakeLaunchTimeouts = &metricsfakes.Counter{}
    74  		fakeLaunchTimeouts.WithReturns(fakeLaunchTimeouts)
    75  
    76  		launchMetrics := &chaincode.LaunchMetrics{
    77  			LaunchDuration: fakeLaunchDuration,
    78  			LaunchFailures: fakeLaunchFailures,
    79  			LaunchTimeouts: fakeLaunchTimeouts,
    80  		}
    81  		fakeCertGenerator = &mock.CertGenerator{}
    82  		fakeCertGenerator.GenerateReturns(&accesscontrol.CertAndPrivKeyPair{Cert: []byte("cert"), Key: []byte("key")}, nil)
    83  		runtimeLauncher = &chaincode.RuntimeLauncher{
    84  			Runtime:           fakeRuntime,
    85  			Registry:          fakeRegistry,
    86  			StartupTimeout:    5 * time.Second,
    87  			Metrics:           launchMetrics,
    88  			PeerAddress:       "peer-address",
    89  			ConnectionHandler: fakeConnHandler,
    90  			CertGenerator:     fakeCertGenerator,
    91  		}
    92  	})
    93  
    94  	AfterEach(func() {
    95  		close(exitedCh)
    96  		close(extCCConnExited)
    97  	})
    98  
    99  	It("registers the chaincode as launching", func() {
   100  		err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   101  		Expect(err).NotTo(HaveOccurred())
   102  
   103  		Expect(fakeRegistry.LaunchingCallCount()).To(Equal(1))
   104  		cname := fakeRegistry.LaunchingArgsForCall(0)
   105  		Expect(cname).To(Equal("chaincode-name:chaincode-version"))
   106  	})
   107  
   108  	Context("build does not return external chaincode info", func() {
   109  		BeforeEach(func() {
   110  			fakeRuntime.BuildReturns(nil, nil)
   111  		})
   112  
   113  		It("chaincode is launched", func() {
   114  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   115  			Expect(err).NotTo(HaveOccurred())
   116  
   117  			Expect(fakeRuntime.BuildCallCount()).To(Equal(1))
   118  			ccciArg := fakeRuntime.BuildArgsForCall(0)
   119  			Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   120  
   121  			Expect(fakeRuntime.StartCallCount()).To(Equal(1))
   122  		})
   123  	})
   124  
   125  	Context("build returns external chaincode info", func() {
   126  		BeforeEach(func() {
   127  			fakeRuntime.BuildReturns(&ccintf.ChaincodeServerInfo{Address: "ccaddress:12345"}, nil)
   128  		})
   129  
   130  		It("chaincode is not launched", func() {
   131  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   132  			Expect(err).NotTo(HaveOccurred())
   133  
   134  			Expect(fakeRuntime.BuildCallCount()).To(Equal(1))
   135  			ccciArg := fakeRuntime.BuildArgsForCall(0)
   136  			Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   137  
   138  			Expect(fakeRuntime.StartCallCount()).To(Equal(0))
   139  		})
   140  	})
   141  
   142  	It("starts the runtime for the chaincode", func() {
   143  		err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   144  		Expect(err).NotTo(HaveOccurred())
   145  
   146  		Expect(fakeRuntime.BuildCallCount()).To(Equal(1))
   147  		ccciArg := fakeRuntime.BuildArgsForCall(0)
   148  		Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   149  		Expect(fakeRuntime.StartCallCount()).To(Equal(1))
   150  		ccciArg, ccinfoArg := fakeRuntime.StartArgsForCall(0)
   151  		Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   152  
   153  		Expect(ccinfoArg).To(Equal(&ccintf.PeerConnection{Address: "peer-address", TLSConfig: &ccintf.TLSConfig{ClientCert: []byte("cert"), ClientKey: []byte("key"), RootCert: nil}}))
   154  	})
   155  
   156  	Context("tls is not enabled", func() {
   157  		BeforeEach(func() {
   158  			runtimeLauncher.CertGenerator = nil
   159  		})
   160  
   161  		It("starts the runtime for the chaincode with no TLS", func() {
   162  
   163  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   164  			Expect(err).NotTo(HaveOccurred())
   165  
   166  			Expect(fakeRuntime.BuildCallCount()).To(Equal(1))
   167  			ccciArg := fakeRuntime.BuildArgsForCall(0)
   168  			Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   169  			Expect(fakeRuntime.StartCallCount()).To(Equal(1))
   170  			ccciArg, ccinfoArg := fakeRuntime.StartArgsForCall(0)
   171  			Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   172  
   173  			Expect(ccinfoArg).To(Equal(&ccintf.PeerConnection{Address: "peer-address"}))
   174  		})
   175  	})
   176  
   177  	It("waits for the launch to complete", func() {
   178  		fakeRuntime.StartReturns(nil)
   179  
   180  		errCh := make(chan error, 1)
   181  		go func() { errCh <- runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) }()
   182  
   183  		Consistently(errCh).ShouldNot(Receive())
   184  		launchState.Notify(nil)
   185  		Eventually(errCh).Should(Receive(BeNil()))
   186  	})
   187  
   188  	It("does not deregister the chaincode", func() {
   189  		err := runtimeLauncher.Launch("chaincode-name:chaincode-version, fakeStreamHandler", fakeStreamHandler)
   190  		Expect(err).NotTo(HaveOccurred())
   191  
   192  		Expect(fakeRegistry.DeregisterCallCount()).To(Equal(0))
   193  	})
   194  
   195  	It("records launch duration", func() {
   196  		err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   197  		Expect(err).NotTo(HaveOccurred())
   198  
   199  		Expect(fakeLaunchDuration.WithCallCount()).To(Equal(1))
   200  		labelValues := fakeLaunchDuration.WithArgsForCall(0)
   201  		Expect(labelValues).To(Equal([]string{
   202  			"chaincode", "chaincode-name:chaincode-version",
   203  			"success", "true",
   204  		}))
   205  		Expect(fakeLaunchDuration.ObserveArgsForCall(0)).NotTo(BeZero())
   206  		Expect(fakeLaunchDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0))
   207  	})
   208  
   209  	Context("when starting connection to external chaincode", func() {
   210  		BeforeEach(func() {
   211  			fakeRuntime.BuildReturns(&ccintf.ChaincodeServerInfo{Address: "peer-address"}, nil)
   212  		})
   213  		It("registers the chaincode as launching", func() {
   214  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   215  			Expect(err).NotTo(HaveOccurred())
   216  
   217  			Expect(fakeRegistry.LaunchingCallCount()).To(Equal(1))
   218  			cname := fakeRegistry.LaunchingArgsForCall(0)
   219  			Expect(cname).To(Equal("chaincode-name:chaincode-version"))
   220  		})
   221  
   222  		It("starts the runtime for the chaincode", func() {
   223  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   224  			Expect(err).NotTo(HaveOccurred())
   225  
   226  			Expect(fakeRuntime.BuildCallCount()).To(Equal(1))
   227  			ccciArg := fakeRuntime.BuildArgsForCall(0)
   228  			Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   229  			Expect(fakeConnHandler.StreamCallCount()).To(Equal(1))
   230  			ccciArg, ccinfoArg, ccshandler := fakeConnHandler.StreamArgsForCall(0)
   231  			Expect(ccciArg).To(Equal("chaincode-name:chaincode-version"))
   232  
   233  			Expect(ccinfoArg).To(Equal(&ccintf.ChaincodeServerInfo{Address: "peer-address"}))
   234  			Expect(ccshandler).To(Equal(fakeStreamHandler))
   235  		})
   236  
   237  		It("does not deregister the chaincode", func() {
   238  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   239  			Expect(err).NotTo(HaveOccurred())
   240  
   241  			Expect(fakeRegistry.DeregisterCallCount()).To(Equal(0))
   242  		})
   243  
   244  		It("records launch duration", func() {
   245  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   246  			Expect(err).NotTo(HaveOccurred())
   247  
   248  			Expect(fakeLaunchDuration.WithCallCount()).To(Equal(1))
   249  			labelValues := fakeLaunchDuration.WithArgsForCall(0)
   250  			Expect(labelValues).To(Equal([]string{
   251  				"chaincode", "chaincode-name:chaincode-version",
   252  				"success", "true",
   253  			}))
   254  			Expect(fakeLaunchDuration.ObserveArgsForCall(0)).NotTo(BeZero())
   255  			Expect(fakeLaunchDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0))
   256  		})
   257  
   258  		Context("when starting the external connection fails", func() {
   259  			BeforeEach(func() {
   260  				fakeConnHandler.StreamReturns(errors.New("banana"))
   261  			})
   262  
   263  			It("returns a wrapped error", func() {
   264  				err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   265  				Expect(err).To(MatchError("connection to chaincode-name:chaincode-version failed: banana"))
   266  			})
   267  
   268  			It("notifies the LaunchState", func() {
   269  				runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   270  				Eventually(launchState.Done()).Should(BeClosed())
   271  				Expect(launchState.Err()).To(MatchError("connection to chaincode-name:chaincode-version failed: banana"))
   272  			})
   273  
   274  			It("records chaincode launch failures", func() {
   275  				runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   276  				Expect(fakeLaunchFailures.WithCallCount()).To(Equal(1))
   277  				labelValues := fakeLaunchFailures.WithArgsForCall(0)
   278  				Expect(labelValues).To(Equal([]string{
   279  					"chaincode", "chaincode-name:chaincode-version",
   280  				}))
   281  				Expect(fakeLaunchFailures.AddCallCount()).To(Equal(1))
   282  				Expect(fakeLaunchFailures.AddArgsForCall(0)).To(BeNumerically("~", 1.0))
   283  			})
   284  
   285  			It("deregisters the chaincode", func() {
   286  				runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   287  
   288  				Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1))
   289  				cname := fakeRegistry.DeregisterArgsForCall(0)
   290  				Expect(cname).To(Equal("chaincode-name:chaincode-version"))
   291  			})
   292  		})
   293  	})
   294  
   295  	Context("when starting the runtime fails", func() {
   296  		BeforeEach(func() {
   297  			fakeRuntime.StartReturns(errors.New("banana"))
   298  		})
   299  
   300  		It("returns a wrapped error", func() {
   301  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   302  			Expect(err).To(MatchError("error starting container: banana"))
   303  		})
   304  
   305  		It("notifies the LaunchState", func() {
   306  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   307  			Eventually(launchState.Done()).Should(BeClosed())
   308  			Expect(launchState.Err()).To(MatchError("error starting container: banana"))
   309  		})
   310  
   311  		It("records chaincode launch failures", func() {
   312  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   313  			Expect(fakeLaunchFailures.WithCallCount()).To(Equal(1))
   314  			labelValues := fakeLaunchFailures.WithArgsForCall(0)
   315  			Expect(labelValues).To(Equal([]string{
   316  				"chaincode", "chaincode-name:chaincode-version",
   317  			}))
   318  			Expect(fakeLaunchFailures.AddCallCount()).To(Equal(1))
   319  			Expect(fakeLaunchFailures.AddArgsForCall(0)).To(BeNumerically("~", 1.0))
   320  		})
   321  
   322  		It("deregisters the chaincode", func() {
   323  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   324  
   325  			Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1))
   326  			cname := fakeRegistry.DeregisterArgsForCall(0)
   327  			Expect(cname).To(Equal("chaincode-name:chaincode-version"))
   328  		})
   329  	})
   330  
   331  	Context("when the contaienr terminates before registration", func() {
   332  		BeforeEach(func() {
   333  			fakeRuntime.StartReturns(nil)
   334  			fakeRuntime.WaitReturns(-99, nil)
   335  		})
   336  
   337  		It("returns an error", func() {
   338  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   339  			Expect(err).To(MatchError("chaincode registration failed: container exited with -99"))
   340  		})
   341  
   342  		It("deregisters the chaincode", func() {
   343  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   344  
   345  			Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1))
   346  			cname := fakeRegistry.DeregisterArgsForCall(0)
   347  			Expect(cname).To(Equal("chaincode-name:chaincode-version"))
   348  		})
   349  	})
   350  
   351  	Context("when handler registration fails", func() {
   352  		BeforeEach(func() {
   353  			fakeRuntime.StartStub = func(string, *ccintf.PeerConnection) error {
   354  				launchState.Notify(errors.New("papaya"))
   355  				return nil
   356  			}
   357  		})
   358  
   359  		It("returns an error", func() {
   360  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   361  			Expect(err).To(MatchError("chaincode registration failed: papaya"))
   362  		})
   363  
   364  		It("deregisters the chaincode", func() {
   365  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   366  
   367  			Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1))
   368  			cname := fakeRegistry.DeregisterArgsForCall(0)
   369  			Expect(cname).To(Equal("chaincode-name:chaincode-version"))
   370  		})
   371  	})
   372  
   373  	Context("when the runtime startup times out", func() {
   374  		BeforeEach(func() {
   375  			fakeRuntime.StartReturns(nil)
   376  			runtimeLauncher.StartupTimeout = 250 * time.Millisecond
   377  		})
   378  
   379  		It("returns a meaningful error", func() {
   380  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   381  			Expect(err).To(MatchError("timeout expired while starting chaincode chaincode-name:chaincode-version for transaction"))
   382  		})
   383  
   384  		It("notifies the LaunchState", func() {
   385  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   386  			Eventually(launchState.Done()).Should(BeClosed())
   387  			Expect(launchState.Err()).To(MatchError("timeout expired while starting chaincode chaincode-name:chaincode-version for transaction"))
   388  		})
   389  
   390  		It("records chaincode launch timeouts", func() {
   391  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   392  			Expect(fakeLaunchTimeouts.WithCallCount()).To(Equal(1))
   393  			labelValues := fakeLaunchTimeouts.WithArgsForCall(0)
   394  			Expect(labelValues).To(Equal([]string{
   395  				"chaincode", "chaincode-name:chaincode-version",
   396  			}))
   397  			Expect(fakeLaunchTimeouts.AddCallCount()).To(Equal(1))
   398  			Expect(fakeLaunchTimeouts.AddArgsForCall(0)).To(BeNumerically("~", 1.0))
   399  		})
   400  
   401  		It("deregisters the chaincode", func() {
   402  			runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   403  
   404  			Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1))
   405  			cname := fakeRegistry.DeregisterArgsForCall(0)
   406  			Expect(cname).To(Equal("chaincode-name:chaincode-version"))
   407  		})
   408  	})
   409  
   410  	Context("when the registry indicates the chaincode has already been started", func() {
   411  		BeforeEach(func() {
   412  			fakeRegistry.LaunchingReturns(launchState, true)
   413  		})
   414  
   415  		It("does not start the runtime for the chaincode", func() {
   416  			launchState.Notify(nil)
   417  
   418  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   419  			Expect(err).NotTo(HaveOccurred())
   420  
   421  			Expect(fakeRuntime.StartCallCount()).To(Equal(0))
   422  		})
   423  
   424  		It("waits for the launch to complete", func() {
   425  			fakeRuntime.StartReturns(nil)
   426  
   427  			errCh := make(chan error, 1)
   428  			go func() { errCh <- runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) }()
   429  
   430  			Consistently(errCh).ShouldNot(Receive())
   431  			launchState.Notify(nil)
   432  			Eventually(errCh).Should(Receive(BeNil()))
   433  		})
   434  
   435  		Context("when the launch fails", func() {
   436  			BeforeEach(func() {
   437  				launchState.Notify(errors.New("gooey-guac"))
   438  			})
   439  
   440  			It("does not deregister the chaincode", func() {
   441  				err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   442  				Expect(err).To(MatchError("chaincode registration failed: gooey-guac"))
   443  				Expect(fakeRegistry.DeregisterCallCount()).To(Equal(0))
   444  			})
   445  		})
   446  	})
   447  
   448  	Context("when stopping the runtime fails", func() {
   449  		BeforeEach(func() {
   450  			fakeRuntime.StartReturns(errors.New("whirled-peas"))
   451  			fakeRuntime.StopReturns(errors.New("applesauce"))
   452  		})
   453  
   454  		It("preserves the initial error", func() {
   455  			err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler)
   456  			Expect(err).To(MatchError("error starting container: whirled-peas"))
   457  		})
   458  	})
   459  })