From 7ed95334f68057d948e8241904a568f25f60a320 Mon Sep 17 00:00:00 2001 From: Satheesh Kathamuthu Date: Fri, 12 Aug 2016 17:26:00 +0530 Subject: [PATCH] Allow deploying Java chaincode from remote git repositories Currently only local deployment of Java chaincode is supported. Change-Id: Id7651f6eeae232260f76f2280b935f7c9a15d169 Signed-off-by: Satheesh Kathamuthu --- bddtests/java_shim.feature | 37 +++++++ core/chaincode/platforms/java/hash.go | 29 +++-- core/chaincode/platforms/java/hash_test.go | 119 +++++++++++++++++++++ core/chaincode/platforms/java/package.go | 26 +++-- core/container/util/writer.go | 38 ++++--- 5 files changed, 217 insertions(+), 32 deletions(-) create mode 100644 core/chaincode/platforms/java/hash_test.go diff --git a/bddtests/java_shim.feature b/bddtests/java_shim.feature index ee1ee0fdcea..f66a9b6869a 100644 --- a/bddtests/java_shim.feature +++ b/bddtests/java_shim.feature @@ -173,3 +173,40 @@ Scenario: java RangeExample chaincode single peer |arg1| | 2 | Then I should get a JSON response with "result.message" = "No record found !" + Scenario: Java chaincode example from remote git repository + Given we compose "docker-compose-1.yml" + When requesting "/chain" from "vp0" + Then I should get a JSON response with "height" = "1" + # TODO Needs to be replaced with an official test repo in the future. + When I deploy lang chaincode "http://github.com/xspeedcruiser/javachaincode" of "JAVA" with ctor "init" to "vp0" + | arg1 | arg2 | arg3 | arg4 | + | a | 100 | b | 200 | + Then I should have received a chaincode name + Then I wait up to "300" seconds for transaction to be committed to all peers + + When requesting "/chain" from "vp0" + Then I should get a JSON response with "height" = "2" + + When I query chaincode "SimpleSample" function name "query" on "vp0": + |arg1| + | a | + Then I should get a JSON response with "result.message" = "{'Name':'a','Amount':'100'}" + + When I invoke chaincode "SimpleSample" function name "transfer" on "vp0" + |arg1|arg2|arg3| + | a | b | 10 | + Then I should have received a transactionID + Then I wait up to "25" seconds for transaction to be committed to all peers + + When requesting "/chain" from "vp0" + Then I should get a JSON response with "height" = "3" + + When I query chaincode "SimpleSample" function name "query" on "vp0": + |arg1| + | a | + Then I should get a JSON response with "result.message" = "{'Name':'a','Amount':'90'}" + + When I query chaincode "SimpleSample" function name "query" on "vp0": + |arg1| + | b | + Then I should get a JSON response with "result.message" = "{'Name':'b','Amount':'210'}" diff --git a/core/chaincode/platforms/java/hash.go b/core/chaincode/platforms/java/hash.go index 14abec13486..466375ca068 100644 --- a/core/chaincode/platforms/java/hash.go +++ b/core/chaincode/platforms/java/hash.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "strings" "github.com/golang/protobuf/proto" @@ -71,8 +72,24 @@ func isCodeExist(tmppath string) error { } func getCodeFromHTTP(path string) (codegopath string, err error) { - //TODO - return "", nil + + var tmp string + tmp, err = ioutil.TempDir("", "javachaincode") + + if err != nil { + return "", fmt.Errorf("Error creating temporary file: %s", err) + } + var out bytes.Buffer + + cmd := exec.Command("git", "clone", path, tmp) + cmd.Stderr = &out + cmderr := cmd.Run() + if cmderr != nil { + return "", fmt.Errorf("Error cloning git repository %s", cmderr) + } + + return tmp, nil + } //generateHashcode gets hashcode of the code under path. If path is a HTTP(s) url @@ -106,13 +123,9 @@ func generateHashcode(spec *pb.ChaincodeSpec, tw *tar.Writer) (string, error) { }() var err error - if strings.HasPrefix(codepath, "http://") { - ishttp = true - codepath = codepath[7:] - codepath, err = getCodeFromHTTP(codepath) - } else if strings.HasPrefix(codepath, "https://") { + if strings.HasPrefix(codepath, "http://") || + strings.HasPrefix(codepath, "https://") { ishttp = true - codepath = codepath[8:] codepath, err = getCodeFromHTTP(codepath) } else if !strings.HasPrefix(codepath, "/") { wd := "" diff --git a/core/chaincode/platforms/java/hash_test.go b/core/chaincode/platforms/java/hash_test.go new file mode 100644 index 00000000000..8b525a3851f --- /dev/null +++ b/core/chaincode/platforms/java/hash_test.go @@ -0,0 +1,119 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package java + +import ( + "testing" + + "encoding/hex" + + "github.com/hyperledger/fabric/core/util" + + "bytes" + "os" +) + +func TestHashDiffRemoteRepo(t *testing.T) { + b := []byte("firstcontent") + hash := util.ComputeCryptoHash(b) + // TODO Change reference to an official test repo once established + // Same repo being used in behave tests + + srcPath1, err := getCodeFromHTTP("https://github.com/xspeedcruiser/javachaincodemvn") + srcPath2, err := getCodeFromHTTP("https://github.com/xspeedcruiser/javachaincode") + + if err != nil { + t.Logf("Error getting code from remote repo %s", err) + t.Fail() + } + + defer func() { + os.RemoveAll(srcPath1) + }() + defer func() { + os.RemoveAll(srcPath2) + }() + hash1, err := hashFilesInDir(srcPath1, srcPath1, hash, nil) + if err != nil { + t.Logf("Error getting code %s", err) + t.Fail() + } + hash2, err := hashFilesInDir(srcPath2, srcPath2, hash, nil) + if err != nil { + t.Logf("Error getting code %s", err) + t.Fail() + } + if bytes.Compare(hash1, hash2) == 0 { + t.Logf("Hash should be different for 2 different remote repos") + t.Fail() + } + +} +func TestHashSameRemoteRepo(t *testing.T) { + b := []byte("firstcontent") + hash := util.ComputeCryptoHash(b) + + srcPath1, err := getCodeFromHTTP("https://github.com/xspeedcruiser/javachaincodemvn") + srcPath2, err := getCodeFromHTTP("https://github.com/xspeedcruiser/javachaincodemvn") + + if err != nil { + t.Logf("Error getting code from remote repo %s", err) + t.Fail() + } + + defer func() { + os.RemoveAll(srcPath1) + }() + defer func() { + os.RemoveAll(srcPath2) + }() + hash1, err := hashFilesInDir(srcPath1, srcPath1, hash, nil) + if err != nil { + t.Logf("Error getting code %s", err) + t.Fail() + } + hash2, err := hashFilesInDir(srcPath2, srcPath2, hash, nil) + if err != nil { + t.Logf("Error getting code %s", err) + t.Fail() + } + if bytes.Compare(hash1, hash2) != 0 { + t.Logf("Hash should be same across multiple downloads") + t.Fail() + } +} + +func TestHashOverLocalDir(t *testing.T) { + b := []byte("firstcontent") + hash := util.ComputeCryptoHash(b) + + hash, err := hashFilesInDir(".", "../golang/hashtestfiles", hash, nil) + + if err != nil { + t.Fail() + t.Logf("error : %s", err) + } + + expectedHash := "7b3b2193bed2bd7c19300aa5d6d7f6bb4d61602e4978a78bc08028379cb5cf0ed877bd9db3e990230e8bf6c974edd765f3027f061fd8657d30fc858a676a6f4a" + + computedHash := hex.EncodeToString(hash[:]) + + if expectedHash != computedHash { + t.Fail() + t.Logf("Hash expected to be unchanged") + } +} diff --git a/core/chaincode/platforms/java/package.go b/core/chaincode/platforms/java/package.go index 928ee067a64..fc1a35220f1 100644 --- a/core/chaincode/platforms/java/package.go +++ b/core/chaincode/platforms/java/package.go @@ -6,6 +6,8 @@ import ( "strings" "time" + "os" + cutil "github.com/hyperledger/fabric/core/container/util" pb "github.com/hyperledger/fabric/protos" "github.com/spf13/viper" @@ -16,17 +18,20 @@ import ( func writeChaincodePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error { var urlLocation string - if strings.HasPrefix(spec.ChaincodeID.Path, "http://") { - urlLocation = spec.ChaincodeID.Path[7:] - } else if strings.HasPrefix(spec.ChaincodeID.Path, "https://") { - urlLocation = spec.ChaincodeID.Path[8:] + var err error + + if strings.HasPrefix(spec.ChaincodeID.Path, "http://") || + strings.HasPrefix(spec.ChaincodeID.Path, "https://") { + + urlLocation, err = getCodeFromHTTP(spec.ChaincodeID.Path) + defer func() { + os.RemoveAll(urlLocation) + }() + if err != nil { + return err + } } else { urlLocation = spec.ChaincodeID.Path - // if !strings.HasPrefix(urlLocation, "/") { - // wd := "" - // wd, _ = os.Getwd() - // urlLocation = wd + "/" + urlLocation - // } } if urlLocation == "" { @@ -36,7 +41,6 @@ func writeChaincodePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error { if strings.LastIndex(urlLocation, "/") == len(urlLocation)-1 { urlLocation = urlLocation[:len(urlLocation)-1] } - urlLocation = urlLocation[strings.LastIndex(urlLocation, "/")+1:] var dockerFileContents string var buf []string @@ -58,7 +62,7 @@ func writeChaincodePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error { var zeroTime time.Time tw.WriteHeader(&tar.Header{Name: "Dockerfile", Size: dockerFileSize, ModTime: zeroTime, AccessTime: zeroTime, ChangeTime: zeroTime}) tw.Write([]byte(dockerFileContents)) - err := cutil.WriteJavaProjectToPackage(tw, spec.ChaincodeID.Path) + err = cutil.WriteJavaProjectToPackage(tw, urlLocation) if err != nil { return fmt.Errorf("Error writing Chaincode package contents: %s", err) } diff --git a/core/container/util/writer.go b/core/container/util/writer.go index d5dc29f3a31..90903d7c2b1 100644 --- a/core/container/util/writer.go +++ b/core/container/util/writer.go @@ -32,20 +32,21 @@ import ( var vmLogger = logging.MustGetLogger("container") -var fileTypes = map[string]bool{ +var includeFileTypes = map[string]bool{ ".c": true, ".h": true, ".go": true, ".yaml": true, ".json": true, } -var javaFileTypes = map[string]bool{ - ".java": true, - ".properties": true, - ".gradle": true, + +// These filetypes are excluded while creating the tar package sent to Docker +// Generated .class and other temporary files can be excluded +var javaExcludeFileTypes = map[string]bool{ + ".class": true, } -func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string, includeFileTypeMap map[string]bool) error { +func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string, includeFileTypeMap map[string]bool, excludeFileTypeMap map[string]bool) error { rootDirectory := srcPath vmLogger.Infof("rootDirectory = %s", rootDirectory) @@ -75,11 +76,20 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string, if len(path[rootDirLen:]) == 0 { return nil } - - // we only want 'fileTypes' source files at this point ext := filepath.Ext(path) - if _, ok := includeFileTypeMap[ext]; ok != true { - return nil + + if includeFileTypeMap != nil { + // we only want 'fileTypes' source files at this point + if _, ok := includeFileTypeMap[ext]; ok != true { + return nil + } + } + + //exclude the given file types + if excludeFileTypeMap != nil { + if exclude, ok := excludeFileTypeMap[ext]; ok && exclude { + return nil + } } newPath := fmt.Sprintf("src%s", path[rootDirLen:]) @@ -89,7 +99,6 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string, if err != nil { return fmt.Errorf("Error writing file to package: %s", err) } - return nil } @@ -109,7 +118,7 @@ func WriteGopathSrc(tw *tar.Writer, excludeDir string) error { rootDirectory := filepath.Join(gopath, "src") vmLogger.Infof("rootDirectory = %s", rootDirectory) - if err := WriteFolderToTarPackage(tw, rootDirectory, excludeDir, fileTypes); err != nil { + if err := WriteFolderToTarPackage(tw, rootDirectory, excludeDir, includeFileTypes, nil); err != nil { vmLogger.Errorf("Error writing folder to tar package %s", err) return err } @@ -130,9 +139,12 @@ func WriteGopathSrc(tw *tar.Writer, excludeDir string) error { return nil } +//Package Java project to tar file from the source path func WriteJavaProjectToPackage(tw *tar.Writer, srcPath string) error { - if err := WriteFolderToTarPackage(tw, srcPath, "", javaFileTypes); err != nil { + vmLogger.Debugf("Packaging Java project from path %s", srcPath) + + if err := WriteFolderToTarPackage(tw, srcPath, "", nil, javaExcludeFileTypes); err != nil { vmLogger.Errorf("Error writing folder to tar package %s", err) return err