Skip to content

Commit

Permalink
Allow deploying Java chaincode from remote git repo
Browse files Browse the repository at this point in the history
Currently only local deployment of Java chaincode is supported.

Change-Id: Ib22ec81e7d1df137ee5ecd0f92353efdcad78d25
Signed-off-by: Satheesh Kathamuthu <satheesh.ceg@gmail.com>
  • Loading branch information
xspeedcruiser committed Dec 1, 2016
1 parent 2570f8f commit 80140c9
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 32 deletions.
37 changes: 37 additions & 0 deletions bddtests/java_shim.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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/hyperledger/fabric-test-resources/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'}"
29 changes: 21 additions & 8 deletions core/chaincode/platforms/java/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"

"github.com/golang/protobuf/proto"
Expand Down Expand Up @@ -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

}

//collectChaincodeFiles collects chaincode files and generates hashcode for the
Expand Down Expand Up @@ -106,13 +123,9 @@ func collectChaincodeFiles(spec *pb.ChaincodeSpec, tw *tar.Writer) (string, erro
}()

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 := ""
Expand Down
124 changes: 124 additions & 0 deletions core/chaincode/platforms/java/hash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
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)

srcPath1, err := getCodeFromHTTP("https://github.com/hyperledger/fabric-test-resources/")
if err != nil {
t.Logf("Error getting code from remote repo %s", err)
t.Fail()
}
srcPath2, err := getCodeFromHTTP("https://github.com/hyperledger/fabric-sdk-java")
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/hyperledger/fabric-test-resources")
if err != nil {
t.Logf("Error getting code from remote repo %s", err)
t.Fail()
}
srcPath2, err := getCodeFromHTTP("https://github.com/hyperledger/fabric-test-resources")

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")
}
}
26 changes: 15 additions & 11 deletions core/chaincode/platforms/java/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"strings"
"time"

"os"

cutil "github.com/hyperledger/fabric/core/container/util"
pb "github.com/hyperledger/fabric/protos/peer"
"github.com/spf13/viper"
Expand All @@ -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 == "" {
Expand All @@ -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
Expand All @@ -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)
}
Expand Down
38 changes: 25 additions & 13 deletions core/container/util/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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:])
Expand All @@ -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
}

Expand All @@ -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
}
Expand All @@ -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
Expand Down

0 comments on commit 80140c9

Please sign in to comment.