2017-01-31 15:56:42 +00:00
|
|
|
/* From http://stackoverflow.com/questions/1985260/javascript-array-rotate */
|
|
|
|
Array.prototype.rotate = (function() {
|
|
|
|
// save references to array functions to make lookup faster
|
|
|
|
var push = Array.prototype.push,
|
|
|
|
splice = Array.prototype.splice;
|
|
|
|
|
|
|
|
return function(count) {
|
|
|
|
var len = this.length >>> 0, // convert to uint
|
|
|
|
count = count >> 0; // convert to int
|
|
|
|
|
|
|
|
// convert count to value in range [0, len)
|
|
|
|
count = ((count % len) + len) % len;
|
|
|
|
|
|
|
|
// use splice.call() instead of this.splice() to make function generic
|
|
|
|
push.apply(this, splice.call(this, 0, count));
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
const EventEmitter = require('eventemitter2').EventEmitter2;
|
|
|
|
const async = require('async');
|
|
|
|
const TorProcess = require('./TorProcess');
|
|
|
|
const _ = require('lodash');
|
2018-05-10 01:15:58 +00:00
|
|
|
const path = require('path');
|
|
|
|
const nanoid = require('nanoid');
|
|
|
|
const fs = require('fs');
|
2018-05-10 04:09:38 +00:00
|
|
|
const WeightedList = require('js-weighted-list');
|
|
|
|
|
|
|
|
const load_balance_methods = {
|
|
|
|
round_robin: function (instances) {
|
|
|
|
return instances.rotate(1);
|
|
|
|
},
|
|
|
|
weighted: function (instances) {
|
|
|
|
if (!instances._weighted_list) {
|
|
|
|
instances._weighted_list = new WeightedList(
|
|
|
|
instances.map((instance) => {
|
|
|
|
return [ instance.id, instance.definition.Weight, instance ]
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return instances._weighted_list.peek(instances.length).map((element) => element.data);
|
|
|
|
}
|
|
|
|
};
|
2017-01-31 15:56:42 +00:00
|
|
|
|
|
|
|
class TorPool extends EventEmitter {
|
2018-05-10 04:09:38 +00:00
|
|
|
constructor(tor_path, default_config, logger, nconf, data_directory, load_balance_method) {
|
2017-01-31 15:56:42 +00:00
|
|
|
super();
|
2018-05-10 04:09:38 +00:00
|
|
|
this._instances = [];
|
2018-05-10 01:15:58 +00:00
|
|
|
default_config = _.extend({}, (default_config || {}), nconf.get('torConfig'));
|
|
|
|
this.default_tor_config = default_config;
|
2018-05-10 04:09:38 +00:00
|
|
|
this.data_directory = data_directory || nconf.get('parentDataDirectory');
|
|
|
|
this.load_balance_method = load_balance_method || nconf.get('loadBalanceMethod');
|
2018-05-10 01:15:58 +00:00
|
|
|
!fs.existsSync(this.data_directory) && fs.mkdirSync(this.data_directory);
|
2017-01-31 15:56:42 +00:00
|
|
|
this.tor_path = tor_path || 'tor';
|
2017-01-31 23:11:36 +00:00
|
|
|
this.logger = logger;
|
2018-05-10 01:15:58 +00:00
|
|
|
this.nconf = nconf;
|
2017-01-31 15:56:42 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 07:50:52 +00:00
|
|
|
get instances() {
|
|
|
|
return this._instances.filter((tor) => tor.ready).slice(0);
|
|
|
|
}
|
2017-01-31 15:56:42 +00:00
|
|
|
|
2018-05-10 01:15:58 +00:00
|
|
|
create_instance(instance_definition, callback) {
|
|
|
|
instance_definition.Config = instance_definition.Config || {};
|
|
|
|
instance_definition.Config = _.extend(instance_definition.Config, this.default_tor_config);
|
|
|
|
let instance_id = nanoid();
|
2018-05-10 04:09:38 +00:00
|
|
|
instance_definition.Config.DataDirectory = instance_definition.Config.DataDirectory || path.join(this.data_directory, (instance_definition.Name || instance_id));
|
2018-05-10 01:15:58 +00:00
|
|
|
let instance = new TorProcess(this.tor_path, instance_definition.Config, this.logger, this.nconf);
|
|
|
|
instance.id = instance_id;
|
|
|
|
instance.definition = instance_definition;
|
2017-01-31 15:56:42 +00:00
|
|
|
instance.create((error) => {
|
|
|
|
if (error) return callback(error);
|
|
|
|
this._instances.push(instance);
|
2017-03-22 17:08:50 +00:00
|
|
|
|
2017-01-31 15:56:42 +00:00
|
|
|
instance.once('error', callback)
|
|
|
|
instance.once('ready', () => {
|
2017-03-26 01:02:56 +00:00
|
|
|
this.emit('instance_created', instance);
|
2017-01-31 23:11:36 +00:00
|
|
|
callback && callback(null, instance);
|
2017-01-31 15:56:42 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-10 01:15:58 +00:00
|
|
|
add(instance_definitions, callback) {
|
|
|
|
async.each(instance_definitions, (instance_definition, next) => {
|
|
|
|
this.create_instance(instance_definition, next);
|
2017-01-31 23:11:36 +00:00
|
|
|
}, (callback || (() => {})));
|
2017-01-31 15:56:42 +00:00
|
|
|
}
|
|
|
|
|
2018-05-10 01:15:58 +00:00
|
|
|
create(instances, callback) {
|
|
|
|
if (typeof(instances) === 'number') {
|
|
|
|
instances = Array.from(Array(instances)).map(() => {
|
|
|
|
return {
|
|
|
|
Config: {}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return this.add(instances, callback);
|
|
|
|
}
|
|
|
|
|
2017-03-25 00:11:06 +00:00
|
|
|
remove(instances, callback) {
|
|
|
|
let instances_to_remove = this._instances.splice(0, instances);
|
|
|
|
async.each(instances_to_remove, (instance, next) => {
|
|
|
|
instance.exit(next);
|
|
|
|
}, callback);
|
|
|
|
}
|
|
|
|
|
2018-05-10 01:15:58 +00:00
|
|
|
remove_at(instance_index, callback) {
|
|
|
|
let instance = this._instances.slice(instance_index, 1);
|
|
|
|
instance.exit(() => {
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-01-31 15:56:42 +00:00
|
|
|
next() {
|
2018-05-10 04:09:38 +00:00
|
|
|
this._instances = load_balance_methods[this.nconf.get('loadBalanceMethod')](this._instances);
|
2017-01-31 15:56:42 +00:00
|
|
|
return this.instances[0];
|
|
|
|
}
|
|
|
|
|
2018-05-10 01:15:58 +00:00
|
|
|
exit(callback) {
|
|
|
|
async.each(this.instances, (instance,next) => {
|
|
|
|
instance.exit(next);
|
|
|
|
}, (error) => {
|
|
|
|
callback && callback(error);
|
|
|
|
});
|
2017-01-31 15:56:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
new_ips() {
|
|
|
|
this.instances.forEach((tor) => tor.new_ip());
|
|
|
|
}
|
2018-05-10 01:15:58 +00:00
|
|
|
|
|
|
|
new_ip_at(index) {
|
|
|
|
this.instances[index].new_ip();
|
|
|
|
}
|
2017-01-31 15:56:42 +00:00
|
|
|
};
|
|
|
|
|
2018-05-10 04:09:38 +00:00
|
|
|
TorPool.LoadBalanceMethods = load_balance_methods;
|
|
|
|
|
2017-01-31 15:56:42 +00:00
|
|
|
module.exports = TorPool;
|