Skip to content

Commit dab4f39

Browse files
authored
[FABCN-413] Add e2e test for chaincode server (#162)
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>
1 parent bea333d commit dab4f39

13 files changed

Lines changed: 319 additions & 2 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
package.lock.json
3+
package

test/chaincodes/server/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM hyperledger/fabric-nodeenv:latest
2+
3+
ADD . /opt/chaincode
4+
RUN cd /opt/chaincode; npm install
5+
6+
WORKDIR /opt/chaincode
7+
ENTRYPOINT ["npm", "start"]

test/chaincodes/server/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
# Copyright Hitachi America, Ltd. All Rights Reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
*/
6+
"use strict";
7+
8+
const { Contract } = require('fabric-contract-api');
9+
10+
class ServerTestChaincode extends Contract {
11+
async unknownTransaction({stub}) {
12+
const {fcn, params} = stub.getFunctionAndParameters();
13+
throw new Error(`Could not find chaincode function: ${fcn}`);
14+
}
15+
16+
constructor() {
17+
super('org.mynamespace.server');
18+
}
19+
20+
async putValue(ctx, value) {
21+
await ctx.stub.putState('state1', Buffer.from(JSON.stringify(value)));
22+
}
23+
24+
async getValue(ctx) {
25+
const value = await ctx.stub.getState('state1');
26+
return JSON.parse(value.toString());
27+
}
28+
}
29+
30+
exports.contracts = [ ServerTestChaincode ];
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "chaincode",
3+
"description": "Chaincode server",
4+
"engines": {
5+
"node": "^12.13.0",
6+
"npm": ">=5.3.0"
7+
},
8+
"scripts": {
9+
"start": "fabric-chaincode-node server"
10+
},
11+
"main": "index.js",
12+
"engine-strict": true,
13+
"engineStrict": true,
14+
"version": "1.0.0",
15+
"author": "",
16+
"license": "Apache-2.0",
17+
"dependencies": {
18+
"fabric-shim": "3.0.0-unstable",
19+
"fabric-contract-api": "3.0.0-unstable"
20+
}
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"address": "cc-server:9999",
3+
"dial_timeout": "10s",
4+
"tls_required": false
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"path": "",
3+
"type": "external",
4+
"label": "server_v0"
5+
}

test/e2e/scenario.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,7 @@ const installChaincode = async () => {
185185
]);
186186
};
187187

188-
exports.default = series(installChaincode, instantiateChaincode, invokeFunctions, queryFunctions);
188+
const clientTests = series(installChaincode, instantiateChaincode, invokeFunctions, queryFunctions);
189+
const serverTests = require('./server').default;
190+
191+
exports.default = series(clientTests, serverTests);

