Skip to content

Commit 335b3e2

Browse files
shultsluin
authored andcommitted
feat: nat support for sentinel connector (#799)
1 parent 6246b52 commit 335b3e2

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
node_modules
22
*.cpuprofile
33
/test.js
4+
/.idea
45
built
56

67
.vscode

lib/connectors/SentinelConnector/index.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {createConnection, Socket} from 'net'
2+
import {NatMap} from '../../cluster/ClusterOptions';
23
import {CONNECTION_CLOSED_ERROR_MSG, packObject, sample} from '../../utils'
34
import {connect as createTLSConnection, TLSSocket, SecureContextOptions} from 'tls'
45
import {ITcpConnectionOptions, isIIpcConnectionOptions} from '../StandaloneConnector'
@@ -30,6 +31,7 @@ interface ISentinelConnectionOptions extends ITcpConnectionOptions {
3031
connectTimeout?: number
3132
enableTLSForSentinelMode?: boolean
3233
sentinelTLS?: SecureContextOptions
34+
natMap: NatMap
3335
updateSentinels?: boolean
3436
}
3537

@@ -152,7 +154,7 @@ export default class SentinelConnector extends AbstractConnector {
152154
result.map<IAddressFromResponse>(packObject as (value: any) => IAddressFromResponse).forEach(sentinel => {
153155
const flags = sentinel.flags ? sentinel.flags.split(',') : []
154156
if (flags.indexOf('disconnected') === -1 && sentinel.ip && sentinel.port) {
155-
const endpoint = addressResponseToAddress(sentinel)
157+
const endpoint = this.sentinelNatResolve(addressResponseToAddress(sentinel))
156158
if (this.sentinelIterator.add(endpoint)) {
157159
debug('adding sentinel %s:%s', endpoint.host, endpoint.port)
158160
}
@@ -174,7 +176,10 @@ export default class SentinelConnector extends AbstractConnector {
174176
if (err) {
175177
return callback(err)
176178
}
177-
callback(null, Array.isArray(result) ? { host: result[0], port: Number(result[1]) } : null)
179+
180+
callback(null, this.sentinelNatResolve(
181+
Array.isArray(result) ? { host: result[0], port: Number(result[1]) } : null
182+
))
178183
})
179184
})
180185
}
@@ -194,10 +199,19 @@ export default class SentinelConnector extends AbstractConnector {
194199
slave.flags && !slave.flags.match(/(disconnected|s_down|o_down)/)
195200
))
196201

197-
callback(null, selectPreferredSentinel(availableSlaves, this.options.preferredSlaves))
202+
callback(null, this.sentinelNatResolve(
203+
selectPreferredSentinel(availableSlaves, this.options.preferredSlaves)
204+
))
198205
})
199206
}
200207

208+
sentinelNatResolve (item: ISentinelAddress) {
209+
if (!item || !this.options.natMap)
210+
return item;
211+
212+
return this.options.natMap[`${item.host}:${item.port}`] || item
213+
}
214+
201215
private resolve (endpoint, callback: NodeCallback<ITcpConnectionOptions>): void {
202216
if (typeof Redis === 'undefined') {
203217
Redis = require('../../redis')

lib/redis.js

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ var PromiseContainer = require('./promiseContainer');
8888
* strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range).
8989
* @param {boolean} [options.enableTLSForSentinelMode=false] - Whether to support the `tls` option
9090
* when connecting to Redis via sentinel mode.
91+
* @param {NatMap} [options.natMap=null] NAT map for sentinel connector.
9192
* @param {boolean} [options.updateSentinels=true] - Update the given `sentinels` list with new IP
9293
* addresses when communicating with existing sentinels.
9394
* @extends [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
@@ -174,6 +175,7 @@ Redis.defaultOptions = {
174175
sentinelRetryStrategy: function (times) {
175176
return Math.min(times * 10, 1000);
176177
},
178+
natMap: null,
177179
enableTLSForSentinelMode: false,
178180
updateSentinels: true,
179181
// Status

test/functional/sentinel_nat.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
describe('sentinel_nat', function() {
2+
it('connects to server as expected', function(done) {
3+
4+
var sentinel = new MockServer(27379, function (argv) {
5+
if (argv[0] === 'sentinel' && argv[1] === 'get-master-addr-by-name') {
6+
return ['127.0.0.1', '17380'];
7+
}
8+
})
9+
10+
var redis = new Redis({
11+
sentinels: [
12+
{ host: '127.0.0.1', port: '27379' }
13+
],
14+
natMap: {
15+
'127.0.0.1:17380': {
16+
host: 'localhost',
17+
port: 6379,
18+
}
19+
},
20+
name: 'master',
21+
lazyConnect: true,
22+
})
23+
24+
redis.connect(function(err) {
25+
if (err) {
26+
sentinel.disconnect(function() {})
27+
return done(err)
28+
}
29+
sentinel.disconnect(done)
30+
})
31+
})
32+
33+
it('rejects connection if host is not defined in map', function(done) {
34+
var sentinel = new MockServer(27379, function (argv) {
35+
if (argv[0] === 'sentinel' && argv[1] === 'get-master-addr-by-name') {
36+
return ['127.0.0.1', '17380']
37+
}
38+
39+
if (argv[0] === 'sentinel' && argv[1] === 'sentinels' &&argv[2] === 'master') {
40+
return ['127.0.0.1', '27379']
41+
}
42+
})
43+
44+
var redis = new Redis({
45+
sentinels: [
46+
{ host: '127.0.0.1', port: '27379' }
47+
],
48+
natMap: {
49+
'127.0.0.1:17381': {
50+
host: 'localhost',
51+
port: 6379,
52+
}
53+
},
54+
maxRetriesPerRequest: 1,
55+
name: 'master',
56+
lazyConnect: true,
57+
})
58+
59+
redis
60+
.connect()
61+
.then(function() {
62+
throw new Error("Should not call")
63+
})
64+
.catch(function(err) {
65+
if (err.message === 'Connection is closed.') {
66+
return done(null)
67+
}
68+
sentinel.disconnect(done)
69+
})
70+
})
71+
72+
})

0 commit comments

Comments
 (0)