github.com/ahlemtn/fabric@v2.1.1+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 MSPID: "mspid", 114 } 115 116 t.Run("nil TLS config", func(t *testing.T) { 117 env := vm.GetEnv("test", nil) 118 assert.Equal(t, []string{"CORE_CHAINCODE_ID_NAME=test", "LOG_ENV=foo", "CORE_PEER_TLS_ENABLED=false", "CORE_PEER_LOCALMSPID=mspid"}, env) 119 }) 120 121 t.Run("real TLS config", func(t *testing.T) { 122 env := vm.GetEnv("test", &ccintf.TLSConfig{ 123 ClientKey: []byte("key"), 124 ClientCert: []byte("cert"), 125 RootCert: []byte("root"), 126 }) 127 assert.Equal(t, []string{ 128 "CORE_CHAINCODE_ID_NAME=test", 129 "LOG_ENV=foo", 130 "CORE_PEER_TLS_ENABLED=true", 131 "CORE_TLS_CLIENT_KEY_PATH=/etc/hyperledger/fabric/client.key", 132 "CORE_TLS_CLIENT_CERT_PATH=/etc/hyperledger/fabric/client.crt", 133 "CORE_TLS_CLIENT_KEY_FILE=/etc/hyperledger/fabric/client_pem.key", 134 "CORE_TLS_CLIENT_CERT_FILE=/etc/hyperledger/fabric/client_pem.crt", 135 "CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/peer.crt", 136 "CORE_PEER_LOCALMSPID=mspid", 137 }, env) 138 }) 139 } 140 141 func Test_Start(t *testing.T) { 142 gt := NewGomegaWithT(t) 143 dockerClient := &mock.DockerClient{} 144 dvm := DockerVM{ 145 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 146 Client: dockerClient, 147 } 148 149 ccid := "simple:1.0" 150 peerConnection := &ccintf.PeerConnection{ 151 Address: "peer-address", 152 TLSConfig: &ccintf.TLSConfig{ 153 ClientKey: []byte("key"), 154 ClientCert: []byte("cert"), 155 RootCert: []byte("root"), 156 }, 157 } 158 159 // case 1: dockerClient.CreateContainer returns error 160 testError1 := errors.New("junk1") 161 dockerClient.CreateContainerReturns(nil, testError1) 162 err := dvm.Start(ccid, "GOLANG", peerConnection) 163 gt.Expect(err).To(MatchError(testError1)) 164 dockerClient.CreateContainerReturns(&docker.Container{}, nil) 165 166 // case 2: dockerClient.UploadToContainer returns error 167 testError2 := errors.New("junk2") 168 dockerClient.UploadToContainerReturns(testError2) 169 err = dvm.Start(ccid, "GOLANG", peerConnection) 170 gt.Expect(err.Error()).To(ContainSubstring("junk2")) 171 dockerClient.UploadToContainerReturns(nil) 172 173 // case 3: start called and dockerClient.CreateContainer returns 174 // docker.noSuchImgErr and dockerClient.Start returns error 175 testError3 := errors.New("junk3") 176 dvm.AttachStdOut = true 177 dockerClient.CreateContainerReturns(nil, testError3) 178 err = dvm.Start(ccid, "GOLANG", peerConnection) 179 gt.Expect(err).To(MatchError(testError3)) 180 dockerClient.CreateContainerReturns(&docker.Container{}, nil) 181 182 // case 4: GetArgs returns error 183 err = dvm.Start(ccid, "FAKE_TYPE", peerConnection) 184 gt.Expect(err).To(MatchError("could not get args: unknown chaincodeType: FAKE_TYPE")) 185 186 // Success cases 187 err = dvm.Start(ccid, "GOLANG", peerConnection) 188 gt.Expect(err).NotTo(HaveOccurred()) 189 190 // dockerClient.StopContainer returns error 191 err = dvm.Start(ccid, "GOLANG", peerConnection) 192 gt.Expect(err).NotTo(HaveOccurred()) 193 194 // dockerClient.KillContainer returns error 195 err = dvm.Start(ccid, "GOLANG", peerConnection) 196 gt.Expect(err).NotTo(HaveOccurred()) 197 198 // dockerClient.RemoveContainer returns error 199 err = dvm.Start(ccid, "GOLANG", peerConnection) 200 gt.Expect(err).NotTo(HaveOccurred()) 201 202 err = dvm.Start(ccid, "GOLANG", peerConnection) 203 gt.Expect(err).NotTo(HaveOccurred()) 204 } 205 206 func Test_streamOutput(t *testing.T) { 207 gt := NewGomegaWithT(t) 208 209 logger, recorder := floggingtest.NewTestLogger(t) 210 containerLogger, containerRecorder := floggingtest.NewTestLogger(t) 211 212 client := &mock.DockerClient{} 213 errCh := make(chan error, 1) 214 optsCh := make(chan docker.AttachToContainerOptions, 1) 215 client.AttachToContainerStub = func(opts docker.AttachToContainerOptions) error { 216 optsCh <- opts 217 return <-errCh 218 } 219 220 streamOutput(logger, client, "container-name", containerLogger) 221 222 var opts docker.AttachToContainerOptions 223 gt.Eventually(optsCh).Should(Receive(&opts)) 224 gt.Eventually(opts.Success).Should(BeSent(struct{}{})) 225 gt.Eventually(opts.Success).Should(BeClosed()) 226 227 fmt.Fprintf(opts.OutputStream, "message-one\n") 228 fmt.Fprintf(opts.OutputStream, "message-two") // does not get written until after stream closed 229 gt.Eventually(containerRecorder).Should(gbytes.Say("message-one")) 230 gt.Consistently(containerRecorder.Entries).Should(HaveLen(1)) 231 232 close(errCh) 233 234 gt.Eventually(recorder).Should(gbytes.Say("Container container-name has closed its IO channel")) 235 gt.Consistently(recorder.Entries).Should(HaveLen(1)) 236 gt.Eventually(containerRecorder).Should(gbytes.Say("message-two")) 237 gt.Consistently(containerRecorder.Entries).Should(HaveLen(2)) 238 } 239 240 func Test_BuildMetric(t *testing.T) { 241 ccid := "simple:1.0" 242 client := &mock.DockerClient{} 243 244 tests := []struct { 245 desc string 246 buildErr bool 247 expectedLabels []string 248 }{ 249 {desc: "success", buildErr: false, expectedLabels: []string{"chaincode", "simple:1.0", "success", "true"}}, 250 {desc: "failure", buildErr: true, expectedLabels: []string{"chaincode", "simple:1.0", "success", "false"}}, 251 } 252 for _, tt := range tests { 253 t.Run(tt.desc, func(t *testing.T) { 254 gt := NewGomegaWithT(t) 255 fakeChaincodeImageBuildDuration := &metricsfakes.Histogram{} 256 fakeChaincodeImageBuildDuration.WithReturns(fakeChaincodeImageBuildDuration) 257 dvm := DockerVM{ 258 BuildMetrics: &BuildMetrics{ 259 ChaincodeImageBuildDuration: fakeChaincodeImageBuildDuration, 260 }, 261 Client: client, 262 } 263 264 if tt.buildErr { 265 client.BuildImageReturns(errors.New("Error building image")) 266 } 267 dvm.buildImage(ccid, &bytes.Buffer{}) 268 269 gt.Expect(fakeChaincodeImageBuildDuration.WithCallCount()).To(Equal(1)) 270 gt.Expect(fakeChaincodeImageBuildDuration.WithArgsForCall(0)).To(Equal(tt.expectedLabels)) 271 gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).NotTo(BeZero()) 272 gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0)) 273 }) 274 } 275 } 276 277 func Test_Stop(t *testing.T) { 278 dvm := DockerVM{Client: &mock.DockerClient{}} 279 ccid := "simple" 280 281 // Success case 282 err := dvm.Stop(ccid) 283 assert.NoError(t, err) 284 } 285 286 func Test_Wait(t *testing.T) { 287 dvm := DockerVM{} 288 289 // happy path 290 client := &mock.DockerClient{} 291 dvm.Client = client 292 293 client.WaitContainerReturns(99, nil) 294 exitCode, err := dvm.Wait("the-name:the-version") 295 assert.NoError(t, err) 296 assert.Equal(t, 99, exitCode) 297 assert.Equal(t, "the-name-the-version", client.WaitContainerArgsForCall(0)) 298 299 // wait fails 300 client.WaitContainerReturns(99, errors.New("no-wait-for-you")) 301 _, err = dvm.Wait("") 302 assert.EqualError(t, err, "no-wait-for-you") 303 } 304 305 func TestHealthCheck(t *testing.T) { 306 client := &mock.DockerClient{} 307 vm := &DockerVM{Client: client} 308 309 err := vm.HealthCheck(context.Background()) 310 assert.NoError(t, err) 311 312 client.PingWithContextReturns(errors.New("Error pinging daemon")) 313 err = vm.HealthCheck(context.Background()) 314 assert.Error(t, err) 315 assert.Contains(t, err.Error(), "Error pinging daemon") 316 } 317 318 type testCase struct { 319 name string 320 vm *DockerVM 321 ccid string 322 expectedOutput string 323 } 324 325 func TestGetVMNameForDocker(t *testing.T) { 326 tc := []testCase{ 327 { 328 name: "mycc", 329 vm: &DockerVM{NetworkID: "dev", PeerID: "peer0"}, 330 ccid: "mycc:1.0", 331 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-mycc-1.0")))), 332 }, 333 { 334 name: "mycc-nonetworkid", 335 vm: &DockerVM{PeerID: "peer1"}, 336 ccid: "mycc:1.0", 337 expectedOutput: fmt.Sprintf("%s-%s", "peer1-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("peer1-mycc-1.0")))), 338 }, 339 { 340 name: "myCC-UCids", 341 vm: &DockerVM{NetworkID: "Dev", PeerID: "Peer0"}, 342 ccid: "myCC:1.0", 343 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev-Peer0-myCC-1.0")))), 344 }, 345 { 346 name: "myCC-idsWithSpecialChars", 347 vm: &DockerVM{NetworkID: "Dev$dev", PeerID: "Peer*0"}, 348 ccid: "myCC:1.0", 349 expectedOutput: fmt.Sprintf("%s-%s", "dev-dev-peer-0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev$dev-Peer*0-myCC-1.0")))), 350 }, 351 { 352 name: "mycc-nopeerid", 353 vm: &DockerVM{NetworkID: "dev"}, 354 ccid: "mycc:1.0", 355 expectedOutput: fmt.Sprintf("%s-%s", "dev-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-mycc-1.0")))), 356 }, 357 { 358 name: "myCC-LCids", 359 vm: &DockerVM{NetworkID: "dev", PeerID: "peer0"}, 360 ccid: "myCC:1.0", 361 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-myCC-1.0")))), 362 }, 363 } 364 365 for _, test := range tc { 366 name, err := test.vm.GetVMNameForDocker(test.ccid) 367 assert.Nil(t, err, "Expected nil error") 368 assert.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name) 369 } 370 371 } 372 373 func TestGetVMName(t *testing.T) { 374 tc := []testCase{ 375 { 376 name: "myCC-preserveCase", 377 vm: &DockerVM{NetworkID: "Dev", PeerID: "Peer0"}, 378 ccid: "myCC:1.0", 379 expectedOutput: fmt.Sprintf("%s", "Dev-Peer0-myCC-1.0"), 380 }, 381 } 382 383 for _, test := range tc { 384 name := test.vm.GetVMName(test.ccid) 385 assert.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name) 386 } 387 388 } 389 390 func Test_buildImage(t *testing.T) { 391 client := &mock.DockerClient{} 392 dvm := DockerVM{ 393 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 394 Client: client, 395 NetworkMode: "network-mode", 396 } 397 398 err := dvm.buildImage("simple", &bytes.Buffer{}) 399 assert.NoError(t, err) 400 assert.Equal(t, 1, client.BuildImageCallCount()) 401 402 opts := client.BuildImageArgsForCall(0) 403 assert.Equal(t, "simple-a7a39b72f29718e653e73503210fbb597057b7a1c77d1fe321a1afcff041d4e1", opts.Name) 404 assert.False(t, opts.Pull) 405 assert.Equal(t, "network-mode", opts.NetworkMode) 406 assert.Equal(t, &bytes.Buffer{}, opts.InputStream) 407 assert.NotNil(t, opts.OutputStream) 408 } 409 410 func Test_buildImageFailure(t *testing.T) { 411 client := &mock.DockerClient{} 412 client.BuildImageReturns(errors.New("oh-bother-we-failed-badly")) 413 dvm := DockerVM{ 414 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 415 Client: client, 416 NetworkMode: "network-mode", 417 } 418 419 err := dvm.buildImage("simple", &bytes.Buffer{}) 420 assert.EqualError(t, err, "oh-bother-we-failed-badly") 421 } 422 423 func TestBuild(t *testing.T) { 424 buildMetrics := NewBuildMetrics(&disabled.Provider{}) 425 md := &persistence.ChaincodePackageMetadata{ 426 Type: "type", 427 Path: "path", 428 } 429 430 t.Run("when the image does not exist", func(t *testing.T) { 431 client := &mock.DockerClient{} 432 client.InspectImageReturns(nil, docker.ErrNoSuchImage) 433 434 fakePlatformBuilder := &mock.PlatformBuilder{} 435 fakePlatformBuilder.GenerateDockerBuildReturns(&bytes.Buffer{}, nil) 436 437 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder} 438 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 439 assert.NoError(t, err, "should have built successfully") 440 441 assert.Equal(t, 1, client.BuildImageCallCount()) 442 443 require.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount()) 444 ccType, path, codePackageStream := fakePlatformBuilder.GenerateDockerBuildArgsForCall(0) 445 assert.Equal(t, "TYPE", ccType) 446 assert.Equal(t, "path", path) 447 codePackage, err := ioutil.ReadAll(codePackageStream) 448 require.NoError(t, err) 449 assert.Equal(t, []byte("code-package"), codePackage) 450 451 }) 452 453 t.Run("when inspecting the image fails", func(t *testing.T) { 454 client := &mock.DockerClient{} 455 client.InspectImageReturns(nil, errors.New("inspecting-image-fails")) 456 457 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics} 458 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 459 assert.EqualError(t, err, "docker image inspection failed: inspecting-image-fails") 460 461 assert.Equal(t, 0, client.BuildImageCallCount()) 462 }) 463 464 t.Run("when the image exists", func(t *testing.T) { 465 client := &mock.DockerClient{} 466 467 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics} 468 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 469 assert.NoError(t, err) 470 471 assert.Equal(t, 0, client.BuildImageCallCount()) 472 }) 473 474 t.Run("when the platform builder fails", func(t *testing.T) { 475 client := &mock.DockerClient{} 476 client.InspectImageReturns(nil, docker.ErrNoSuchImage) 477 client.BuildImageReturns(errors.New("no-build-for-you")) 478 479 fakePlatformBuilder := &mock.PlatformBuilder{} 480 fakePlatformBuilder.GenerateDockerBuildReturns(nil, errors.New("fake-builder-error")) 481 482 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder} 483 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 484 assert.Equal(t, 1, client.InspectImageCallCount()) 485 assert.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount()) 486 assert.Equal(t, 0, client.BuildImageCallCount()) 487 assert.EqualError(t, err, "platform builder failed: fake-builder-error") 488 }) 489 490 t.Run("when building the image fails", func(t *testing.T) { 491 client := &mock.DockerClient{} 492 client.InspectImageReturns(nil, docker.ErrNoSuchImage) 493 client.BuildImageReturns(errors.New("no-build-for-you")) 494 495 fakePlatformBuilder := &mock.PlatformBuilder{} 496 497 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder} 498 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 499 assert.Equal(t, 1, client.InspectImageCallCount()) 500 assert.Equal(t, 1, client.BuildImageCallCount()) 501 assert.EqualError(t, err, "docker image build failed: no-build-for-you") 502 }) 503 } 504 505 type InMemBuilder struct{} 506 507 func (imb InMemBuilder) Build() (io.Reader, error) { 508 buf := &bytes.Buffer{} 509 fmt.Fprintln(buf, "FROM busybox:latest") 510 fmt.Fprintln(buf, `RUN ln -s /bin/true /bin/chaincode`) 511 fmt.Fprintln(buf, `CMD ["tail", "-f", "/dev/null"]`) 512 513 startTime := time.Now() 514 inputbuf := bytes.NewBuffer(nil) 515 gw := gzip.NewWriter(inputbuf) 516 tr := tar.NewWriter(gw) 517 tr.WriteHeader(&tar.Header{ 518 Name: "Dockerfile", 519 Size: int64(buf.Len()), 520 ModTime: startTime, 521 AccessTime: startTime, 522 ChangeTime: startTime, 523 }) 524 tr.Write(buf.Bytes()) 525 tr.Close() 526 gw.Close() 527 return inputbuf, nil 528 } 529 530 type mockBuilder struct { 531 buildFunc func() (io.Reader, error) 532 } 533 534 func (m *mockBuilder) Build() (io.Reader, error) { 535 return m.buildFunc() 536 }