Skip to content

Commit

Permalink
[FABCN-413] Add e2e test for chaincode server
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 <taku.shimosawa@hal.hitachi.com>
  • Loading branch information
shimos committed Jun 9, 2020
1 parent bea333d commit 964f449
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": "3.0.0-unstable",
"fabric-contract-api": "3.0.0-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 @@ -32,9 +32,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 @@ -94,6 +97,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 @@ -151,10 +158,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=true
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 964f449

Please sign in to comment.