Skip to content

Commit

Permalink
NodeSDK add hierarchical configuration support FAB-741
Browse files Browse the repository at this point in the history
The configuration settings hierarchy:
   static code initialized defaults
   file: __dirname/config/default.json
   file: ...user added file(s)
   environment: export HFC_LOGGING={"debug":"console"} will be mapped to hfc-logging
   command line invocation: node myapp.js --hfc-logging={"debug":"console"}
   API setConfigSetting(name, value)

Restructured the target node classes (Peer, Orderer, MemberServices)
  to extend the same Remote class to centralize the endpoint and
  connection settings.
Converted any calls to process.env.xxxx to be utils.getConfigSetting(xxxx)

Change-Id: I4f7fc6476ab68bb46c086495358a93f0763eeeb0
Signed-off-by: Bret Harrison <beharrison@nc.rr.com>
  • Loading branch information
harrisob committed Oct 27, 2016
1 parent 7ba3992 commit 9731107
Show file tree
Hide file tree
Showing 16 changed files with 987 additions and 177 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ npm-shrinkwrap.json
npm-debug.log
tmp
.project
.DS_Store
4 changes: 4 additions & 0 deletions config/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"request-timeout" : 3000,
"tcert-batch-size" : 10
}
77 changes: 77 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

var util = require('util');
var Chain = require('./lib/Chain.js');
var Config = require('./lib/Config.js');
var Peer = require('./lib/Peer.js');
var utils = require('./lib/utils.js');

var _chains = {};
Expand Down Expand Up @@ -61,6 +63,20 @@ module.exports.getChain = function(chainName, create) {
return chain;
};

/**
* Constructs and returns a Peer given its endpoint configuration settings.
*
* @param {string} url The URL with format of "grpcs://host:port".
* @param {Object} opts The options for the connection to the peer.
* @returns {Peer} Returns the new peer.
*/
module.exports.getPeer = function(url, opts) {
var peer = new Peer(url, opts);

return peer;
};


/**
* Obtains an instance of the [KeyValueStore]{@link module:api.KeyValueStore} class. By default
* it returns the built-in implementation, which is based on files ([FileKeyValueStore]{@link module:api.FileKeyValueStore}).
Expand Down Expand Up @@ -121,3 +137,64 @@ module.exports.setLogger = function(logger) {
}
};

/**
* Adds a file to the top of the list of configuration setting files that are
* part of the hierarchical configuration.
* These files will override the default settings and be overriden by environment,
* command line arguments, and settings programmatically set into configuration settings.
*
* hierarchy search order:
* 1. memory - all settings added with utils.setConfigSetting(name,value)
* 2. Command-line arguments
* 3. Environment variables (names will be change from AAA-BBB to aaa-bbb)
* 4. Custom Files - all files added with the addConfigFile(path)
* will be ordered by when added, were last one added will override previously added files
* 5. The file located at 'config/default.json' with default settings
*
* @param {String} path - The path to the file to be added to the top of list of configuration files
*/
module.exports.addConfigFile = function(path) {

utils.addConfigFile(path);
};

/**
* Adds a setting to override all settings that are
* part of the hierarchical configuration.
*
* hierarchy search order:
* 1. memory - settings added with this call
* 2. Command-line arguments
* 3. Environment variables (names will be change from AAA-BBB to aaa-bbb)
* 4. Custom Files - all files added with the addConfigFile(path)
* will be ordered by when added, were last one added will override previously added files
* 5. The file located at 'config/default.json' with default settings
*
* @param {String} name - The name of a setting
* @param {Object} value - The value of a setting
*/
module.exports.setConfigSetting = function(name, value) {

utils.setConfigSetting(name, value);
};

/**
* Retrieves a setting from the hierarchical configuration and if not found
* will return the provided default value.
*
* hierarchy search order:
* 1. memory - settings added with utils.setConfigSetting(name,value)
* 2. Command-line arguments
* 3. Environment variables (names will be change from AAA-BBB to aaa-bbb)
* 4. Custom Files - all files added with the addConfigFile(path)
* will be ordered by when added, were last one added will override previously added files
* 5. The file located at 'config/default.json' with default settings
*
* @param {String} name - The name of a setting
* @param {Object} default_value - The value of a setting if not found in the hierarchical configuration
*/
module.exports.getConfigSetting = function(name, default_value) {

return utils.getConfigSetting(name, default_value);
};

