/*-
*
* This file is part of Oracle NoSQL Database
* Copyright (C) 2011, 2014 Oracle and/or its affiliates. All rights reserved.
*
* If you have received this file as part of Oracle NoSQL Database the
* following applies to the work as a whole:
*
* Oracle NoSQL Database server software is free software: you can
* redistribute it and/or modify it under the terms of the GNU Affero
* General Public License as published by the Free Software Foundation,
* version 3.
*
* Oracle NoSQL Database is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* If you have received this file as part of Oracle NoSQL Database Client or
* distributed separately the following applies:
*
* Oracle NoSQL Database client software is free software: you can
* redistribute it and/or modify it under the terms of the Apache License
* as published by the Apache Software Foundation, version 2.0.
*
* You should have received a copy of the GNU Affero General Public License
* and/or the Apache License in the LICENSE file along with Oracle NoSQL
* Database client or server distribution. If not, see
* <http://www.gnu.org/licenses/>
* or
* <http://www.apache.org/licenses/LICENSE-2.0>.
*
* An active Oracle commercial licensing agreement for this product supersedes
* these licenses and in such case the license notices, but not the copyright
* notice, may be removed by you in connection with your distribution that is
* in accordance with the commercial licensing terms.
*
* For more information please contact:
*
* berkeleydb-info_us@oracle.com
*
*/
"use strict";
/*global kvLogger*/
/*global Errors*/
/*global kvmodule_dir*/
/*global fs*/
var child_process = require('child_process');
var PROXY_TIMEOUT = 4000;
var PROXY_ERROR_TIMEOUT = 1000;
var proxyPid = null;
var PID_FILE = "/tmp/kvstore_proxy.pid";
function getPortFlag(hostPort){
var colon = hostPort.indexOf(':');
var port = hostPort.substr(colon+1);
if (port)
return ' -port ' + port;
return '';
}
function getHostsFlag(kvStoreHelperHosts) {
kvLogger.debug("[PROXY] Helper hosts: " + kvStoreHelperHosts);
var hosts, flag="";
if (kvStoreHelperHosts){
kvLogger.debug("[PROXY] Helper hosts: " + typeof kvStoreHelperHosts);
var hosts;
if (typeof kvStoreHelperHosts === 'array') {
var firstComa = true;
for (var _host in kvStoreHelperHosts) {
hosts += (firstComa?"":",") + _host;
firstComa = false;
}
} else {
hosts = kvStoreHelperHosts;
}
flag = " -helper-hosts " + hosts;
kvLogger.debug("Host flag for proxy: " + flag);
}
return flag;
}
function getStoreFlag(store) {
if (typeof store === 'string')
if (store.length > 0)
return " -store " + store;
return "";
}
function getProxyClasspath(proxyConfiguration) {
var separator = (process.platform==='win32'?";":":");
return " -cp " +
path.normalize(proxyConfiguration.PROXY_HOME + '/kvproxy.jar') + separator +
path.normalize(proxyConfiguration.PROXY_HOME + '/lib/libthrift-0.9.1.jar') + separator +
path.normalize(proxyConfiguration.KVCLIENT_JAR) + separator +
path.normalize(proxyConfiguration.PROXY_HOME + '/lib/slf4j-log4j12-1.5.8.jar') + separator +
path.normalize(proxyConfiguration.PROXY_HOME + '/lib/log4j-1.2.14.jar') + separator +
path.normalize(proxyConfiguration.PROXY_HOME + '/lib/slf4j-api-1.5.8.jar');
}
/**
* Defines a set of configuration values used to connect or create a proxy instance, this is just the constructor with
* no parameters, once this object is created feel free to change any parameter.
* @property {boolean} startProxy indicates if the module should try to start a proxy instance from which it will
* connect to a NoSQL Server
* @property {string} host indicates the host:port for a proxy to connect, not used when
* startProxy = true
* @property {string} KVCLIENT_JAR the path where the file kvclient.jar is located, required to start a local proxy
* @property {string} PROXY_HOME the path where the proxy files are located, by default they are located in this module
* path, but a different value can be specified to start a local proxy with different binaries
* @property {String} kvStoreName the store name to be used
* @property {String|Array} kvStoreHelperHosts the helper hosts to be used by the proxy when connecting to a NoSQL Server
* @property {String|Array} readZones the read zones to be used by the proxy when connecting to a NoSQL Server
* @property {SecurityProperties} securityProperties used for the connection
* @property {Number} requestTimeout a number in milliseconds to specify the timeout default value for a request
* @property {Number} socketOpenTimeout a number in milliseconds to specify the timeout for the socket to connect
* @property {Number} socketReadTimeout a number in milliseconds to specify the timeout for the socket to read
* @constructor
*/
function ProxyConfiguration () {
this.startProxy = false;
this.host = "localhost:5010";
this.KVCLIENT_JAR = process.env.KVSTORE_HOME_DIR + "/dist/lib/kvclient.jar";
this.PROXY_HOME = path.normalize(kvmodule_dir + "/proxy/");
this.kvStoreName = "kvstore";
this.kvStoreHelperHosts = "localhost:5000";
this.readZones = "";
this.username = "";
this.securityProperties = new Types.SecurityProperties();
this.requestTimeout = 1000; //todo: send this parameter to the proxy
this.socketOpenTimeout = 1000; //todo: send this parameter to the proxy
this.socketReadTimeout = 1000; //todo: send this parameter to the proxy
}
exports.ProxyConfiguration = ProxyConfiguration;
function validateConfiguration (/*ProxyConfiguration*/ conf) {
if (typeof conf.startProxy === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "startProxy");
if (typeof conf.host === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "host");
if (typeof conf.KVCLIENT_JAR === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "KVCLIENT_JAR");
if (typeof conf.PROXY_HOME === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "PROXY_HOME");
if (typeof conf.kvStoreName === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "kvStoreName");
if (typeof conf.kvStoreHelperHosts === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "kvStoreHelperHosts");
if (typeof conf.readZones === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "readZones");
if (typeof conf.requestTimeout === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "requestTimeout");
if (typeof conf.socketOpenTimeout === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "socketOpenTimeout");
if (typeof conf.socketReadTimeout === 'undefined') throw new Error(Errors.ERROR_IN_PARAMETER + "socketReadTimeout");
}
exports.validateConfiguration = validateConfiguration;
/**
* Tries to read a file with a ProxyConfiguration object
* @param {string} filename the full path for the file
* @returns {ProxyConfiguration}
*/
function readProxyConfiguration (filename) {
var conf = {};
if (fs.existsSync(filename)){
conf = JSON.parse(fs.readFileSync(filename));
validateConfiguration(conf);
return conf;
} else {
throw (new Error(Errors.FILE_NOT_FOUND));
}
}
exports.readProxyConfiguration = readProxyConfiguration;
function checkJava (callback) {
kvLogger.debug('Check that Java environment is installed');
var java = child_process.spawn('java', ['-version']);
java.stderr.on('data', function (data) {
if (callback) callback();
callback = null;
clearTimeout(timeout);
removeall();
});
java.stdin.on('data', function (data) {
if (callback) callback();
callback = null;
clearTimeout(timeout);
removeall();
});
java.stdout.on('data', function (data) {
if (callback) callback();
callback = null;
clearTimeout(timeout);
removeall();
});
java.on('error', function (error) {
if (callback) callback(error);
callback = null;
clearTimeout(timeout);
removeall();
});
var timeout = setTimeout(function(){ if (callback) callback(false) }, 4000);
function removeall() {
java.stderr.removeAllListeners('data');
java.stdin.removeAllListeners('data');
java.stdout.removeAllListeners('data');
java.removeAllListeners('error');
}
}
exports.checkJava = checkJava;
/**
* starts a proxy with the given configuration
* @param {ProxyConfiguration} proxyConfiguration the configuration used to start the proxy
* @param {function} callback function called after the proxy is started
*/
function startProxy (proxyConfiguration, callback) {
kvLogger.info('[PROXY] Start proxy');
checkJava(function(err){
if (err) {
kvLogger.error('Java not found')
if (callback) callback(new Error(Errors.NO_JAVA_CLIENT));
return;
}
var launchFile = '/tmp/launch.sh';
/// flags...
var jarClasspath = getProxyClasspath(proxyConfiguration);
var hostsFlag = getHostsFlag(proxyConfiguration.kvStoreHelperHosts);
var portFlag = getPortFlag(proxyConfiguration.host);
var logConfiguration =
' -Dlog4j.configuration=' +
path.normalize(kvmodule_dir + '/proxy/log4j.ini');
var storeFlag = getStoreFlag(proxyConfiguration.kvStoreName);
var commandLine =
'java ' +
logConfiguration +
jarClasspath +
' oracle.kv.proxy.KVProxy ' +
hostsFlag +
portFlag +
storeFlag +
' &';
kvLogger.debug('[PROXY] Launch proxy cmd: ' + commandLine);
fs.writeFileSync(launchFile, commandLine);
fs.appendFileSync(launchFile, 'echo $! > ' + PID_FILE);
fs.chmodSync(launchFile, '777');
var timeoutOnError, timeoutStartProxy;
var proxy = child_process.exec(launchFile, function(error, stdout, stderr){
kvLogger.debug('[PROXY] error: ' + error);
kvLogger.debug('[PROXY] stdout: ' + stdout);
kvLogger.debug('[PROXY] stderr: ' + stderr);
if (callback) timeoutOnError = setTimeout(function(){
clearTimeout(timeoutStartProxy);
kvLogger.debug('[PROXY] Error setting up the proxy');
if (callback) callback(new Error(Errors.PROXY_ERROR));
callback=null;
}, PROXY_ERROR_TIMEOUT);
});
proxy.on('exit', function(code){
if (fs.existsSync(launchFile)) fs.unlinkSync(launchFile);
if (fs.existsSync(PID_FILE)) {
proxyPid = fs.readFileSync(PID_FILE);
fs.unlinkSync(PID_FILE);
}
kvLogger.debug('[PROXY] Proxy launched with pid:' + proxyPid);
if (callback) timeoutStartProxy = setTimeout(function(){
clearTimeout(timeoutOnError);
kvLogger.debug('[PROXY] Return after ' + (PROXY_TIMEOUT / 1000) + ' secs');
if(callback) callback(null, proxyPid);
callback=null;
}, PROXY_TIMEOUT);
proxy.removeAllListeners('on');
});
})
}
exports.startProxy = startProxy;
/**
* Shutdown the proxy. If a proxy was started, this method will shutdown it.
* @param {ProxyConfiguration} proxyConfiguration the configuration used to start the proxy
* @param {function} callback function called after trying to shutdown the proxy
*/
function stopProxy (proxyConfiguration, callback) {
kvLogger.info('Shutdown proxy');
var colon = proxyConfiguration.host.indexOf(':');
var host = proxyConfiguration.host.substr(0, colon);
var port = proxyConfiguration.host.substr(colon+1);
var connection = thrift.createConnection(host, port, {
transport: thrift.TFramedTransport,
protocol: thrift.TBinaryProtocol
}).on('error', function(err) {
kvLogger.debug("Can't connect to the proxy to stop it - " + err);
if (callback) callback(err);
connection.removeAllListeners('error');
connection.removeAllListeners('connect');
}).on('connect', function(err){
kvLogger.debug('Thrift Connection successful');
var client = thrift.createClient(ONDBClient, that.thriftConnection);
client.shutdown();
connection.end();
if (callback) callback();
connection.removeAllListeners('error');
connection.removeAllListeners('connect');
});
}
exports.stopProxy = stopProxy;