Skip to content

Commit

Permalink
[FABCN-413] Add e2e test for chaincode server (hyperledger#162)
Browse files Browse the repository at this point in the history
This patch adds new e2e test for the chaincode gRPC server feature.

The test performs as following:
  - Create a package which contains server and cc information
  - Build a container image of the chaincode
  - Install the package into peers
  - Obtain the installed package ID from the peers
  - Start the chaincode container with the package ID
  - Approve and commit the chaincode definition
  - Invoke and query the chaincode

"rush test:e2e" will perform both tests for both server and client mode.

This patch also modifies "rush start-fabric" to use external builder
scripts.

Signed-off-by: Taku Shimosawa <[email protected]>
  • Loading branch information
shimos committed Jun 16, 2020
1 parent 25352d7 commit 6595d68
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 2 deletions.
3 changes: 3 additions & 0 deletions test/chaincodes/server/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
package.lock.json
package
7 changes: 7 additions & 0 deletions test/chaincodes/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM hyperledger/fabric-nodeenv:latest

ADD . /opt/chaincode
RUN cd /opt/chaincode; npm install

WORKDIR /opt/chaincode
ENTRYPOINT ["npm", "start"]
30 changes: 30 additions & 0 deletions test/chaincodes/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
# Copyright Hitachi America, Ltd. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
*/
"use strict";

const { Contract } = require('fabric-contract-api');

class ServerTestChaincode extends Contract {
async unknownTransaction({stub}) {
const {fcn, params} = stub.getFunctionAndParameters();
throw new Error(`Could not find chaincode function: ${fcn}`);
}

constructor() {
super('org.mynamespace.server');
}

async putValue(ctx, value) {
await ctx.stub.putState('state1', Buffer.from(JSON.stringify(value)));
}

async getValue(ctx) {
const value = await ctx.stub.getState('state1');
return JSON.parse(value.toString());
}
}

exports.contracts = [ ServerTestChaincode ];
21 changes: 21 additions & 0 deletions test/chaincodes/server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "chaincode",
"description": "Chaincode server",
"engines": {
"node": "^12.13.0",
"npm": ">=5.3.0"
},
"scripts": {
"start": "fabric-chaincode-node server"
},
"main": "index.js",
"engine-strict": true,
"engineStrict": true,
"version": "1.0.0",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"fabric-shim": "2.1.3-unstable",
"fabric-contract-api": "2.1.3-unstable"
}
}
5 changes: 5 additions & 0 deletions test/chaincodes/server/package/connection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"address": "cc-server:9999",
"dial_timeout": "10s",
"tls_required": false
}
5 changes: 5 additions & 0 deletions test/chaincodes/server/package/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"path": "",
"type": "external",
"label": "server_v0"
}
5 changes: 4 additions & 1 deletion test/e2e/scenario.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,7 @@ const installChaincode = async () => {
]);
};

exports.default = series(installChaincode, instantiateChaincode, invokeFunctions, queryFunctions);
const clientTests = series(installChaincode, instantiateChaincode, invokeFunctions, queryFunctions);
const serverTests = require('./server').default;

exports.default = series(clientTests, serverTests);
165 changes: 165 additions & 0 deletions test/e2e/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
# Copyright Hitachi America, Ltd. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
*/
'use strict';

const {series} = require('gulp');

const util = require('util');
const path = require('path');

const { shell: runcmds , getTLSArgs, getPeerAddresses } = require('toolchain');
const ip = require('ip');

const CHANNEL_NAME = 'mychannel';

const chaincodeDir = path.join(__dirname, '..', '..', 'test', 'chaincodes', 'server');

async function packageChaincode() {
await runcmds([
util.format(
'tar -C %s/package -cvzf %s/package/code.tar.gz connection.json',
chaincodeDir, chaincodeDir
),
util.format(
'tar -C %s/package -cvzf %s/package/chaincode.tar.gz code.tar.gz metadata.json',
chaincodeDir, chaincodeDir
),
]);
}

