Skip to content

Commit

Permalink
[FABCN-411] Add server command to CLI (hyperledger#161)
Browse files Browse the repository at this point in the history
This patch adds a "server" command to the "fabric-chaincode-node" CLI.
The command starts the contracts as a chaincode server.

Example: fabric-chaincode-node server --chaincode-address 0.0.0.0:9999 \
           --chaincode-id mycc_v0:a1233bb13227a05932

Signed-off-by: Taku Shimosawa <[email protected]>
  • Loading branch information
shimos committed Jun 16, 2020
1 parent 91fd3fc commit 25352d7
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 8 deletions.
56 changes: 56 additions & 0 deletions libraries/fabric-shim/lib/cmds/serverCommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
# Copyright Hitachi America, Ltd. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
*/

'use strict';

exports.command = 'server [options]';
exports.desc = 'Start the chaincode as a server';

const validOptions = {
'chaincode-address': {type: 'string', required: true},
'grpc.max_send_message_length': {type: 'number', default: -1},
'grpc.max_receive_message_length': {type: 'number', default: -1},
'grpc.keepalive_time_ms': {type: 'number', default: 110000},
'grpc.http2.min_time_between_pings_ms': {type: 'number', default: 110000},
'grpc.keepalive_timeout_ms': {type: 'number', default: 20000},
'grpc.http2.max_pings_without_data': {type: 'number', default: 0},
'grpc.keepalive_permit_without_calls': {type: 'number', default: 1},
'chaincode-id': {type: 'string', required: true},
'module-path': {type: 'string', default: process.cwd()}
};

exports.validOptions = validOptions;

exports.builder = function (yargs) {
yargs.options(validOptions);

yargs.usage('fabric-chaincode-node server --chaincode-address 0.0.0.0:9999 --chaincode-id mycc_v0:abcdef12345678...');

return yargs;
};

exports.handler = function (argv) {
const Bootstrap = require('../contract-spi/bootstrap');

return argv.thePromise = Bootstrap.bootstrap(true);
};

exports.getArgs = function (yargs) {
const argv = {};

for (const name in validOptions) {
argv[name] = yargs.argv[name];
}

// Translate the options to server options
argv.ccid = argv['chaincode-id'];
argv.address = argv['chaincode-address'];

delete argv['chaincode-id'];
delete argv['chaincode-address'];

return argv;
};
19 changes: 13 additions & 6 deletions libraries/fabric-shim/lib/contract-spi/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const shim = require('../chaincode');
const ChaincodeFromContract = require('./chaincodefromcontract');
const Logger = require('../logger');
const StartCommand = require('../cmds/startCommand.js');
const ServerCommand = require('../cmds/serverCommand.js');

const logger = Logger.getLogger('contracts-spi/bootstrap.js');

Expand All @@ -28,25 +29,31 @@ class Bootstrap {
* @ignore
* @param {Contract} contracts contract to register to use
*/
static register(contracts, serializers, fileMetadata, title, version) {
static register(contracts, serializers, fileMetadata, title, version, opts, serverMode = false) {
// load up the meta data that the user may have specified
// this will need to passed in and rationalized with the
// code as implemented
const chaincode = new ChaincodeFromContract(contracts, serializers, fileMetadata, title, version);

// say hello to the peer
shim.start(chaincode);
if (serverMode) {
const server = shim.server(chaincode, opts);
server.start();
} else {
// say hello to the peer
shim.start(chaincode);
}
}

/**
*
* @ignore
* @param {boolean} serverMode set true if the chaincode should be started as a server
*/
static async bootstrap() {
const opts = StartCommand.getArgs(yargs);
static async bootstrap(serverMode = false) {
const opts = serverMode ? ServerCommand.getArgs(yargs) : StartCommand.getArgs(yargs);
const {contracts, serializers, title, version} = this.getInfoFromContract(opts['module-path']);
const fileMetadata = await Bootstrap.getMetadata(opts['module-path']);
Bootstrap.register(contracts, serializers, fileMetadata, title, version);
Bootstrap.register(contracts, serializers, fileMetadata, title, version, opts, serverMode);
}

static getInfoFromContract(modulePath) {
Expand Down
87 changes: 87 additions & 0 deletions libraries/fabric-shim/test/unit/cmds/serverCommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
# Copyright Hitachi America, Ltd. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
*/

'use strict';

const sinon = require('sinon');

const chai = require('chai');
const expect = chai.expect;

const yargs = require('yargs');
const Bootstrap = require('../../../lib/contract-spi/bootstrap');
const chaincodeServerCommand = require('../../../lib/cmds/serverCommand.js');

describe('server cmd', () => {
let sandbox;

beforeEach(() => {
sandbox = sinon.createSandbox();
});

afterEach(() => {
sandbox.restore();
});

describe('.builder', () => {
it('should configure the builder function', () => {
sandbox.stub(yargs, 'options');
sandbox.stub(yargs, 'usage');

chaincodeServerCommand.builder(yargs);

expect(yargs.options.calledOnce).to.be.true;

const args = yargs.options.getCall(0).args[0];

expect(args['chaincode-address'].required).to.be.true;
expect(args['chaincode-id'].required).to.be.true;
expect(args['grpc.max_send_message_length'].default).to.deep.equal(-1);
expect(args['grpc.max_receive_message_length'].default).to.deep.equal(-1);
expect(args['grpc.keepalive_time_ms'].default).to.deep.equal(110000);
expect(args['grpc.http2.min_time_between_pings_ms'].default).to.deep.equal(110000);
expect(args['grpc.keepalive_timeout_ms'].default).to.deep.equal(20000);
expect(args['grpc.http2.max_pings_without_data'].default).to.deep.equal(0);
expect(args['grpc.keepalive_permit_without_calls'].default).to.deep.equal(1);
expect(args['module-path'].default).to.deep.equal(process.cwd());

expect(yargs.usage.calledOnce).to.be.true;
});
});

describe('.handle', () => {
it('should handle properly and call bootstrap', () => {
sandbox.stub(Bootstrap, 'bootstrap');

const argv = {};
chaincodeServerCommand.handler(argv);

expect(Bootstrap.bootstrap.calledOnce).to.be.true;
});
});

describe('.getArgs', () => {
it('should return the arguments properly', () => {
const argv = {
'chaincode-address': '0.0.0.0:9999',
'chaincode-id': 'test_id:1',
'grpc.keepalive_time_ms': 1000,
'module-path': '/tmp/example',
'extra-options': 'something'
};

const ret = chaincodeServerCommand.getArgs({argv});

expect(ret.address).to.equal('0.0.0.0:9999');
expect(ret.ccid).to.equal('test_id:1');
expect(ret['grpc.keepalive_time_ms']).to.equal(1000);
expect(ret['module-path']).to.equal('/tmp/example');
expect(ret['chaincode-address']).to.be.undefined;
expect(ret['chaincode-id']).to.be.undefined;
expect(ret['extra-options']).to.be.undefined;
});
});
});
33 changes: 31 additions & 2 deletions libraries/fabric-shim/test/unit/contract-spi/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('bootstrap.js', () => {

let sandbox;
let mockShim;
let mockServer;
let mockCmd;
let readFileStub;
let pathExistsStub;
Expand All @@ -73,7 +74,8 @@ describe('bootstrap.js', () => {
useCleanCache: true
});
sandbox = sinon.createSandbox();
mockShim = {start : sandbox.stub()};
mockServer = {start: sandbox.stub()};
mockShim = {start : sandbox.stub(), server: sandbox.stub().returns(mockServer)};
getArgsStub = sandbox.stub();

mockCmd = {getArgs : getArgsStub};
Expand All @@ -84,6 +86,7 @@ describe('bootstrap.js', () => {
mockery.registerMock('yargs', {});
mockery.registerMock('../chaincode', mockShim);
mockery.registerMock('../cmds/startCommand.js', mockCmd);
mockery.registerMock('../cmds/serverCommand.js', mockCmd);
mockery.registerMock('./chaincodefromcontract', MockChaincodeFromContract);
mockery.registerMock('fs-extra', {pathExists:pathExistsStub, readFileSync : readFileStub});

Expand All @@ -103,6 +106,16 @@ describe('bootstrap.js', () => {
sinon.assert.calledOnce(mockShim.start);
});

it('should pass on the register to the shim in the server mode', async () => {
const opts = {ccid: 'abcdef', address: '0.0.0.0:9999'};
await Bootstrap.register([sc], {}, {}, 'some title', 'some version', opts, true);

sinon.assert.calledOnce(mockShim.server);
sinon.assert.calledOnce(mockServer.start);

expect(mockShim.server.getCall(0).args[1]).to.deep.equal(opts);
});

});

describe('#bootstrap', () => {
Expand All @@ -129,8 +142,24 @@ describe('bootstrap.js', () => {
sinon.assert.calledOnce(getMetadataStub);
sinon.assert.calledOnce(getInfoFromContractStub);
sinon.assert.calledOnce(registerStub);
sinon.assert.calledWith(registerStub, [sc], {}, {}, 'some title', 'some version');
sinon.assert.calledWith(registerStub, [sc], {}, {}, 'some title', 'some version', {'module-path':'fakepath'}, false);
});

it ('should correctly call the register method in the server mode', async () => {
getMetadataStub.resolves({});
mockery.registerMock(path.resolve(process.cwd(), 'fakepath', 'entrypoint'), {contracts: [sc]});
const registerStub = sandbox.stub();
Bootstrap.register = registerStub;
getInfoFromContractStub.returns({contracts: [sc], serializers : {}, title: 'some title', version: 'some version'});

await Bootstrap.bootstrap(true);

sinon.assert.calledOnce(getMetadataStub);
sinon.assert.calledOnce(getInfoFromContractStub);
sinon.assert.calledOnce(registerStub);
sinon.assert.calledWith(registerStub, [sc], {}, {}, 'some title', 'some version', {'module-path':'fakepath'}, true);
});

});

describe('#getInfoFromContract', () => {
Expand Down

0 comments on commit 25352d7

Please sign in to comment.