github.com/renegr87/renegr87@v2.1.1+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 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 163 Expect(err).NotTo(HaveOccurred()) 164 165 Expect(fakeRuntime.BuildCallCount()).To(Equal(1)) 166 ccciArg := fakeRuntime.BuildArgsForCall(0) 167 Expect(ccciArg).To(Equal("chaincode-name:chaincode-version")) 168 Expect(fakeRuntime.StartCallCount()).To(Equal(1)) 169 ccciArg, ccinfoArg := fakeRuntime.StartArgsForCall(0) 170 Expect(ccciArg).To(Equal("chaincode-name:chaincode-version")) 171 172 Expect(ccinfoArg).To(Equal(&ccintf.PeerConnection{Address: "peer-address"})) 173 }) 174 }) 175 176 It("waits for the launch to complete", func() { 177 fakeRuntime.StartReturns(nil) 178 179 errCh := make(chan error, 1) 180 go func() { errCh <- runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) }() 181 182 Consistently(errCh).ShouldNot(Receive()) 183 launchState.Notify(nil) 184 Eventually(errCh).Should(Receive(BeNil())) 185 }) 186 187 It("does not deregister the chaincode", func() { 188 err := runtimeLauncher.Launch("chaincode-name:chaincode-version, fakeStreamHandler", fakeStreamHandler) 189 Expect(err).NotTo(HaveOccurred()) 190 191 Expect(fakeRegistry.DeregisterCallCount()).To(Equal(0)) 192 }) 193 194 It("records launch duration", func() { 195 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 196 Expect(err).NotTo(HaveOccurred()) 197 198 Expect(fakeLaunchDuration.WithCallCount()).To(Equal(1)) 199 labelValues := fakeLaunchDuration.WithArgsForCall(0) 200 Expect(labelValues).To(Equal([]string{ 201 "chaincode", "chaincode-name:chaincode-version", 202 "success", "true", 203 })) 204 Expect(fakeLaunchDuration.ObserveArgsForCall(0)).NotTo(BeZero()) 205 Expect(fakeLaunchDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0)) 206 }) 207 208 Context("when starting connection to external chaincode", func() { 209 BeforeEach(func() { 210 fakeRuntime.BuildReturns(&ccintf.ChaincodeServerInfo{Address: "peer-address"}, nil) 211 }) 212 It("registers the chaincode as launching", func() { 213 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 214 Expect(err).NotTo(HaveOccurred()) 215 216 Expect(fakeRegistry.LaunchingCallCount()).To(Equal(1)) 217 cname := fakeRegistry.LaunchingArgsForCall(0) 218 Expect(cname).To(Equal("chaincode-name:chaincode-version")) 219 }) 220 221 It("starts the runtime for the chaincode", func() { 222 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 223 Expect(err).NotTo(HaveOccurred()) 224 225 Expect(fakeRuntime.BuildCallCount()).To(Equal(1)) 226 ccciArg := fakeRuntime.BuildArgsForCall(0) 227 Expect(ccciArg).To(Equal("chaincode-name:chaincode-version")) 228 Expect(fakeConnHandler.StreamCallCount()).To(Equal(1)) 229 ccciArg, ccinfoArg, ccshandler := fakeConnHandler.StreamArgsForCall(0) 230 Expect(ccciArg).To(Equal("chaincode-name:chaincode-version")) 231 232 Expect(ccinfoArg).To(Equal(&ccintf.ChaincodeServerInfo{Address: "peer-address"})) 233 Expect(ccshandler).To(Equal(fakeStreamHandler)) 234 }) 235 236 It("does not deregister the chaincode", func() { 237 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 238 Expect(err).NotTo(HaveOccurred()) 239 240 Expect(fakeRegistry.DeregisterCallCount()).To(Equal(0)) 241 }) 242 243 It("records launch duration", func() { 244 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 245 Expect(err).NotTo(HaveOccurred()) 246 247 Expect(fakeLaunchDuration.WithCallCount()).To(Equal(1)) 248 labelValues := fakeLaunchDuration.WithArgsForCall(0) 249 Expect(labelValues).To(Equal([]string{ 250 "chaincode", "chaincode-name:chaincode-version", 251 "success", "true", 252 })) 253 Expect(fakeLaunchDuration.ObserveArgsForCall(0)).NotTo(BeZero()) 254 Expect(fakeLaunchDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0)) 255 }) 256 257 Context("when starting the external connection fails", func() { 258 BeforeEach(func() { 259 fakeConnHandler.StreamReturns(errors.New("banana")) 260 }) 261 262 It("returns a wrapped error", func() { 263 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 264 Expect(err).To(MatchError("connection to chaincode-name:chaincode-version failed: banana")) 265 }) 266 267 It("notifies the LaunchState", func() { 268 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 269 Eventually(launchState.Done()).Should(BeClosed()) 270 Expect(launchState.Err()).To(MatchError("connection to chaincode-name:chaincode-version failed: banana")) 271 }) 272 273 It("records chaincode launch failures", func() { 274 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 275 Expect(fakeLaunchFailures.WithCallCount()).To(Equal(1)) 276 labelValues := fakeLaunchFailures.WithArgsForCall(0) 277 Expect(labelValues).To(Equal([]string{ 278 "chaincode", "chaincode-name:chaincode-version", 279 })) 280 Expect(fakeLaunchFailures.AddCallCount()).To(Equal(1)) 281 Expect(fakeLaunchFailures.AddArgsForCall(0)).To(BeNumerically("~", 1.0)) 282 }) 283 284 It("deregisters the chaincode", func() { 285 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 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 294 Context("when starting the runtime fails", func() { 295 BeforeEach(func() { 296 fakeRuntime.StartReturns(errors.New("banana")) 297 }) 298 299 It("returns a wrapped error", func() { 300 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 301 Expect(err).To(MatchError("error starting container: banana")) 302 }) 303 304 It("notifies the LaunchState", func() { 305 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 306 Eventually(launchState.Done()).Should(BeClosed()) 307 Expect(launchState.Err()).To(MatchError("error starting container: banana")) 308 }) 309 310 It("records chaincode launch failures", func() { 311 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 312 Expect(fakeLaunchFailures.WithCallCount()).To(Equal(1)) 313 labelValues := fakeLaunchFailures.WithArgsForCall(0) 314 Expect(labelValues).To(Equal([]string{ 315 "chaincode", "chaincode-name:chaincode-version", 316 })) 317 Expect(fakeLaunchFailures.AddCallCount()).To(Equal(1)) 318 Expect(fakeLaunchFailures.AddArgsForCall(0)).To(BeNumerically("~", 1.0)) 319 }) 320 321 It("deregisters the chaincode", func() { 322 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 323 324 Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1)) 325 cname := fakeRegistry.DeregisterArgsForCall(0) 326 Expect(cname).To(Equal("chaincode-name:chaincode-version")) 327 }) 328 }) 329 330 Context("when the contaienr terminates before registration", func() { 331 BeforeEach(func() { 332 fakeRuntime.StartReturns(nil) 333 fakeRuntime.WaitReturns(-99, nil) 334 }) 335 336 It("returns an error", func() { 337 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 338 Expect(err).To(MatchError("chaincode registration failed: container exited with -99")) 339 }) 340 341 It("deregisters the chaincode", func() { 342 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 343 344 Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1)) 345 cname := fakeRegistry.DeregisterArgsForCall(0) 346 Expect(cname).To(Equal("chaincode-name:chaincode-version")) 347 }) 348 }) 349 350 Context("when handler registration fails", func() { 351 BeforeEach(func() { 352 fakeRuntime.StartStub = func(string, *ccintf.PeerConnection) error { 353 launchState.Notify(errors.New("papaya")) 354 return nil 355 } 356 }) 357 358 It("returns an error", func() { 359 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 360 Expect(err).To(MatchError("chaincode registration failed: papaya")) 361 }) 362 363 It("deregisters the chaincode", func() { 364 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 365 366 Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1)) 367 cname := fakeRegistry.DeregisterArgsForCall(0) 368 Expect(cname).To(Equal("chaincode-name:chaincode-version")) 369 }) 370 }) 371 372 Context("when the runtime startup times out", func() { 373 BeforeEach(func() { 374 fakeRuntime.StartReturns(nil) 375 runtimeLauncher.StartupTimeout = 250 * time.Millisecond 376 }) 377 378 It("returns a meaningful error", func() { 379 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 380 Expect(err).To(MatchError("timeout expired while starting chaincode chaincode-name:chaincode-version for transaction")) 381 }) 382 383 It("notifies the LaunchState", func() { 384 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 385 Eventually(launchState.Done()).Should(BeClosed()) 386 Expect(launchState.Err()).To(MatchError("timeout expired while starting chaincode chaincode-name:chaincode-version for transaction")) 387 }) 388 389 It("records chaincode launch timeouts", func() { 390 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 391 Expect(fakeLaunchTimeouts.WithCallCount()).To(Equal(1)) 392 labelValues := fakeLaunchTimeouts.WithArgsForCall(0) 393 Expect(labelValues).To(Equal([]string{ 394 "chaincode", "chaincode-name:chaincode-version", 395 })) 396 Expect(fakeLaunchTimeouts.AddCallCount()).To(Equal(1)) 397 Expect(fakeLaunchTimeouts.AddArgsForCall(0)).To(BeNumerically("~", 1.0)) 398 }) 399 400 It("deregisters the chaincode", func() { 401 runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 402 403 Expect(fakeRegistry.DeregisterCallCount()).To(Equal(1)) 404 cname := fakeRegistry.DeregisterArgsForCall(0) 405 Expect(cname).To(Equal("chaincode-name:chaincode-version")) 406 }) 407 }) 408 409 Context("when the registry indicates the chaincode has already been started", func() { 410 BeforeEach(func() { 411 fakeRegistry.LaunchingReturns(launchState, true) 412 }) 413 414 It("does not start the runtime for the chaincode", func() { 415 launchState.Notify(nil) 416 417 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 418 Expect(err).NotTo(HaveOccurred()) 419 420 Expect(fakeRuntime.StartCallCount()).To(Equal(0)) 421 }) 422 423 It("waits for the launch to complete", func() { 424 fakeRuntime.StartReturns(nil) 425 426 errCh := make(chan error, 1) 427 go func() { errCh <- runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) }() 428 429 Consistently(errCh).ShouldNot(Receive()) 430 launchState.Notify(nil) 431 Eventually(errCh).Should(Receive(BeNil())) 432 }) 433 434 Context("when the launch fails", func() { 435 BeforeEach(func() { 436 launchState.Notify(errors.New("gooey-guac")) 437 }) 438 439 It("does not deregister the chaincode", func() { 440 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 441 Expect(err).To(MatchError("chaincode registration failed: gooey-guac")) 442 Expect(fakeRegistry.DeregisterCallCount()).To(Equal(0)) 443 }) 444 }) 445 }) 446 447 Context("when stopping the runtime fails while launching", func() { 448 BeforeEach(func() { 449 fakeRuntime.StartReturns(errors.New("whirled-peas")) 450 fakeRuntime.StopReturns(errors.New("applesauce")) 451 }) 452 453 It("preserves the initial error", func() { 454 err := runtimeLauncher.Launch("chaincode-name:chaincode-version", fakeStreamHandler) 455 Expect(err).To(MatchError("error starting container: whirled-peas")) 456 }) 457 }) 458 459 It("stops the runtime for the chaincode", func() { 460 err := runtimeLauncher.Stop("chaincode-name:chaincode-version") 461 Expect(err).NotTo(HaveOccurred()) 462 463 Expect(fakeRuntime.StopCallCount()).To(Equal(1)) 464 ccidArg := fakeRuntime.StopArgsForCall(0) 465 Expect(ccidArg).To(Equal("chaincode-name:chaincode-version")) 466 }) 467 468 Context("when stopping the runtime fails while stopping", func() { 469 BeforeEach(func() { 470 fakeRuntime.StopReturns(errors.New("liver-mush")) 471 }) 472 473 It("preserves the initial error", func() { 474 err := runtimeLauncher.Stop("chaincode-name:chaincode-version") 475 Expect(err).To(MatchError("failed to stop chaincode chaincode-name:chaincode-version: liver-mush")) 476 }) 477 }) 478 })