async function buildChaincode() {
const npmrc = path.join(chaincodeDir, '.npmrc');

await runcmds([
`echo "registry=http://${ip.address()}:4873" > ${npmrc}`,
util.format(
'docker build --no-cache -t chaincode-e2e-server %s',
chaincodeDir
),
`rm -f ${npmrc}`
]);
}

async function installChaincode() {
const peerInstall = 'peer lifecycle chaincode install /opt/gopath/src/github.com/chaincode/server/package/chaincode.tar.gz';

await runcmds([
util.format(
'docker exec %s %s',
'org1_cli',
peerInstall
),
util.format(
'docker exec %s %s',
'org2_cli',
peerInstall
)
]);
};

function findPackageId(queryOutput, label) {
const output = JSON.parse(queryOutput);

const cc = output.installed_chaincodes.filter((chaincode) => chaincode.label === label);
if (cc.length !== 1) {
throw new Error('Failed to find installed chaincode');
}

return cc[0].package_id;
}

async function instantiateChaincode() {
const endorsementPolicy = '"OR (\'Org1MSP.member\', \'Org2MSP.member\')"';
const queryInstalled = util.format(
'peer lifecycle chaincode queryinstalled --output json'
);
const sequence = 1;

const approveChaincode = util.format(
'peer lifecycle chaincode approveformyorg -o %s %s -C %s -n %s -v %s --package-id %s --sequence %d --signature-policy %s',
'orderer.example.com:7050',
getTLSArgs(),
CHANNEL_NAME,
'server',
'v0',
'%s', // To be filled in for each org
sequence,
endorsementPolicy
);

const outputs = await runcmds([
util.format(
'docker exec %s %s',
'org1_cli',
queryInstalled
),
util.format(
'docker exec %s %s',
'org2_cli',
queryInstalled
),
]);

const packageIdOrg1 = findPackageId(outputs[0], 'server_v0');
const packageIdOrg2 = findPackageId(outputs[1], 'server_v0');

// TODO: Assuming the two package IDs are the same
await runcmds([
// Start the CC Server container
`docker run -e CORE_CHAINCODE_ID=${packageIdOrg1} -e CORE_CHAINCODE_ADDRESS=0.0.0.0:9999 -h cc-server --name cc-server -d --network node_default chaincode-e2e-server`,
// Approve the chaincode definition by each org
util.format('docker exec %s %s',
'org1_cli',
util.format(approveChaincode, packageIdOrg1)
),
util.format('docker exec %s %s',
'org2_cli',
util.format(approveChaincode, packageIdOrg2)
),
// Commit the chaincode definition
util.format('docker exec org1_cli peer lifecycle chaincode commit -o %s %s -C %s -n %s -v %s --sequence %d --signature-policy %s %s',
'orderer.example.com:7050',
getTLSArgs(),
CHANNEL_NAME,
'server',
'v0',
sequence,
endorsementPolicy,
getPeerAddresses()
)
]);
}

const invokeFunctions = async () => {
const args = util.format('docker exec org1_cli peer chaincode invoke %s -C %s -n %s -c %s --waitForEvent',
getTLSArgs(),
CHANNEL_NAME,
'server',
'\'{"Args":["putValue","\'42\'"]}\'');

await runcmds([args]);
};

const queryFunctions = async () => {
const args = util.format('docker exec org1_cli peer chaincode query %s -C %s -n %s -c %s',
getTLSArgs(),
CHANNEL_NAME,
'server',
'\'{"Args":["getValue"]}\'');

const ret = await runcmds([args]);

const response = JSON.parse(ret[0]);

if (response !== 42) {
throw new Error("Unexpected result from chaincode");
}
}

