Adds methods to the RPC Interface to interact with groups. Adds tests for those methods

This commit is contained in:
Zachary Boyd 2018-09-11 02:48:56 -04:00
parent 99c59541e0
commit 998185d31e
9 changed files with 227 additions and 43 deletions

View file

@ -6,15 +6,43 @@ The following functions are available via the RPC
Returns an array containing information on the instances currently running under the router.
## queryInstanceByName(String)
## queryInstanceByName(instance_name: String)
Returns information on an instance identified by name
## queryInstanceAt(Integer)
## queryInstancesByGroup(instance_name: String)
Returns information on an instance identified by index
Returns an array containing information on the instances within a group.
## createInstances(Array or Integer)
## queryInstanceAt(instance_index: Integer)
Returns information on an instance identified by index.
## queryInstanceNames()
Returns a list of all instance names.
## queryGroupNames()
Returns a list of all instance groups.
## addInstanceToGroupByName(group: String, instance_name: String)
Adds an instance, identified by name, to a group
## addInstanceToGroupAt(group: String, instance_index: Integer)
Adds an instance, identified by index, to a group
## removeInstanceFromGroupByName(group: String, instance_name: String)
Removes an instance, identified by name, from a group
## removeInstanceFromGroupAt(group: String, instance_index: Integer)
Removes an instance, identified by index, from a group
## createInstances(instances: Array or Integer)
If passed an Integer, creates that many Tor instances. An array can also be passed describing the names, weights and configurations of prospective instances. :
@ -38,19 +66,19 @@ var rpcRequest = {
Will wait until the Tor Instance has fully connected to the network before returning
## addInstances(Array)
## addInstances(instances: Array)
Serves the same purpose as "createInstances" but only takes an Array
## removeInstances(Integer)
## removeInstances(instances: Integer)
Removes a number of instances
## removeInstanceAt(Integer)
## removeInstanceAt(instance_index: Integer)
Remove a specific instance from the pool by its index
## removeInstanceByName(String)
## removeInstanceByName(instance_name: String)
Remove a specific instance from the pool by its name
@ -58,14 +86,18 @@ Remove a specific instance from the pool by its name
Get new identites for all instances
## newIdentityAt(Integer)
## newIdentityAt(instance_index: Integer)
Get a new identity for a specific instance by its index
## newIdentityByName(String)
## newIdentityByName(instance_name: String)
Get a new identity for a specific instance by its name
## newIdentitiesByGroup(group: String)
Get new identities for all instances in a group
## nextInstance()
Cycle to the next instance using the load balancing method
@ -74,15 +106,19 @@ Cycle to the next instance using the load balancing method
Shutdown all Tor instances
## setTorConfig(Object)
## setTorConfig(config: Object)
Applies the configuration to all active instances
Apples the provided configuration to all instances using the control protocol. Changes will be applied immediately.
## setTorConfigByGroup(group: String, config: Object)
Apples the provided configuration to all instances in a group using the control protocol. Changes will be applied immediately.
## getDefaultTorConfig()
Retrieve the default Tor Config for all future instances
## setDefaultTorConfig(Object)
## setDefaultTorConfig(config: Object)
Set the default Tor Config for all future instances
@ -90,11 +126,11 @@ Set the default Tor Config for all future instances
Get the current load balance method
## setLoadBalanceMethod(String)
## setLoadBalanceMethod(load_balance_method: String)
Set the current load balance method
## getInstanceConfigAt(Integer: index, String: keyword)
## getInstanceConfigAt(instance_index: Integer, keyword: String)
Retrieves the current value of an option set in the configuration by the index of the instance using the control protocol.
@ -111,28 +147,32 @@ var rpcRequest = {
};
```
## getInstanceConfigByName(String: name, String: keyword)
## getInstanceConfigByName(name: String, keyword: String)
Works the same way as `getInstanceConfigAt` except takes an instance name instead of an index
## setInstanceConfigAt(Integer: index, String: keyword, String: value)
## setInstanceConfigAt(index: Integer, keyword: String, value: String)
Sets the value in the configuration of an instance using the control protocol. Changes will be applied immediately.
## setInstanceConfigByName(Integer: index, String: keyword, String: value)
## setInstanceConfigByName(index: Integer, keyword: String, value: String)
Works the same way as `setInstanceConfigAt` except takes an instance name instead of an index
## signalAllInstances(String)
## signalAllInstances(signal: String)
Sends a signal using the control protocol to all instances
A list of all signals can be [found here](https://gitweb.torproject.org/torspec.git/tree/control-spec.txt)
## signalInstanceAt(Integer: index, String: signal)
## signalInstancesByGroup(group: String, signal: String)
Sends a signal using the control protocol to all instances in a group
## signalInstanceAt(index: Integer, signal: String)
Sends a signal using the control protocol to an instance identified by its index
## signalInstanceByName(String: name, String: signal)
## signalInstanceByName(name: String, signal: String)
Sends a signal using the control protocol to an instance identified by its name

6
package-lock.json generated
View file

@ -1085,9 +1085,9 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"nanoid": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.0.2.tgz",
"integrity": "sha512-sCTwJt690lduNHyqknXJp8pRwzm80neOLGaiTHU2KUJZFVSErl778NNCIivEQCX5gNT0xR1Jy3HEMe/TABT6lw=="
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.2.3.tgz",
"integrity": "sha512-BAnxAdaihzMoszwhqRy8FPOX+dijs7esUEUYTIQ1KsOSKmCVNYnitAMmBDFxYzA6VQYvuUKw7o2K1AcMBTGzIg=="
},
"native-dns": {
"version": "git+https://github.com/znetstar/node-dns.git#336f1d3027b2a3da719b5cd65380219267901aeb",

View file

@ -29,7 +29,7 @@
"jrpc2": "git+https://github.com/znetstar/jrpc2.git#f1521bd3f2fa73d716e74bf8f746d08d5e03e7d7",
"js-weighted-list": "^0.1.1",
"lodash": "^4.17.4",
"nanoid": "^1.0.2",
"nanoid": "^1.2.3",
"native-dns": "git+https://github.com/znetstar/node-dns.git#336f1d3027b2a3da719b5cd65380219267901aeb",
"nconf": "^0.10.0",
"shelljs": "^0.8.2",

View file

@ -53,6 +53,12 @@ class ControlServer {
return instance_info(this.torPool.instance_at(index));
}).bind(this));
server.expose('queryInstanceNames', (() => this.torPool.instance_names).bind(this));
server.expose('queryGroupNames', (() => Array.from(this.torPool.group_names)).bind(this));
server.expose('queryInstancesByGroup', ((group) => this.torPool.instances_by_group(group).map(instance_info)).bind(this));
server.expose('createInstances', (async (num) => {
let instances = await this.torPool.create(num);
@ -77,6 +83,8 @@ class ControlServer {
server.expose('newIdentityByName', this.torPool.new_identity_by_name.bind(this.torPool));
server.expose('newIdentitiesByGroup', (async (group) => await this.torPool.new_identites_by_group(group)).bind(this));
server.expose('nextInstance', (async () => instance_info( await this.torPool.next() )).bind(this));
server.expose('closeInstances', (async () => this.torPool.exit()).bind(this));
@ -97,6 +105,14 @@ class ControlServer {
}));
}).bind(this));
server.expose('setTorConfigByGroup', (async (group, config) => {
await Promise.all(Object.keys(config).map((key) => {
let value = config[key];
return this.torPool.set_config_by_group(group, key, value);
}));
}).bind(this));
server.expose('getLoadBalanceMethod', (async () => {
return this.torPool.load_balance_method;
}).bind(this));
@ -118,6 +134,16 @@ class ControlServer {
server.expose('signalInstanceAt', this.torPool.signal_at.bind(this.torPool));
server.expose('signalInstanceByName', this.torPool.signal_by_name.bind(this.torPool));
server.expose('signalInstancesByGroup', (async (group, signal) => await this.torPool.signal_by_group(group, signal)).bind(this));
server.expose('addInstanceToGroupByName', ((group, instance_name) => this.torPool.add_instance_to_group_by_name(group, instance_name)).bind(this));
server.expose('addInstanceToGroupAt', ((group, instance_index) => this.torPool.add_instance_to_group_at(group, instance_index)).bind(this));
server.expose('removeInstanceFromGroupByName', ((group, instance_name) => this.torPool.remove_instance_from_group_by_name(group, instance_name)).bind(this));
server.expose('removeInstanceFromGroupAt', ((group, instance_index) => this.torPool.remove_instance_from_group_at(group, instance_index)).bind(this));
}
async listenTcp(port, hostname) {

View file

@ -23,8 +23,6 @@ const fs = require('fs');
const { EventEmitter } = require('eventemitter3');
const Promise = require("bluebird");
const _ = require('lodash');
const nanoid = require('nanoid');
const WeightedList = require('js-weighted-list');
const TorProcess = require('./TorProcess');
@ -46,7 +44,7 @@ class TorPool extends EventEmitter {
}
get group_names() {
return new Set(_.uniq(_.flatten(this.instances.map((instance) => instance.instance_group).filter(Boolean))).sort());
return new Set(_.flatten(this.instances.map((instance) => instance.instance_group).filter(Boolean)));
}
instances_by_group(group_name) {
@ -203,13 +201,10 @@ class TorPool extends EventEmitter {
this._instances._weighted_list = void(0);
instance_definition.Config = _.extend(_.cloneDeep(this.default_tor_config), (instance_definition.Config || {}));
let instance_id = nanoid();
instance_definition.Config.DataDirectory = instance_definition.Config.DataDirectory || path.join(this.data_directory, (instance_definition.Name || instance_id));
let instance = new TorProcess(this.tor_path, instance_definition, this.granax_options, this.logger);
instance.id = instance_id;
await instance.create();
this._instances.push(instance);

View file

@ -13,6 +13,7 @@ const getPort = require('get-port');
const del = require('del');
const temp = require('temp');
const { TorController } = require('granax');
const nanoid = require("nanoid");
Promise.promisifyAll(temp);
Promise.promisifyAll(fs);
@ -31,6 +32,7 @@ class TorProcess extends EventEmitter {
this.tor_path = tor_path;
this.granax_options = granax_options;
this.control_password = crypto.randomBytes(128).toString('base64');
this._id = nanoid();
this.tor_config.DataDirectory = this.tor_config.DataDirectory || temp.mkdirSync();
}
@ -41,17 +43,20 @@ class TorProcess extends EventEmitter {
resolve();
});
});
this.process.kill('SIGINT');
await p;
}
get id() { return this._id; }
get instance_group() {
return (this.definition && this.definition.Group) || null;
}
get instance_name() {
return (this.definition && this.definition.Name) || this.process.pid;
return (this.definition && this.definition.Name) || this.id;
}
get definition() { return this._definition; }

View file

@ -93,7 +93,7 @@ async function main(nconf, logger) {
let thereWasAnExitError = false;
let { handleError } = this;
try {
await Promise.all(control.torPool.instances.map((instance) => instance.exit()));
await control.torPool.exit();
} catch (exitError) {
logger.error(`[global]: error closing tor instances: ${exitError.message}`);
thereWasAnExitError = true;

View file

@ -26,7 +26,39 @@ describe('ControlServer - RPC Interface', function () {
describe('#createInstances(number_of_instances)', function () {
this.timeout(WAIT_FOR_CREATE*2);
it('should create an instance', async function () {
await rpcClient.invokeAsync('createInstances', [2]);
await rpcClient.invokeAsync('createInstances', [{ Name: 'instance-1', Group: "foo" }]);
});
});
describe('#queryInstanceNames()', function () {
it("should have an instance named \"instance-1\"", async function () {
let raw = await rpcClient.invokeAsync('queryInstanceNames', [ ]);
let instances = JSON.parse(raw).result;
assert.deepEqual(instances, [ 'instance-1' ]);
});
});
describe('#queryGroupNames()', function () {
it("should have a group named \"foo\"", async function () {
let raw = await rpcClient.invokeAsync('queryGroupNames', [ ]);
let groups = JSON.parse(raw).result;
assert.deepEqual(groups, [ 'foo' ]);
});
});
describe('#queryInstancesByGroup()', function () {
it("should return an instance named \"instance-1\"", async function () {
let raw = await rpcClient.invokeAsync('queryInstancesByGroup', [ 'foo' ]);
let instances = JSON.parse(raw).result;
assert.equal(instances.length, 1);
assert.ok(instances[0]);
assert.equal(instances[0].name, 'instance-1');
});
});
@ -46,14 +78,15 @@ describe('ControlServer - RPC Interface', function () {
describe('#addInstances(definitions)', function () {
this.timeout(WAIT_FOR_CREATE);
it("should add an instance based on a defintion", async function () {
var def = {
Name: 'instance-1'
let def = {
Name: 'instance-2',
Group: 'bar'
};
await rpcClient.invokeAsync('addInstances', [ [ def ] ]);
await rpcClient.invokeAsync('addInstances', [ def ]);
});
it("tor pool should now contain and instance that has the same name as the name specified in the defintion", function () {
assert.ok(rpcControlServer.torPool.instance_by_name('instance-1'));
assert.ok(rpcControlServer.torPool.instance_by_name('instance-2'));
});
});
@ -79,6 +112,63 @@ describe('ControlServer - RPC Interface', function () {
});
});
describe('#addInstanceToGroupByName()', function () {
it(`should add "instance-1" to baz`, async function () {
await rpcClient.invokeAsync('addInstanceToGroupByName', [ 'baz', "instance-1" ]);
});
it('"instance-1" should be added to "baz"', function () {
assert.include(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
after('remove from group', function () {
rpcControlServer.torPool.groups['baz'].remove_by_name('instance-1');
});
});
describe('#addInstanceToGroupAt()', function () {
it(`should add "instance-1" to baz`, async function () {
await rpcClient.invokeAsync('addInstanceToGroupAt', [ 'baz', 0 ]);
});
it('"instance-1" should be added to "baz"', function () {
assert.include(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
after('remove from group', function () {
rpcControlServer.torPool.groups['baz'].remove_by_name('instance-1');
});
});
describe('#removeInstanceFromGroupByName()', function () {
before('add to group', function () {
rpcControlServer.torPool.groups['baz'].add_by_name('instance-1');
});
it(`should remove "instance-1" from baz`, async function () {
await rpcClient.invokeAsync('removeInstanceFromGroupByName', [ 'baz', "instance-1" ]);
});
it('"instance-1" should be remove from to "baz"', function () {
assert.notInclude(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
});
describe('#removeInstanceFromGroupAt()', function () {
before('add to group', function () {
rpcControlServer.torPool.groups['baz'].add_by_name('instance-1');
});
it(`should remove "instance-1" from baz`, async function () {
await rpcClient.invokeAsync('removeInstanceFromGroupAt', [ 'baz', 0 ]);
});
it('"instance-1" should be remove from to "baz"', function () {
assert.notInclude(rpcControlServer.torPool.instances_by_group('baz').map((i) => i.instance_name), "instance-1");
});
});
describe('#newIdentites()', function () {
this.timeout(3000);
it('should request new identities for all instances', async function () {
@ -100,6 +190,13 @@ describe('ControlServer - RPC Interface', function () {
});
});
describe('#newIdentitiesByGroup()', function () {
it(`should get new identites for all instances in group`, async function () {
await rpcClient.invokeAsync('newIdentitiesByGroup', [ 'foo' ]);
});
});
describe("#setTorConfig(config_object)", function () {
this.timeout(3000);
it('should set several config variables on all instances', async function () {
@ -145,6 +242,22 @@ describe('ControlServer - RPC Interface', function () {
});
});
describe('#setTorConfigByGroup()', function () {
it(`should set the config value on all instances`, async function () {
await rpcClient.invokeAsync('setTorConfigByGroup', [ 'foo', { 'ProtocolWarnings': 1 } ]);
});
it('all instances should have the config value set', async function () {
let values = _.flatten(await Promise.all(rpcControlServer.torPool.instances_by_group('foo').map((i) => i.get_config('ProtocolWarnings'))));
assert.isTrue(values.every((v) => v === "1"));
});
after('unset config values', async function () {
rpcControlServer.torPool.set_config_by_group('foo', 'ProtocolWarnings', 0);
});
});
describe('#getDefaultTorConfig()', function () {
before('set tor config', function () {
nconf.set('torConfig', { TestSocks: 1 });
@ -295,6 +408,12 @@ describe('ControlServer - RPC Interface', function () {
});
});
describe('#signalInstancesByGroup()', function () {
it(`should get new identites for all instances in group`, async function () {
await rpcClient.invokeAsync('signalInstancesByGroup', [ 'foo', 'DEBUG' ]);
});
});
describe("#nextInstance()", function () {
this.timeout(3000);
let instance_name;
@ -341,7 +460,6 @@ describe('ControlServer - RPC Interface', function () {
it('no instances should be present in the pool', function () {
assert.equal(rpcControlServer.torPool.instances.length, 0);
assert.notEqual(rpcControlServer.torPool.instances.length, instance_num);
});
});

View file

@ -248,7 +248,7 @@ describe('TorPool', function () {
await torPool.remove_by_name('instance-3');
assert.notIncludeDeepOrderedMembers(torPool.instance_names, [ "instance-3" ]);
assert.notInclude(torPool.instance_names, "instance-3");
});
after('shutdown tor pool', async function () { await torPool.exit(); });
@ -672,7 +672,7 @@ describe('TorPool', function () {
});
it('the instance should be no longer be in the group', function () {
assert.notIncludeOrderedMembers(instance.instance_group, ["foo"]);
assert.notInclude(instance.instance_group, "foo");
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
@ -693,7 +693,7 @@ describe('TorPool', function () {
});
it('the instance should no longer be in the group', function () {
assert.notIncludeOrderedMembers(instance.instance_group, ["foo"]);
assert.notInclude(instance.instance_group, "foo");
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });
@ -714,7 +714,7 @@ describe('TorPool', function () {
});
it('the instance should no longer be in the group', function () {
assert.notIncludeOrderedMembers(instance.instance_group, ["foo"]);
assert.notInclude(instance.instance_group, "foo");
});
after('shutdown tor pool', async function () { await tor_pool.exit(); });