test/e2e/server.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
# Copyright Hitachi America, Ltd. All Rights Reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
*/
6+
'use strict';
7+
8+
const {series} = require('gulp');
9+
10+
const util = require('util');
11+
const path = require('path');
12+
13+
const { shell: runcmds , getTLSArgs, getPeerAddresses } = require('toolchain');
14+
const ip = require('ip');
15+
16+
const CHANNEL_NAME = 'mychannel';
17+
18+
const chaincodeDir = path.join(__dirname, '..', '..', 'test', 'chaincodes', 'server');
19+
20+
async function packageChaincode() {
21+
await runcmds([
22+
util.format(
23+
'tar -C %s/package -cvzf %s/package/code.tar.gz connection.json',
24+
chaincodeDir, chaincodeDir
25+
),
26+
util.format(
27+
'tar -C %s/package -cvzf %s/package/chaincode.tar.gz code.tar.gz metadata.json',
28+
chaincodeDir, chaincodeDir
29+
),
30+
]);
31+
}
32+
33+
async function buildChaincode() {
34+
const npmrc = path.join(chaincodeDir, '.npmrc');
35+
36+
await runcmds([
37+
`echo "registry=http://${ip.address()}:4873" > ${npmrc}`,
38+
util.format(
39+
'docker build --no-cache -t chaincode-e2e-server %s',
40+
chaincodeDir
41+
),
42+
`rm -f ${npmrc}`
43+
]);
44+
}
45+
46+
async function installChaincode() {
47+
const peerInstall = 'peer lifecycle chaincode install /opt/gopath/src/github.com/chaincode/server/package/chaincode.tar.gz';
48+
49+
await runcmds([
50+
util.format(
51+
'docker exec %s %s',
52+
'org1_cli',
53+
peerInstall
54+
),
55+
util.format(
56+
'docker exec %s %s',
57+
'org2_cli',
58+
peerInstall
59+
)
60+
]);
61+
};
62+
63+
function findPackageId(queryOutput, label) {
64+
const output = JSON.parse(queryOutput);
65+
66+
const cc = output.installed_chaincodes.filter((chaincode) => chaincode.label === label);
67+
if (cc.length !== 1) {
68+
throw new Error('Failed to find installed chaincode');
69+
}
70+
71+
return cc[0].package_id;
72+
}
73+
74+
async function instantiateChaincode() {
75+
const endorsementPolicy = '"OR (\'Org1MSP.member\', \'Org2MSP.member\')"';
76+
const queryInstalled = util.format(
77+
'peer lifecycle chaincode queryinstalled --output json'
78+
);
79+
const sequence = 1;
80+
81+
const approveChaincode = util.format(
82+
'peer lifecycle chaincode approveformyorg -o %s %s -C %s -n %s -v %s --package-id %s --sequence %d --signature-policy %s',
83+
'orderer.example.com:7050',
84+
getTLSArgs(),
85+
CHANNEL_NAME,
86+
'server',
87+
'v0',
88+
'%s', // To be filled in for each org
89+
sequence,
90+
endorsementPolicy
91+
);
92+
93+
const outputs = await runcmds([
94+
util.format(
95+
'docker exec %s %s',
96+
'org1_cli',
97+
queryInstalled
98+
),
99+
util.format(
100+
'docker exec %s %s',
101+
'org2_cli',
102+
queryInstalled
103+
),
104+
]);
105+
106+
const packageIdOrg1 = findPackageId(outputs[0], 'server_v0');
107+
const packageIdOrg2 = findPackageId(outputs[1], 'server_v0');
108+
109+
// TODO: Assuming the two package IDs are the same
110+
await runcmds([
111+
// Start the CC Server container
112+
`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`,
113+
// Approve the chaincode definition by each org
114+
util.format('docker exec %s %s',
115+
'org1_cli',
116+
util.format(approveChaincode, packageIdOrg1)
117+
),
118+
util.format('docker exec %s %s',
119+
'org2_cli',
120+
util.format(approveChaincode, packageIdOrg2)
121+
),
122+
// Commit the chaincode definition
123+
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',
124+
'orderer.example.com:7050',
125+
getTLSArgs(),
126+
CHANNEL_NAME,
127+
'server',
128+
'v0',
129+
sequence,
130+
endorsementPolicy,
131+
getPeerAddresses()
132+
)
133+
]);
134+
}
135+
136+
const invokeFunctions = async () => {
137+
const args = util.format('docker exec org1_cli peer chaincode invoke %s -C %s -n %s -c %s --waitForEvent',
138+
getTLSArgs(),
139+
CHANNEL_NAME,
140+
'server',
141+
'\'{"Args":["putValue","\'42\'"]}\'');
142+
143+
await runcmds([args]);
144+
};
145+
146+
const queryFunctions = async () => {
147+
const args = util.format('docker exec org1_cli peer chaincode query %s -C %s -n %s -c %s',
148+
getTLSArgs(),
149+
CHANNEL_NAME,
150+
'server',
151+
'\'{"Args":["getValue"]}\'');
152+
153+
const ret = await runcmds([args]);
154+
155+
const response = JSON.parse(ret[0]);
156+
157+
if (response !== 42) {
158+
throw new Error("Unexpected result from chaincode");
159+
}
160+
}
161+
162+
exports.default = series(
163+
packageChaincode, buildChaincode, installChaincode, instantiateChaincode,
164+
invokeFunctions, queryFunctions
165+
);

tools/toolchain/fabric.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ const _docker_clean = async () => {
3232
// stop and remove chaincode docker instances
3333
'docker kill $(docker ps | grep "dev-peer0.org[12].example.com" | awk \'{print $1}\') || echo ok',
3434
'docker rm $(docker ps -a | grep "dev-peer0.org[12].example.com" | awk \'{print $1}\') || echo ok',
35+
'docker kill $(docker ps | grep "cc-server" | awk \'{print $1}\') || echo ok',
36+
'docker rm $(docker ps -a | grep "cc-server" | awk \'{print $1}\') || echo ok',
3537

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

3942
// clean up all the containers created by docker-compose
4043
util.format('docker-compose -f %s down --volumes', fs.realpathSync(path.join(dockerComposeDir, 'docker-compose-cli.yaml'))),
@@ -94,6 +97,10 @@ const _generate_config = async () => {
9497
'docker exec cli cp /etc/hyperledger/fabric/core.yaml %s',
9598
dockerCfgPath
9699
),
100+
util.format(
101+
'docker exec cli sed -i \'s/externalBuilders: \\[\\]/externalBuilders: [{path: \\/opt\\/chaincode, name: test}]/\' %s/core.yaml',
102+
dockerCfgPath
103+
),
97104
util.format(
98105
'docker exec cli sh %s/rename_sk.sh',
99106
dockerCfgPath
@@ -151,10 +158,18 @@ async function _channel_create() {
151158
]);
152159
}
153160

161+
async function _peer_setup() {
162+
// Install the 'jq' command in the peer containers to run external builder scripts.
163+
await runcmds([
164+
'docker exec peer0.org1.example.com apk add jq',
165+
'docker exec peer0.org2.example.com apk add jq',
166+
]);
167+
}
168+
154169
const channelSetup = series(_channel_create, _channel_init);
155170

156171
// --
157-
const startFabric = series(dockerReady, channelSetup);
172+
const startFabric = series(dockerReady, _peer_setup, channelSetup);
158173
exports.default = startFabric;
159174

160175
exports.stopFabric = series(_docker_clean);

tools/toolchain/network/docker-compose/docker-compose-base.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ services:
9999
command: peer node start --peer-chaincodedev=true
100100
volumes:
101101
- /var/run/:/host/var/run/
102+
- ../external:/opt/chaincode/bin:ro
103+
- ../crypto-material/core.yaml:/etc/hyperledger/fabric/core.yaml:ro
102104

103105
clibase:
104106
extends:

0 commit comments

Comments
 (0)