Source: Proxy.js

/*-
 *
 *  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;