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 })