14 changes: 7 additions & 7 deletions lib/Chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var Chain = class {
this._members = {}; // associated array of [name] <-> Member

// The number of tcerts to get in each batch
this._tcertBatchSize = 200;
this._tcertBatchSize = utils.getConfigSetting('tcert-batch-size',200);

// The registrar (if any) that registers & enrolls new members/users
this._registrar = null; // Member
Expand All @@ -69,8 +69,8 @@ var Chain = class {

// Temporary variables to control how long to wait for deploy and invoke to complete before
// emitting events. This will be removed when the SDK is able to receive events from the
this._deployWaitTime = 20;
this._invokeWaitTime = 5;
this._deployWaitTime = utils.getConfigSetting('deploy-wait-time',20);
this._invokeWaitTime = utils.getConfigSetting('invoke-wait-time',5);

/**
* @member [CryptoSuite]{@link module:api.CryptoSuite} cryptoPrimitives The crypto primitives object provides access to the crypto suite
Expand Down Expand Up @@ -109,10 +109,10 @@ var Chain = class {
/**
* Set the member services URL
* @param {string} url Member services URL of the form: 'grpc://host:port' or 'grpcs://host:port'
* @param {string} pem String value of the TLS certificate for the local client
* @param {Object} opts Object with all connections settings including the 'pem' value of the TLS certificate for the local client
*/
setMemberServicesUrl(url, pem) {
this.setMemberServices(new MemberServices(url, pem));
setMemberServicesUrl(url, opts) {
this.setMemberServices(new MemberServices(url, opts));
}

/**
Expand Down Expand Up @@ -396,7 +396,7 @@ var Chain = class {
*/
setOrderer(url, opts) {
logger.debug('Chain.setOrderer - start url:'+url);
var orderer = new Orderer(url, this, opts);
var orderer = new Orderer(url, opts);
this._orderer = orderer;
return orderer;
}
Expand Down
151 changes: 151 additions & 0 deletions lib/Config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* Copyright 2016 IBM 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.
*/

/**
* This is the configuration class for the "hfc" (Hyperledger Fabric Client) package.
* It provides all configuration settings using "config" node.js package to retrieve the
* settings from JSON based files, environment settings, and command line startup settings
*
* configuration settings will be overridden in this order
* first files are loaded in this order
* $NODE_CONFIG_DIR/default.json
* $NODE_CONFIG_DIR/$NODE_ENV.json
*
* NODE_CONFIG_DIR defaults to './config' the configuration directory is relative to where the application is started
* NODE_ENV defaults to 'development'
*
* then then following environment setting will override file settings
* $NODE_CONFIG
* $ export NODE_CONFIG='{"request-timeout": 3000 }'
*
* then the command line setting will override all
* node myapp.js --NODE_CONFIG='{"request-timeout": 7000 }'
*
*
* see the following for complete information on the configuration settings
* https://www.npmjs.com/package/config
*/

'use strict';

var nconf = require('nconf');
var path = require('path');

//
// The class representing the hierarchy of configuration settings.
//
//

var Config = class {

// Setup nconf to use (search order):
// 1. in memory - all settings added with utils.setConfigSetting(name,value)
// 2. Command-line arguments
// 3. Environment variables (names will be change from AAA-BBB to aaa-bbb)
// 4. user Files - all files added with the addConfigFile(path)
// will be ordered by when added, were last one added will override previously added files
// 5. The file located at 'config/default.json' with default settings
//
constructor() {
nconf.use('memory');
nconf.argv();
nconf.env();
nconf.use('mapenv', {type:'memory'});
this.mapSettings(nconf.stores['mapenv'], process.env);
this._fileStores = [];
// reference to configuration settings
this._config = nconf;
// setup the location of the default config shipped with code
var default_config = path.resolve( __dirname, '../config/default.json');
this.reorderFileStores(default_config);
}

//
// utility method to map (convert) the environment(upper case and underscores) style
// names to configuration (lower case and dashes) style names
//
mapSettings(store, settings) {
for(var key in settings) {
var value = settings[key];
key = key.toLowerCase();
key = key.replace(/_/g, '-');
// if(store.get(key)) {
// throw new Error('Unable to map environment variable to configuration setting. There is another config setting with the same converted name:'+key);
// }
store.set(key,value);
}
}

//
// utility method to reload the file based stores so
// the last one added is on the top of the files hierarchy
//
reorderFileStores(path) {
// first remove all the file stores
for(var x in this._fileStores) {
this._config.remove(this._fileStores[x]);
}
// now add this new file store to the front of the list
this._fileStores.unshift(path);
// now load all the file stores
for(var x in this._fileStores) {
var name = this._fileStores[x];
this._config.file(name, name);
}
}

//
// Add an additional file
//
file(path) {
if(typeof path !== 'string') {
throw new Error('The "path" parameter must be a string');
}
// just reuse the path name as the store name...will be unique
this.reorderFileStores(path);
}

//
// Get the config setting with name.
// If the setting is not found returns the default value provided.
//
get(name, default_value) {
var return_value = null;

try {
return_value = this._config.get(name);
}
catch(err) {
return_value = default_value;
}

if(return_value === null || return_value === undefined) {
return_value = default_value;
}

return return_value;
}

//
// Set a value into the 'memory' store of config settings. This will override all other settings
//
set(name, value) {
this._config.set(name,value);
}

};

module.exports = Config;
8 changes: 4 additions & 4 deletions lib/Member.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,10 @@ var Member = class {
* Sends a deployment proposal to an endorser.
*
* @param {Object} request An object containing the following fields:
* endorserUrl
* chaincodePath
* fcn
* args
* endorserUrl: Peer URL
* chaincodePath : String
* fcn : String
* args : Strings
* @returns Promise for a ProposalResponse
*/
sendDeploymentProposal(request) {
Expand Down
Loading

0 comments on commit 9731107

Please sign in to comment.