github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+incompatible/core/container/dockercontroller/dockercontroller_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package dockercontroller 8 9 import ( 10 "archive/tar" 11 "bytes" 12 "compress/gzip" 13 "context" 14 "encoding/hex" 15 "errors" 16 "fmt" 17 "io" 18 "io/ioutil" 19 "testing" 20 "time" 21 22 docker "github.com/fsouza/go-dockerclient" 23 pb "github.com/hyperledger/fabric-protos-go/peer" 24 "github.com/hyperledger/fabric/common/flogging/floggingtest" 25 "github.com/hyperledger/fabric/common/metrics/disabled" 26 "github.com/hyperledger/fabric/common/metrics/metricsfakes" 27 "github.com/hyperledger/fabric/common/util" 28 "github.com/hyperledger/fabric/core/chaincode/persistence" 29 "github.com/hyperledger/fabric/core/container/ccintf" 30 "github.com/hyperledger/fabric/core/container/dockercontroller/mock" 31 . "github.com/onsi/gomega" 32 "github.com/onsi/gomega/gbytes" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 ) 36 37 // This test used to be part of an integration style test in core/container, moved to here 38 func TestIntegrationPath(t *testing.T) { 39 client, err := docker.NewClientFromEnv() 40 assert.NoError(t, err) 41 42 fakePlatformBuilder := &mock.PlatformBuilder{} 43 fakePlatformBuilder.GenerateDockerBuildReturns(InMemBuilder{}.Build()) 44 45 dc := DockerVM{ 46 PeerID: "", 47 NetworkID: util.GenerateUUID(), 48 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 49 Client: client, 50 PlatformBuilder: fakePlatformBuilder, 51 } 52 ccid := "simple" 53 54 instance, err := dc.Build("simple", &persistence.ChaincodePackageMetadata{ 55 Type: "type", 56 Path: "path", 57 }, bytes.NewBuffer([]byte("code-package"))) 58 require.NoError(t, err) 59 60 assert.Equal(t, &ContainerInstance{ 61 CCID: "simple", 62 Type: "TYPE", 63 DockerVM: &dc, 64 }, instance) 65 66 err = dc.Start(ccid, "GOLANG", &ccintf.PeerConnection{ 67 Address: "peer-address", 68 }) 69 require.NoError(t, err) 70 71 err = dc.Stop(ccid) 72 require.NoError(t, err) 73 } 74 75 var expectedNodeStartScript = ` 76 set -e 77 if [ -x /chaincode/start.sh ]; then 78 /chaincode/start.sh --peer.address peer-address 79 else 80 cd /usr/local/src 81 npm start -- --peer.address peer-address 82 fi 83 ` 84 85 func TestGetArgs(t *testing.T) { 86 tests := []struct { 87 name string 88 ccType pb.ChaincodeSpec_Type 89 expectedArgs []string 90 expectedErr string 91 }{ 92 {"golang-chaincode", pb.ChaincodeSpec_GOLANG, []string{"chaincode", "-peer.address=peer-address"}, ""}, 93 {"java-chaincode", pb.ChaincodeSpec_JAVA, []string{"/root/chaincode-java/start", "--peerAddress", "peer-address"}, ""}, 94 {"node-chaincode", pb.ChaincodeSpec_NODE, []string{"/bin/sh", "-c", expectedNodeStartScript}, ""}, 95 {"unknown-chaincode", pb.ChaincodeSpec_Type(999), []string{}, "unknown chaincodeType: 999"}, 96 } 97 for _, tc := range tests { 98 vm := &DockerVM{} 99 100 args, err := vm.GetArgs(tc.ccType.String(), "peer-address") 101 if tc.expectedErr != "" { 102 assert.EqualError(t, err, tc.expectedErr) 103 continue 104 } 105 assert.NoError(t, err) 106 assert.Equal(t, tc.expectedArgs, args) 107 } 108 } 109 110 func TestGetEnv(t *testing.T) { 111 vm := &DockerVM{ 112 LoggingEnv: []string{"LOG_ENV=foo"}, 113 } 114 115 t.Run("nil TLS config", func(t *testing.T) { 116 env := vm.GetEnv("test", nil) 117 assert.Equal(t, []string{"CORE_CHAINCODE_ID_NAME=test", "LOG_ENV=foo", "CORE_PEER_TLS_ENABLED=false"}, env) 118 }) 119 120 t.Run("real TLS config", func(t *testing.T) { 121 env := vm.GetEnv("test", &ccintf.TLSConfig{ 122 ClientKey: []byte("key"), 123 ClientCert: []byte("cert"), 124 RootCert: []byte("root"), 125 }) 126 assert.Equal(t, []string{ 127 "CORE_CHAINCODE_ID_NAME=test", 128 "LOG_ENV=foo", 129 "CORE_PEER_TLS_ENABLED=true", 130 "CORE_TLS_CLIENT_KEY_PATH=/etc/hyperledger/fabric/client.key", 131 "CORE_TLS_CLIENT_CERT_PATH=/etc/hyperledger/fabric/client.crt", 132 "CORE_TLS_CLIENT_KEY_FILE=/etc/hyperledger/fabric/client_pem.key", 133 "CORE_TLS_CLIENT_CERT_FILE=/etc/hyperledger/fabric/client_pem.crt", 134 "CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/peer.crt", 135 }, env) 136 }) 137 } 138 139 func Test_Start(t *testing.T) { 140 gt := NewGomegaWithT(t) 141 dockerClient := &mock.DockerClient{} 142 dvm := DockerVM{ 143 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 144 Client: dockerClient, 145 } 146 147 ccid := "simple:1.0" 148 peerConnection := &ccintf.PeerConnection{ 149 Address: "peer-address", 150 TLSConfig: &ccintf.TLSConfig{ 151 ClientKey: []byte("key"), 152 ClientCert: []byte("cert"), 153 RootCert: []byte("root"), 154 }, 155 } 156 157 // case 1: dockerClient.CreateContainer returns error 158 testError1 := errors.New("junk1") 159 dockerClient.CreateContainerReturns(nil, testError1) 160 err := dvm.Start(ccid, "GOLANG", peerConnection) 161 gt.Expect(err).To(MatchError(testError1)) 162 dockerClient.CreateContainerReturns(&docker.Container{}, nil) 163 164 // case 2: dockerClient.UploadToContainer returns error 165 testError2 := errors.New("junk2") 166 dockerClient.UploadToContainerReturns(testError2) 167 err = dvm.Start(ccid, "GOLANG", peerConnection) 168 gt.Expect(err.Error()).To(ContainSubstring("junk2")) 169 dockerClient.UploadToContainerReturns(nil) 170 171 // case 3: start called and dockerClient.CreateContainer returns 172 // docker.noSuchImgErr and dockerClient.Start returns error 173 testError3 := errors.New("junk3") 174 dvm.AttachStdOut = true 175 dockerClient.CreateContainerReturns(nil, testError3) 176 err = dvm.Start(ccid, "GOLANG", peerConnection) 177 gt.Expect(err).To(MatchError(testError3)) 178 dockerClient.CreateContainerReturns(&docker.Container{}, nil) 179 180 // case 4: GetArgs returns error 181 err = dvm.Start(ccid, "FAKE_TYPE", peerConnection) 182 gt.Expect(err).To(MatchError("could not get args: unknown chaincodeType: FAKE_TYPE")) 183 184 // Success cases 185 err = dvm.Start(ccid, "GOLANG", peerConnection) 186 gt.Expect(err).NotTo(HaveOccurred()) 187 188 // dockerClient.StopContainer returns error 189 err = dvm.Start(ccid, "GOLANG", peerConnection) 190 gt.Expect(err).NotTo(HaveOccurred()) 191 192 // dockerClient.KillContainer returns error 193 err = dvm.Start(ccid, "GOLANG", peerConnection) 194 gt.Expect(err).NotTo(HaveOccurred()) 195 196 // dockerClient.RemoveContainer returns error 197 err = dvm.Start(ccid, "GOLANG", peerConnection) 198 gt.Expect(err).NotTo(HaveOccurred()) 199 200 err = dvm.Start(ccid, "GOLANG", peerConnection) 201 gt.Expect(err).NotTo(HaveOccurred()) 202 } 203 204 func Test_streamOutput(t *testing.T) { 205 gt := NewGomegaWithT(t) 206 207 logger, recorder := floggingtest.NewTestLogger(t) 208 containerLogger, containerRecorder := floggingtest.NewTestLogger(t) 209 210 client := &mock.DockerClient{} 211 errCh := make(chan error, 1) 212 optsCh := make(chan docker.AttachToContainerOptions, 1) 213 client.AttachToContainerStub = func(opts docker.AttachToContainerOptions) error { 214 optsCh <- opts 215 return <-errCh 216 } 217 218 streamOutput(logger, client, "container-name", containerLogger) 219 220 var opts docker.AttachToContainerOptions 221 gt.Eventually(optsCh).Should(Receive(&opts)) 222 gt.Eventually(opts.Success).Should(BeSent(struct{}{})) 223 gt.Eventually(opts.Success).Should(BeClosed()) 224 225 fmt.Fprintf(opts.OutputStream, "message-one\n") 226 fmt.Fprintf(opts.OutputStream, "message-two") // does not get written 227 gt.Eventually(containerRecorder).Should(gbytes.Say("message-one")) 228 gt.Consistently(containerRecorder.Entries).Should(HaveLen(1)) 229 230 close(errCh) 231 gt.Eventually(recorder).Should(gbytes.Say("Container container-name has closed its IO channel")) 232 gt.Consistently(recorder.Entries).Should(HaveLen(1)) 233 gt.Consistently(containerRecorder.Entries).Should(HaveLen(1)) 234 } 235 236 func Test_BuildMetric(t *testing.T) { 237 ccid := "simple:1.0" 238 client := &mock.DockerClient{} 239 240 tests := []struct { 241 desc string 242 buildErr bool 243 expectedLabels []string 244 }{ 245 {desc: "success", buildErr: false, expectedLabels: []string{"chaincode", "simple:1.0", "success", "true"}}, 246 {desc: "failure", buildErr: true, expectedLabels: []string{"chaincode", "simple:1.0", "success", "false"}}, 247 } 248 for _, tt := range tests { 249 t.Run(tt.desc, func(t *testing.T) { 250 gt := NewGomegaWithT(t) 251 fakeChaincodeImageBuildDuration := &metricsfakes.Histogram{} 252 fakeChaincodeImageBuildDuration.WithReturns(fakeChaincodeImageBuildDuration) 253 dvm := DockerVM{ 254 BuildMetrics: &BuildMetrics{ 255 ChaincodeImageBuildDuration: fakeChaincodeImageBuildDuration, 256 }, 257 Client: client, 258 } 259 260 if tt.buildErr { 261 client.BuildImageReturns(errors.New("Error building image")) 262 } 263 dvm.buildImage(ccid, &bytes.Buffer{}) 264 265 gt.Expect(fakeChaincodeImageBuildDuration.WithCallCount()).To(Equal(1)) 266 gt.Expect(fakeChaincodeImageBuildDuration.WithArgsForCall(0)).To(Equal(tt.expectedLabels)) 267 gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).NotTo(BeZero()) 268 gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0)) 269 }) 270 } 271 } 272 273 func Test_Stop(t *testing.T) { 274 dvm := DockerVM{Client: &mock.DockerClient{}} 275 ccid := "simple" 276 277 // Success case 278 err := dvm.Stop(ccid) 279 assert.NoError(t, err) 280 } 281 282 func Test_Wait(t *testing.T) { 283 dvm := DockerVM{} 284 285 // happy path 286 client := &mock.DockerClient{} 287 dvm.Client = client 288 289 client.WaitContainerReturns(99, nil) 290 exitCode, err := dvm.Wait("the-name:the-version") 291 assert.NoError(t, err) 292 assert.Equal(t, 99, exitCode) 293 assert.Equal(t, "the-name-the-version", client.WaitContainerArgsForCall(0)) 294 295 // wait fails 296 client.WaitContainerReturns(99, errors.New("no-wait-for-you")) 297 _, err = dvm.Wait("") 298 assert.EqualError(t, err, "no-wait-for-you") 299 } 300 301 func TestHealthCheck(t *testing.T) { 302 client := &mock.DockerClient{} 303 vm := &DockerVM{Client: client} 304 305 err := vm.HealthCheck(context.Background()) 306 assert.NoError(t, err) 307 308 client.PingWithContextReturns(errors.New("Error pinging daemon")) 309 err = vm.HealthCheck(context.Background()) 310 assert.Error(t, err) 311 assert.Contains(t, err.Error(), "Error pinging daemon") 312 } 313 314 type testCase struct { 315 name string 316 vm *DockerVM 317 ccid string 318 expectedOutput string 319 } 320 321 func TestGetVMNameForDocker(t *testing.T) { 322 tc := []testCase{ 323 { 324 name: "mycc", 325 vm: &DockerVM{NetworkID: "dev", PeerID: "peer0"}, 326 ccid: "mycc:1.0", 327 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-mycc-1.0")))), 328 }, 329 { 330 name: "mycc-nonetworkid", 331 vm: &DockerVM{PeerID: "peer1"}, 332 ccid: "mycc:1.0", 333 expectedOutput: fmt.Sprintf("%s-%s", "peer1-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("peer1-mycc-1.0")))), 334 }, 335 { 336 name: "myCC-UCids", 337 vm: &DockerVM{NetworkID: "Dev", PeerID: "Peer0"}, 338 ccid: "myCC:1.0", 339 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev-Peer0-myCC-1.0")))), 340 }, 341 { 342 name: "myCC-idsWithSpecialChars", 343 vm: &DockerVM{NetworkID: "Dev$dev", PeerID: "Peer*0"}, 344 ccid: "myCC:1.0", 345 expectedOutput: fmt.Sprintf("%s-%s", "dev-dev-peer-0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev$dev-Peer*0-myCC-1.0")))), 346 }, 347 { 348 name: "mycc-nopeerid", 349 vm: &DockerVM{NetworkID: "dev"}, 350 ccid: "mycc:1.0", 351 expectedOutput: fmt.Sprintf("%s-%s", "dev-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-mycc-1.0")))), 352 }, 353 { 354 name: "myCC-LCids", 355 vm: &DockerVM{NetworkID: "dev", PeerID: "peer0"}, 356 ccid: "myCC:1.0", 357 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-myCC-1.0")))), 358 }, 359 } 360 361 for _, test := range tc { 362 name, err := test.vm.GetVMNameForDocker(test.ccid) 363 assert.Nil(t, err, "Expected nil error") 364 assert.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name) 365 } 366 367 } 368 369 func TestGetVMName(t *testing.T) { 370 tc := []testCase{ 371 { 372 name: "myCC-preserveCase", 373 vm: &DockerVM{NetworkID: "Dev", PeerID: "Peer0"}, 374 ccid: "myCC:1.0", 375 expectedOutput: fmt.Sprintf("%s", "Dev-Peer0-myCC-1.0"), 376 }, 377 } 378 379 for _, test := range tc { 380 name := test.vm.GetVMName(test.ccid) 381 assert.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name) 382 } 383 384 } 385 386 func Test_buildImage(t *testing.T) { 387 client := &mock.DockerClient{} 388 dvm := DockerVM{ 389 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 390 Client: client, 391 NetworkMode: "network-mode", 392 } 393 394 err := dvm.buildImage("simple", &bytes.Buffer{}) 395 assert.NoError(t, err) 396 assert.Equal(t, 1, client.BuildImageCallCount()) 397 398 opts := client.BuildImageArgsForCall(0) 399 assert.Equal(t, "simple-a7a39b72f29718e653e73503210fbb597057b7a1c77d1fe321a1afcff041d4e1", opts.Name) 400 assert.False(t, opts.Pull) 401 assert.Equal(t, "network-mode", opts.NetworkMode) 402 assert.Equal(t, &bytes.Buffer{}, opts.InputStream) 403 assert.NotNil(t, opts.OutputStream) 404 } 405 406 func Test_buildImageFailure(t *testing.T) { 407 client := &mock.DockerClient{} 408 client.BuildImageReturns(errors.New("oh-bother-we-failed-badly")) 409 dvm := DockerVM{ 410 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 411 Client: client, 412 NetworkMode: "network-mode", 413 } 414 415 err := dvm.buildImage("simple", &bytes.Buffer{}) 416 assert.EqualError(t, err, "oh-bother-we-failed-badly") 417 } 418 419 func TestBuild(t *testing.T) { 420 buildMetrics := NewBuildMetrics(&disabled.Provider{}) 421 md := &persistence.ChaincodePackageMetadata{ 422 Type: "type", 423 Path: "path", 424 } 425 426 t.Run("when the image does not exist", func(t *testing.T) { 427 client := &mock.DockerClient{} 428 client.InspectImageReturns(nil, docker.ErrNoSuchImage) 429 430 fakePlatformBuilder := &mock.PlatformBuilder{} 431 fakePlatformBuilder.GenerateDockerBuildReturns(&bytes.Buffer{}, nil) 432 433 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder} 434 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 435 assert.NoError(t, err, "should have built successfully") 436 437 assert.Equal(t, 1, client.BuildImageCallCount()) 438 439 require.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount()) 440 ccType, path, codePackageStream := fakePlatformBuilder.GenerateDockerBuildArgsForCall(0) 441 assert.Equal(t, "TYPE", ccType) 442 assert.Equal(t, "path", path) 443 codePackage, err := ioutil.ReadAll(codePackageStream) 444 require.NoError(t, err) 445 assert.Equal(t, []byte("code-package"), codePackage) 446 447 }) 448 449 t.Run("when inspecting the image fails", func(t *testing.T) { 450 client := &mock.DockerClient{} 451 client.InspectImageReturns(nil, errors.New("inspecting-image-fails")) 452 453 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics} 454 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 455 assert.EqualError(t, err, "docker image inspection failed: inspecting-image-fails") 456 457 assert.Equal(t, 0, client.BuildImageCallCount()) 458 }) 459 460 t.Run("when the image exists", func(t *testing.T) { 461 client := &mock.DockerClient{} 462 463 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics} 464 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 465 assert.NoError(t, err) 466 467 assert.Equal(t, 0, client.BuildImageCallCount()) 468 }) 469 470 t.Run("when the platform builder fails", func(t *testing.T) { 471 client := &mock.DockerClient{} 472 client.InspectImageReturns(nil, docker.ErrNoSuchImage) 473 client.BuildImageReturns(errors.New("no-build-for-you")) 474 475 fakePlatformBuilder := &mock.PlatformBuilder{} 476 fakePlatformBuilder.GenerateDockerBuildReturns(nil, errors.New("fake-builder-error")) 477 478 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder} 479 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 480 assert.Equal(t, 1, client.InspectImageCallCount()) 481 assert.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount()) 482 assert.Equal(t, 0, client.BuildImageCallCount()) 483 assert.EqualError(t, err, "platform builder failed: fake-builder-error") 484 }) 485 486 t.Run("when building the image fails", func(t *testing.T) { 487 client := &mock.DockerClient{} 488 client.InspectImageReturns(nil, docker.ErrNoSuchImage) 489 client.BuildImageReturns(errors.New("no-build-for-you")) 490 491 fakePlatformBuilder := &mock.PlatformBuilder{} 492 493 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder} 494 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 495 assert.Equal(t, 1, client.InspectImageCallCount()) 496 assert.Equal(t, 1, client.BuildImageCallCount()) 497 assert.EqualError(t, err, "docker image build failed: no-build-for-you") 498 }) 499 } 500 501 type InMemBuilder struct{} 502 503 func (imb InMemBuilder) Build() (io.Reader, error) { 504 buf := &bytes.Buffer{} 505 fmt.Fprintln(buf, "FROM busybox:latest") 506 fmt.Fprintln(buf, `RUN ln -s /bin/true /bin/chaincode`) 507 fmt.Fprintln(buf, `CMD ["tail", "-f", "/dev/null"]`) 508 509 startTime := time.Now() 510 inputbuf := bytes.NewBuffer(nil) 511 gw := gzip.NewWriter(inputbuf) 512 tr := tar.NewWriter(gw) 513 tr.WriteHeader(&tar.Header{ 514 Name: "Dockerfile", 515 Size: int64(buf.Len()), 516 ModTime: startTime, 517 AccessTime: startTime, 518 ChangeTime: startTime, 519 }) 520 tr.Write(buf.Bytes()) 521 tr.Close() 522 gw.Close() 523 return inputbuf, nil 524 } 525 526 type mockBuilder struct { 527 buildFunc func() (io.Reader, error) 528 } 529 530 func (m *mockBuilder) Build() (io.Reader, error) { 531 return m.buildFunc() 532 }