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