exports.default = series(
packageChaincode, buildChaincode, installChaincode, instantiateChaincode,
invokeFunctions, queryFunctions
);
17 changes: 16 additions & 1 deletion tools/toolchain/fabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ const _docker_clean = async () => {
// stop and remove chaincode docker instances
'docker kill $(docker ps | grep "dev-peer0.org[12].example.com" | awk \'{print $1}\') || echo ok',
'docker rm $(docker ps -a | grep "dev-peer0.org[12].example.com" | awk \'{print $1}\') || echo ok',
'docker kill $(docker ps | grep "cc-server" | awk \'{print $1}\') || echo ok',
'docker rm $(docker ps -a | grep "cc-server" | awk \'{print $1}\') || echo ok',

// remove chaincode images so that they get rebuilt during test
'docker rmi $(docker images | grep "^dev-peer0.org[12].example.com" | awk \'{print $3}\') || echo ok',
'docker rmi $(docker images | grep "^chaincode-e2e-server" | awk \'{print $3}\') || echo ok',

// clean up all the containers created by docker-compose
util.format('docker-compose -f %s down --volumes', fs.realpathSync(path.join(dockerComposeDir, 'docker-compose-cli.yaml'))),
Expand Down Expand Up @@ -140,6 +143,10 @@ const _generate_config = async () => {
'docker exec cli cp /etc/hyperledger/fabric/core.yaml %s',
dockerCfgPath
),
util.format(
'docker exec cli sed -i \'s/externalBuilders: \\[\\]/externalBuilders: [{path: \\/opt\\/chaincode, name: test}]/\' %s/core.yaml',
dockerCfgPath
),
util.format(
'docker exec cli sh %s/rename_sk.sh',
dockerCfgPath
Expand Down Expand Up @@ -197,10 +204,18 @@ async function _channel_create() {
]);
}

async function _peer_setup() {
// Install the 'jq' command in the peer containers to run external builder scripts.
await runcmds([
'docker exec peer0.org1.example.com apk add jq',
'docker exec peer0.org2.example.com apk add jq',
]);
}

const channelSetup = series(_channel_create, _channel_init);

// --
const startFabric = series(dockerReady, channelSetup);
const startFabric = series(dockerReady, _peer_setup, channelSetup);
exports.default = startFabric;

exports.stopFabric = series(_docker_clean);
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ services:
command: peer node start --peer-chaincodedev=${DOCKER_DEVMODE}
volumes:
- /var/run/:/host/var/run/
- ../external:/opt/chaincode/bin:ro
- ../crypto-material/core.yaml:/etc/hyperledger/fabric/core.yaml:ro

clibase:
extends:
Expand Down
23 changes: 23 additions & 0 deletions tools/toolchain/network/external/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh
#
# Copyright Hitachi America, Ltd. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0

set -e

SOURCE="$1"
OUTPUT="$3"

if [ ! -f "${SOURCE}/connection.json" ]; then
echo "Error: ${SOURCE}/connection.json not found" 1>&2
exit 1
fi

cp "${SOURCE}/connection.json" "${OUTPUT}/connection.json"

if [ -d "${SOURCE}/metadata" ]; then
cp -a ${SOURCE}/metadata ${OUTPUT}/metadata
fi

exit 0
15 changes: 15 additions & 0 deletions tools/toolchain/network/external/detect
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
#
# Copyright Hitachi America, Ltd. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0

set -e

METADIR="$2"

if [ `jq -r .type "${METADIR}/metadata.json"` = "external" ]; then
exit 0
fi

exit 1
23 changes: 23 additions & 0 deletions tools/toolchain/network/external/release
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh
#
# Copyright Hitachi America, Ltd. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0

set -e

BUILD="$1"
RELEASE="$2"

if [ -d "${BUILD}/metadata" ]; then
cp -a "${BUILD}/metadata/*" "${RELEASE}/"
fi

if [ -f "${BUILD}/connection.json" ]; then
mkdir -p "${RELEASE}/chaincode/server"
cp "${BUILD}/connection.json" "${RELEASE}/chaincode/server"

# TODO: TLS

exit 0
fi

0 comments on commit 6595d68

Please sign in to comment.