Skip to content

Commit 1ee604b

Browse files
committed
test cluster and sentinel
1 parent 9c60e40 commit 1ee604b

File tree

5 files changed

+92
-41
lines changed

5 files changed

+92
-41
lines changed

packages/client/lib/client/index.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,22 @@ describe('Client', () => {
261261
})
262262
}, GLOBAL.SERVERS.OPEN);
263263

264+
testUtils.testWithCluster('Timeout with custom timeout config (cluster)', async cluster => {
265+
await blockSetImmediate(async () => {
266+
await assert.rejects(cluster.sendCommand(undefined, true, ['PING'], {
267+
timeout: 5
268+
}), TimeoutError);
269+
})
270+
}, GLOBAL.CLUSTERS.OPEN);
271+
272+
testUtils.testWithClientSentinel('Timeout with custom timeout config (sentinel)', async sentinel => {
273+
await blockSetImmediate(async () => {
274+
await assert.rejects(sentinel.sendCommand(true, ['PING'], {
275+
timeout: 5
276+
}), TimeoutError);
277+
})
278+
}, GLOBAL.CLUSTERS.OPEN);
279+
264280
testUtils.testWithClient('Timeout with global timeout config', async client => {
265281
await blockSetImmediate(async () => {
266282
await assert.rejects(client.ping(), TimeoutError);
@@ -275,6 +291,34 @@ describe('Client', () => {
275291
}
276292
});
277293

294+
testUtils.testWithCluster('Timeout with global timeout config (cluster)', async cluster => {
295+
await blockSetImmediate(async () => {
296+
await assert.rejects(cluster.HSET('key', 'foo', 'value'), TimeoutError);
297+
await assert.rejects(cluster.sendCommand(undefined, true, ['PING']), TimeoutError);
298+
});
299+
}, {
300+
...GLOBAL.CLUSTERS.OPEN,
301+
clusterConfiguration: {
302+
commandOptions: {
303+
timeout: 5
304+
}
305+
}
306+
});
307+
308+
testUtils.testWithClientSentinel('Timeout with global timeout config (sentinel)', async sentinel => {
309+
await blockSetImmediate(async () => {
310+
await assert.rejects(sentinel.HSET('key', 'foo', 'value'), TimeoutError);
311+
await assert.rejects(sentinel.sendCommand(true, ['PING']), TimeoutError);
312+
});
313+
}, {
314+
...GLOBAL.SENTINEL.OPEN,
315+
clientOptions: {
316+
commandOptions: {
317+
timeout: 5
318+
}
319+
}
320+
});
321+
278322
testUtils.testWithClient('undefined and null should not break the client', async client => {
279323
await assert.rejects(
280324
client.sendCommand([null as any, undefined as any]),

packages/client/lib/cluster/index.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ export interface RedisClusterOptions<
3838
// POLICIES extends CommandPolicies = CommandPolicies
3939
> extends ClusterCommander<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/> {
4040
/**
41-
* Should contain details for some of the cluster nodes that the client will use to discover
41+
* Should contain details for some of the cluster nodes that the client will use to discover
4242
* the "cluster topology". We recommend including details for at least 3 nodes here.
4343
*/
4444
rootNodes: Array<RedisClusterClientOptions>;
4545
/**
46-
* Default values used for every client in the cluster. Use this to specify global values,
46+
* Default values used for every client in the cluster. Use this to specify global values,
4747
* for example: ACL credentials, timeouts, TLS configuration etc.
4848
*/
4949
defaults?: Partial<RedisClusterClientOptions>;
@@ -68,13 +68,13 @@ export interface RedisClusterOptions<
6868
nodeAddressMap?: NodeAddressMap;
6969
/**
7070
* Client Side Caching configuration for the pool.
71-
*
72-
* Enables Redis Servers and Clients to work together to cache results from commands
71+
*
72+
* Enables Redis Servers and Clients to work together to cache results from commands
7373
* sent to a server. The server will notify the client when cached results are no longer valid.
7474
* In pooled mode, the cache is shared across all clients in the pool.
75-
*
75+
*
7676
* Note: Client Side Caching is only supported with RESP3.
77-
*
77+
*
7878
* @example Anonymous cache configuration
7979
* ```
8080
* const client = createCluster({
@@ -86,7 +86,7 @@ export interface RedisClusterOptions<
8686
* minimum: 5
8787
* });
8888
* ```
89-
*
89+
*
9090
* @example Using a controllable cache
9191
* ```
9292
* const cache = new BasicPooledClientSideCache({
@@ -406,7 +406,7 @@ export default class RedisCluster<
406406
proxy._commandOptions[key] = value;
407407
return proxy as RedisClusterType<
408408
M,
409-
F,
409+
F,
410410
S,
411411
RESP,
412412
K extends 'typeMapping' ? V extends TypeMapping ? V : {} : TYPE_MAPPING
@@ -489,15 +489,15 @@ export default class RedisCluster<
489489
myFn = this._handleAsk(fn);
490490
continue;
491491
}
492-
492+
493493
if (err.message.startsWith('MOVED')) {
494494
await this._slots.rediscover(client);
495495
client = await this._slots.getClient(firstKey, isReadonly);
496496
continue;
497497
}
498498

499499
throw err;
500-
}
500+
}
501501
}
502502
}
503503

@@ -508,10 +508,16 @@ export default class RedisCluster<
508508
options?: ClusterCommandOptions,
509509
// defaultPolicies?: CommandPolicies
510510
): Promise<T> {
511+
512+
// Merge global options with local options
513+
const opts = {
514+
...this._self._commandOptions,
515+
...options
516+
}
511517
return this._self._execute(
512518
firstKey,
513519
isReadonly,
514-
options,
520+
opts,
515521
(client, opts) => client.sendCommand(args, opts)
516522
);
517523
}

packages/client/lib/sentinel/index.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class RedisSentinelClient<
3535

3636
/**
3737
* Indicates if the client connection is open
38-
*
38+
*
3939
* @returns `true` if the client connection is open, `false` otherwise
4040
*/
4141

@@ -45,7 +45,7 @@ export class RedisSentinelClient<
4545

4646
/**
4747
* Indicates if the client connection is ready to accept commands
48-
*
48+
*
4949
* @returns `true` if the client connection is ready, `false` otherwise
5050
*/
5151
get isReady() {
@@ -54,7 +54,7 @@ export class RedisSentinelClient<
5454

5555
/**
5656
* Gets the command options configured for this client
57-
*
57+
*
5858
* @returns The command options for this client or `undefined` if none were set
5959
*/
6060
get commandOptions() {
@@ -241,10 +241,10 @@ export class RedisSentinelClient<
241241

242242
/**
243243
* Releases the client lease back to the pool
244-
*
244+
*
245245
* After calling this method, the client instance should no longer be used as it
246246
* will be returned to the client pool and may be given to other operations.
247-
*
247+
*
248248
* @returns A promise that resolves when the client is ready to be reused, or undefined
249249
* if the client was immediately ready
250250
* @throws Error if the lease has already been released
@@ -274,7 +274,7 @@ export default class RedisSentinel<
274274

275275
/**
276276
* Indicates if the sentinel connection is open
277-
*
277+
*
278278
* @returns `true` if the sentinel connection is open, `false` otherwise
279279
*/
280280
get isOpen() {
@@ -283,7 +283,7 @@ export default class RedisSentinel<
283283

284284
/**
285285
* Indicates if the sentinel connection is ready to accept commands
286-
*
286+
*
287287
* @returns `true` if the sentinel connection is ready, `false` otherwise
288288
*/
289289
get isReady() {
@@ -554,15 +554,15 @@ export default class RedisSentinel<
554554

555555
/**
556556
* Acquires a master client lease for exclusive operations
557-
*
557+
*
558558
* Used when multiple commands need to run on an exclusive client (for example, using `WATCH/MULTI/EXEC`).
559559
* The returned client must be released after use with the `release()` method.
560-
*
560+
*
561561
* @returns A promise that resolves to a Redis client connected to the master node
562562
* @example
563563
* ```javascript
564564
* const clientLease = await sentinel.acquire();
565-
*
565+
*
566566
* try {
567567
* await clientLease.watch('key');
568568
* const resp = await clientLease.multi()
@@ -670,7 +670,7 @@ class RedisSentinelInternal<
670670
super();
671671

672672
this.#validateOptions(options);
673-
673+
674674
this.#name = options.name;
675675

676676
this.#sentinelRootNodes = Array.from(options.sentinelRootNodes);
@@ -728,7 +728,7 @@ class RedisSentinelInternal<
728728

729729
/**
730730
* Gets a client lease from the master client pool
731-
*
731+
*
732732
* @returns A client info object or a promise that resolves to a client info object
733733
* when a client becomes available
734734
*/
@@ -743,10 +743,10 @@ class RedisSentinelInternal<
743743

744744
/**
745745
* Releases a client lease back to the pool
746-
*
746+
*
747747
* If the client was used for a transaction that might have left it in a dirty state,
748748
* it will be reset before being returned to the pool.
749-
*
749+
*
750750
* @param clientInfo The client info object representing the client to release
751751
* @returns A promise that resolves when the client is ready to be reused, or undefined
752752
* if the client was immediately ready or no longer exists
@@ -786,10 +786,10 @@ class RedisSentinelInternal<
786786

787787
async #connect() {
788788
let count = 0;
789-
while (true) {
789+
while (true) {
790790
this.#trace("starting connect loop");
791791

792-
count+=1;
792+
count+=1;
793793
if (this.#destroy) {
794794
this.#trace("in #connect and want to destroy")
795795
return;
@@ -842,7 +842,7 @@ class RedisSentinelInternal<
842842

843843
try {
844844
/*
845-
// force testing of READONLY errors
845+
// force testing of READONLY errors
846846
if (clientInfo !== undefined) {
847847
if (Math.floor(Math.random() * 10) < 1) {
848848
console.log("throwing READONLY error");
@@ -856,7 +856,7 @@ class RedisSentinelInternal<
856856
throw err;
857857
}
858858

859-
/*
859+
/*
860860
rediscover and retry if doing a command against a "master"
861861
a) READONLY error (topology has changed) but we haven't been notified yet via pubsub
862862
b) client is "not ready" (disconnected), which means topology might have changed, but sentinel might not see it yet
@@ -1569,4 +1569,4 @@ export class RedisSentinelFactory extends EventEmitter {
15691569
}
15701570
});
15711571
}
1572-
}
1572+
}

packages/client/lib/sentinel/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { NamespaceProxySentinel, NamespaceProxySentinelClient, ProxySentinel, Pr
66

77
/* TODO: should use map interface, would need a transform reply probably? as resp2 is list form, which this depends on */
88
export function parseNode(node: Record<string, string>): RedisNode | undefined{
9-
9+
1010
if (node.flags.includes("s_down") || node.flags.includes("disconnected") || node.flags.includes("failover_in_progress")) {
1111
return undefined;
1212
}

packages/test-utils/lib/index.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export default class TestUtils {
179179
this.#VERSION_NUMBERS = numbers;
180180
this.#DOCKER_IMAGE = {
181181
image: dockerImageName,
182-
version: string,
182+
version: string,
183183
mode: "server"
184184
};
185185
}
@@ -315,7 +315,7 @@ export default class TestUtils {
315315
if (passIndex != 0) {
316316
password = options.serverArguments[passIndex];
317317
}
318-
318+
319319
if (this.isVersionGreaterThan(options.minimumDockerVersion)) {
320320
const dockerImage = this.#DOCKER_IMAGE;
321321
before(function () {
@@ -333,17 +333,18 @@ export default class TestUtils {
333333

334334
const promises = await dockerPromises;
335335
const rootNodes: Array<RedisNode> = promises.map(promise => ({
336-
host: "127.0.0.1",
336+
host: "127.0.0.1",
337337
port: promise.port
338338
}));
339339

340340
const sentinel = createSentinel({
341-
name: 'mymaster',
342-
sentinelRootNodes: rootNodes,
343-
nodeClientOptions: {
341+
name: 'mymaster',
342+
sentinelRootNodes: rootNodes,
343+
nodeClientOptions: {
344+
commandOptions: options.clientOptions?.commandOptions,
344345
password: password || undefined,
345346
},
346-
sentinelClientOptions: {
347+
sentinelClientOptions: {
347348
password: password || undefined,
348349
},
349350
replicaPoolSize: options?.replicaPoolSize || 0,
@@ -505,7 +506,7 @@ export default class TestUtils {
505506

506507
it(title, async function () {
507508
if (!dockersPromise) return this.skip();
508-
509+
509510
const dockers = await dockersPromise,
510511
cluster = createCluster({
511512
rootNodes: dockers.map(({ port }) => ({
@@ -578,12 +579,12 @@ export default class TestUtils {
578579
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), appPrefix));
579580

580581
sentinels.push(await spawnSentinelNode(this.#DOCKER_IMAGE, options.serverArguments, masterPort, sentinelName, tmpDir))
581-
582+
582583
if (tmpDir) {
583584
fs.rmSync(tmpDir, { recursive: true });
584585
}
585586
}
586-
587+
587588
return sentinels
588589
}
589590
}

0 commit comments

Comments
 (0)