Skip to content

Commit

Permalink
feat: nat support for sentinel connector (#799)
Browse files Browse the repository at this point in the history
  • Loading branch information
shults authored and luin committed Mar 12, 2019
1 parent 6246b52 commit 335b3e2
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules
*.cpuprofile
/test.js
/.idea
built

.vscode
20 changes: 17 additions & 3 deletions lib/connectors/SentinelConnector/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -30,6 +31,7 @@ interface ISentinelConnectionOptions extends ITcpConnectionOptions {
connectTimeout?: number
enableTLSForSentinelMode?: boolean
sentinelTLS?: SecureContextOptions
natMap: NatMap
updateSentinels?: boolean
}

Expand Down Expand Up @@ -152,7 +154,7 @@ export default class SentinelConnector extends AbstractConnector {
result.map<IAddressFromResponse>(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)
}
Expand All @@ -174,7 +176,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
))
})
})
}
Expand All @@ -194,10 +199,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<ITcpConnectionOptions>): void {
if (typeof Redis === 'undefined') {
Redis = require('../../redis')
Expand Down
2 changes: 2 additions & 0 deletions lib/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* @param {boolean} [options.updateSentinels=true] - Update the given `sentinels` list with new IP
* addresses when communicating with existing sentinels.
* @extends [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
Expand Down Expand Up @@ -174,6 +175,7 @@ Redis.defaultOptions = {
sentinelRetryStrategy: function (times) {
return Math.min(times * 10, 1000);
},
natMap: null,
enableTLSForSentinelMode: false,
updateSentinels: true,
// Status
Expand Down
72 changes: 72 additions & 0 deletions test/functional/sentinel_nat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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)
})
})

})

0 comments on commit 335b3e2

Please sign in to comment.