github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/container/dockercontroller/dockercontroller_test.go (about) 1 /* 2 Copyright hechain. 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 "github.com/hechain20/hechain/common/flogging/floggingtest" 24 "github.com/hechain20/hechain/common/metrics/disabled" 25 "github.com/hechain20/hechain/common/metrics/metricsfakes" 26 "github.com/hechain20/hechain/common/util" 27 "github.com/hechain20/hechain/core/chaincode/persistence" 28 "github.com/hechain20/hechain/core/container/ccintf" 29 "github.com/hechain20/hechain/core/container/dockercontroller/mock" 30 pb "github.com/hyperledger/fabric-protos-go/peer" 31 . "github.com/onsi/gomega" 32 "github.com/onsi/gomega/gbytes" 33 "github.com/stretchr/testify/require" 34 ) 35 36 // This test used to be part of an integration style test in core/container, moved to here 37 func TestIntegrationPath(t *testing.T) { 38 client, err := docker.NewClientFromEnv() 39 require.NoError(t, err) 40 41 fakePlatformBuilder := &mock.PlatformBuilder{} 42 fakePlatformBuilder.GenerateDockerBuildReturns(InMemBuilder{}.Build()) 43 44 dc := DockerVM{ 45 PeerID: "", 46 NetworkID: util.GenerateUUID(), 47 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 48 Client: client, 49 PlatformBuilder: fakePlatformBuilder, 50 } 51 ccid := "simple" 52 53 instance, err := dc.Build("simple", &persistence.ChaincodePackageMetadata{ 54 Type: "type", 55 Path: "path", 56 }, bytes.NewBuffer([]byte("code-package"))) 57 require.NoError(t, err) 58 59 require.Equal(t, &ContainerInstance{ 60 CCID: "simple", 61 Type: "TYPE", 62 DockerVM: &dc, 63 }, instance) 64 65 err = dc.Start(ccid, "GOLANG", &ccintf.PeerConnection{ 66 Address: "peer-address", 67 }) 68 require.NoError(t, err) 69 70 err = dc.Stop(ccid) 71 require.NoError(t, err) 72 } 73 74 var expectedNodeStartScript = ` 75 set -e 76 if [ -x /chaincode/start.sh ]; then 77 /chaincode/start.sh --peer.address peer-address 78 else 79 cd /usr/local/src 80 npm start -- --peer.address peer-address 81 fi 82 ` 83 84 func TestGetArgs(t *testing.T) { 85 tests := []struct { 86 name string 87 ccType pb.ChaincodeSpec_Type 88 expectedArgs []string 89 expectedErr string 90 }{ 91 {"golang-chaincode", pb.ChaincodeSpec_GOLANG, []string{"chaincode", "-peer.address=peer-address"}, ""}, 92 {"java-chaincode", pb.ChaincodeSpec_JAVA, []string{"/root/chaincode-java/start", "--peerAddress", "peer-address"}, ""}, 93 {"node-chaincode", pb.ChaincodeSpec_NODE, []string{"/bin/sh", "-c", expectedNodeStartScript}, ""}, 94 {"unknown-chaincode", pb.ChaincodeSpec_Type(999), []string{}, "unknown chaincodeType: 999"}, 95 } 96 for _, tc := range tests { 97 vm := &DockerVM{} 98 99 args, err := vm.GetArgs(tc.ccType.String(), "peer-address") 100 if tc.expectedErr != "" { 101 require.EqualError(t, err, tc.expectedErr) 102 continue 103 } 104 require.NoError(t, err) 105 require.Equal(t, tc.expectedArgs, args) 106 } 107 } 108 109 func TestGetEnv(t *testing.T) { 110 vm := &DockerVM{ 111 LoggingEnv: []string{"LOG_ENV=foo"}, 112 MSPID: "mspid", 113 } 114 115 t.Run("nil TLS config", func(t *testing.T) { 116 env := vm.GetEnv("test", nil) 117 require.Equal(t, []string{"CORE_CHAINCODE_ID_NAME=test", "LOG_ENV=foo", "CORE_PEER_TLS_ENABLED=false", "CORE_PEER_LOCALMSPID=mspid"}, 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 require.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 "CORE_PEER_LOCALMSPID=mspid", 136 }, env) 137 }) 138 } 139 140 func Test_Start(t *testing.T) { 141 gt := NewGomegaWithT(t) 142 dockerClient := &mock.DockerClient{} 143 dvm := DockerVM{ 144 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 145 Client: dockerClient, 146 } 147 148 ccid := "simple:1.0" 149 peerConnection := &ccintf.PeerConnection{ 150 Address: "peer-address", 151 TLSConfig: &ccintf.TLSConfig{ 152 ClientKey: []byte("key"), 153 ClientCert: []byte("cert"), 154 RootCert: []byte("root"), 155 }, 156 } 157 158 // case 1: dockerClient.CreateContainer returns error 159 testError1 := errors.New("junk1") 160 dockerClient.CreateContainerReturns(nil, testError1) 161 err := dvm.Start(ccid, "GOLANG", peerConnection) 162 gt.Expect(err).To(MatchError(testError1)) 163 dockerClient.CreateContainerReturns(&docker.Container{}, nil) 164 165 // case 2: dockerClient.UploadToContainer returns error 166 testError2 := errors.New("junk2") 167 dockerClient.UploadToContainerReturns(testError2) 168 err = dvm.Start(ccid, "GOLANG", peerConnection) 169 gt.Expect(err.Error()).To(ContainSubstring("junk2")) 170 dockerClient.UploadToContainerReturns(nil) 171 172 // case 3: start called and dockerClient.CreateContainer returns 173 // docker.noSuchImgErr and dockerClient.Start returns error 174 testError3 := errors.New("junk3") 175 dvm.AttachStdOut = true 176 dockerClient.CreateContainerReturns(nil, testError3) 177 err = dvm.Start(ccid, "GOLANG", peerConnection) 178 gt.Expect(err).To(MatchError(testError3)) 179 dockerClient.CreateContainerReturns(&docker.Container{}, nil) 180 181 // case 4: GetArgs returns error 182 err = dvm.Start(ccid, "FAKE_TYPE", peerConnection) 183 gt.Expect(err).To(MatchError("could not get args: unknown chaincodeType: FAKE_TYPE")) 184 185 // Success cases 186 err = dvm.Start(ccid, "GOLANG", peerConnection) 187 gt.Expect(err).NotTo(HaveOccurred()) 188 189 // dockerClient.StopContainer returns error 190 err = dvm.Start(ccid, "GOLANG", peerConnection) 191 gt.Expect(err).NotTo(HaveOccurred()) 192 193 // dockerClient.KillContainer returns error 194 err = dvm.Start(ccid, "GOLANG", peerConnection) 195 gt.Expect(err).NotTo(HaveOccurred()) 196 197 // dockerClient.RemoveContainer returns error 198 err = dvm.Start(ccid, "GOLANG", peerConnection) 199 gt.Expect(err).NotTo(HaveOccurred()) 200 201 err = dvm.Start(ccid, "GOLANG", peerConnection) 202 gt.Expect(err).NotTo(HaveOccurred()) 203 } 204 205 func Test_streamOutput(t *testing.T) { 206 gt := NewGomegaWithT(t) 207 208 logger, recorder := floggingtest.NewTestLogger(t) 209 containerLogger, containerRecorder := floggingtest.NewTestLogger(t) 210 211 client := &mock.DockerClient{} 212 errCh := make(chan error, 1) 213 optsCh := make(chan docker.AttachToContainerOptions, 1) 214 client.AttachToContainerStub = func(opts docker.AttachToContainerOptions) error { 215 optsCh <- opts 216 return <-errCh 217 } 218 219 streamOutput(logger, client, "container-name", containerLogger) 220 221 var opts docker.AttachToContainerOptions 222 gt.Eventually(optsCh).Should(Receive(&opts)) 223 gt.Eventually(opts.Success).Should(BeSent(struct{}{})) 224 gt.Eventually(opts.Success).Should(BeClosed()) 225 226 fmt.Fprintf(opts.OutputStream, "message-one\n") 227 fmt.Fprintf(opts.OutputStream, "message-two") // does not get written until after stream closed 228 gt.Eventually(containerRecorder).Should(gbytes.Say("message-one")) 229 gt.Consistently(containerRecorder.Entries).Should(HaveLen(1)) 230 231 close(errCh) 232 233 gt.Eventually(recorder).Should(gbytes.Say("Container container-name has closed its IO channel")) 234 gt.Consistently(recorder.Entries).Should(HaveLen(1)) 235 gt.Eventually(containerRecorder).Should(gbytes.Say("message-two")) 236 gt.Consistently(containerRecorder.Entries).Should(HaveLen(2)) 237 } 238 239 func Test_BuildMetric(t *testing.T) { 240 ccid := "simple:1.0" 241 client := &mock.DockerClient{} 242 243 tests := []struct { 244 desc string 245 buildErr bool 246 expectedLabels []string 247 }{ 248 {desc: "success", buildErr: false, expectedLabels: []string{"chaincode", "simple:1.0", "success", "true"}}, 249 {desc: "failure", buildErr: true, expectedLabels: []string{"chaincode", "simple:1.0", "success", "false"}}, 250 } 251 for _, tt := range tests { 252 t.Run(tt.desc, func(t *testing.T) { 253 gt := NewGomegaWithT(t) 254 fakeChaincodeImageBuildDuration := &metricsfakes.Histogram{} 255 fakeChaincodeImageBuildDuration.WithReturns(fakeChaincodeImageBuildDuration) 256 dvm := DockerVM{ 257 BuildMetrics: &BuildMetrics{ 258 ChaincodeImageBuildDuration: fakeChaincodeImageBuildDuration, 259 }, 260 Client: client, 261 } 262 263 if tt.buildErr { 264 client.BuildImageReturns(errors.New("Error building image")) 265 } 266 dvm.buildImage(ccid, &bytes.Buffer{}) 267 268 gt.Expect(fakeChaincodeImageBuildDuration.WithCallCount()).To(Equal(1)) 269 gt.Expect(fakeChaincodeImageBuildDuration.WithArgsForCall(0)).To(Equal(tt.expectedLabels)) 270 gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).NotTo(BeZero()) 271 gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0)) 272 }) 273 } 274 } 275 276 func Test_Stop(t *testing.T) { 277 dvm := DockerVM{Client: &mock.DockerClient{}} 278 ccid := "simple" 279 280 // Success case 281 err := dvm.Stop(ccid) 282 require.NoError(t, err) 283 } 284 285 func Test_Wait(t *testing.T) { 286 dvm := DockerVM{} 287 288 // happy path 289 client := &mock.DockerClient{} 290 dvm.Client = client 291 292 client.WaitContainerReturns(99, nil) 293 exitCode, err := dvm.Wait("the-name:the-version") 294 require.NoError(t, err) 295 require.Equal(t, 99, exitCode) 296 require.Equal(t, "the-name-the-version", client.WaitContainerArgsForCall(0)) 297 298 // wait fails 299 client.WaitContainerReturns(99, errors.New("no-wait-for-you")) 300 _, err = dvm.Wait("") 301 require.EqualError(t, err, "no-wait-for-you") 302 } 303 304 func TestHealthCheck(t *testing.T) { 305 client := &mock.DockerClient{} 306 vm := &DockerVM{Client: client} 307 308 err := vm.HealthCheck(context.Background()) 309 require.NoError(t, err) 310 311 client.PingWithContextReturns(errors.New("Error pinging daemon")) 312 err = vm.HealthCheck(context.Background()) 313 require.Error(t, err) 314 require.Contains(t, err.Error(), "Error pinging daemon") 315 } 316 317 type testCase struct { 318 name string 319 vm *DockerVM 320 ccid string 321 expectedOutput string 322 } 323 324 func TestGetVMNameForDocker(t *testing.T) { 325 tc := []testCase{ 326 { 327 name: "mycc", 328 vm: &DockerVM{NetworkID: "dev", PeerID: "peer0"}, 329 ccid: "mycc:1.0", 330 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-mycc-1.0")))), 331 }, 332 { 333 name: "mycc-nonetworkid", 334 vm: &DockerVM{PeerID: "peer1"}, 335 ccid: "mycc:1.0", 336 expectedOutput: fmt.Sprintf("%s-%s", "peer1-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("peer1-mycc-1.0")))), 337 }, 338 { 339 name: "myCC-UCids", 340 vm: &DockerVM{NetworkID: "Dev", PeerID: "Peer0"}, 341 ccid: "myCC:1.0", 342 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev-Peer0-myCC-1.0")))), 343 }, 344 { 345 name: "myCC-idsWithSpecialChars", 346 vm: &DockerVM{NetworkID: "Dev$dev", PeerID: "Peer*0"}, 347 ccid: "myCC:1.0", 348 expectedOutput: fmt.Sprintf("%s-%s", "dev-dev-peer-0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev$dev-Peer*0-myCC-1.0")))), 349 }, 350 { 351 name: "mycc-nopeerid", 352 vm: &DockerVM{NetworkID: "dev"}, 353 ccid: "mycc:1.0", 354 expectedOutput: fmt.Sprintf("%s-%s", "dev-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-mycc-1.0")))), 355 }, 356 { 357 name: "myCC-LCids", 358 vm: &DockerVM{NetworkID: "dev", PeerID: "peer0"}, 359 ccid: "myCC:1.0", 360 expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-myCC-1.0")))), 361 }, 362 } 363 364 for _, test := range tc { 365 name, err := test.vm.GetVMNameForDocker(test.ccid) 366 require.Nil(t, err, "Expected nil error") 367 require.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name) 368 } 369 } 370 371 func TestGetVMName(t *testing.T) { 372 tc := []testCase{ 373 { 374 name: "myCC-preserveCase", 375 vm: &DockerVM{NetworkID: "Dev", PeerID: "Peer0"}, 376 ccid: "myCC:1.0", 377 expectedOutput: "Dev-Peer0-myCC-1.0", 378 }, 379 } 380 381 for _, test := range tc { 382 name := test.vm.GetVMName(test.ccid) 383 require.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name) 384 } 385 } 386 387 func Test_buildImage(t *testing.T) { 388 client := &mock.DockerClient{} 389 dvm := DockerVM{ 390 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 391 Client: client, 392 NetworkMode: "network-mode", 393 } 394 395 err := dvm.buildImage("simple", &bytes.Buffer{}) 396 require.NoError(t, err) 397 require.Equal(t, 1, client.BuildImageCallCount()) 398 399 opts := client.BuildImageArgsForCall(0) 400 require.Equal(t, "simple-a7a39b72f29718e653e73503210fbb597057b7a1c77d1fe321a1afcff041d4e1", opts.Name) 401 require.False(t, opts.Pull) 402 require.Equal(t, "network-mode", opts.NetworkMode) 403 require.Equal(t, &bytes.Buffer{}, opts.InputStream) 404 require.NotNil(t, opts.OutputStream) 405 } 406 407 func Test_buildImageFailure(t *testing.T) { 408 client := &mock.DockerClient{} 409 client.BuildImageReturns(errors.New("oh-bother-we-failed-badly")) 410 dvm := DockerVM{ 411 BuildMetrics: NewBuildMetrics(&disabled.Provider{}), 412 Client: client, 413 NetworkMode: "network-mode", 414 } 415 416 err := dvm.buildImage("simple", &bytes.Buffer{}) 417 require.EqualError(t, err, "oh-bother-we-failed-badly") 418 } 419 420 func TestBuild(t *testing.T) { 421 buildMetrics := NewBuildMetrics(&disabled.Provider{}) 422 md := &persistence.ChaincodePackageMetadata{ 423 Type: "type", 424 Path: "path", 425 } 426 427 t.Run("when the image does not exist", func(t *testing.T) { 428 client := &mock.DockerClient{} 429 client.InspectImageReturns(nil, docker.ErrNoSuchImage) 430 431 fakePlatformBuilder := &mock.PlatformBuilder{} 432 fakePlatformBuilder.GenerateDockerBuildReturns(&bytes.Buffer{}, nil) 433 434 dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder} 435 _, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package"))) 436 require.NoError(t, err, "should have built successfully") 437 438 require.Equal(t, 1, client.BuildImageCallCount()) 439 440 require.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount()) 441 ccType, path, codePackageStream := fakePlatformBuilder.GenerateDockerBuildArgsForCall(0) 442 require.Equal(t, "TYPE", ccType) 443 require.Equal(t, "path", path) 444 codePackage, err := ioutil.ReadAll(codePackageStream) 445 require.NoError(t, err) 446 require.Equal(t, []byte("code-package"), codePackage) 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 require.EqualError(t, err, "docker image inspection failed: inspecting-image-fails") 456 457 require.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 require.NoError(t, err) 466 467 require.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 require.Equal(t, 1, client.InspectImageCallCount()) 481 require.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount()) 482 require.Equal(t, 0, client.BuildImageCallCount()) 483 require.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 require.Equal(t, 1, client.InspectImageCallCount()) 496 require.Equal(t, 1, client.BuildImageCallCount()) 497 require.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 }