From 4f1ffc4ec56fbcd23226c11b18e1dbd6badd32df Mon Sep 17 00:00:00 2001 From: Yarik Kotsur Date: Tue, 19 Feb 2019 15:36:41 +0100 Subject: [PATCH 1/4] nat support for sentinel connector --- .gitignore | 1 + lib/connectors/SentinelConnector/index.ts | 22 +++++- lib/redis.js | 1 + test/functional/sentinel_nat.js | 90 +++++++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 test/functional/sentinel_nat.js diff --git a/.gitignore b/.gitignore index f8b21dfd..5c0edfae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules *.cpuprofile /test.js +/.idea built .vscode diff --git a/lib/connectors/SentinelConnector/index.ts b/lib/connectors/SentinelConnector/index.ts index 07cbec1c..a580dce2 100644 --- a/lib/connectors/SentinelConnector/index.ts +++ b/lib/connectors/SentinelConnector/index.ts @@ -1,4 +1,5 @@ import {createConnection, Socket} from 'net' +import {NatMap} from '../../cluster/ClusterOptions'; import {CONNECTION_CLOSED_ERROR_MSG, packObject, sample} from '../../utils' import {connect as createTLSConnection, TLSSocket, SecureContextOptions} from 'tls' import {ITcpConnectionOptions, isIIpcConnectionOptions} from '../StandaloneConnector' @@ -30,6 +31,7 @@ interface ISentinelConnectionOptions extends ITcpConnectionOptions { connectTimeout?: number enableTLSForSentinelMode?: boolean sentinelTLS?: SecureContextOptions + natMap: NatMap } export default class SentinelConnector extends AbstractConnector { @@ -46,6 +48,10 @@ export default class SentinelConnector extends AbstractConnector { throw new Error('Requires the name of master.') } + if (this.options.natMap && Object.keys(this.options.natMap).length === 0) { + throw new Error('Empty natMap is not allowed.') + } + this.sentinelIterator = new SentinelIterator(this.options.sentinels) } @@ -168,7 +174,10 @@ export default class SentinelConnector extends AbstractConnector { if (err) { return callback(err) } - callback(null, Array.isArray(result) ? { host: result[0], port: Number(result[1]) } : null) + + callback(null, this.sentinelNatResolve( + Array.isArray(result) ? { host: result[0], port: Number(result[1]) } : null + )) }) }) } @@ -188,10 +197,19 @@ export default class SentinelConnector extends AbstractConnector { slave.flags && !slave.flags.match(/(disconnected|s_down|o_down)/) )) - callback(null, selectPreferredSentinel(availableSlaves, this.options.preferredSlaves)) + callback(null, this.sentinelNatResolve( + selectPreferredSentinel(availableSlaves, this.options.preferredSlaves) + )) }) } + sentinelNatResolve (item: ISentinelAddress) { + if (!item || !this.options.natMap) + return item; + + return this.options.natMap[`${item.host}:${item.port}`] || item + } + private resolve (endpoint, callback: NodeCallback): void { if (typeof Redis === 'undefined') { Redis = require('../../redis') diff --git a/lib/redis.js b/lib/redis.js index 029b752b..d5bc43c7 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -172,6 +172,7 @@ Redis.defaultOptions = { sentinelRetryStrategy: function (times) { return Math.min(times * 10, 1000); }, + natMap: null, enableTLSForSentinelMode: false, // Status password: null, diff --git a/test/functional/sentinel_nat.js b/test/functional/sentinel_nat.js new file mode 100644 index 00000000..89ebb006 --- /dev/null +++ b/test/functional/sentinel_nat.js @@ -0,0 +1,90 @@ +describe('sentinel_nat', function() { + it('connects to server as expected', function(done) { + + var sentinel = new MockServer(27379, function (argv) { + if (argv[0] === 'sentinel' && argv[1] === 'get-master-addr-by-name') { + return ['127.0.0.1', '17380']; + } + }) + + var redis = new Redis({ + sentinels: [ + { host: '127.0.0.1', port: '27379' } + ], + natMap: { + '127.0.0.1:17380': { + host: 'localhost', + port: 6379, + } + }, + name: 'master', + lazyConnect: true, + }) + + redis.connect(function(err) { + if (err) { + sentinel.disconnect(function() {}) + return done(err) + } + sentinel.disconnect(done) + }) + }) + + it('rejects connection if host is not defined in map', function(done) { + var sentinel = new MockServer(27379, function (argv) { + if (argv[0] === 'sentinel' && argv[1] === 'get-master-addr-by-name') { + return ['127.0.0.1', '17380'] + } + + if (argv[0] === 'sentinel' && argv[1] === 'sentinels' &&argv[2] === 'master') { + return ['127.0.0.1', '27379'] + } + }) + + var redis = new Redis({ + sentinels: [ + { host: '127.0.0.1', port: '27379' } + ], + natMap: { + '127.0.0.1:17381': { + host: 'localhost', + port: 6379, + } + }, + maxRetriesPerRequest: 1, + name: 'master', + lazyConnect: true, + }) + + redis + .connect() + .then(function() { + throw new Error("Should not call") + }) + .catch(function(err) { + if (err.message === 'Connection is closed.') { + return done(null) + } + sentinel.disconnect(done) + }) + }) + + it('throws \'Empty natMap is not allowed.\' when empty natMap was given', function(done) { + try { + new Redis({ + sentinels: [ + { host: '127.0.0.1', port: '27379' } + ], + natMap: {}, + name: 'master', + }) + } catch (error) { + if (error.message === 'Empty natMap is not allowed.') { + done(null) + } else { + done(new Error('Should not call')) + } + } + }) + +}) \ No newline at end of file From b6717a311427823ba2252559c43ba7621d79d8f9 Mon Sep 17 00:00:00 2001 From: Yarik Kotsur Date: Tue, 19 Feb 2019 17:03:41 +0100 Subject: [PATCH 2/4] add jsdoc --- lib/redis.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/redis.js b/lib/redis.js index d5bc43c7..3ab60b87 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -88,6 +88,7 @@ var PromiseContainer = require('./promiseContainer'); * strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range). * @param {boolean} [options.enableTLSForSentinelMode=false] - Whether to support the `tls` option * when connecting to Redis via sentinel mode. + * @param {NatMap} [options.natMap=null] NAT map for sentinel connector. * @extends [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) * @extends Commander * @example From 4cdc20d9cec6a04979ffd6791010ab0d7aee5875 Mon Sep 17 00:00:00 2001 From: Yarik Kotsur Date: Tue, 26 Feb 2019 10:33:35 +0100 Subject: [PATCH 3/4] add natResolution to sentinel servers --- lib/connectors/SentinelConnector/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connectors/SentinelConnector/index.ts b/lib/connectors/SentinelConnector/index.ts index a580dce2..1a51aa87 100644 --- a/lib/connectors/SentinelConnector/index.ts +++ b/lib/connectors/SentinelConnector/index.ts @@ -152,7 +152,7 @@ export default class SentinelConnector extends AbstractConnector { result.map(packObject as (value: any) => IAddressFromResponse).forEach(sentinel => { const flags = sentinel.flags ? sentinel.flags.split(',') : [] if (flags.indexOf('disconnected') === -1 && sentinel.ip && sentinel.port) { - const endpoint = addressResponseToAddress(sentinel) + const endpoint = this.sentinelNatResolve(addressResponseToAddress(sentinel)) if (this.sentinelIterator.add(endpoint)) { debug('adding sentinel %s:%s', endpoint.host, endpoint.port) } From d578980424d7c5d04783b92006af10645a2bab49 Mon Sep 17 00:00:00 2001 From: Yarik Kotsur Date: Tue, 12 Mar 2019 12:40:05 +0100 Subject: [PATCH 4/4] allow empty map; remove tests --- lib/connectors/SentinelConnector/index.ts | 4 ---- test/functional/sentinel_nat.js | 18 ------------------ 2 files changed, 22 deletions(-) diff --git a/lib/connectors/SentinelConnector/index.ts b/lib/connectors/SentinelConnector/index.ts index 1a51aa87..ae286e32 100644 --- a/lib/connectors/SentinelConnector/index.ts +++ b/lib/connectors/SentinelConnector/index.ts @@ -48,10 +48,6 @@ export default class SentinelConnector extends AbstractConnector { throw new Error('Requires the name of master.') } - if (this.options.natMap && Object.keys(this.options.natMap).length === 0) { - throw new Error('Empty natMap is not allowed.') - } - this.sentinelIterator = new SentinelIterator(this.options.sentinels) } diff --git a/test/functional/sentinel_nat.js b/test/functional/sentinel_nat.js index 89ebb006..e956fab4 100644 --- a/test/functional/sentinel_nat.js +++ b/test/functional/sentinel_nat.js @@ -69,22 +69,4 @@ describe('sentinel_nat', function() { }) }) - it('throws \'Empty natMap is not allowed.\' when empty natMap was given', function(done) { - try { - new Redis({ - sentinels: [ - { host: '127.0.0.1', port: '27379' } - ], - natMap: {}, - name: 'master', - }) - } catch (error) { - if (error.message === 'Empty natMap is not allowed.') { - done(null) - } else { - done(new Error('Should not call')) - } - } - }) - }) \ No newline at end of file